init
This commit is contained in:
commit
1f6773592f
5 changed files with 194 additions and 0 deletions
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
* - Copy*
|
||||
*.7z
|
||||
*.State
|
||||
*.bin
|
||||
*.exe
|
||||
*.z64
|
||||
__pycache__/*
|
86
NOTES
Normal file
86
NOTES
Normal file
|
@ -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
|
1
README.md
Normal file
1
README.md
Normal file
|
@ -0,0 +1 @@
|
|||
nothing to see here (yet)
|
64
fs.py
Normal file
64
fs.py
Normal file
|
@ -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)
|
36
util.py
Normal file
36
util.py
Normal file
|
@ -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)
|
Loading…
Add table
Reference in a new issue