mirror of
				https://github.com/coop-deluxe/sm64coopdx.git
				synced 2025-10-30 08:01:01 +00:00 
			
		
		
		
	
		
			Some checks are pending
		
		
	
	Build coop / build-linux (push) Waiting to run
				
			Build coop / build-steamos (push) Waiting to run
				
			Build coop / build-windows-opengl (push) Waiting to run
				
			Build coop / build-windows-directx (push) Waiting to run
				
			Build coop / build-macos-arm (push) Waiting to run
				
			Build coop / build-macos-intel (push) Waiting to run
				
			* modfs; optional function parameters in autogen * errors and stuff * script to turn a directory into a modfs file and vice versa * bug fixes * read: raise error on eof * properly check eof on read_string * fill; reload; check pointer validity; skip private files when loading non-active modfs * added bytestrings * move ByteString to smlua_utils.h
		
			
				
	
	
		
			190 lines
		
	
	
	
		
			6.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			190 lines
		
	
	
	
		
			6.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import os, sys, re
 | 
						|
 | 
						|
 | 
						|
MOD_FS_MAGIC        = "MODFSSM64COOPDX"
 | 
						|
MOD_FS_HEADER_SIZE  = 32
 | 
						|
MOD_FS_EXTENSION    = ".modfs"
 | 
						|
MOD_FS_VERSION      = 1
 | 
						|
MOD_FS_MAX_SIZE     = 0x1000000
 | 
						|
MOD_FS_MAX_FILES    = 0x100
 | 
						|
MOD_FS_MAX_PATH     = 0x100
 | 
						|
 | 
						|
 | 
						|
def usage():
 | 
						|
    print("""
 | 
						|
Directory to modfs:
 | 
						|
 | 
						|
    python dir2modfs.py <dirpath> [--set-public] [--set-file-public <files>...]
 | 
						|
 | 
						|
    Parameters:
 | 
						|
        dirpath                         Path to directory to turn into a .modfs file
 | 
						|
 | 
						|
    Options:
 | 
						|
        --set-public                    Set modfs file as public (readable by other mods)
 | 
						|
        --set-file-public <files>       Set the provided files as public (readable by other mods)
 | 
						|
 | 
						|
modfs to directory:
 | 
						|
 | 
						|
    python dir2modfs.py <filepath> --extract
 | 
						|
 | 
						|
    Parameters:
 | 
						|
        filepath                        Path to modfs file to extract files from
 | 
						|
""")
 | 
						|
    exit(0)
 | 
						|
 | 
						|
 | 
						|
def is_binary_file(bytes: bytes):
 | 
						|
    textchars = bytearray({0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x1B} | set(range(0x20, 0x100)) - {0x7F})
 | 
						|
    return bool(bytes.translate(None, textchars))
 | 
						|
 | 
						|
 | 
						|
def get_files(dirpath: str, public_files: list):
 | 
						|
    files = []
 | 
						|
    for root, _, filenames in os.walk(dirpath):
 | 
						|
        for filename in filenames:
 | 
						|
            relpath = os.path.join(root, filename)
 | 
						|
            filepath = relpath.removeprefix(dirpath).strip("/\\").replace('\\', '/')
 | 
						|
            is_public = False
 | 
						|
            for public_file in public_files:
 | 
						|
                if re.match(public_file, relpath) or re.match(public_file, filepath):
 | 
						|
                    is_public = True
 | 
						|
                    break
 | 
						|
            files.append({
 | 
						|
                "relpath": relpath,
 | 
						|
                "filepath": filepath,
 | 
						|
                "is_public": is_public,
 | 
						|
                "is_text": False,
 | 
						|
                "data": None
 | 
						|
            })
 | 
						|
    return files
 | 
						|
 | 
						|
 | 
						|
