Last active
April 9, 2024 19:10
-
-
Save Kyuuhachi/c294fe054c71838f308b5d5e48900481 to your computer and use it in GitHub Desktop.
Extract and inject textures to Falcom's it3 files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import typing as T | |
from pathlib import Path | |
from dataclasses import dataclass | |
@dataclass | |
class Chunk: | |
type: bytes | |
data: bytearray | |
def read_chunks(f: T.IO[bytes]) -> list[Chunk]: | |
chunks = [] | |
while True: | |
fourcc = f.read(4) | |
if len(fourcc) != 4: break | |
n = int.from_bytes(f.read(4), "little") | |
chunks.append(Chunk(fourcc, bytearray(f.read(n)))) | |
return chunks | |
def write_chunks(f: T.IO[bytes], chunks: list[Chunk]): | |
for chunk in chunks: | |
assert len(chunk.type) == 4 | |
f.write(chunk.type) | |
f.write(int.to_bytes(len(chunk.data), 4, "little")) | |
f.write(chunk.data) | |
def dec(x: bytes) -> str: | |
return x.split(b"\0")[0].decode("cp932") | |
def extract(it3file: Path, texdir: Path): | |
with it3file.open("rb") as f: | |
chunks = read_chunks(f) | |
for chunk in chunks: | |
match chunk.type: | |
case b"TEXI": | |
name = dec(chunk.data[:36]) | |
path = texdir/f"{name}.itp" | |
path.write_bytes(chunk.data[36:]) | |
print(f"TEXI: {name} extracted to {path}") | |
case b"TEXF": | |
name = dec(chunk.data[:36]) | |
path = dec(chunk.data[36:]) | |
print(f"TEXF: {name} is external at {path}") | |
case b"TEX2": | |
n = int.from_bytes(chunk.data[:4], "little") | |
name = dec(chunk.data[4:4+n]) | |
path = texdir/f"{name}.itp" | |
path.write_bytes(chunk.data[4+n:]) | |
print(f"TEX2: {name} extracted to {path}") | |
case a if a.startswith(b"TEX"): | |
print("{a.decode('cp932')}: unknown texture type") | |
case _: pass | |
def inject(it3file: Path, texdir: Path): | |
with it3file.open("rb") as f: | |
chunks = read_chunks(f) | |
for chunk in chunks: | |
match chunk.type: | |
case b"TEXI": | |
name = dec(chunk.data[:36]) | |
path = texdir/f"{name}.itp" | |
chunk.data[36:] = path.read_bytes() | |
print(f"TEXI: {name} injected from {path}") | |
case b"TEXF": | |
name = dec(chunk.data[:36]) | |
path = dec(chunk.data[36:]) | |
print(f"TEXF: {name} is external at {path}") | |
case b"TEX2": | |
n = int.from_bytes(chunk.data[:4], "little") | |
name = dec(chunk.data[4:4+n]) | |
path = texdir/f"{name}.itp" | |
chunk.data[4+n:] = path.read_bytes() | |
print(f"TEX2: {name} injected from {path}") | |
case a if a.startswith(b"TEX"): | |
print("{a.decode('cp932')}: unknown texture type") | |
case _: pass | |
with it3file.open("wb") as f: | |
write_chunks(f, chunks) | |
def __main__(): | |
import argparse | |
argp = argparse.ArgumentParser() | |
sub = argp.add_subparsers(dest="cmd", required=True) | |
ext = sub.add_parser("extract") | |
ext.add_argument("-d", "--texdir", type=Path) | |
ext.add_argument("it3file", type=Path) | |
inj = sub.add_parser("inject") | |
inj.add_argument("-d", "--texdir", type=Path) | |
inj.add_argument("it3file", type=Path) | |
args = argp.parse_args() | |
it3file: Path = args.it3file | |
texdir: Path|None = args.texdir | |
if texdir is None: texdir = it3file.with_suffix("") | |
assert it3file.is_file() | |
if args.cmd == "extract": texdir.mkdir(parents=True, exist_ok=True) | |
assert texdir.is_dir() | |
match args.cmd: | |
case "extract": extract(it3file, texdir) | |
case "inject": inject(it3file, texdir) | |
case a: raise Exception(a) | |
if __name__ == "__main__": __main__() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment