Last active
August 21, 2024 17:17
-
-
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__() |
match
requires python 3.10, released 2021.
Ah, I had 3.9.9. Thanks for the quick reply! I'll try it now.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I got an invalid syntax error on line 34
match chunk.type:
. Does this require a certain version of Python?