def convert(dirpath: str, set_public: bool, public_files: list):
 | 
						|
    dirpath = dirpath.rstrip("/\\")
 | 
						|
    files = sorted(get_files(dirpath, public_files), key=lambda file: file["filepath"])
 | 
						|
    if len(files) > MOD_FS_MAX_FILES:
 | 
						|
        raise Exception(f"Max number of files exceeded: {len(files)} (max is: {MOD_FS_MAX_FILES})")
 | 
						|
 | 
						|
    total_size = 0
 | 
						|
    for file in files:
 | 
						|
        filepath = file["filepath"]
 | 
						|
        if len(filepath) >= MOD_FS_MAX_PATH:
 | 
						|
            raise Exception(f"{filepath} - Exceeded filepath length: {len(filepath)} (max is: {MOD_FS_MAX_PATH-1})")
 | 
						|
        
 | 
						|
        with open(file["relpath"], "rb") as f:
 | 
						|
            data = f.read()
 | 
						|
            total_size += len(data)
 | 
						|
            if total_size > MOD_FS_MAX_SIZE:
 | 
						|
                raise Exception(f"{filepath} - Total size exceeded: {total_size} (max is: {MOD_FS_MAX_SIZE})")
 | 
						|
 | 
						|
        file["data"] = data
 | 
						|
        file["is_text"] = not is_binary_file(data)
 | 
						|
 | 
						|
    # write file
 | 
						|
    destpath = dirpath + MOD_FS_EXTENSION
 | 
						|
    with open(destpath, "wb") as f:
 | 
						|
 | 
						|
        # magic + version
 | 
						|
        f.write(MOD_FS_MAGIC.encode())
 | 
						|
        f.write(MOD_FS_VERSION.to_bytes(1, byteorder="little", signed=False))
 | 
						|
 | 
						|
        # header
 | 
						|
        f.write(len(files).to_bytes(2, byteorder="little", signed=False))
 | 
						|
        f.write(set_public.to_bytes(1, byteorder="little", signed=False))
 | 
						|
 | 
						|
        # padding (empty space for future versions)
 | 
						|
        padding = MOD_FS_HEADER_SIZE - f.tell()
 | 
						|
        f.write(b'\0' * padding)
 | 
						|
 | 
						|
        # files
 | 
						|
        for file in files:
 | 
						|
 | 
						|
            # filepath
 | 
						|
            f.write(len(file["filepath"]).to_bytes(2, byteorder="little", signed=False))
 | 
						|
            f.write(file["filepath"].encode())
 | 
						|
 | 
						|
            # data
 | 
						|
            f.write(len(file["data"]).to_bytes(4, byteorder="little", signed=False))
 | 
						|
            f.write(file["is_public"].to_bytes(1, byteorder="little", signed=False))
 | 
						|
            f.write(file["is_text"].to_bytes(1, byteorder="little", signed=False))
 | 
						|
            f.write(file["data"])
 | 
						|
 | 
						|
    # summary
 | 
						|
    print("")
 | 
						|
    print(f"Directory:     {dirpath}")
 | 
						|
    print(f"Num files:     {len(files)}")
 | 
						|
    print(f"Total size:    {total_size}")
 | 
						|
    print(f"Is public:     {set_public}")
 | 
						|
 | 
						|
    filepaths_max = max(8, len(max([file["filepath"] for file in files], key=len)))
 | 
						|
    sizes_max = max(4, len(max([str(len(file["data"])) for file in files], key=len)))
 | 
						|
    print("")
 | 
						|
    print(f"{'FILEPATH'.ljust(filepaths_max)}  {'SIZE'.rjust(sizes_max)}  {'TEXT'.rjust(5)}  {'PUBLIC'.rjust(6)}")
 | 
						|
    print(f"{'--------'.ljust(filepaths_max)}  {'----'.rjust(sizes_max)}  {'----'.rjust(5)}  {'------'.rjust(6)}")
 | 
						|
    for file in files:
 | 
						|
        filepath = file["filepath"]
 | 
						|
        size = str(len(file["data"]))
 | 
						|
        is_text = str(file["is_text"])
 | 
						|
        is_public = str(file["is_public"])
 | 
						|
        print(f"{filepath.ljust(filepaths_max)}  {size.rjust(sizes_max)}  {is_text.rjust(5)}  {is_public.rjust(6)}")
 | 
						|
 | 
						|
 | 
						|
def extract(filepath: str):
 | 
						|
    if not filepath.endswith(MOD_FS_EXTENSION):
 | 
						|
        raise Exception("Not a modfs file")
 | 
						|
 | 
						|
    with open(filepath, "rb") as f:
 | 
						|
 | 
						|
        # magic + version
 | 
						|
        magic = f.read(len(MOD_FS_MAGIC)).decode()
 | 
						|
        if magic != MOD_FS_MAGIC:
 | 
						|
            raise Exception("Not a modfs file")
 | 
						|
        version = int.from_bytes(f.read(1), byteorder="little", signed=False)
 | 
						|
        if version != MOD_FS_VERSION:
 | 
						|
            raise Exception("Version mismatch")
 | 
						|
 | 
						|
        # header
 | 
						|
        num_files = int.from_bytes(f.read(2), byteorder="little", signed=False)
 | 
						|
        is_public = bool.from_bytes(f.read(1), byteorder="little", signed=False)
 | 
						|
 | 
						|
        # padding (empty space for future versions)
 | 
						|
        f.seek(MOD_FS_HEADER_SIZE, 0)
 | 
						|
 | 
						|
        # create directory
 | 
						|
        dirpath = filepath.removesuffix(MOD_FS_EXTENSION)
 | 
						|
        os.makedirs(dirpath, exist_ok=True)
 | 
						|
 | 
						|
        # files
 | 
						|
        for _ in range(num_files):
 | 
						|
 | 
						|
            # filepath
 | 
						|
            filepath_len = int.from_bytes(f.read(2), byteorder="little", signed=False)
 | 
						|
            filepath = os.path.join(dirpath, f.read(filepath_len).decode())
 | 
						|
 | 
						|
            # data
 | 
						|
            file_size = int.from_bytes(f.read(4), byteorder="little", signed=False)
 | 
						|
            file_is_public = bool.from_bytes(f.read(1), byteorder="little", signed=False)
 | 
						|
            file_is_text = bool.from_bytes(f.read(1), byteorder="little", signed=False)
 | 
						|
            file_data = f.read(file_size)
 | 
						|
 | 
						|
            # write file
 | 
						|
            os.makedirs(os.path.dirname(filepath), exist_ok=True)
 | 
						|
            with open(filepath, "wb") as g:
 | 
						|
                g.write(file_data)
 | 
						|
            print(f"Extracted file of size {file_size} to: {filepath}")
 | 
						|
 | 
						|
 | 
						|
def main(argc: int, argv: list):
 | 
						|
    if argc < 2:
 | 
						|
        usage()
 | 
						|
 | 
						|
    if "--extract" in argv:
 | 
						|
        extract(argv[1])
 | 
						|
    else:
 | 
						|
        set_public = "--set-public" in argv
 | 
						|
        set_file_public_index = argv.index("--set-file-public") if "--set-file-public" in argv else argc
 | 
						|
        convert(argv[1], set_public, argv[set_file_public_index+1:])
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    main(len(sys.argv), sys.argv)
 |