Run: restool.py decode to extract assets into assets/defs/* Run: restool.py compile to compile the assets back into a rom
1568 lines
50 KiB
Python
1568 lines
50 KiB
Python
import sys
|
|
import plm_decode, projectile_decode, eproj_decode, animtiles_decode, palfx_decode, enemy_instr_decode
|
|
import consts
|
|
import text_lexer
|
|
import re
|
|
from decode_common import *
|
|
import glob
|
|
import inspect
|
|
|
|
plm_parser = plm_decode.PlmParser()
|
|
projectile_parser = projectile_decode.ProjParser()
|
|
eproj_parser = eproj_decode.EprojParser()
|
|
animtiles_parser = animtiles_decode.AnimtilesParser()
|
|
palfx_parser = palfx_decode.PalfxParser()
|
|
enemy_instr_parser = enemy_instr_decode.EnemyInstrParser()
|
|
|
|
kGlobalObjects = {}
|
|
def add_global(obj):
|
|
kGlobalObjects[obj.__name__] = obj
|
|
|
|
class LinePrinter:
|
|
def __init__(self):
|
|
self.lines = []
|
|
self.indent = 0
|
|
def print(self, s):
|
|
self.lines.append(' ' * self.indent + s)
|
|
def add_indent(self):
|
|
self.indent += 1
|
|
def remove_indent(self):
|
|
self.indent -= 1
|
|
|
|
class TUint8:
|
|
def __init__(self, hex = False, signed = False):
|
|
self.hex = hex
|
|
self.signed = signed
|
|
def parse_binary(self, ea, extra):
|
|
v = ROM.get_byte(ea)
|
|
if self.signed and v >= 0x80: v -= 0x100
|
|
return 1, (v, self)
|
|
def print_text(self, v):
|
|
return '0x%.2x' % v if (self.hex and v > 0) else str(v)
|
|
def is_default(self, v):
|
|
return v == 0
|
|
def write_binary(self, writer, v, extra):
|
|
writer.write_byte((v or 0) & 0xff)
|
|
|
|
uint8 = TUint8()
|
|
uint8_hex = TUint8(hex = True)
|
|
int8 = TUint8(signed = True)
|
|
|
|
class Skip:
|
|
def __init__(self, size = 1):
|
|
self.size = size
|
|
def parse_binary(self, ea, extra):
|
|
return self.size, None
|
|
def write_binary(self, writer, v, extra):
|
|
assert (v or 0) == 0
|
|
writer.write_byte(0)
|
|
|
|
class TUint16:
|
|
def __init__(self, hex = False, signed = False):
|
|
self.hex = hex
|
|
self.signed = signed
|
|
def parse_binary(self, ea, extra):
|
|
return 2, (ROM.get_word(ea), self)
|
|
def print_text(self, v):
|
|
if self.signed and not self.hex:
|
|
if v >= 0x8000: v -= 0x10000
|
|
return '0x%.2x' % v if (self.hex and v > 0) else str(v)
|
|
def is_default(self, v):
|
|
return v == 0
|
|
def write_binary(self, writer, v, extra):
|
|
writer.write_word(v or 0)
|
|
|
|
uint16 = TUint16()
|
|
int16 = TUint16(signed = True)
|
|
uint16_hex = TUint16(hex = True)
|
|
|
|
class TLongPtr:
|
|
def __init__(self, hex = False):
|
|
self.hex = hex
|
|
def parse_binary(self, ea, extra):
|
|
return 3, (ROM.get_word(ea) + ROM.get_byte(ea + 2) * 65536, self)
|
|
def print_text(self, v):
|
|
return get_ea_name(v)
|
|
def is_default(self, v):
|
|
return v == 0
|
|
def write_binary(self, writer, v, extra):
|
|
writer.write_symbol_long(v or 0)
|
|
LongPtr = TLongPtr()
|
|
|
|
def get_bank(self, ea, extra):
|
|
bank = self.__bank__ or extra.get('bank')
|
|
if bank == None:
|
|
raise Exception('Bank not specified %s for object at %s' % (self, get_ea_name(ea)))
|
|
if bank == 'auto':
|
|
bank = ea >> 16
|
|
return bank
|
|
|
|
class CodePtr:
|
|
def __init__(self, bank, unknown_prefix = 'unk', visitor = None):
|
|
self.__bank__ = bank
|
|
self.unknown_prefix = unknown_prefix
|
|
self.visitor = visitor
|
|
def parse_binary(self, ea, extra):
|
|
bank = get_bank(self, ea, extra)
|
|
v = ROM.get_word(ea)
|
|
if v != 0:
|
|
v += (bank << 16)
|
|
if self.visitor: self.visitor(v, extra)
|
|
return 2, (mark_address(v), self)
|
|
def print_text(self, v):
|
|
return get_ea_name(v, unknown_prefix = self.unknown_prefix)
|
|
def is_default(self, v):
|
|
return v == 0
|
|
def write_binary(self, writer, v, extra):
|
|
bank = get_bank(self, writer.get_ea(), extra)
|
|
writer.write_symbol_word(v, bank)
|
|
|
|
class Fields:
|
|
def __init__(self, tp, ea):
|
|
self.tp = tp
|
|
self.name = get_ea_name(ea) if ea else None
|
|
self.fields = {}
|
|
self.list = []
|
|
self.args = None
|
|
|
|
def print(self, out, compact = False):
|
|
s = self.tp.__name__
|
|
if self.args:
|
|
s += '(%s)' % ', '.join(str(a) for a in self.args)
|
|
if self.name != None:
|
|
s += ' %s' % self.name
|
|
if compact and len(self.list) == 0:
|
|
s += ' {'
|
|
r = []
|
|
for k, v in self.fields.items():
|
|
r.append(' %s: %s' % (k, v[1].print_text(v[0])))
|
|
out.print(s + ','.join(r) + ' }')
|
|
else:
|
|
out.print(s + ' {')
|
|
out.add_indent()
|
|
for k, v in self.fields.items():
|
|
out.print('%s: %s' % (k, v[1].print_text(v[0])))
|
|
for v, tp in self.list:
|
|
if not compact: out.print('')
|
|
tp.print_actual_text(out, v)
|
|
out.remove_indent()
|
|
out.print('}')
|
|
def set_args(self, args):
|
|
self.args = args
|
|
def add_list_entry(self, v, tp):
|
|
self.list.append((v, tp))
|
|
|
|
class TLocationBased:
|
|
__bank__ = None
|
|
def parse_binary(self, ea, extra):
|
|
bank = get_bank(self, ea, extra)
|
|
v = ROM.get_word(ea)
|
|
if v & 0x8000:
|
|
v += (bank << 16)
|
|
visit_location(v, self, extra)
|
|
return 2, (v, self)
|
|
def print_text(self, ea):
|
|
return get_ea_name(ea)
|
|
def write_binary(self, writer, v, extra):
|
|
if v == None or isinstance(v, int):
|
|
writer.write_word(v or 0)
|
|
else:
|
|
bank = get_bank(self, writer.get_ea(), extra)
|
|
writer.write_symbol_word(v, bank)
|
|
|
|
class TLongLocationBased:
|
|
__bank__ = None
|
|
def parse_binary(self, ea, extra):
|
|
v = ROM.get_long(ea)
|
|
if v & 0x8000:
|
|
visit_location(v, self, extra)
|
|
return 3, (v, self)
|
|
def print_text(self, ea):
|
|
return get_ea_name(ea)
|
|
def write_binary(self, writer, v, extra):
|
|
writer.write_symbol_long(v or 0)
|
|
|
|
class EnableIf:
|
|
def __init__(self, cond, tp):
|
|
self.cond = cond
|
|
self.tp = tp
|
|
def parse_binary(self, ea, extra):
|
|
if not (extra['enable_if'] & self.cond):
|
|
return 0, None
|
|
return self.tp.parse_binary(ea, extra)
|
|
def write_binary(self, writer, v, extra):
|
|
if v == None: return
|
|
extra['enable_if'] |= self.cond
|
|
self.tp.write_binary(writer, v, extra)
|
|
|
|
def parse_binary_fields(rv, ea, fields, extra, remove_default = False):
|
|
assert (ea & 0x8000) != 0
|
|
start_ea = ea
|
|
for name, tp in fields.items():
|
|
try:
|
|
size, value = tp.parse_binary(ea, extra)
|
|
if remove_default and value != None and hasattr(value[1], 'is_default') and value[1].is_default(value[0]):
|
|
value = None
|
|
except:
|
|
print('Error parsing field %s at 0x%x of type %s' % (name, ea, tp))
|
|
raise
|
|
ea += size
|
|
if value != None:
|
|
rv.fields[name] = value
|
|
return ea - start_ea
|
|
|
|
class TStruct(TLocationBased):
|
|
__compact__ = False
|
|
__remove_default__ = False
|
|
__remove_name__ = False
|
|
def parse_actual_binary(self, ea, extra):
|
|
rv = Fields(self, None if self.__remove_name__ else ea)
|
|
size = parse_binary_fields(rv, ea, self.__fields__, extra, self.__remove_default__)
|
|
assert self.__size__ == None or size == self.__size__
|
|
return size, (rv, self)
|
|
def print_actual_text(self, out, v):
|
|
v.print(out, self.__compact__)
|
|
def write_actual_binary(self, writer, fields, extra):
|
|
for name, tp in self.__fields__.items():
|
|
tp.write_binary(writer, fields.dict.get(name), extra)
|
|
for v in fields.list:
|
|
write_global_object(writer, v, extra)
|
|
|
|
# Array of primitive objects
|
|
class TArray(TLocationBased):
|
|
def __init__(self, size = None):
|
|
self.size = size
|
|
def parse_actual_binary(self, ea, extra):
|
|
start_ea, rv = ea, []
|
|
size = self.size if self.size != None else extra.get(self.__name__ + '_size')
|
|
if size == None:
|
|
raise Exception('Array size not specified')
|
|
for i in range(size):
|
|
size, value = self.__type__.parse_binary(ea, extra)
|
|
ea += size
|
|
rv.append(value)
|
|
return ea - start_ea, ((get_ea_name(start_ea), rv), self)
|
|
def get_array_size(self, extra):
|
|
return
|
|
def print_actual_text(self, out, v):
|
|
assert len(v) == 2
|
|
inner = ', '.join(a[1].print_text(a[0]) for a in v[1])
|
|
if v[0] == None:
|
|
out.print('%s { %s }' % (self.__name__, inner))
|
|
else:
|
|
out.print('%s %s { %s }' % (self.__name__, v[0], inner))
|
|
def write_actual_binary(self, writer, fields, extra):
|
|
if len(fields.dict) != 0:
|
|
writer.error('Array cannot have named fields')
|
|
size = self.size if self.size != None else extra.get(self.__name__ + '_size')
|
|
if size != None and len(fields.list) != size:
|
|
writer.error('Array size mismatch')
|
|
for value in fields.list:
|
|
self.__type__.write_binary(writer, value, extra)
|
|
|
|
def MakeArray(tp, size, name, bank = None):
|
|
class CustomArray(TArray):
|
|
__name__ = name
|
|
__type__ = tp
|
|
__bank__ = bank
|
|
return CustomArray(size)
|
|
|
|
def print_struct_array(self, out, v):
|
|
if v[0] == None:
|
|
out.print('%s {' % self.__name__)
|
|
else:
|
|
out.print('%s %s {' % (self.__name__, v[0]))
|
|
out.add_indent()
|
|
for a in v[1]:
|
|
a[1].print_actual_text(out, a[0])
|
|
out.remove_indent()
|
|
out.print('}')
|
|
|
|
class StructArray(TLocationBased):
|
|
def __init__(self, tp, deref_with_bank = None, name = None, terminator = 0xffff):
|
|
self.tp = tp
|
|
self.__name__ = self.tp.__name__ + 's' if name == None else name
|
|
self.deref_with_bank = deref_with_bank
|
|
self.__bank__ = tp.__bank__
|
|
self.terminator = terminator
|
|
def parse_actual_binary(self, ea, extra):
|
|
ea_org = ea
|
|
f = Fields(self, ea)
|
|
while True:
|
|
if self.terminator != None:
|
|
if ROM.get_word(ea) == self.terminator:
|
|
break
|
|
else:
|
|
if len(f.list) == extra[self.__name__ + '_size']:
|
|
break
|
|
if self.deref_with_bank != None:
|
|
addr = self.deref_with_bank << 16 | ROM.get_word(ea)
|
|
g_already_emitted.add(addr)
|
|
size, elem = self.tp.parse_actual_binary(addr, extra)
|
|
ea += 2
|
|
else:
|
|
size, elem = self.tp.parse_actual_binary(ea, extra)
|
|
elem[0].name = None
|
|
ea += size
|
|
f.list.append(elem)
|
|
return ea + 2 - ea_org, (f, self)
|
|
def print_actual_text(self, out, v):
|
|
assert isinstance(v, Fields), repr(v)
|
|
v.print(out, True)
|
|
def want_terminator(self, writer, fields):
|
|
return True
|
|
def write_actual_binary(self, writer, fields, extra):
|
|
if len(fields.dict) != 0:
|
|
writer.error('Array cannot have named fields')
|
|
if self.deref_with_bank == None:
|
|
for f in fields.list:
|
|
if f.ident == self.tp.__name__:
|
|
self.tp.write_actual_binary(writer, f, extra)
|
|
if self.terminator != None and self.want_terminator(writer, fields):
|
|
writer.write_word(self.terminator)
|
|
for f in fields.list:
|
|
if f.ident != self.tp.__name__:
|
|
write_global_object(writer, f)
|
|
else:
|
|
assert self.terminator == None
|
|
for f in fields.list:
|
|
assert f.name != None
|
|
if f.ident == self.tp.__name__:
|
|
self.tp.write_binary(writer, f.name, extra)
|
|
write_global_object(writer, f)
|
|
|
|
class Blob(TLongLocationBased):
|
|
__name__ = 'Blob'
|
|
def parse_actual_binary(self, ea, extra):
|
|
size = extra['blob_size']
|
|
if size == None:
|
|
return 0, None
|
|
return size, ((get_ea_name(ea), ROM.get_bytes(ea, size)), self)
|
|
def write_actual_binary(self, writer, f, extra):
|
|
assert isinstance(f.value, bytes)
|
|
writer.write_bytes_with_wrap(f.value)
|
|
def print_actual_text(self, out, v):
|
|
b = v[1]
|
|
out.print('%s %s = $hex(' % (self.__name__, v[0]))
|
|
out.add_indent()
|
|
for i in range(0, len(b), 64):
|
|
out.print(b[i:i+64].hex())
|
|
out.remove_indent()
|
|
out.print(')')
|
|
add_global(Blob())
|
|
|
|
class PackedBlob(Blob):
|
|
__name__ = 'PackedBlob'
|
|
def parse_actual_binary(self, ea, extra):
|
|
size = get_compressed_size(ROM, ea)
|
|
return size, ((get_ea_name(ea), ROM.get_bytes(ea, size)), self)
|
|
add_global(PackedBlob())
|
|
|
|
g_locations = {}
|
|
g_already_emitted = set()
|
|
g_location_file_map = {
|
|
'Plm' : 'plm',
|
|
'Enemy' : 'enemy',
|
|
}
|
|
g_current_file = {}
|
|
g_file_sets = {'' : g_current_file}
|
|
|
|
def push_current_file(which_file):
|
|
global g_current_file
|
|
old_file = g_current_file
|
|
g_current_file = g_file_sets.setdefault(which_file, {})
|
|
return old_file
|
|
|
|
def pop_current_file(old_file):
|
|
global g_current_file
|
|
g_current_file = old_file
|
|
|
|
def visit_location(ea, tp, extra):
|
|
if (ea & 0x8000) == 0:
|
|
raise Exception('While parsing %s, Invalid location 0x%x' % (tp, ea))
|
|
loc = g_locations.get(ea)
|
|
if loc == None:
|
|
g_locations[ea] = tp # avoid infinite recursion
|
|
which_file = g_location_file_map.get(tp.__name__)
|
|
if which_file != None:
|
|
old_file = push_current_file(which_file)
|
|
res = tp.parse_actual_binary(ea, extra)[1]
|
|
g_current_file[ea] = res
|
|
g_locations[ea] = res
|
|
if which_file != None:
|
|
pop_current_file(old_file)
|
|
|
|
Palette = MakeArray(uint16_hex, 16, 'Palette')
|
|
ItemDropChances = MakeArray(uint8, 6, 'ItemDropChances', 0xb4)
|
|
Vulnerability = MakeArray(uint8, 22, 'Vulnerability', 0xb4)
|
|
add_global(Palette)
|
|
add_global(ItemDropChances)
|
|
add_global(Vulnerability)
|
|
|
|
add_global(MakeArray(uint16_hex, None, 'Palette256'))
|
|
add_global(MakeArray(uint16_hex, None, 'Tile16'))
|
|
|
|
class Name:
|
|
def parse_binary(self, ea, extra):
|
|
v = ROM.get_word(ea)
|
|
if v == 0: return 2, (None, self)
|
|
ea = 0xb40000 | v
|
|
s = ''
|
|
for i in range(10):
|
|
s += chr(ROM.get_byte(ea+i))
|
|
return 2, (s.strip(), self)
|
|
def print_text(self, s):
|
|
return 'null' if s == None else f'"{s}"'
|
|
def write_binary(self, writer, v, extra):
|
|
writer.write_word(0 if v == None else (consts.kEnemyNameToAddr[v] & 0xffff))
|
|
|
|
CodePtrNoBank = CodePtr(None)
|
|
|
|
class Sprite(TStruct):
|
|
__name__ = 'Sprite'
|
|
__size__ = 5
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'x': uint16,
|
|
'y': int8,
|
|
'chr': uint16_hex
|
|
}
|
|
|
|
class SpriteMap(TLocationBased):
|
|
__name__ = 'SpriteMap'
|
|
tp = Sprite()
|
|
def __init__(self, bank = None):
|
|
if bank:
|
|
self.__bank__ = bank
|
|
def parse_actual_binary(self, ea, extra):
|
|
ea_org = ea
|
|
n, r = ROM.get_word(ea), []
|
|
assert n < 256, (hex(ea), n)
|
|
ea += 2
|
|
for i in range(n):
|
|
size, elem = self.tp.parse_actual_binary(ea, extra)
|
|
elem[0].name = None
|
|
ea += size
|
|
r.append(elem)
|
|
return ea + 2 - ea_org, ((get_ea_name(ea_org), r), self)
|
|
def print_actual_text(self, out, v):
|
|
print_struct_array(self, out, v)
|
|
def write_actual_binary(self, writer, v, extra):
|
|
writer.write_word(len(v.list))
|
|
assert len(v.dict) == 0
|
|
for x in v.list:
|
|
assert x.ident == 'Sprite', x.ident
|
|
self.tp.write_actual_binary(writer, x, extra)
|
|
add_global(SpriteMap())
|
|
add_global(MakeArray(SpriteMap('auto'), None, 'SpriteMapArray'))
|
|
|
|
|
|
class Hitbox(TStruct):
|
|
__name__ = 'Hitbox'
|
|
__size__ = 12
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'left': int16,
|
|
'top': int16,
|
|
'right': int16,
|
|
'bottom': int16,
|
|
'f0' : CodePtr('auto'),
|
|
'f1': CodePtr('auto'),
|
|
}
|
|
|
|
class Hitboxes(TLocationBased):
|
|
__name__ = 'Hitboxes'
|
|
__bank__ = 'auto'
|
|
tp = Hitbox()
|
|
def parse_actual_binary(self, ea, extra):
|
|
ea_org = ea
|
|
n, r = ROM.get_word(ea), []
|
|
assert n < 256
|
|
ea += 2
|
|
for i in range(n):
|
|
size, elem = self.tp.parse_actual_binary(ea, extra)
|
|
elem[0].name = None
|
|
ea += size
|
|
r.append(elem)
|
|
return ea + 2 - ea_org, ((get_ea_name(ea_org), r), self)
|
|
def print_actual_text(self, out, v):
|
|
print_struct_array(self, out, v)
|
|
def write_actual_binary(self, writer, v, extra):
|
|
writer.write_word(len(v.list))
|
|
assert len(v.dict) == 0
|
|
for x in v.list:
|
|
assert x.ident == 'Hitbox'
|
|
self.tp.write_actual_binary(writer, x, extra)
|
|
add_global(Hitboxes())
|
|
|
|
BigSprite = MakeArray(uint16_hex, None, 'BigSprite', bank = 'auto')
|
|
|
|
class BigSpriteMap(TLocationBased):
|
|
__name__ = 'BigSpriteMap'
|
|
__bank__ = 'auto'
|
|
def parse_actual_binary(self, ea, extra):
|
|
f = Fields(self, ea)
|
|
ea += 2
|
|
while (ram := ROM.get_word(ea)) != 0xffff:
|
|
n = ROM.get_word(ea + 2)
|
|
(_,rv), rt = BigSprite.parse_actual_binary(ea, {'BigSprite_size' : n + 2})[1]
|
|
f.list.append(((None, rv), rt))
|
|
ea += 2 * n + 4
|
|
return 0, (f, self)
|
|
def print_actual_text(self, out, v):
|
|
v.print(out, True)
|
|
def write_actual_binary(self, writer, v, extra):
|
|
writer.write_word(0xfffe)
|
|
assert len(v.dict) == 0
|
|
for x in v.list:
|
|
assert x.ident == 'BigSprite'
|
|
BigSprite.write_actual_binary(writer, x, extra)
|
|
writer.write_word(0xffff)
|
|
|
|
add_global(BigSpriteMap())
|
|
|
|
class ExtSprite(TStruct):
|
|
__name__ = 'ExtSprite'
|
|
__size__ = 8
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'x': int16,
|
|
'y': int16,
|
|
'spr' : SpriteMap('auto'),
|
|
'box': Hitboxes(),
|
|
}
|
|
|
|
class ExtSpriteBig(TStruct):
|
|
__name__ = 'ExtSpriteBig'
|
|
__size__ = 8
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'x': int16,
|
|
'y': int16,
|
|
'big' : BigSpriteMap(),
|
|
'box': Hitboxes(),
|
|
}
|
|
|
|
class ExtSpriteMap(TLocationBased):
|
|
__name__ = 'ExtSpriteMap'
|
|
__bank__ = 'auto'
|
|
tp = ExtSprite()
|
|
tpb = ExtSpriteBig()
|
|
def parse_actual_binary(self, ea, extra):
|
|
ea_org = ea
|
|
f = Fields(self, ea)
|
|
n = ROM.get_byte(ea)
|
|
v = ROM.get_byte(ea + 1)
|
|
if v != 0:
|
|
f.fields['upper_byte'] = ROM.get_byte(ea + 1), uint8
|
|
ea += 2
|
|
for i in range(n):
|
|
if get_word(get_word(ea + 4) | (ea & 0xff0000)) == 0xfffe:
|
|
size, elem = self.tpb.parse_actual_binary(ea, extra)
|
|
else:
|
|
size, elem = self.tp.parse_actual_binary(ea, extra)
|
|
elem[0].name = None
|
|
ea += size
|
|
f.list.append(elem)
|
|
return ea + 2 - ea_org, (f, self)
|
|
def print_actual_text(self, out, v):
|
|
v.print(out, True)
|
|
def write_actual_binary(self, writer, v, extra):
|
|
upper = v.dict.get('upper_byte', 0)
|
|
writer.write_word(upper << 8 | len(v.list))
|
|
for x in v.list:
|
|
assert x.ident in ('ExtSprite', 'ExtSpriteBig')
|
|
if x.ident == 'ExtSpriteBig':
|
|
self.tpb.write_actual_binary(writer, x, {})
|
|
else:
|
|
self.tp.write_actual_binary(writer, x, {})
|
|
add_global(ExtSpriteMap())
|
|
|
|
class Enemy(TStruct):
|
|
__name__ = 'Enemy'
|
|
__size__ = 0x40
|
|
__bank__ = 0xa0
|
|
__remove_default__ = True
|
|
__fields__ = {
|
|
'tile_data_size' : uint16_hex,
|
|
'palette_ptr': Palette,
|
|
'health': uint16,
|
|
'damage': uint16,
|
|
'x_radius': uint16,
|
|
'y_radius': uint16,
|
|
'bank': uint8_hex,
|
|
'hurt_ai_time': uint8,
|
|
'hurt_sfx': uint16,
|
|
'boss_fight_value': uint16,
|
|
'ai_init': CodePtrNoBank,
|
|
'num_parts': uint16,
|
|
'field_16': uint16,
|
|
'main_ai': CodePtrNoBank,
|
|
'grapple_ai': CodePtrNoBank,
|
|
'hurt_ai': CodePtrNoBank,
|
|
'frozen_ai': CodePtrNoBank,
|
|
'time_is_frozen_ai': CodePtrNoBank,
|
|
'death_anim': uint16,
|
|
'field_24': uint16,
|
|
'field_26': uint16,
|
|
'powerbomb_reaction': CodePtrNoBank,
|
|
'field_2A': uint16,
|
|
'field_2C': uint16,
|
|
'field_2E': uint16,
|
|
'touch_ai': CodePtrNoBank,
|
|
'shot_ai': CodePtrNoBank,
|
|
'field_34': uint16,
|
|
'tile_data': Blob(),
|
|
'layer': uint8,
|
|
'item_drop_chances': ItemDropChances,
|
|
'vulnerability': Vulnerability,
|
|
'name': Name(),
|
|
}
|
|
def parse_actual_binary(self, ea, extra):
|
|
blob_size = (ROM.get_word(ea) & 0x7fff) or None
|
|
bank = ROM.get_byte(ea + 12)
|
|
return super().parse_actual_binary(ea, {'bank' : bank, 'blob_size' : blob_size})
|
|
def write_actual_binary(self, writer, fields, extra):
|
|
bank = fields.dict.get('bank') or writer.error('Enemy must have a bank')
|
|
super().write_actual_binary(writer, fields, {'bank' : bank})
|
|
add_global(Enemy())
|
|
|
|
class EnemyPopulation(TStruct):
|
|
__name__ = 'EnemyPopulation'
|
|
__bank__ = 0xa1
|
|
__size__ = 16
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'enemy': Enemy(),
|
|
'x': uint16,
|
|
'y': uint16,
|
|
'init': uint16_hex,
|
|
'props': uint16_hex,
|
|
'eprops': uint16_hex,
|
|
'p1': uint16_hex,
|
|
'p2': uint16_hex,
|
|
}
|
|
class EnemyPopulations(StructArray):
|
|
def __init__(self):
|
|
super().__init__(EnemyPopulation())
|
|
def parse_actual_binary(self, ea, extra):
|
|
size, data = super().parse_actual_binary(ea, extra)
|
|
v = ROM.get_byte(ea + size)
|
|
if v != 0:
|
|
data[0].fields['enemies_to_clear'] = v, uint8
|
|
return size + 1, data
|
|
def write_actual_binary(self, writer, fields, extra):
|
|
for f in fields.list:
|
|
assert f.ident == self.tp.__name__
|
|
self.tp.write_actual_binary(writer, f, extra)
|
|
writer.write_word(self.terminator)
|
|
writer.write_byte(fields.dict.get('enemies_to_clear', 0))
|
|
|
|
add_global(EnemyPopulation())
|
|
add_global(EnemyPopulations())
|
|
EnemyPopulationArrayV = EnemyPopulations()
|
|
|
|
class Layer3Fx(TStruct):
|
|
__name__ = 'Layer3Fx'
|
|
__bank__ = 0x83
|
|
__size__ = 16
|
|
__remove_name__ = True
|
|
__fields__ = {
|
|
'door': CodePtr(0x83),
|
|
'base_y': int16,
|
|
'target_y': int16,
|
|
'y_vel': int16,
|
|
'timer': uint8,
|
|
'type': uint8,
|
|
'default_layer_blend': uint8,
|
|
'layer3_layer_blend': uint8,
|
|
'fx_liquid_options': uint8,
|
|
'palette_fx_bitset': uint8,
|
|
'animtiles_bitset': uint8,
|
|
'palette_blend': uint8,
|
|
}
|
|
|
|
class Layer3FxArray(StructArray):
|
|
def parse_actual_binary(self, ea, extra):
|
|
ea_org = ea
|
|
f = Fields(self, ea)
|
|
while (head := ROM.get_word(ea)) != 0xffff:
|
|
size, elem = self.tp.parse_actual_binary(ea, extra)
|
|
ea += size
|
|
f.list.append(elem)
|
|
if head == 0:
|
|
break
|
|
return ea + 2 - ea_org, (f, self)
|
|
def want_terminator(self, writer, fields):
|
|
for f in fields.list[::-1]:
|
|
if f.ident == self.tp.__name__:
|
|
return f.dict.get('door') != None
|
|
return True
|
|
add_global(Layer3FxArray(Layer3Fx()))
|
|
|
|
class EnemyTileset(TStruct):
|
|
__name__ = 'EnemyTileset'
|
|
__bank__ = 0xb4
|
|
__size__ = 4
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'enemy': Enemy(),
|
|
'vram_dst': uint16_hex,
|
|
}
|
|
add_global(StructArray(EnemyTileset()))
|
|
|
|
class Scrolls(TArray):
|
|
__name__ = 'Scrolls'
|
|
__type__ = uint8
|
|
__bank__ = 0x8f
|
|
def print_text(self, ea):
|
|
return str(ea) if ea < 0x8000 else get_ea_name(ea)
|
|
add_global(Scrolls())
|
|
|
|
class PlmScrolls(TLocationBased):
|
|
__name__ = 'PlmScrolls'
|
|
__bank__ = 0x8f
|
|
def parse_actual_binary(self, ea, extra):
|
|
start_ea, rv = ea, []
|
|
while ROM.get_byte(ea) != 0x80:
|
|
rv.append(uint8.parse_binary(ea + 0, extra)[1])
|
|
rv.append(uint8.parse_binary(ea + 1, extra)[1])
|
|
ea += 2
|
|
return ea - start_ea, ((get_ea_name(start_ea), rv), self)
|
|
def print_actual_text(self, out, v):
|
|
inner = ', '.join(a[1].print_text(a[0]) for a in v[1])
|
|
out.print('%s %s { %s }' % (self.__name__, v[0], inner))
|
|
def write_actual_binary(self, writer, fields, extra):
|
|
if len(fields.dict) != 0:
|
|
writer.error('Array cannot have named fields')
|
|
if len(fields.list) % 2:
|
|
writer.error('Array size mismatch')
|
|
for value in fields.list:
|
|
uint8.write_binary(writer, value, extra)
|
|
writer.write_byte(0x80)
|
|
add_global(PlmScrolls())
|
|
|
|
class Door(TStruct):
|
|
__name__ = 'Door'
|
|
__size__ = 12
|
|
__bank__ = 0x83
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'room': CodePtr(0x8f), # lazy loaded
|
|
'flags': uint8,
|
|
'orientation': uint8,
|
|
'x': uint8,
|
|
'y': uint8,
|
|
'screenx': uint8,
|
|
'screeny': uint8,
|
|
'dist': uint16,
|
|
'code': CodePtr(0x8f),
|
|
}
|
|
@classmethod
|
|
def lazy_load(self):
|
|
self.__fields__['room'] = Room()
|
|
def parse_actual_binary(self, ea, extra):
|
|
if ROM.get_word(ea) == 0:
|
|
return 2, (Fields(self, ea), self)
|
|
return super().parse_actual_binary(ea, extra)
|
|
|
|
def write_actual_binary(self, writer, fields, extra):
|
|
if len(fields.dict) == 0:
|
|
writer.write_word(0)
|
|
else:
|
|
super().write_actual_binary(writer, fields, extra)
|
|
add_global(Door())
|
|
|
|
def MakeLoadBgType(name, fields):
|
|
class LoadBgType(TStruct):
|
|
__name__ = name
|
|
__size__ = None
|
|
__fields__ = fields
|
|
__compact__ = True
|
|
__remove_name__ = True
|
|
__bank__ = 0x8f
|
|
return LoadBgType()
|
|
|
|
class LoadBgCmd(TLocationBased):
|
|
__name__ = 'LoadBgCmd'
|
|
__bank__ = 0x8f
|
|
__commands__ = {
|
|
2 : MakeLoadBgType('TransferToVram', {'src': LongPtr, 'dst': uint16_hex, 'size' : uint16}),
|
|
4 : MakeLoadBgType('Decompress', {'src': PackedBlob(), 'dst': CodePtr(0x7e)}),
|
|
6 : MakeLoadBgType('ClearFxTilemap', {}),
|
|
8 : MakeLoadBgType('TransferToVramSetBG3', {'src': LongPtr, 'dst': uint16_hex, 'size' : uint16}),
|
|
10: MakeLoadBgType('ClearBG2Tilemap', {}),
|
|
12: MakeLoadBgType('ClearKraidLayer2', {}),
|
|
14: MakeLoadBgType('DoorDepXferVram', {'door' : Door(), 'src': Blob(), 'dst': uint16_hex, 'size' : uint16}),
|
|
}
|
|
def parse_actual_binary(self, ea, extra):
|
|
rv, ea_start = Fields(self, ea), ea
|
|
while True:
|
|
cmd = ROM.get_word(ea)
|
|
if cmd == 0:
|
|
break
|
|
cmd = self.__commands__[cmd]
|
|
extra2 = extra;
|
|
if cmd.__name__ == 'DoorDepXferVram':
|
|
extra2 = {'blob_size': ROM.get_word(ea + 9)}
|
|
size, r = cmd.parse_actual_binary(ea + 2, extra2)
|
|
rv.add_list_entry(r[0], r[1])
|
|
ea += 2 + size
|
|
return ea - ea_start, (rv, self)
|
|
def print_actual_text(self, out, v):
|
|
v.print(out, True)
|
|
def write_actual_binary(self, writer, fields, extra):
|
|
cmdmap = {a.__name__ : (i, a) for i, a in self.__commands__.items()}
|
|
for f in fields.list:
|
|
value, cmd = cmdmap[f.ident]
|
|
writer.write_word(value)
|
|
cmd.write_actual_binary(writer, f, extra)
|
|
writer.write_word(0)
|
|
add_global(LoadBgCmd())
|
|
|
|
class XraySpecialCase(TStruct):
|
|
__name__ = 'XraySpecialCase'
|
|
__size__ = 4
|
|
__bank__ = 0x8F
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'x' : uint8,
|
|
'y' : uint8,
|
|
'data' : uint16_hex,
|
|
}
|
|
add_global(StructArray(XraySpecialCase(), terminator = 0))
|
|
|
|
PlmParserPtr = CodePtr(0x84, visitor = lambda x, e: plm_parser.visit(x))
|
|
|
|
class Plm(TStruct):
|
|
__name__ = 'Plm'
|
|
__size__ = None
|
|
__bank__ = 0x84
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'code' : CodePtr(0x84, unknown_prefix='sub'),
|
|
'instr' : PlmParserPtr,
|
|
'instr2' : EnableIf(1, PlmParserPtr)
|
|
}
|
|
def parse_actual_binary(self, ea, extra):
|
|
plm_size = plm_decode.plm_header_size(ea)
|
|
return super().parse_actual_binary(ea, {'enable_if': 1 if plm_size == 6 else 0})
|
|
def write_actual_binary(self, writer, fields, extra):
|
|
super().write_actual_binary(writer, fields, {'enable_if' : 0})
|
|
add_global(Plm())
|
|
|
|
class RoomPlm(TStruct):
|
|
__name__ = 'RoomPlm'
|
|
__size__ = 6
|
|
__bank__ = 0x8f
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'plm' : Plm(),
|
|
'x' : uint8,
|
|
'y' : uint8,
|
|
'arg' : EnableIf(1, uint16_hex),
|
|
'arg_plm' : EnableIf(2, PlmScrolls()),
|
|
}
|
|
def parse_actual_binary(self, ea, extra):
|
|
extra = {'enable_if' : 2 if ROM.get_word(ea) in (0xB703, 0xB707) else 1}
|
|
return super().parse_actual_binary(ea, extra)
|
|
def write_actual_binary(self, writer, fields, extra):
|
|
extra = {'enable_if' : 0}
|
|
super().write_actual_binary(writer, fields, extra)
|
|
assert extra['enable_if'] in (1, 2)
|
|
add_global(StructArray(RoomPlm(), terminator = 0))
|
|
|
|
class RoomState(TStruct):
|
|
__name__ = 'RoomState'
|
|
__size__ = 26
|
|
__fields__ = {
|
|
'data': PackedBlob(),
|
|
'gfx_set': uint8,
|
|
'music': uint8,
|
|
'music_ctrl': uint8,
|
|
'layer3_fx': Layer3FxArray(Layer3Fx()),
|
|
'enemy_population': EnemyPopulationArrayV,
|
|
'enemy_tilesets': StructArray(EnemyTileset()),
|
|
'nudge_limit': uint16_hex,
|
|
'scrolls': Scrolls(),
|
|
'xray_special_case': StructArray(XraySpecialCase(), terminator = 0),
|
|
'main_code_ptr': CodePtr(0x8f),
|
|
'room_plm': StructArray(RoomPlm(), terminator = 0),
|
|
'bg_data_ptr': LoadBgCmd(),
|
|
'room_setup_code': CodePtr(0x8f),
|
|
}
|
|
|
|
class Room(TLocationBased):
|
|
__name__ = 'Room'
|
|
__bank__ = 0x8f
|
|
__fields__ = {
|
|
'roomnr': uint8,
|
|
'area': uint8,
|
|
'x': uint8,
|
|
'y': uint8,
|
|
'w': uint8,
|
|
'h': uint8,
|
|
'up_scroll': uint8,
|
|
'down_scroll': uint8,
|
|
'cre_bitset': uint8,
|
|
'doorouts': CodePtr(0x8f),
|
|
}
|
|
def parse_door_defs(self, roomdef_ea, lvl_data):
|
|
ea = 0x8f0000 | ROM.get_word(roomdef_ea + 9)
|
|
num_doors = 0
|
|
while True:
|
|
w = ROM.get_word(ea + num_doors * 2)
|
|
if not (w >= 0x88FC and w <= 0xABF0):
|
|
break
|
|
num_doors += 1
|
|
new_ea = 0x8f0000 | ROM.get_word(roomdef_ea + 9)
|
|
assert(new_ea & 0x8000)
|
|
sl = StructArray(Door(), deref_with_bank = 0x83, name = 'DoorOuts', terminator = None)
|
|
size, res = sl.parse_actual_binary(new_ea, {'DoorOuts_size' : num_doors})
|
|
return res
|
|
|
|
kRoomStateSelects = {
|
|
0xe5e6 : 'Default',
|
|
0xe612 : 'IsEventSet',
|
|
0xe629 : 'IsBossDead',
|
|
0xe669 : 'PowerBombs',
|
|
0xe652 : 'MorphBallMissiles',
|
|
0xe5ff : 'TourianBoss01',
|
|
}
|
|
|
|
def parse_actual_binary(self, ea, extra):
|
|
fout, ea_org = Fields(self, ea), ea
|
|
fout.fields['name'] = consts.kRoomNames[(ROM.get_byte(ea + 1), ROM.get_byte(ea))], Name()
|
|
parse_binary_fields(fout, ea, self.__fields__, extra)
|
|
extra = {'Scrolls_size': ROM.get_byte(ea + 4) * ROM.get_byte(ea + 5) }
|
|
ea += 11
|
|
selects = []
|
|
while True:
|
|
f = Fields(RoomState(), ea = None)
|
|
fout.add_list_entry(f, f.tp)
|
|
w = ROM.get_word(ea)
|
|
f.tp.__name__ = self.kRoomStateSelects[w]
|
|
if w == 0xe5e6: # finish
|
|
parse_binary_fields(f, ea + 2, f.tp.__fields__, extra)
|
|
selects.append(f)
|
|
# Add an DoorOuts {} block
|
|
doorouts = self.parse_door_defs(ea_org, ROM.get_long(ea + 2))
|
|
fout.add_list_entry(doorouts[0], doorouts[1])
|
|
del fout.fields['doorouts']
|
|
break
|
|
elif w in (0xe612, 0xe629):
|
|
f.set_args((ROM.get_byte(ea + 2), ))
|
|
ea += 5
|
|
elif w in (0xe669, 0xE652, 0xE5FF):
|
|
ea += 4
|
|
else:
|
|
raise Exception('RoomDefStateSelect unknown 0x%x' % w)
|
|
parse_binary_fields(f, 0x8f0000 | ROM.get_word(ea - 2), f.tp.__fields__, extra)
|
|
selects.append(f)
|
|
|
|
def move_in(ea):
|
|
if ea != 0 and (t := g_locations.get(ea)) and ea not in g_already_emitted:
|
|
g_already_emitted.add(ea)
|
|
fout.add_list_entry(t[0], t[1])
|
|
|
|
for f in selects:
|
|
move_in(f.fields['scrolls'][0])
|
|
|
|
# move in the room plm crap
|
|
for ea in [f.fields['room_plm'][0] for f in selects]:
|
|
if (t := g_locations.get(ea)) == None:
|
|
continue
|
|
for f, tp in t[0].list:
|
|
if tp.__name__ == 'RoomPlm' and (v := f.fields.get('arg_plm')) != None:
|
|
move_in(v[0])
|
|
|
|
# go through fields and create inner blocks
|
|
for movein in ('xray_special_case', 'enemy_population', 'enemy_tilesets', 'room_plm', 'bg_data_ptr', 'layer3_fx', 'data'):
|
|
for f in selects:
|
|
move_in(f.fields[movein][0])
|
|
# go through the nondefault selects and clear the fields that match the defaults
|
|
for nd in selects[:-1]:
|
|
for k, v in list(nd.fields.items()):
|
|
if k in f.fields and v == f.fields[k]:
|
|
del nd.fields[k]
|
|
return ea - ea_org, (fout, self)
|
|
def print_actual_text(self, out, v):
|
|
v.print(out)
|
|
def write_actual_binary(self, writer, fields, extra):
|
|
extra = {'Scrolls_size': fields.dict['w'] * fields.dict['h'] }
|
|
kRoomStateSelectsRev = {a: b for b, a in self.kRoomStateSelects.items()}
|
|
state_select, default, door_outs = [], None, None
|
|
end_ea = 11
|
|
for f in fields.list:
|
|
if (default == None) and f.ident in kRoomStateSelectsRev:
|
|
state_select.append(f)
|
|
end_ea += 5 if f.ident in ('IsEventSet', 'IsBossDead') else (2 if f.ident == 'Default' else 4)
|
|
if f.ident == 'Default': default = f
|
|
elif f.ident == 'DoorOuts':
|
|
assert door_outs == None
|
|
door_outs = f
|
|
else:
|
|
write_global_object(writer, f, extra)
|
|
assert default != None and door_outs != None
|
|
end_ea += writer.get_ea() + 26 * len(state_select)
|
|
fields.dict['doorouts'] = door_outs.name#end_ea & 0xffff
|
|
|
|
for name, tp in self.__fields__.items():
|
|
tp.write_binary(writer, fields.dict.get(name), extra)
|
|
|
|
for f in state_select:
|
|
end_ea -= 26
|
|
writer.write_word(kRoomStateSelectsRev[f.ident])
|
|
if f.ident in ('IsEventSet', 'IsBossDead'):
|
|
writer.write_byte(f.args[0])
|
|
if f.ident != 'Default':
|
|
assert (end_ea >> 16) == 0x8f
|
|
writer.write_word(end_ea & 0xffff)
|
|
for f in state_select[::-1]:
|
|
if f.ident != 'Default':
|
|
for k, v in default.dict.items():
|
|
if k not in f.dict:
|
|
f.dict[k] = v
|
|
RoomState().write_actual_binary(writer, f, extra)
|
|
if door_outs:
|
|
assert len(door_outs.dict) == 0
|
|
if door_outs.name != None:
|
|
writer.cur_addr = get_name_ea(door_outs.name)
|
|
for f in door_outs.list:
|
|
assert f.ident == 'Door'
|
|
write_global_object(writer, f, extra)
|
|
kGlobalObjects['Door'].write_binary(writer, f.name, extra)
|
|
add_global(Room())
|
|
|
|
class LoadStation(TStruct):
|
|
__name__ = 'LoadStation'
|
|
__bank__ = 0x80
|
|
__size__ = 14
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'room': Room(),
|
|
'door': Door(),
|
|
'door_bts' : uint16,
|
|
'screen_x' : uint16,
|
|
'screen_y' : uint16,
|
|
'samus_y' : uint16,
|
|
'samus_x' : uint16,
|
|
}
|
|
add_global(StructArray(LoadStation(), terminator = None))
|
|
|
|
class TileSet(TStruct):
|
|
__name__ = 'TileSet'
|
|
__bank__ = 0x8f
|
|
__size__ = 9
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'tile_table': PackedBlob(),
|
|
'tiles': PackedBlob(),
|
|
'palette': PackedBlob(),
|
|
}
|
|
add_global(TileSet())
|
|
add_global(StructArray(TileSet(), deref_with_bank = 0x8f, terminator = None))
|
|
|
|
class SpriteTilesTransfer(TStruct):
|
|
__name__ = 'SpriteTilesTransfer'
|
|
__bank__ = 0x8f
|
|
__size__ = 7
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'size': uint16,
|
|
'src': Blob(),
|
|
'dst': uint16_hex,
|
|
}
|
|
def parse_actual_binary(self, ea, extra):
|
|
blob_size = ROM.get_word(ea)
|
|
return super().parse_actual_binary(ea, {'blob_size' : blob_size})
|
|
add_global(StructArray(SpriteTilesTransfer(), terminator = 0))
|
|
|
|
class PoseEntry(TStruct):
|
|
__name__ = 'PoseEntry'
|
|
__bank__ = 0x91
|
|
__size__ = 6
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'new': uint16_hex,
|
|
'cur': uint16_hex,
|
|
'pose' : uint16_hex,
|
|
}
|
|
add_global(StructArray(PoseEntry()))
|
|
add_global(MakeArray(StructArray(PoseEntry()), 253, 'PoseTransitionTable', None))
|
|
|
|
class PoseParams(TStruct):
|
|
__name__ = 'PoseParams'
|
|
__bank__ = 0x91
|
|
__size__ = 8
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'x_dir': uint8,
|
|
'move_type': uint8,
|
|
'new_pose' : uint8,
|
|
'dir' : uint8,
|
|
'y_offs' : uint8,
|
|
'junk1' : Skip(1),
|
|
'y_radius' : uint8,
|
|
'junk2' : Skip(1),
|
|
}
|
|
add_global(StructArray(PoseParams(), terminator = None, name = 'PoseParamsList'))
|
|
|
|
class SamusTileAnimationDefs(TStruct):
|
|
__name__ = 'SamusTileAnimationDefs'
|
|
__bank__ = 0x92
|
|
__size__ = 4
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'top_idx': uint8,
|
|
'top_pos': uint8,
|
|
'bottom_idx': uint8,
|
|
'bottom_pos': uint8,
|
|
}
|
|
add_global(StructArray(SamusTileAnimationDefs(), terminator = None, name = 'SamusTileAnimationDefsList'))
|
|
add_global(MakeArray(CodePtr(0x92), 253, 'SamusTileAnimationDefsToplevel', bank = 0x92))
|
|
add_global(MakeArray(CodePtr(0x92), None, 'SamusTileDefsHalf', bank = 0x92))
|
|
|
|
|
|
class SamusTileAnimationTileDefs(TStruct):
|
|
__name__ = 'SamusTileAnimationTileDefs'
|
|
__bank__ = 0x92
|
|
__size__ = 7
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'src': Blob(),
|
|
'part1_size': uint16_hex,
|
|
'part2_size': uint16_hex,
|
|
}
|
|
def parse_actual_binary(self, ea, extra):
|
|
blob_size = ROM.get_word(ea + 3) + ROM.get_word(ea + 5)
|
|
return super().parse_actual_binary(ea, {'blob_size' : blob_size})
|
|
|
|
SamusTileAnimationTileDefsList = StructArray(SamusTileAnimationTileDefs(), terminator = None, name = 'SamusTileAnimationTileDefsList')
|
|
add_global(SamusTileAnimationTileDefsList)
|
|
|
|
class SamusSpeedTableEntry(TStruct):
|
|
__name__ = 'SamusSpeedTableEntry'
|
|
__bank__ = 0x90
|
|
__size__ = 12
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'accel': uint16_hex,
|
|
'accel_sub': uint16_hex,
|
|
'max_speed': uint16_hex,
|
|
'max_speed_sub': uint16_hex,
|
|
'decel': uint16_hex,
|
|
'decel_sub': uint16_hex,
|
|
}
|
|
add_global(StructArray(SamusSpeedTableEntry(), terminator = None, name = 'SamusSpeedTableArray'))
|
|
|
|
ProjectileParserPtr = CodePtr(0x93, visitor = lambda x, e: projectile_parser.visit(mark_address(x)))
|
|
|
|
class ProjectileDataTable(TStruct):
|
|
__name__ = 'ProjectileDataTable'
|
|
__bank__ = 0x93
|
|
__compact__ = True
|
|
__size__ = 0x16
|
|
__fields__ = {
|
|
'dmg': uint16,
|
|
'p0' : ProjectileParserPtr,
|
|
'p1' : ProjectileParserPtr,
|
|
'p2' : ProjectileParserPtr,
|
|
'p3' : ProjectileParserPtr,
|
|
'p4' : ProjectileParserPtr,
|
|
'p5' : ProjectileParserPtr,
|
|
'p6' : ProjectileParserPtr,
|
|
'p7' : ProjectileParserPtr,
|
|
'p8' : ProjectileParserPtr,
|
|
'p9' : ProjectileParserPtr,
|
|
}
|
|
add_global(MakeArray(ProjectileDataTable(), None, 'ProjectileDataTableArray'))
|
|
add_global(ProjectileDataTable())
|
|
|
|
class ProjectileDataTableOnlyOne(TStruct):
|
|
__name__ = 'ProjectileDataTableOnlyOne'
|
|
__bank__ = 0x93
|
|
__compact__ = True
|
|
__size__ = 4
|
|
__fields__ = {
|
|
'dmg': uint16,
|
|
'p0' : ProjectileParserPtr,
|
|
}
|
|
add_global(ProjectileDataTableOnlyOne())
|
|
|
|
EprojParserPtr = CodePtr(0x86, visitor = lambda x, e: eproj_parser.visit(mark_address(x)))
|
|
|
|
class EprojDef(TStruct):
|
|
__name__ = 'EprojDef'
|
|
__bank__ = 0x86
|
|
__size__ = 14
|
|
__fields__ = {
|
|
'init_code': CodePtr(0x86),
|
|
'pre_instr' : CodePtr(0x86),
|
|
'instrs' : EprojParserPtr,
|
|
'radius' : uint16_hex,
|
|
'props' : uint16_hex,
|
|
'hit_instrs' : EprojParserPtr,
|
|
'shot_instrs' : EprojParserPtr,
|
|
}
|
|
add_global(EprojDef())
|
|
|
|
HEX6_REGEXP = re.compile('^.*_([0-9A-Fa-f]{6})$')
|
|
HEX4_REGEXP = re.compile('^.*_([0-9A-Fa-f]{4})$')
|
|
|
|
def get_name_ea(name, bank = None):
|
|
assert isinstance(name, str), name
|
|
if name in name2ea:
|
|
return name2ea[name]
|
|
elif (m := HEX6_REGEXP.match(name)) != None:
|
|
return int(m.group(1), 16)
|
|
elif bank != None and (m := HEX4_REGEXP.match(name)) != None:
|
|
return bank << 16 | int(m.group(1), 16)
|
|
|
|
def write_global_object(writer, f, extra = {}):
|
|
old_addr = writer.cur_addr
|
|
if f.name == None:
|
|
writer.error('no name for %s' % f.ident)
|
|
assert f.name != None
|
|
writer.cur_addr = get_name_ea(f.name)
|
|
if writer.cur_addr == None:
|
|
writer.error('address for %s %s not found' % (f.ident, f.name))
|
|
try:
|
|
kGlobalObjects[f.ident].write_actual_binary(writer, f, extra)
|
|
except:
|
|
print('error writing %s %s' % (f.ident, f.name), file = sys.stderr)
|
|
raise
|
|
writer.cur_addr = old_addr
|
|
|
|
def visit_rooms():
|
|
old_file = push_current_file('rooms')
|
|
def visit_load_stations():
|
|
kLoadStationCounts = [19, 19, 23, 18, 20, 18, 17, 17]
|
|
for i in range(8):
|
|
ea = 0x800000 | ROM.get_word(0x80C4B5 + i * 2)
|
|
visit_location(ea, kGlobalObjects['LoadStations'], {'LoadStations_size': kLoadStationCounts[i]})
|
|
loc = g_locations[ea][0].list
|
|
for f, tp in list(loc):
|
|
ea = f.fields['door'][0]
|
|
if ea != 0 and (t := g_locations.get(ea)) and ea not in g_already_emitted:
|
|
g_already_emitted.add(ea)
|
|
loc.append((t[0], t[1]))
|
|
visit_load_stations()
|
|
visit_location(0x8f91f8, Room(), {})
|
|
pop_current_file(old_file)
|
|
|
|
def visit_blobs():
|
|
old_file = push_current_file('blobs')
|
|
visit_location(0x8FE7A7, kGlobalObjects['TileSets'], {'TileSets_size': 29})
|
|
|
|
kCompressedDataExtra = [
|
|
0xb98000,0xb9a09d,0xb9a09d,0x978DF4,0x978FCD,0x9791C4,0x97938D,0x97953A,
|
|
0x9580D8,0x94E000,0x96FC04,0x9580D8,0x95A5E1,0x95f90e,0x95d089,0x9788cc,
|
|
0x96ff14,0x95e4c2,0x978d12,0x95D713,0x95A82F,0x96FE69,0x96D10A,0x95A82F,
|
|
0x96FE69,0x96D10A,0x978ADB,0x96EC76,0x98bcd6,0x99a56f,0x99d17e,0x988304,
|
|
0x95a82f,0x96fe69,0x98b5c1,0x98b857,0x98baed,0x98bccd,0x97e7de,0x99d65b,
|
|
0x99d932,0x98ED4F,0x999101,0x979803,0x97B957,0x97D7FC,0x97E7DE,0x9796F4,
|
|
0x97F987,0x99DA9F,0x99DAB1,0x99E089,0x99ECC4,0x97EEFF,0xB9FA38,0xB9FE3E,
|
|
]
|
|
for ea in kCompressedDataExtra:
|
|
visit_location(ea, kGlobalObjects['PackedBlob'], {})
|
|
|
|
for ea in [0xA6C4CB, 0xA98F8F, 0xA98FE5, 0xA99003, 0xA9902F, 0xA98FC7]:
|
|
visit_location(ea, kGlobalObjects['SpriteTilesTransfers'], {})
|
|
|
|
for ea, size in [(0xB6F000, 512), (0x8EE400, 512)]:
|
|
visit_location(ea, kGlobalObjects['Blob'], {'blob_size': size})
|
|
|
|
# StartDmaCopy
|
|
for ea, size in [(0x80988b, 0x0040),(0x8e8000, 0x5600),(0x8ed600, 0x0600),(0x8edc00, 0x0800),
|
|
(0x9ab200, 0x2000),(0x9ad200, 0x2e00),(0xa7a716, 0x0200),
|
|
(0xb68000, 0x4000),(0xb6c000, 0x2000),(0xb6e000, 0x0800),(0xb6e800, 0x0800),]:
|
|
visit_location(ea, kGlobalObjects['Blob'], {'blob_size': size})
|
|
|
|
# kPauseMenuMapTilemaps
|
|
for ea, size in [(0xB58000, 0x1000), (0xB59000, 0x1000), (0xB5A000, 0x1000), (0xB5B000, 0x1000),
|
|
(0xB5C000, 0x1000), (0xB5D000, 0x1000), (0xB5E000, 0x1000),]:
|
|
visit_location(ea, kGlobalObjects['Blob'], {'blob_size': size})
|
|
|
|
visit_location(0x9a8200, kGlobalObjects['Blob'], {'blob_size': 1024 * 2})
|
|
visit_location(0x9a8a00, kGlobalObjects['Blob'], {'blob_size': 1024 * 2})
|
|
visit_location(0x9a9200, kGlobalObjects['Blob'], {'blob_size': 1024 * 2})
|
|
|
|
for i in range(12):
|
|
visit_location(0x9a9a00 + i * 512, kGlobalObjects['Blob'], {'blob_size': 256 * 2})
|
|
|
|
pop_current_file(old_file)
|
|
|
|
add_global(MakeArray(uint16, 253, 'SamusPoseToSpritemapIndex'))
|
|
|
|
def visit_samus():
|
|
old_file = push_current_file('samus')
|
|
visit_location(0x919ee2, kGlobalObjects['PoseTransitionTable'], {})
|
|
visit_location(0x91b629, kGlobalObjects['PoseParamsList'], {'PoseParamsList_size': 253})
|
|
kCounts = [32, 30, 32, 17, 17, 17, 17, 20, 10, 16,
|
|
32, 32, 24, 23, 24, 23, 3, 2, 21, 7, 17, 8, 3, 8]
|
|
ea = 0x92cbee
|
|
for count in kCounts:
|
|
visit_location(ea, SamusTileAnimationTileDefsList, {'SamusTileAnimationTileDefsList_size' : count})
|
|
ea += count * 7
|
|
kAnimdefSizes = [96, 9, 9, 2, 2, 1, 1, 1, 1, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 2, 2, 2, 2, 2, 2, 12, 12, 12, 12, 10, 10, 10, 12, 12, 12, 12, 12, 3, 3, 9, 9, 7, 7, 3, 3, 2, 2, 3, 3, 10, 10, 12, 12, 1, 1, 2, 2, 12, 12, 1, 1, 2, 2, 10, 10, 10, 12, 3, 3, 1, 1, 9, 9, 6, 6, 1, 1, 6, 6, 10, 10, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 66, 66, 66, 66, 66, 66, 2, 2, 9, 9, 7, 7, 2, 2, 2, 2, 3, 3, 3, 3, 1, 1, 1, 1, 6, 6, 6, 6, 10, 10, 10, 10, 10, 10, 10, 10, 28, 28, 47, 47, 2, 2, 3, 3, 9, 9, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 96, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 3, 3, 9, 9, 1, 1, 2, 2, 2, 2, 2, 2, 66, 66, 9, 9, 1, 1, 1, 1, 1, 1, 1, 1, 6, 3, 3, 3, 3, 3, 3, 10, 3, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 15, 5, 5, 6, 6, 5, 5, 3, 3, 3, 3, 10, 2, 2, 2, 2, 2, 2, 2, 2, 15, 32, 6, 6, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
|
for i in range(253):
|
|
visit_location(0x920000 | ROM.get_word(0x92D94E + i*2), kGlobalObjects['SamusTileAnimationDefsList'], {'SamusTileAnimationDefsList_size' : kAnimdefSizes[i]})
|
|
visit_location(0x92d91e, kGlobalObjects['SamusTileDefsHalf'], {'SamusTileDefsHalf_size': 13})
|
|
visit_location(0x92d938, kGlobalObjects['SamusTileDefsHalf'], {'SamusTileDefsHalf_size': 11})
|
|
visit_location(0x92d94e, kGlobalObjects['SamusTileAnimationDefsToplevel'], {})
|
|
visit_location(0x92808d, kGlobalObjects['SpriteMapArray'], {'SpriteMapArray_size' : 2096})
|
|
visit_location(0x909F25, kGlobalObjects['SamusSpeedTableArray'], {'SamusSpeedTableArray_size' : 1})
|
|
visit_location(0x909F31, kGlobalObjects['SamusSpeedTableArray'], {'SamusSpeedTableArray_size' : 1})
|
|
visit_location(0x909F3D, kGlobalObjects['SamusSpeedTableArray'], {'SamusSpeedTableArray_size' : 1})
|
|
visit_location(0x909f49, kGlobalObjects['SamusSpeedTableArray'], {'SamusSpeedTableArray_size' : 27})
|
|
visit_location(0x90A08D, kGlobalObjects['SamusSpeedTableArray'], {'SamusSpeedTableArray_size' : 28})
|
|
visit_location(0x90A1DD, kGlobalObjects['SamusSpeedTableArray'], {'SamusSpeedTableArray_size' : 28})
|
|
|
|
visit_location(0x929263, kGlobalObjects['SamusPoseToSpritemapIndex'], {})
|
|
visit_location(0x92945D, kGlobalObjects['SamusPoseToSpritemapIndex'], {})
|
|
|
|
|
|
pop_current_file(old_file)
|
|
|
|
def visit_projectiles():
|
|
old_file = push_current_file('proj')
|
|
visit_location(0x9383C1, MakeArray(ProjectileDataTable(), 12, 'ProjectileDataTableArray'), {})
|
|
visit_location(0x9383D9, MakeArray(ProjectileDataTable(), 12, 'ProjectileDataTableArray'), {})
|
|
visit_location(0x9383F1, MakeArray(CodePtr(0x93), 9, 'ProjectileDataTableArray'), {})
|
|
visit_location(0x938403, MakeArray(CodePtr(0x93), 8, 'ProjectileDataTableArray'), {})
|
|
visit_location(0x938413, MakeArray(CodePtr(0x93), 12, 'ProjectileDataTableArray'), {})
|
|
visit_location(0x93842B, MakeArray(CodePtr(0x93), 3, 'ProjectileDataTableArray'), {})
|
|
for ea in range(0x93866D, 0x938695, 4):
|
|
visit_location(ea, ProjectileDataTableOnlyOne(), {})
|
|
visit_location(0x9386D7, ProjectileDataTableOnlyOne(), {})
|
|
for ea in range(0x938695, 0x9386D7, 22):
|
|
visit_location(ea, ProjectileDataTable(), {})
|
|
for ea in range(0x938641, 0x93866D, 22):
|
|
visit_location(ea, ProjectileDataTable(), {})
|
|
visit_location(0x93a1a1, kGlobalObjects['SpriteMapArray'], {'SpriteMapArray_size' : 66})
|
|
projectile_parser.process_queue()
|
|
for spritemap in projectile_parser.sprite_maps:
|
|
visit_location(spritemap, SpriteMap(bank = 'auto'), {})
|
|
pop_current_file(old_file)
|
|
|
|
PalfxParserPtr = CodePtr(0x8d, visitor = lambda x, e: palfx_parser.visit(mark_address(x)))
|
|
class Palfx(TStruct):
|
|
__name__ = 'Palfx'
|
|
__bank__ = 0x8d
|
|
__size__ = 4
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'code': CodePtr(0x8d),
|
|
'instrs': PalfxParserPtr,
|
|
}
|
|
add_global(Palfx())
|
|
add_global(MakeArray(PalfxParserPtr, 16, 'PalfxArray', bank = 0x8d))
|
|
|
|
def visit_palettes():
|
|
old_file = push_current_file('palettes')
|
|
|
|
for ea in consts.kPalfxDefs:
|
|
visit_location(ea, Palfx(), {})
|
|
|
|
visit_location(0x8de3e0, kGlobalObjects['PalfxArray'], {})
|
|
visit_location(0x8de400, kGlobalObjects['PalfxArray'], {})
|
|
visit_location(0x8de420, kGlobalObjects['PalfxArray'], {})
|
|
|
|
palfx_parser.process_queue()
|
|
|
|
visit_location(0x8CE1E9, kGlobalObjects['Palette256'], {'Palette256_size' : 256})
|
|
visit_location(0x8CE3E9, kGlobalObjects['Palette256'], {'Palette256_size' : 256})
|
|
visit_location(0x8CE5E9, kGlobalObjects['Palette256'], {'Palette256_size' : 256})
|
|
visit_location(0x8CE7E9, kGlobalObjects['Palette256'], {'Palette256_size' : 256})
|
|
visit_location(0x8CE9E9, kGlobalObjects['Palette256'], {'Palette256_size' : 256})
|
|
visit_location(0x8CEBE9, kGlobalObjects['Palette256'], {'Palette256_size' : 256})
|
|
visit_location(0x8CEDE9, kGlobalObjects['Palette256'], {'Palette256_size' : 256})
|
|
visit_location(0x8CEFE9, kGlobalObjects['Palette256'], {'Palette256_size' : 256})
|
|
visit_location(0x9a8000, kGlobalObjects['Palette256'], {'Palette256_size' : 256})
|
|
pop_current_file(old_file)
|
|
|
|
def visit_tilemaps():
|
|
old_file = push_current_file('tilemaps')
|
|
for i in range(20):
|
|
if t := get_word(0x83ABF0 + i*2):
|
|
visit_location(t | 0x8a0000, Blob(), {'blob_size' : 1056 * 2})
|
|
for i in range(6):
|
|
if t := get_word(0x88ADA6 + i*2):
|
|
visit_location(t | 0x8a0000, Blob(), {'blob_size' : 1024 * 2})
|
|
pop_current_file(old_file)
|
|
|
|
def visit_plms():
|
|
old_file = push_current_file('plm')
|
|
pp = Plm()
|
|
for ea in consts.kPlmHeaderAddrs:
|
|
visit_location(ea, pp, {})
|
|
|
|
plm_parser.visit(mark_address(0x84D519))
|
|
plm_parser.process_queue()
|
|
for k, v in plm_parser.blobs.items():
|
|
visit_location(k, Blob(), {'blob_size': v})
|
|
|
|
pop_current_file(old_file)
|
|
|
|
def visit_eproj():
|
|
old_file = push_current_file('eproj')
|
|
for ea in consts.kEprojHeaders:
|
|
visit_location(ea, kGlobalObjects['EprojDef'], {})
|
|
for ea in consts.kEprojInstrLists:
|
|
eproj_parser.visit(mark_address(ea))
|
|
eproj_parser.process_queue()
|
|
for spritemap in eproj_parser.sprite_maps:
|
|
visit_location(spritemap, SpriteMap(bank = 'auto'), {})
|
|
pop_current_file(old_file)
|
|
|
|
class MsgBoxConfig(TStruct):
|
|
__name__ = 'MsgBoxConfig'
|
|
__bank__ = 0x85
|
|
__size__ = 6
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'modify': CodePtr(0x85),
|
|
'draw': CodePtr(0x85),
|
|
'tilemap': CodePtr(0x85),
|
|
}
|
|
add_global(StructArray(MsgBoxConfig(), terminator = None, name = 'MessageBoxConfigArray'))
|
|
|
|
def visit_msgbox():
|
|
old_file = push_current_file('msgbox')
|
|
visit_location(0x85869B, kGlobalObjects['MessageBoxConfigArray'], {'MessageBoxConfigArray_size' : 29})
|
|
arr = sorted(set(0x850000 | ROM.get_word(0x85869B + i * 6 + 4) for i in range(29)))
|
|
for i in range(len(arr)):
|
|
visit_location(arr[i], kGlobalObjects['Tile16'], {'Tile16_size' : (0 if i == (len(arr) - 1) else arr[i + 1] - arr[i]) // 2 })
|
|
pop_current_file(old_file)
|
|
|
|
AnimtilesParserPtr = CodePtr(0x87, visitor = lambda x, e: animtiles_parser.visit(mark_address(x), e['size']))
|
|
|
|
class Animtiles(TStruct):
|
|
__name__ = 'Animtiles'
|
|
__bank__ = 0x87
|
|
__size__ = 6
|
|
__compact__ = True
|
|
__fields__ = {
|
|
'instrs': AnimtilesParserPtr,
|
|
'size': uint16_hex,
|
|
'vram_addr': uint16_hex
|
|
}
|
|
def parse_actual_binary(self, ea, extra):
|
|
return super().parse_actual_binary(ea, {'size' : ROM.get_word(ea + 2)})
|
|
add_global(Animtiles())
|
|
|
|
|
|
def visit_animtiles():
|
|
old_file = push_current_file('animtiles')
|
|
for ea in consts.kAnimtilesDefs:
|
|
visit_location(ea, Animtiles(), {})
|
|
# ia = get_word(ea) | 0x870000
|
|
# size = get_word(ea + 2)
|
|
# animtiles_parser.visit(mark_address(ia), size)
|
|
animtiles_parser.process_queue()
|
|
for k, v in animtiles_parser.blobs.items():
|
|
visit_location(k, Blob(), {'blob_size': v})
|
|
|
|
pop_current_file(old_file)
|
|
|
|
def visit_misc():
|
|
visit_location(0xA0DAFF, kGlobalObjects['Enemy'], {})
|
|
visit_location(0xA0DF7F, kGlobalObjects['Enemy'], {})
|
|
visit_location(0xA0EF3F, kGlobalObjects['Enemy'], {})
|
|
visit_location(0xA0EFBF, kGlobalObjects['Enemy'], {})
|
|
|
|
for x in consts.kEnemyPopulation:
|
|
visit_location(x, kGlobalObjects['EnemyPopulation'], {})
|
|
|
|
def visit_enemy():
|
|
old_file = push_current_file('enemy')
|
|
for k, v in name2ea.items():
|
|
if '_Ilist_' in k and k != 'kRidley_Ilist_DCBA':
|
|
if (v >> 16) != 0xb4:
|
|
enemy_instr_parser.visit(mark_address(v))
|
|
enemy_instr_parser.process_queue()
|
|
|
|
for ea in enemy_instr_parser.sprite_maps:
|
|
if 'ExtSprmap' in ea2name[ea]:
|
|
visit_location(ea, ExtSpriteMap(), {})
|
|
else:
|
|
visit_location(ea, SpriteMap(bank = 'auto'), {})
|
|
|
|
visit_location(0xADC600, Blob(), {'blob_size' : 0x1800})
|
|
|
|
pop_current_file(old_file)
|
|
|
|
for name, obj in inspect.getmembers(inspect.getmodule(inspect.currentframe()), inspect.isclass):
|
|
if hasattr(obj, 'lazy_load') and callable(getattr(obj, 'lazy_load')):
|
|
obj.lazy_load()
|
|
|
|
def compile_code():
|
|
try:
|
|
parser = text_lexer.ParserAndWriter(write_global_object, get_name_ea)
|
|
#parser.output = bytearray(0xff for i in range(1024*1024*3))
|
|
#parser.output_is_set = bytearray(0 for i in range(1024*1024*3))
|
|
parser.output = bytearray(ROM.data)
|
|
parser.output_is_set = bytearray(0 for i in range(1024*1024*3))
|
|
for k in glob.glob('defs/*.txt'):
|
|
parser.parse_toplevel(k)
|
|
parser.apply_relocs()
|
|
open('out.bin', 'wb').write(parser.output)
|
|
# open('out_is_set.bin', 'wb').write(parser.output_is_set)
|
|
print(sum(parser.output_is_set))
|
|
except Exception as e:
|
|
print(e.args)
|
|
raise
|
|
|
|
def visit_all():
|
|
visit_enemy()
|
|
visit_tilemaps()
|
|
visit_plms()
|
|
visit_rooms()
|
|
visit_blobs()
|
|
visit_samus()
|
|
visit_projectiles()
|
|
visit_palettes()
|
|
visit_eproj()
|
|
visit_msgbox()
|
|
visit_animtiles()
|
|
visit_misc()
|
|
|
|
def print_things(what, file):
|
|
out = LinePrinter()
|
|
for k, v in sorted(what.items()):
|
|
if k not in g_already_emitted:
|
|
g_already_emitted.add(k)
|
|
v[1].print_actual_text(out, v[0])
|
|
for l in out.lines:
|
|
print(l, file = file)
|
|
|
|
import os
|
|
try:
|
|
os.mkdir('defs')
|
|
except FileExistsError:
|
|
pass
|
|
|
|
if sys.argv[1] == 'decode':
|
|
visit_all()
|
|
|
|
for k, v in g_file_sets.items():
|
|
with open('defs/%s.txt' % (k if k != '' else 'out'), 'w') as f:
|
|
if k == 'plm':
|
|
plm_parser.print(f)
|
|
elif k == 'eproj':
|
|
eproj_parser.print(f)
|
|
elif k == 'proj':
|
|
projectile_parser.print(f)
|
|
elif k == 'animtiles':
|
|
animtiles_parser.print(f)
|
|
elif k == 'palettes':
|
|
palfx_parser.print(f)
|
|
elif k == 'enemy':
|
|
enemy_instr_parser.print(f)
|
|
print_things(v, file = f)
|
|
|
|
|
|
if sys.argv[1] == 'compile':
|
|
compile_code()
|