papermario/tools/build/mapfs/combine.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

106 lines
3.5 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
2020-10-18 05:54:32 +02:00
from sys import argv
2020-10-18 05:54:32 +02:00
from pathlib import Path
2021-10-21 19:38:17 +02:00
import struct
2020-10-18 05:54:32 +02:00
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
def get_version_date(version):
if version == "us":
return "Map Ver.00/11/07 15:36"
elif version == "jp":
return "Map Ver.00/07/05 19:13"
elif version == "pal":
return "Map Ver.01/03/23 16:30"
elif version == "ique":
return "Map Ver.04/05/18 13:41"
else:
return "Map Ver.??/??/?? ??:??"
def build_mapfs(out_bin, assets, version, pre_write_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
2021-01-15 00:55:05 +01:00
# of `string`), but the original devs' equivalent of this script had this bug so we need to replicate it to match.
2020-10-18 16:04:49 +02:00
2020-10-18 05:54:32 +02:00
with open(out_bin, "wb") as f:
f.write(get_version_date(version).encode("ascii"))
2020-10-18 05:54:32 +02:00
next_data_pos = (len(assets) + 1) * 0x1C
2020-10-18 05:54:32 +02:00
asset_idx = 0
2021-10-21 19:38:17 +02:00
lastname = ""
2021-01-15 00:55:05 +01:00
for decompressed, compressed 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-18 16:04:49 +02:00
# data for TOC entry
name = decompressed.stem + "\0"
2020-10-18 16:04:49 +02:00
offset = next_data_pos
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
if version == "ique" and decompressed.stem == "title_data":
size = compressed.stat().st_size
# print(f"{name} {offset:08X} {size:08X} {decompressed_size:08X}")
2020-10-18 16:04:49 +02:00
# write all previously-written names; required to match
lastname = name + lastname[len(name) :]
2021-10-21 19:38:17 +02:00
f.seek(toc_entry_pos)
f.write(lastname.encode("ascii"))
2020-10-18 16:04:49 +02:00
# write TOC entry.
f.seek(toc_entry_pos + 0x10)
2021-10-21 19:38:17 +02:00
f.write(struct.pack(">III", offset, size, decompressed_size))
2020-10-18 16:04:49 +02:00
# initial data to be overwritten back, provided by .raw.dat files
pre_write_bytes = b""
if pre_write_assets.get(decompressed.stem):
with open(pre_write_assets[decompressed.stem], "rb") as pwf:
pre_write_bytes = pwf.read()
f.seek(0x20 + next_data_pos)
f.write(pre_write_bytes)
2020-10-18 16:04:49 +02:00
# write data.
f.seek(0x20 + next_data_pos)
f.write(compressed.read_bytes() if compressed.exists() else decompressed.read_bytes())
next_data_pos += max(len(pre_write_bytes), 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
2021-10-21 19:38:17 +02:00
last_name_entry = "end_data\0"
f.seek(toc_entry_pos)
lastname = last_name_entry + lastname[len(last_name_entry) :]
f.write(lastname.encode("ascii"))
2020-10-18 16:04:49 +02:00
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__":
argv.pop(0) # python3
version = argv.pop(0)
out = argv.pop(0)
2021-01-15 00:55:05 +01:00
assets = []
pre_write_assets = {}
for path in argv:
path = Path(path)
if path.suffixes[-2:] == [".raw", ".dat"]:
pre_write_assets[path.with_suffix("").stem] = path
else:
assets.append(path)
2021-01-15 00:55:05 +01:00
# turn them into pairs
assets = list(zip(assets[::2], assets[1::2]))
2021-01-15 00:55:05 +01:00
build_mapfs(out, assets, version, pre_write_assets)