Skip to content

Instantly share code, notes, and snippets.

@Kyuuhachi
Last active April 9, 2024 19:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Kyuuhachi/c294fe054c71838f308b5d5e48900481 to your computer and use it in GitHub Desktop.
Save Kyuuhachi/c294fe054c71838f308b5d5e48900481 to your computer and use it in GitHub Desktop.
Extract and inject textures to Falcom's it3 files
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