2020-10-18 05:54:32 +02:00
|
|
|
#! /usr/bin/python3
|
|
|
|
|
|
|
|
import os
|
2020-10-28 21:57:00 +01:00
|
|
|
from sys import argv
|
2020-10-18 05:54:32 +02:00
|
|
|
from pathlib import Path
|
|
|
|
|
2020-10-18 16:04:49 +02:00
|
|
|
def next_multiple(pos, multiple):
|
|
|
|
return pos + pos % multiple
|
2020-10-18 05:54:32 +02:00
|
|
|
|
2020-10-28 21:57:00 +01:00
|
|
|
def build_mapfs(out_bin, assets):
|
2020-10-18 16:04:49 +02:00
|
|
|
# every TOC entry's name field has data after the null terminator made up from all the previous name fields.
|
|
|
|
# we probably don't have to do this for the game to read the data properly (it doesn't read past the null terminator
|
|
|
|
# of `string`), but the original devs' equivalent to build_assets_fs.py had this bug so we need to replicate it to match.
|
|
|
|
written_names = []
|
|
|
|
|
2020-10-18 05:54:32 +02:00
|
|
|
with open(out_bin, "wb") as f:
|
2020-10-28 21:57:00 +01:00
|
|
|
f.write("Map Ver.00/11/07 15:36".encode("ascii"))
|
2020-10-18 05:54:32 +02:00
|
|
|
|
2020-10-28 21:57:00 +01:00
|
|
|
next_data_pos = (len(assets) + 1) * 0x1C
|
2020-10-18 05:54:32 +02:00
|
|
|
|
|
|
|
asset_idx = 0
|
2020-10-28 21:57:00 +01:00
|
|
|
for decompressed in assets:
|
2020-10-18 16:04:49 +02:00
|
|
|
toc_entry_pos = 0x20 + asset_idx * 0x1C
|
2020-10-18 05:54:32 +02:00
|
|
|
|
2020-10-28 21:57:00 +01:00
|
|
|
decompressed = Path(decompressed)
|
|
|
|
compressed = decompressed.with_suffix(".Yay0")
|
|
|
|
|
|
|
|
# non-texture assets should be compressed
|
|
|
|
if not decompressed.stem.endswith("_tex") and not compressed.exists():
|
|
|
|
print(f"uncompressed asset: {decompressed} (expected {compressed} to exist)")
|
|
|
|
exit(1)
|
2020-10-18 05:54:32 +02:00
|
|
|
|
2020-10-18 16:04:49 +02:00
|
|
|
# data for TOC entry
|
2020-10-28 21:57:00 +01:00
|
|
|
name = decompressed.stem + "\0"
|
2020-10-18 16:04:49 +02:00
|
|
|
offset = next_data_pos
|
2020-10-28 21:57:00 +01:00
|
|
|
decompressed_size = decompressed.stat().st_size
|
|
|
|
size = next_multiple(compressed.stat().st_size, 2) if compressed.exists() else decompressed_size
|
2020-10-18 16:04:49 +02:00
|
|
|
|
2020-10-23 02:28:24 +02:00
|
|
|
#print(f"{name} {offset:08X} {size:08X} {decompressed_size:08X}")
|
2020-10-18 16:04:49 +02:00
|
|
|
|
|
|
|
written_names.append(name)
|
|
|
|
# write all previously-written names; required to match
|
|
|
|
for prev_name in written_names:
|
|
|
|
f.seek(toc_entry_pos)
|
|
|
|
f.write(prev_name.encode('ascii'))
|
|
|
|
|
|
|
|
# write TOC entry.
|
|
|
|
f.seek(toc_entry_pos + 0x10)
|
|
|
|
f.write(offset.to_bytes(4, byteorder="big"))
|
|
|
|
f.write(size.to_bytes(4, byteorder="big"))
|
|
|
|
f.write(decompressed_size.to_bytes(4, byteorder="big"))
|
|
|
|
|
|
|
|
# write data.
|
|
|
|
f.seek(0x20 + next_data_pos)
|
2020-10-28 21:57:00 +01:00
|
|
|
f.write(compressed.read_bytes() if compressed.exists() else decompressed.read_bytes())
|
2020-10-18 16:04:49 +02:00
|
|
|
next_data_pos += size
|
2020-10-18 05:54:32 +02:00
|
|
|
|
|
|
|
asset_idx += 1
|
|
|
|
|
2020-10-18 16:04:49 +02:00
|
|
|
# end_data
|
|
|
|
toc_entry_pos = 0x20 + asset_idx * 0x1C
|
|
|
|
|
|
|
|
written_names.append("end_data\0")
|
|
|
|
for prev_name in written_names:
|
|
|
|
f.seek(toc_entry_pos)
|
|
|
|
f.write(prev_name.encode('ascii'))
|
|
|
|
|
|
|
|
f.seek(toc_entry_pos + 0x18)
|
|
|
|
f.write((0x903F0000).to_bytes(4, byteorder="big")) # TODO: figure out purpose
|
2020-10-18 05:54:32 +02:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2020-10-28 21:57:00 +01:00
|
|
|
argv.pop(0) # python3
|
|
|
|
out = argv.pop(0)
|
|
|
|
|
|
|
|
build_mapfs(out, argv)
|