From 1f6773592f0d38687df37284bd8cb3e7f34cb921 Mon Sep 17 00:00:00 2001 From: Connor Olding Date: Thu, 9 Aug 2018 09:29:06 +0200 Subject: [PATCH] init --- .gitignore | 7 +++++ NOTES | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 1 + fs.py | 64 ++++++++++++++++++++++++++++++++++++++++ util.py | 36 +++++++++++++++++++++++ 5 files changed, 194 insertions(+) create mode 100644 .gitignore create mode 100644 NOTES create mode 100644 README.md create mode 100644 fs.py create mode 100644 util.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..18f92d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +* - Copy* +*.7z +*.State +*.bin +*.exe +*.z64 +__pycache__/* diff --git a/NOTES b/NOTES new file mode 100644 index 0000000..e90256e --- /dev/null +++ b/NOTES @@ -0,0 +1,86 @@ +Bomberman 64 (U): + +80225A74 main game loop (the branch, specifically) +802259F4 main game loop (first jal) +800655F8 copyright screen wait timer (and seemingly has other uses) + $12C gets written by 80238DDC (generic?) + start of function: 80238D98 + wrapper function: 8023900C + calls where A0 (frame time) > 1: + RA: 800434B4 (A0=$14) + RA: 8004356C (A0=$12C) + A0=$12C is set at 80043568, so change that to 1 and... + +decomp routine struct: 8014B808 +LW ($4) might be ROM offset? +LW ($8) is current offset into compressed file? +rom$162008 start of compressed file? +rom$160000 points to that start (confirmed, it adds 0x00160000 from A2 at 80226758) +rom$163B62 start of second file (again, starts with decomp length) + breakpoint at 80292C90 to only see raw bytes being written (in V0) + for compression format details, refer to: + https://github.com/PartyPlanner64/PartyPlanner64/wiki/Compression#compression-type-1 +ring-buffer?: 80305000 + +seems like the rom is split into 0x20000 chunks (128 KiB, 1 megabit) +first chunk is at 0x120000? +err wait, that doesn't work for 0x200000; it's 0x40000 long! + +anyway back at the rom:$160000 +let's tear apart this header. +it's entirely s32s, so that's simple enough. +first word: $00002008 +this is the offset from this position into the first (compressed) file. +next: $00000400 +not sure what this is. number of entries in this header? always seems to be 0x400. +next: $00000000 +offset to first file. added to the initial 0x2008. +next: $00000016 +size of first file. +next: $00000016 +offset of second file. added to the initial 0x2008. +next: $00001B44 +this is the compressed size of the second file. +next: the rest of the entries follow this pattern of (offset, size). +(except $FFFFFFFF means no file) + +now what's the data at rom$162008? +first byte: $09 +i think this is the number of entries in this sub-header, +except it's not. maybe it indicates the type of something? + +rom$162008: +09 +00 20 01 +00 21 02 +00 23 03 +00 25 04 +00 26 05 +00 1F 06 +00 22 07 +00 00 2F 80 + +rom$182008: +0A +00 28 01 +00 29 02 +00 2A 03 +00 2B 04 +00 2C 05 +00 2D 06 +00 2E 07 +00 2F 08 +00 30 09 +00 31 0A +00 (halfword padding?) +00 00 79 80 + +rom$1A2008: +07 +00 38 01 +00 39 02 +00 3A 03 +00 3B 04 +00 3C 05 +00 3D 06 +00 3E 07 diff --git a/README.md b/README.md new file mode 100644 index 0000000..fd4d878 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +nothing to see here (yet) diff --git a/fs.py b/fs.py new file mode 100644 index 0000000..7cbe2d9 --- /dev/null +++ b/fs.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 + +from hashlib import sha1 +from io import BytesIO +import argparse +import os +import os.path +import sys + +from util import * + +def create_rom(path): + raise Exception("TODO") + +def dump_files(f): + pass + +def dump_rom(fp): + with open(fp, 'rb') as f: + data = f.read() + + with BytesIO(data) as f: + start = f.read(4) + if start == b'\x37\x80\x40\x12': + swap_order(f) + elif start != b'\x80\x37\x12\x40': + lament('not a .z64:', fn) + return + + f.seek(0) + romhash = sha1(f.read()).hexdigest() + + if romhash != '8a7648d8105ac4fc1ad942291b2ef89aeca921c9': + raise Exception("unknown/unsupported ROM") + + with SubDir(romhash): + f.seek(0) + dump_files(f) + +def run(args): + parser = argparse.ArgumentParser( + description="fs: construct and deconstruct Bomberman 64 ROMs") + + parser.add_argument( + 'path', metavar='ROM or folder', nargs='+', + help="ROM to deconstruct, or folder to construct") + + a = parser.parse_args(args) + + for path in a.path: + # directories are technically files, so check this first: + if os.path.isdir(path): + create_rom(path) + elif os.path.isfile(path): + dump_rom(path) + else: + lament('no-op:', path) + +if __name__ == '__main__': + try: + ret = run(sys.argv[1:]) + sys.exit(ret) + except KeyboardInterrupt: + sys.exit(1) diff --git a/util.py b/util.py new file mode 100644 index 0000000..c704370 --- /dev/null +++ b/util.py @@ -0,0 +1,36 @@ +import os +import struct, array + +R1 = lambda data: struct.unpack('>B', data)[0] +R2 = lambda data: struct.unpack('>H', data)[0] +R4 = lambda data: struct.unpack('>I', data)[0] +W1 = lambda data: struct.pack('>B', data) +W2 = lambda data: struct.pack('>H', data) +W4 = lambda data: struct.pack('>I', data) + +def dump_as(b, fn, size=None): + with open(fn, 'w+b') as f: + if size: + f.write(bytearray(size)) + f.seek(0) + f.write(b) + +def swap_order(f, size='H'): + f.seek(0) + a = array.array(size, f.read()) + a.byteswap() + f.seek(0) + f.write(a.tobytes()) + +class SubDir: + def __init__(self, d): + self.d = d + def __enter__(self): + self.cwd = os.getcwd() + try: + os.mkdir(self.d) + except FileExistsError: + pass + os.chdir(self.d) + def __exit__(self, type_, value, traceback): + os.chdir(self.cwd)