import sys, array, hashlib, struct, argparse, os import util from util import cache, get_byte, get_word, get_24, get_bytes, get_words def flatten(xss): return [x for xs in xss for x in xs] def invert_dict(xs): return {s:i for i,s in xs.items()} assets = {} def add_asset_uint8(name, data): assert name not in assets assets[name] = ('uint8', bytes(array.array('B', data))) def add_asset_uint16(name, data): assert name not in assets assets[name] = ('uint16', bytes(array.array('H', data))) def add_asset_int8(name, data): assert name not in assets assets[name] = ('int8', bytes(array.array('b', data))) def add_asset_int16(name, data): assert name not in assets assets[name] = ('int16', bytes(array.array('h', data))) def add_asset_packed(name, data): assert name not in assets assets[name] = ('packed', pack_arrays(data)) def add_asset_blob(name, data): assert name not in assets assets[name] = ('blob', pack_blob(data)) def pack_24(v): assert v >= 0 and v < (1 << 24) return struct.pack('I', v)[:3] # Pack arrays and determine automatically the index size def pack_arrays(arr): if len(arr) == 0: return b'' backmap, fstmap = {}, [] all_offs, offs = [], 0 saved = 0 for i, v in enumerate(arr): v = bytes(v) k = backmap.get(v) if k == None: k = len(all_offs) backmap[v] = k offs += len(v) all_offs.append(offs) else: saved += len(v) fstmap.append(k) assert len(arr) <= 4096 del all_offs[-1] flags = len(arr) - 1 if len(all_offs) == 0 or all_offs[-1] < 65536: r = [struct.pack('H', i) for i in all_offs] + list(backmap.keys()) flags |= 0x8000 else: r = [struct.pack('I', i) for i in all_offs] + list(backmap.keys()) if len(backmap) != len(arr): if len(all_offs) <= 255: r.append(array.array('B', fstmap).tobytes()) else: r.append(array.array('H', fstmap).tobytes()) r.append(struct.pack('H', len(all_offs))) flags |= 0x4000 r.append(struct.pack('H', flags)) return b''.join(r) def pack_blob(ranges): out = [] for a, b in sorted(ranges): if len(out) and a <= out[-1][1]: # continue existing range if b > out[-1][1]: out[-1] = (out[-1][0], b) else: out.append((a, b)) off = 2 + len(out) * 6 a_arr, b_arr, r_arr = [], [], [] for a, b in out: a_arr.append(pack_24(a)) b_arr.append(pack_24(off)) r_arr.append(util.get_bytes(a, b - a)) off += b - a return struct.pack('H', len(out)) + b''.join(a_arr) + b''.join(b_arr) + b''.join(r_arr) def get_stripe_len(ea): ea_org = ea while True: b = util.get_byte(ea) if b & 0x80: return ea + 1 - ea_org p1 = util.get_byte(ea + 1) p2 = util.get_byte(ea + 2) p3 = util.get_byte(ea + 3) ea += 4 if p2 & 0x40: ea += 2 else: ll = ((p2 << 8 | p3) & 0x3fff) + 1 ea += ll def get_rats_size(p): if p == 0: return 0 p -= 8 if (p & 0x8000) == 0: p -= 0x8000 assert util.get_bytes(p, 4) == b'STAR', hex(p) return get_word(p + 4) + 1 def get_rats_bytes(p): return get_bytes(p, get_rats_size(p)) @cache def unpack_rle(ea): ea_org = ea r = bytearray() while util.get_word(ea) != 0xffff: x = util.get_byte(ea); ea += 1 if x & 0x80: y = util.get_byte(ea); ea += 1 for i in range((x & 0x7f) + 1): r.append(y) else: for i in range((x & 0x7f) + 1): y = util.get_byte(ea); ea += 1 r.append(y) return bytes(r), ea + 2 - ea_org def unpack_rle_of_size(ea, size): ea_org = ea r = bytearray() while len(r) < size: x = util.get_byte(ea); ea += 1 if x & 0x80: y = util.get_byte(ea); ea += 1 for i in range((x & 0x7f) + 1): r.append(y) else: for i in range((x & 0x7f) + 1): y = util.get_byte(ea); ea += 1 r.append(y) return bytes(r), ea - ea_org @cache def calc_level_len(ea, dbg = False): if dbg: print('Now decoding level at 0x%x' % ea) ea_org = ea ea += 5 kObjNames = { 60 : 'StoneBlock', 13 : 'CementBlock', 0x22: 'LmMap16', } while True: b0 = util.get_byte(ea); ea += 1 if b0 == 0xff: break b1 = util.get_byte(ea); ea += 1 b2 = util.get_byte(ea); ea += 1 obj_id = (b1 >> 4) | ((b0 & 0x60) >> 1) blocks_size_or_type = b2 if dbg: print(' %.2X %.2X %.2X: 0x%x, 0x%x, %d: %s' % (b0, b1, b2, ea-3, obj_id, blocks_size_or_type, kObjNames.get(obj_id))) if obj_id == 0 and blocks_size_or_type == 0: ea += 1 elif obj_id in (0x22, 0x23): # lunar magic ea += 1 elif obj_id in (0x24, 0x25): # lunar deprecated ea += 0 elif obj_id in (0x27, ): ea += 2 return ea - ea_org LUNAR_MAGIC = get_bytes(0xFF0A0, 5) == b'Lunar' def extra_lunar_magic_text(): return '\n\nDo the following steps:\n1) Use Lunar Magic 3.33.\n' + \ '2) Open up a level, modify something, save it.\n' + \ '3) Open up the 16x16 tile map editor, edit something, save it.\n' + \ '4) Open up the Exanim editor for some level. Edit something, save.\n' if LUNAR_MAGIC: s = get_bytes(0xFF0A0, 24).decode('utf8') print('Detected %s' % s, file = sys.stderr) if s != 'Lunar Magic Version 3.33': raise Exception('Invalid Lunar Magic version. Expected 3.33, found "%s"\n' % s + extra_lunar_magic_text()) if get_byte(0x6F540) != 0xc9: raise Exception('The map16 file format is incorrect. ' + extra_lunar_magic_text()) def get_comp_data(ea): data, comp_len = util.decomp(ea, util.get_byte, return_length = True) return util.get_bytes(ea, comp_len) def decomp_data(ea): data, comp_len = util.decomp(ea, util.get_byte, return_length = True) return data kLmFeature_LmEnabled = 1 << 0 kLmFeature_Exanim = 1 << 1 kLmFeature_SkipOverworldDecompress = 1 << 2 kLmFeature_OverworldTiles4bpp = 1 << 3 kLmFeature_Copy512colors = 1 << 4 kLmFeature_WeirdPalette = 1 << 5 kLmFeature_SkipLoadPaletteHook = 1 << 6 kLmFeature_GfxUpload = 1 << 7 kLmFeature_LoadLevel = 1 << 8 kLmFeature_4bppgfx = 1 << 9 kLmFeature_CustomTitleScreenDemo = 1 << 10 kLmFeature_CustomDisplayMessage = 1 << 11 kLmFeature_DontSetYposForIntroMarch = 1 << 12 kLmFeature_OwPalette = 1 << 13 kLmFeature_LevelNamesPatch = 1 << 14 kLmFeature_DestroyTileAnims = 1 << 15 kLmFeature_EventStuff = 1 << 16 kLmFeature_MusicRegTweak = 1 << 17 kLmFeature_TideWaterTweak = 1 << 18 kLmFeature_EnemyCollTweak = 1 << 19 kLmFeature_Ow4bppGfx = 1 << 20 kLmFeature_DontResetOwPlayersMap = 1 << 21 kLmFeature_NonStdGfxAA8D = 1 << 22 kLmFeature_TimerTweaks = 1 << 23 kLmFeature_NoDefaultSavePrompts = 1 << 24 kHack_Walljump = 1 << 0 class LmFeatures: flags = 0 kLmLvlInfo_addr = 0 kLmLvlInfo_addr_other = 0 hacks = 0 def serialize(self): return struct.pack('IIII', self.flags, self.kLmLvlInfo_addr, self.kLmLvlInfo_addr_other, self.hacks) def print_all(args): lm_feat = LmFeatures() r = [] lo, hi, bank = util.get_bytes(0xB992, 50), util.get_bytes(0xB9c4, 50), util.get_bytes(0xB9f6, 50) for i in range(50): p = bank[i] << 16 | hi[i] << 8 | lo[i] r.append(get_comp_data(p)) add_asset_packed('kGraphicsPtrs', r) data, comp_len = util.decomp(0x80000 | util.get_word(0xB8D8), util.get_byte, return_length = True) #print('%d: %d: %d' % (0x32, comp_len, len(data))) add_asset_uint8('kGfx32', data) data, comp_len = util.decomp(0x80000 | util.get_word(0xB88B), util.get_byte, return_length = True) #print('%d: %d: %d' % (0x33, comp_len, len(data))) add_asset_uint8('kGfx33', data) r = [b''] for i in range(1, 86): p = util.get_24(0x84D0 + i * 3) pl = get_stripe_len(p) #if i == 1: #print(util.get_bytes(p, 16)) r.append(util.get_bytes(p, pl)) add_asset_packed('kLoadStripeImagePtrs', r) r = [] for i in range(45): p = util.get_24(0x59000 + i * 3) pl = get_stripe_len(p) r.append(util.get_bytes(p, pl)) add_asset_packed('kLayer3ImagePtrs', r) add_asset_uint8('kSpcCreditsMusicBank', util.get_bytes(0x3e400, 6624)) add_asset_uint8('kSpcLevelMusicBank', util.get_bytes(0xEAED6, 16899)) add_asset_uint8('kSpcEngine', util.get_bytes(0xe8000, 6321) + b'\x00\x00') add_asset_uint8('kSpcSamples', util.get_bytes(0xf8000, 28538)) add_asset_uint8('kSpcOverworldMusicBank', util.get_bytes(0xe98b1, 5667)) add_asset_uint16('kMap16Data_OverworldLayer1', util.get_words(0x05d000, 772)) add_asset_uint16('kMap16Data', util.get_words(0xd8000, (0xA100 - 0x8000) // 2)) add_asset_uint16('kMap16Data_Castle', util.get_words(0xdbc00, 712)) add_asset_uint16('kMap16Data_Rope', util.get_words(0xdc800, 712)) add_asset_uint16('kMap16Data_Underground', util.get_words(0xdd400, 712)) add_asset_uint16('kMap16Data_GhostHouse', util.get_words(0xde300, 712)) add_asset_uint16('kGlobalPalettes_Sky', util.get_words(0x00B0A0, 16)) add_asset_uint16('kGlobalPalettes_Background', util.get_words(0x00B0B0, 96)) add_asset_uint16('kGlobalPalettes_Layer3', util.get_words(0xB170, 16)) add_asset_uint16('kGlobalPalettes_Foreground', util.get_words(0x00B190, 96)) add_asset_uint16('kGlobalPalettes_Objects', util.get_words(0x00B250, 60)) add_asset_uint16('kPlayerPalettes', util.get_words(0x00B2C8, 40)) add_asset_uint16('kGlobalPalettes_Sprites', util.get_words(0x00B318, 84)) add_asset_uint16('kGlobalPalettes_YoshiBerry', util.get_words(0x00B674, 21)) add_asset_uint16('kGlobalPalettes_Flashing', util.get_words(0x00B60C, 16)) add_asset_uint16('kGlobalPalettes_OW_Objects', util.get_words(0xB528, 42)) add_asset_uint16('kGlobalPalettes_OW_Sprites', util.get_words(0xB57C, 56)) add_asset_uint16('kGlobalPalettes_B5EC', util.get_words(0xB5EC, 16)) add_asset_uint16('kGlobalPalettes_OW_Areas', util.get_words(0xB3D8, 168)) add_asset_uint16('kGlobalPalettes_OW_AreasPassed', util.get_words(0xB732, 168)) add_asset_uint16('kGlobalPalettes_Bowser', util.get_words(0xB69E, 56)) add_asset_uint16('kGlobalPalettes_Layer3Smasher', util.get_words(0xB66C, 4)) #add_asset_uint16('', util.get_words(0x, )) add_asset_uint8('kGameMode1B_EndingCinema_Tilemaps', util.get_bytes(0xC95C7, 1873)) add_asset_uint16('kGameMode1B_EndingCinema_RowPointers', util.get_words(0xC9D18, 202)) add_asset_uint8('kLevelInfo_05F000', util.get_bytes(0x5f000, 0x200)) add_asset_uint8('kLevelInfo_05F200', util.get_bytes(0x5f200, 0x200)) add_asset_uint8('kLevelInfo_05F400', util.get_bytes(0x5f400, 0x200)) add_asset_uint8('kLevelInfo_05F600', util.get_bytes(0x5f600, 0x200)) add_asset_uint8('kLoadLevel_DATA_05D608', util.get_bytes(0x5D608, 0x100)) add_asset_uint8('kDisplayMessage_DATA_05A5D9', util.get_bytes(0x5A5D9, 2854)) add_asset_uint8('kOverworldLightningAndRandomCloudSpawning', util.get_bytes(0x4F708, 128)) add_asset_uint16('kLevelNames', util.get_words(0x4A0FC, 256)) add_asset_uint8('kLineGuideSpeedTableData', util.get_bytes(0x7F9DB, 536)) def add_packed_levels(name, addr, num, bank = None): r = [] for i in range(num): try: if bank == None: ea = util.get_24(addr + i * 3) else: ea = util.get_word(addr + i * 2) | bank << 16 ln = calc_level_len(ea) r.append(util.get_bytes(ea, ln)) except: print('\n*** Crashed while decoding level 0x%x at 0x%x ***' % (i, ea), file = sys.stderr) print('*** Try opening it in LM and then saving it ***', file = sys.stderr) add_asset_packed(name, r) add_packed_levels('kLevelData_Layer1', 0x5E000, 0x200) add_packed_levels('kEntranceData_Layer1', 0x5d766, 6) add_packed_levels('kChoclateIsland2_Layer1', 0x5DB08, 9, bank = 6) add_packed_levels('kRollCallData_Layer1', 0xCAD58, 13, bank = 0xc) def add_packed_level_bg(name, addr, num, mode = None): r = [] fls = [] for i in range(num): if mode == None: ea = util.get_24(addr + i * 3) elif mode == 'choc': ea = util.get_word(addr + i * 2) | 0xff0000 elif mode == 'end': ea = util.get_word(addr + i * 2) | (0xff0000 if i != 12 else 0xc0000) else: assert 0 fl = util.get_byte(0xEF310 + i) if num == 0x200 and LUNAR_MAGIC else 0 if (ea & 0xff0000) == 0xff0000: ea = (ea & 0xffff) | 0xc0000 fl = ((ea & 0xffff) >= 0xE8FE) << 4 | 2 fls.append(fl) if fl & 2: packed, ln = unpack_rle(ea) else: ln = calc_level_len(ea) r.append(util.get_bytes(ea, ln)) add_asset_packed(name, r) add_asset_uint8(name + '_IsBg', fls) add_packed_level_bg('kLevelData_Layer2', 0x5E600, 0x200) add_packed_level_bg('kEntranceData_Layer2', 0x5d778, 6) add_packed_level_bg('kChoclateIsland2_Layer2', 0x5DB2C, 9, mode = 'choc') add_packed_level_bg('kRollCallData_Layer2', 0xCAD72, 13, mode = 'end') add_packed_level_bg('kBufferCreditsBackgrounds_Layer2', 0xc93c1, 7, mode = 'choc') def get_sprite_data_len(ea): ea_org = ea ea += 1 while util.get_byte(ea) != 0xff: ea += 3 return ea + 1 - ea_org def remove_trail_zero(s): lx = len(s) while lx and s[lx - 1] == 0: lx -= 1 return s[:lx] def remove_trail_empty(s): lx = len(s) while lx and len(s[lx - 1]) == 0: lx -= 1 return s[:lx] def add_sprites(): spr_ranges = [] def touch(ea): lx = get_sprite_data_len(ea) spr_ranges.append((ea, ea + lx)) banks = util.get_bytes(0xef100, 512) if get_byte(0x05D8F5) == 0x22 else [7] * 512 for i in range(0x200): touch(util.get_word(0x5ec00 + i * 2) | (banks[i] << 16)) touch(0x7c3ee) for i in range(9): touch(util.get_word(0x5DB1A + i * 2) | 0x70000) add_asset_blob('kLvlSprBlob', spr_ranges) add_asset_uint8('kLmSpritePtrBankByte', banks) add_sprites() add_asset_uint16('kLoadLevel_SpriteDataPtrs', util.get_words(0x5EC00, 0x200)) add_asset_uint8('kFileSelectText_EraseFile', util.get_bytes(0x5B6FE, 203 + 204)) add_asset_uint8('kInitializeMode7TilemapsAndPalettes_TilemapData', util.get_bytes(0x3D9DE, 912)) def do_u16(name, addr, org_addr, org_size, bank_addr = None): p = get_24(addr) if bank_addr == None else util.get_word(addr) | get_byte(bank_addr) << 16 sz = get_rats_size(p) // 2 if p != org_addr else org_size add_asset_uint16(name, util.get_words(p, sz)) assert p == org_addr or LUNAR_MAGIC return p != org_addr def do_u8(name, addr, org_addr, org_size, bank_addr = None): p = get_24(addr) if bank_addr == None else util.get_word(addr) | get_byte(bank_addr) << 16 sz = get_rats_size(p) if p != org_addr else org_size add_asset_uint8(name, util.get_bytes(p, sz)) assert p == org_addr or LUNAR_MAGIC return p != org_addr def add_event_tile_entries(): do_u16('kLayer2EventData_TileEntries', 0x4E49F, 0x4DD8D, 742) do_u16('kChangingLayer1OverworldTiles_Layer1TileLocation', 0x4EC8C, 0x4D85D, 112) do_u16('kOwEventProcess01_DestroyTileAnimation_DATA_04E587', 0x4EEC9, 0x4E587, 16) if do_u16('kCheckIfDestroyTileEventIsActive_DATA_04E5B6', 0x4E69C, 0x4E5B6, 16): lm_feat.flags |= kLmFeature_DestroyTileAnims do_u16('kOwEventProcess01_DestroyTileAnimation_DATA_04D93D', 0x4EDB8, 0x4D93D, 112) do_u8('kOwEventProcess07_SilentEventsAndEndOfEvent_SilentEventTiles', 0x4E9F4, 0x4E8E4, 44) do_u8('kOwEventProcess07_SilentEventsAndEndOfEvent_SilentEventTiles_TileLayer', 0x4EA27, 0x4E910, 44) do_u16('kOwEventProcess07_SilentEventsAndEndOfEvent_SilentEventTiles_TileNum', 0x4EA31+1, 0x4E994, 44) do_u16('kOwEventProcess07_SilentEventsAndEndOfEvent_SilentEventTiles_TilemapLocation', 0x4EA37+1, 0x4E93C, 44) add_asset_uint8('kOwDestruction_TileToIdx_04E5A7', get_bytes(0x4e5a7, 5)) add_asset_uint8('kOwDestruction_TopTile_04E5AC', get_bytes(0x4e5ac, 5)) add_asset_uint8('kOwDestruction_BottomTile_04E5B1', get_bytes(0x4e5b1, 5)) do_u8('kOwDestruction_TriggerEvent_04E5D6', 0x4E67C, 0x4e5d6, 16) add_event_tile_entries() def add_eventstuff(): def get_words_rats(p): return get_words(p, get_rats_size(p) // 2) def get_u8_rats(p): return get_bytes(p, get_rats_size(p)) r1, r2, r3, r4 = [], [], [], [], if get_byte(0x4E9F7) == 0x22: lm_feat.flags |= kLmFeature_EventStuff p = get_24(0x4E9F8) r1 = get_words_rats(get_24(p - 0x8008 + 0x8014 + 1)) r2 = get_words_rats(get_24(p - 0x8008 + 0x8029 + 1)) r3 = get_words_rats(get_24(p - 0x8008 + 0x802F + 1)) r4 = get_u8_rats(get_24(p - 0x8008 + 0x803B + 1)) add_asset_uint16('kLmEventStuff1', r1) add_asset_uint16('kLmEventStuff2', r2) add_asset_uint16('kLmEventStuff3', r3) add_asset_uint8('kLmEventStuff4', r4) add_eventstuff() do_u8('kOverworldLayer2EventTilemap_Tiles', 0x4EAF5, 0xc8000, 3328) p = get_word(0x4DC72) | get_byte(0x4DC79) << 16 _, sz = unpack_rle_of_size(p, 0x2000) add_asset_uint8('kLoadOverworldLayer2AndEventsTilemaps_OverworldLayer2Tilemap', get_bytes(p, sz)) p = get_word(0x4DC8d) | get_byte(0x4DC79) << 16 _, sz = unpack_rle_of_size(p, 0x2000) add_asset_uint8('kLoadOverworldLayer2AndEventsTilemaps_OverworldLayer2Tilemap_Prop', get_bytes(p, sz)) do_u8('kOverworldLayer2EventTilemap_Prop', addr = 0x4DD45, org_addr = 0xC8D00, org_size = 1642, bank_addr = 0x4DD4a) add_asset_uint8('kLoadOverworldLayer1AndEvents_DATA_0CF7DF', util.get_bytes(0xCF7DF, 0x800)) add_asset_uint8('kRom', util.ROM.ROM if args.include_rom else b'') add_asset_uint8('kUpdateLevelName_LevelNameStrings', util.get_bytes(0x49ac5, 460)) add_asset_uint8('kGameMode25_ShowEnemyRollcallScreen_TileData', util.get_bytes(0xCAF11, 1681)) lm_pals = [] if LUNAR_MAGIC and get_24(0xef577) == 0xf58320: for i in range(0x200): pp = util.get_24(0xEF600 + i * 3) lm_pals.append(util.get_bytes(pp, 0x202) if pp else b'') add_asset_packed('kLmPalettes', lm_pals) add_asset_uint8('kPlayerGFXRt_HeadTilePointerIndex', util.get_bytes(0xE00C, 192)) add_asset_uint8('kPlayerGFXRt_BodyTilePointerIndex', util.get_bytes(0xE0CC, 192)) add_asset_uint8('kLvlInitialFlags', util.get_bytes(0x5DDA0, 96)) add_asset_uint8('kLoadOverworldSprites_SpriteSlotData', util.get_bytes(0x4F625, 65)) add_asset_uint8('kChangingLayer1OverworldTiles_TilesThatChange', util.get_bytes(0x4DA1D, 22)) add_asset_uint8('kChangingLayer1OverworldTiles_TilesToBecome', util.get_bytes(0x4DA33, 22)) add_asset_uint8('kOverworldEventProcess01_DestroyTileAnimation_DATA_04EE7A', util.get_bytes(0x4EE7A, 48)) add_asset_uint16('kLevelTileAnimations_FrameData', util.get_words(0x5B999, 208)) add_asset_uint8('kSetPlayerPose_WalkingPoseCount', util.get_bytes(0xDC78, 4)) add_asset_uint8('kDrawLoadingLetters_TileData', util.get_bytes(0x90d1, 52)) add_asset_uint8('kDrawLoadingLetters_TileData_BottomTiles', util.get_bytes(0x9105, 52)) add_asset_uint8('kDrawLoadingLetters_TileData_TopProp', util.get_bytes(0x9139, 52)) add_asset_uint8('kDrawLoadingLetters_TileData_BottomProp', util.get_bytes(0x916A, 52)) add_asset_uint16('kOwTileAnimations_WaterTileNumbers', util.get_words(0x48000, 3)) add_asset_uint16('kOwTileAnimations_TileNumbers', util.get_words(0x48006, 64)) def add_star_pipe_warp(): if get_byte(0x48509) == 0x22: p = get_24(0x4850a) n = get_word(p + 0xe00f - 0xdfff) // 2 add_asset_uint16('kOwStarPipeWarp_SrcX_048431', get_words(get_24(p + 0xe016 - 0xdfff), n)) add_asset_uint16('kOwStarPipeWarp_SrcY_048467', get_words(get_24(p + 0xe026 - 0xdfff), n)) assert get_byte(0x48566) == 0x22 p = get_24(0x48567) add_asset_uint16('kOwStarPipeWarp_DstX_04849D', get_words(get_24(p + 0xe04b - 0xe03f), n)) add_asset_uint16('kOwStarPipeWarp_DstY_0484D3', get_words(get_24(p + 0xe05d - 0xe03f), n)) else: add_asset_uint16('kOwStarPipeWarp_SrcX_048431', get_words(0x48431, 27)) add_asset_uint16('kOwStarPipeWarp_SrcY_048467', get_words(0x48467, 27)) add_asset_uint16('kOwStarPipeWarp_DstX_04849D', get_words(0x4849d, 27)) add_asset_uint16('kOwStarPipeWarp_DstY_0484D3', get_words(0x484d3, 27)) add_star_pipe_warp() add_asset_uint16('kOwLevelsForcedMusicChange_048D74', get_words(0x48d74, 11)) add_asset_uint8('kOwSubmapMusic_048D8A', get_bytes(0x48d8a, 7)) add_asset_uint16('kOw_KoopaKidTeleportXYPos_048E49', get_words(0x48e49, 6)) add_asset_uint8('kOwTriggerSaveTiles_048F7F', get_bytes(0x48f7f, 8)) add_asset_uint16('kOwNoAutoMoveLevels_04906C', get_words(0x4906c, 6)) add_asset_uint8('kOwHardcodedPathLevel_049078', get_bytes(0x49078, 10)) add_asset_uint16('kOwHardcodedPathChocolateIsland2_049082', get_words(0x49082, 2)) add_asset_uint8('kOwHardcodedPathTiles_049086', get_bytes(0x49086, 68)) add_asset_uint8('kOwHardcodedPathDirs_0490CA', get_bytes(0x490ca, 68)) add_asset_uint8('kOwHardcodedPathStartIndex_04910E', get_bytes(0x4910e, 10)) add_asset_uint8('kOwExitLevelTiles_049426', get_bytes(0x49426, 10)) add_asset_uint16('kUpdateLevelName_DATA_049C91', get_words(0x49c91, 31)) add_asset_uint16('kUpdateLevelName_DATA_049CCF', get_words(0x49ccf, 15)) add_asset_uint16('kUpdateLevelName_DATA_049CED', get_words(0x49ced, 13)) add_asset_uint8('kOwExitSource_049964', get_bytes(0x49964, 70)) add_asset_uint8('kOwExitDest_0499AA', get_bytes(0x499aa, 70)) add_asset_uint8('kOwExitExtra_0499F0', get_bytes(0x499f0, 28)) add_asset_uint16('kOwExitLayerPosition_049A0C', get_words(0x49a0c, 12)) add_asset_uint8('kOwUnknownTableA_From_04A03C', get_bytes(0x4a03c, 24)) add_asset_uint16('kOwUnknownTableA_Alpha_04A054', get_words(0x4a054, 24)) add_asset_uint16('kOwUnknownTableA_XY_04A084', get_words(0x4a084, 48)) add_asset_uint8('kOwUnknownTableA_Direction_04A0E4', get_bytes(0x4a0e4, 24)) add_asset_uint8('kOwDirectionAfterBeatingLevel_04D678', get_bytes(0x4d678, 113)) add_asset_uint8('kOwSubmapTileset_04DC02', get_bytes(0x4dc02, 7)) add_asset_uint16('kLayer2EventData_Ptrs_04E359', get_words(0x4e359, 121)) add_asset_uint8('kLmInitSaveData', get_bytes(0x05DDA0, 96) if get_byte(0x5dd80) != 0xff else b'') add_asset_uint8('kInitializeSaveData_InitialOWPlayerPos', get_bytes(0x9EF0, 22)) add_asset_uint16('kOWSpr07_Smoke_DATA_04FC1E', get_words(0x4FC1E, 4)) add_asset_uint16('kLoadOverworldSprites_SubmapBooXPosOffset', get_words(0x4F666, 3)) add_asset_uint16('kLoadOverworldSprites_SubmapBooYPosOffset', get_words(0x4F66C, 3)) add_asset_uint8('kLoadLevelHeader_LevelMusicTable', get_bytes(0x584DB, 8)) # hack add_asset_uint8('kLevelsThatTriggerCutscenes', get_bytes(0xC9A7, 8) + bytearray([get_byte(0xCA0C), get_byte(0xCA13)])) #add_asset_uint16('', util.get_words(0x, )) #add_asset_uint8('', util.get_bytes(0x, )) def add_exgfx(name, addr, size): r = [] if LUNAR_MAGIC and addr != 0xffffff: for i in range(size): p = util.get_24(addr + i * 3) r.append(get_comp_data(p) if p not in (0, 0xffffff) else b'') while len(r) and r[-1] == b'': r.pop() add_asset_packed(name, r) add_exgfx('kLmExgfx', 0xFF600, 128) add_exgfx('kLmSuperExgfx', get_24(0xFF937), 0x1000 - 0x100) add_asset_uint8('kLmGraphicsRemapped', remove_trail_zero(util.get_bytes(0xFF200, 1024)) if LUNAR_MAGIC else b'') def add_load_level_stuff(): lm_load_level = get_byte(0x5D9A1) == 0x22 p = get_24(0x6F624) add_asset_uint16('kLmModifyMap16Ids', util.get_words(p, 4096) if lm_load_level and p != 0xffffff else b'') add_asset_uint8('kLm5DE00', util.get_bytes(0x5DE00, 512) if lm_load_level else b'') add_asset_uint8('kLm6FC00', util.get_bytes(0x6FC00, 512) if lm_load_level else b'') add_asset_uint8('kLm6FE00', util.get_bytes(0x6FE00, 512) if lm_load_level else b'') p = util.get_24(util.get_24(0x5D9A2) + 0x10BBDF - 0x10BB83) if lm_load_level else 0 add_asset_uint8('kLm10B8BC', util.get_bytes(p, 512) if p != 0 else b'') add_asset_uint8('kLmLevelData3FE00', util.get_bytes(0x3FE00, 512) if lm_load_level else b'') add_asset_uint8('kLmLevelData5DC85', util.get_bytes(get_24(0x5DC86), 512) if lm_load_level else b'') add_asset_uint8('kLmLevelData5DC8A', util.get_bytes(get_24(0x5DC8B), 512) if lm_load_level else b'') add_asset_uint8('kLm5FE00', util.get_bytes(get_24(0x5DC81), 512) if lm_load_level else b'') add_asset_uint8('kLevelInfo_05F800', util.get_bytes(get_24(0xde191), 0x200) if lm_load_level else b'') add_asset_uint8('kLevelInfo_05FA00', util.get_bytes(get_24(0xde198), 0x200) if lm_load_level else b'') add_asset_uint8('kLevelInfo_05FC00', util.get_bytes(get_24(0xde19f), 0x200) if lm_load_level else b'') lm_feat.flags |= kLmFeature_LoadLevel if lm_load_level else 0 add_load_level_stuff() def add_lm_map16(): if LUNAR_MAGIC: B = 0x06F500 half = (get_byte(0x6F54B) != 0xB0) B -= 2 if half else 0 arr = [ get_byte(B + 0x57)<<16|(get_word(B + 0x53)+0x1000&0xFFFF), get_byte(B + 0x60)<<16|(get_word(B + 0x5C)^0x8000), get_byte(B + 0x6B)<<16|(get_word(B + 0x67)+1 & 0xFFFF), get_byte(B + 0x74)<<16|(get_word(B + 0x70)+0x8001&0xFFFF), ] if not half: arr += [ get_byte(B + 0x98)<<16|get_word(B + 0x94), get_byte(B + 0xA1)<<16|(get_word(B + 0x9D)+0x8000&0xFFFF), get_byte(B + 0xAC)<<16|(get_word(B + 0xA8)+1 & 0xFFFF), get_byte(B + 0xB5)<<16|(get_word(B + 0xB1)+0x8001&0xFFFF), ] else: print('Warning: Half map16', file=sys.stderr) arr += [0, 0, 0, 0] arr.append(get_byte(B + 0x8A)<<16|(get_word(B + 0x86)+0x1000&0xFFFF)), # TS else: arr = [0 for i in range(9)] for i, p in enumerate(arr): sz = get_rats_size(p) add_asset_uint16('kMap16_%s' % ('TS' if i == 8 else i), get_words(p, sz // 2)) add_lm_map16() #add_asset_uint16('kMap16Bank11', util.get_words(0x118000, 16384) if LUNAR_MAGIC else b'') #add_asset_uint16('kMap16Bank12', util.get_words(0x128000, 12288) if LUNAR_MAGIC else b'') def calc_one_exanim_end(p): p_org = p tp = util.get_byte(p) trigger = util.get_byte(p + 1) if tp >= 1 and tp <= 0x13: limit = util.get_byte(p + 2) p += 5 # type, trigger, limit, dest(w) p += (limit + 1) * 2 * (1 if trigger==0 else 2) return p raise Exception('exanim type %x not supported' % tp) def calc_exanim_size(p): p_org = p num = util.get_word(p) trig = util.get_word(p + 6) p += 8 for i in range(16): if trig & (1 << i): p += 2 # manual triggers max_p = p for i in range(num): pd = util.get_word(p + i * 2) max_p = max(max_p, calc_one_exanim_end(p + pd)) return max_p - p_org # parse all level exanimation lm_lvl_exanim = [] lm_exanim_ranges = [] if LUNAR_MAGIC and get_byte(0x0583AD) == 0x22: # call LmHook_InitExanimForLevel LmHook_InitExanimForLevel = get_24(0x583AE) if get_bytes(LmHook_InitExanimForLevel, 4) != b'\xe2\x30\x8b\xa2': raise Exception('The Exanim file format is incorrect. ' + extra_lunar_magic_text()) exanim_ptr = get_24((0x10C24E - 0x10C164) + LmHook_InitExanimForLevel) # 0x10CD9C for i in range(512): p = util.get_24(exanim_ptr + i * 3) lm_lvl_exanim.append(0 if (p & 0x8000) == 0 else p) if (p & 0x8000) != 0: sz = calc_exanim_size(p) lm_exanim_ranges.append((p, p + sz)) lm_feat.flags |= kLmFeature_Exanim add_asset_uint8('kLmLvlExAnim', b''.join(pack_24(a) for a in remove_trail_zero(lm_lvl_exanim))) add_asset_blob('kLmExanimBlob', lm_exanim_ranges) kSkipOwDecompressMap = { b'\xea\xea\xea\xea' : kLmFeature_SkipOverworldDecompress, b'\x22\x00\xFC\x0E' : 0, # lm b'\x22\x28\xBA\x00' : 0, # orig } lm_feat.flags |= kLmFeature_LmEnabled if LUNAR_MAGIC else 0 lm_feat.flags |= kSkipOwDecompressMap[bytes(get_bytes(0xA149, 4))] lm_feat.flags |= kLmFeature_OverworldTiles4bpp if get_byte(0x480D0) == 0x60 else 0 lm_feat.flags |= kLmFeature_Copy512colors if get_byte(0xA5E1) == 0xea else 0 lm_feat.flags |= kLmFeature_WeirdPalette if get_byte(0xAF71) == 0x22 else 0 lm_feat.flags |= kLmFeature_SkipLoadPaletteHook if get_byte(0xEF570) != 0xc2 else 0 lm_feat.flags |= kLmFeature_GfxUpload if get_bytes(0xAA6B, 4) != b'\x22\x28\xBA\x00' else 0 lm_feat.flags |= kLmFeature_4bppgfx if get_byte(0xAACE) == 0x10 else 0 lm_feat.flags |= kLmFeature_DontSetYposForIntroMarch if get_byte(0x05B15D) == 0xea else 0 lm_feat.flags |= kLmFeature_MusicRegTweak if get_byte(0x5855C) == 0x8d else 0 lm_feat.flags |= kLmFeature_TideWaterTweak if get_byte(0xa045) == 0x22 else 0 lm_feat.flags |= kLmFeature_EnemyCollTweak if get_byte(0x194B6) == 0x5c else 0 lm_feat.flags |= kLmFeature_Ow4bppGfx if get_bytes(0xA149, 4) != b'\x22\x28\xBA\x00' else 0 lm_feat.flags |= kLmFeature_DontResetOwPlayersMap if get_byte(0xa0a0) == 0xea else 0 lm_feat.flags |= kLmFeature_NonStdGfxAA8D if get_byte(0xAA8D) != 0x08 else 0 lm_feat.flags |= kLmFeature_TimerTweaks if get_byte(0x58E24) == 0x8f else 0 lm_feat.flags |= kLmFeature_NoDefaultSavePrompts if get_byte(0x3BA26) == 0 else 0 # Allows Mario to perform a wall kick by sliding along a wall and pressing the # B button. lm_feat.hacks |= kHack_Walljump if get_24(0xA2A1) != 0x86F122 else 0 def add_custom_ow_palette(): r = b'' if get_byte(0xAD32) == 0x22: lm_feat.flags |= kLmFeature_OwPalette p = get_24(0xAD33) - 0x10813F r = get_rats_bytes(get_byte(p + 0x10815D) << 16 | get_word(p + 0x108151)) add_asset_uint8('kLmOverworldPal', r) add_custom_ow_palette() def add_custom_display_message(): flag = get_byte(0x5B1A3) == 0x22 lm_feat.flags |= kLmFeature_CustomDisplayMessage if flag else 0 add_asset_uint8('kLmDisplayMessage_Tab0', get_rats_bytes(get_24(0x3BC0B)) if flag else b'') add_asset_uint16('kLmDisplayMessage_3BC7F', get_words(0x3BC7F, 8) if flag else b'') add_asset_uint16('kLmDisplayMessage_3BE80', get_words(0x3BE80, 192) if flag else b'') r = [] if flag: for a in [0x3BB9A, 0x3BBA1, 0x3BBA6, 0x3BBAB, 0x3BBB0]: assert get_byte(a) == 0xe0 r.append(get_byte(a+1)) add_asset_uint8('kLmDisplayMessage_Tab1', r) add_custom_display_message() def add_custom_title_screen(): lm_feat.flags |= kLmFeature_CustomTitleScreenDemo if get_byte(0x9c6f) == 0x22 else 0 r = b'' if lm_feat.flags & kLmFeature_CustomTitleScreenDemo: p = get_24(get_24(0x9c70) + 0x10F6B0 - 0x10F68D) - 2 r = get_bytes(p, get_rats_size(p)) add_asset_uint8('kLmTitleScreenMoves', r) add_custom_title_screen() def add_level_names_patch(): lm_feat.flags |= kLmFeature_LevelNamesPatch if get_byte(0x48E81) == 0x22 else 0 r = b'' if lm_feat.flags & kLmFeature_LevelNamesPatch: p = get_24(0x3BB57) r = get_bytes(p, get_rats_size(p)) add_asset_uint8('kLmLevelNamesPatch', r) add_level_names_patch() # compressed layer1 and events def add_overworld(): d = [] if get_byte(0x4d813) == 0x5c: d = get_comp_data(get_byte(0x4d808) << 16 | get_word(0x4d803)) add_asset_uint8('kOwLayer1AndEvents', d) d = [] if get_byte(0x4d832) == 0x5c: d = get_comp_data(get_byte(0x4d827) << 16 | get_word(0x4d822)) add_asset_uint8('kOwLayer1AndEvents2', d) add_overworld() r = b'' if LUNAR_MAGIC and get_byte(0xA140) == 0x22: lm_feat.kLmLvlInfo_addr_other = get_byte(0xFFAC2) << 16 | get_word(0xFFAB9) lm_feat.kLmLvlInfo_addr = get_24(0xFF7FF) r = util.get_words(lm_feat.kLmLvlInfo_addr, (512+8) * 16) add_asset_uint16('kLmLvlInfo', r) #add_lm_lvlinfo() def add_custom_map16_bg(): r = [] if LUNAR_MAGIC: for i in range(16): p = util.get_24(0xEFD50 + i * 3) if p: r.append(util.get_bytes(p, 0x8000 - (p & 0x7fff))) else: r.append(b'') add_asset_packed('kLmCustomMap16Bg', remove_trail_empty(r)) add_custom_map16_bg() def add_sprite_extra_size(): r = b'' if LUNAR_MAGIC: p = util.get_24(0xef30c) if p != 0xffffff: r = util.get_bytes(p, 1024) add_asset_uint8('kLmSprExtraSize', r) add_sprite_extra_size() add_asset_uint8('kLmFeatures', lm_feat.serialize()) def write_assets_to_file(print_header = False): key_sig = b'' all_data = [] if print_header: print('''#pragma once #include "../src/types.h" enum { kNumberOfAssets = %d }; extern const uint8 *g_asset_ptrs[kNumberOfAssets]; extern uint32 g_asset_sizes[kNumberOfAssets]; extern MemBlk FindInAssetArray(int asset, int idx); extern const uint8 *FindPtrInAsset(int asset, uint32 addr); ''' % len(assets)) for i, (k, (tp, data)) in enumerate(assets.items()): if print_header: if tp == 'packed': print('#define %s(idx) FindInAssetArray(%d, idx)' % (k, i)) elif tp == 'blob': print('#define %s(addr) FindPtrInAsset(%d, addr)' % (k, i)) else: if tp == 'uint8': print('#define %s g_asset_ptrs[%d]' % (k, i)) else: print('#define %s ((%s*)g_asset_ptrs[%d])' % (k, tp, i)) print('#define %s_SIZE (g_asset_sizes[%d])' % (k, i)) key_sig += k.encode('utf8') + b'\0' all_data.append(data) assets_sig = b'Smw_v0 \n\0' + hashlib.sha256(key_sig).digest() if print_header: print('#define kAssets_Sig %s' % ", ".join((str(a) for a in assets_sig))) hdr = assets_sig + b'\x00' * 32 + struct.pack('II', len(all_data), len(key_sig)) encoded_sizes = array.array('I', [len(i) for i in all_data]) file_data = hdr + encoded_sizes + key_sig for v in all_data: while len(file_data) & 3: file_data += b'\0' file_data += v open('smw_assets.dat', 'wb').write(file_data) def main(args): print_all(args) write_assets_to_file(args.print_assets_header) if __name__ == "__main__": ROM = util.load_rom(sys.argv[1] if len(sys.argv) >= 2 else None) class DefaultArgs: print_assets_header = True include_rom = True main(DefaultArgs()) else: ROM = util.ROM