Files
sm-vita/assets/restool.py
Snesrev f591d89f84 Some tooling to extract assets
Run:
restool.py decode
to extract assets into assets/defs/*

Run:
restool.py compile
to compile the assets back into a rom
2023-04-17 20:29:29 +02:00

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()