ref: 8f4b242dac63c422aba98fae7f0ff13fafad507b
dir: /tables/extract_resources.py/
from ast import literal_eval as make_tuple import sys import text_compression import util from util import get_bytes, get_words, get_byte, get_word, get_int8, get_int16, cache import tables import yaml import extract_music import os import sprite_sheets def print_map32_to_map16(f): for i in range(2218): def getit(ea): ov = [get_byte(ea + j) for j in range(6)] res=[0]*4 res[0] = ov[0] | (ov[4] >> 4) << 8 res[1] = ov[1] | (ov[4] & 0xf) << 8 res[2] = ov[2] | (ov[5] >> 4) << 8 res[3] = ov[3] | (ov[5] & 0xf) << 8 return res t0 = getit(0x838000 + i * 6) t1 = getit(0x83b400 + i * 6) t2 = getit(0x848000 + i * 6) t3 = getit(0x84b400 + i * 6) for j in range(4): print('%5d: %4d, %4d, %4d, %4d' % (i * 4 + j, t0[j], t1[j], t2[j], t3[j]), file = f) @cache def get_exit_datas(): r = {} for i in range(79): room = get_word(0x82dd8a + i * 2) screen_index = get_byte(0x82DE28 + i) load_offs = get_word(0x82DE77 + i * 2) scroll_y = get_word(0x82DF15 + i * 2) scroll_x = get_word(0x82DFB3 + i * 2) pos_y = get_word(0x82E051 + i * 2) pos_x = get_word(0x82E0EF + i * 2) camera_y = get_word(0x82E18D + i * 2) camera_x = get_word(0x82E22B + i * 2) unk1 = get_int8(0x82E2C9 + i) unk3 = get_int8(0x82E318 + i) ndoor = get_word(0x82E367 + i * 2) fdoor = get_word(0x82E405 + i * 2) base_x = (screen_index & 7) << 9 base_y = (screen_index & 56) << 6 y = { 'index' : i, 'room' : room, 'xy' : [pos_x - base_x, pos_y - base_y], 'scroll_xy' : [scroll_x - base_x, scroll_y - base_y], 'camera_xy' : [camera_x - base_x, camera_y - base_y], } y['load_xy'] = [((load_offs >> 1) - (y['scroll_xy'][0] >> 4)) & 0x3f, (load_offs >> 7) - (y['scroll_xy'][1] >> 4) & 0x3f] y['unk'] = [unk1, unk3] def get_special_exit_info(room_index): return { 'dir' : get_byte(0x82E801 + room_index - 0x180) >> 1, 'spr_gfx' : get_byte(0x82E811 + room_index - 0x180), 'aux_gfx' : get_byte(0x82E821 + room_index - 0x180), 'pal_bg' : get_byte(0x82E831 + room_index - 0x180), 'pal_spr' : get_byte(0x82E841 + room_index - 0x180), 'top' : get_word(0x82e6e1 + (room_index - 0x180) * 2), 'bottom' : get_word(0x82e701 + (room_index - 0x180) * 2), 'left' : get_word(0x82e721 + (room_index - 0x180) * 2), 'right' : get_word(0x82e741 + (room_index - 0x180) * 2), 'left_edge_of_map' : get_word(0x82E7E1 + (room_index - 0x180) * 2), 'unk4' : get_int16(0x82e761 + (room_index - 0x180) * 2), 'unk6' : get_int16(0x82e781 + (room_index - 0x180) * 2), 'unk5' : get_int16(0x82e7a1 + (room_index - 0x180) * 2), 'unk7' : get_int16(0x82e7c1 + (room_index - 0x180) * 2), } if room >= 0x180 and room < 0x190: y['special_exit'] = get_special_exit_info(room) if ndoor != 0: assert fdoor == 0 y['door'] = ['bombable' if ndoor & 0x8000 else 'wooden', (ndoor & 0x7e) >> 1, (ndoor & 0x3f80) >> 7] if fdoor != 0: y['door'] = ['palace' if fdoor & 0x8000 else 'sanctuary', (fdoor & 0x7e) >> 1, (fdoor & 0x3f80) >> 7] r.setdefault(screen_index, []).append(y) return r def get_loadoffs(c, d): x, y = c[0] >> 4, c[1] >> 4 x += d[0] y += d[1] return (y&0x3f) << 7 | (x&0x3f) << 1 @cache def get_ow_travel_infos(): r = {} for i in range(17): screen_index = get_word(0x82EAE5 + i * 2) load_offs = get_word(0x82EB07 + i * 2) scroll_y = get_word(0x82EB29 + i * 2) scroll_x = get_word(0x82EB4B + i * 2) pos_y = get_word(0x82EB6D + i * 2) pos_x = get_word(0x82EB8F + i * 2) camera_y = get_word(0x82EBB1 + i * 2) camera_x = get_word(0x82EBD3 + i * 2) unk1 = get_int8(0x82EBF5 + i * 2) unk3 = get_int8(0x82EC17 + i * 2) base_x = (screen_index & 7) << 9 base_y = (screen_index & 56) << 6 y = {} if i < 9: y['bird_travel_id'] = i else: y['whirlpool_src_area'] = get_word(0x82ECF8 + (i - 9) * 2) y['xy'] = [pos_x - base_x, pos_y - base_y] y['scroll_xy'] = [scroll_x - base_x, scroll_y - base_y] y['camera_xy'] = [camera_x - base_x, camera_y - base_y] y['load_xy'] = [((load_offs >> 1) - (y['scroll_xy'][0] >> 4)) & 0x3f, (load_offs >> 7) - (y['scroll_xy'][1] >> 4) & 0x3f] t0 = get_loadoffs(y['scroll_xy'], y['load_xy']) assert t0 == load_offs, (t0 & 0x7f, load_offs & 0x7f) y['unk'] = [unk1, unk3] r.setdefault(screen_index, []).append(y) return r @cache def get_ow_entrance_info(): r = {} for i in range(129): area = get_word(0x9BB96F + i * 2) pos = get_word(0x9BBA71 + i * 2) entrance_id = get_byte(0x9BBB73 + i) r.setdefault(area, []).append({'index' : i, 'x' : (pos >> 1) & 0x3f, 'y' : (pos >> 7) & 0x3f, 'entrance_id' : entrance_id}) return r @cache def get_hole_infos(): r = {} for i in range(19): pos = get_word(0x9BB800 + i * 2) + 0x400 area = get_word(0x9BB826 + i * 2) entrance_id = get_byte(0x9BB84C + i) r.setdefault(area, []).append({'x' : (pos >> 1) & 0x3f, 'y' : (pos >> 7) & 0x3f, 'entrance_id' : entrance_id}) return r def print_overworld_area(overworld_area): is_small = get_bytes(0x82F88D, 192) y = {} def get_music(ambient): def fn(x): if ambient: return tables.kAmbientSoundName[x >> 4] else: return tables.kMusicNames[x & 0xf] if overworld_area < 64: return { 'beginning' : fn(get_byte(0x82C303 + overworld_area)), 'zelda' : fn(get_byte(0x82C303 + overworld_area + 64)), 'sword' : fn(get_byte(0x82C303 + overworld_area + 128)), 'agahnim' : fn(get_byte(0x82C303 + overworld_area + 192)), } else: assert overworld_area < 64 + 96 return {'agahnim' : fn(get_byte(0x82C403 + overworld_area - 64)) } def get_items(): if overworld_area >= 128: return [] ea = 0x9b0000 | get_word(0x9BC2F9 + overworld_area * 2) xs = [] while get_word(ea) != 0xffff: pos = get_word(ea) assert pos%2==0 x, y = pos//2%64, pos//2//64 xs.append([x, y, tables.kSecretNames[get_byte(ea+2)]]) ea += 3 return xs header = { 'name' : tables.kAreaNames[overworld_area], 'size' : 'small' if is_small[overworld_area] else 'big', 'gfx' : get_byte(0x80FC9C + overworld_area) if overworld_area < 128 else -1, 'palette' : get_byte(0x80FD1C + overworld_area) if overworld_area < 136 else -1, 'sign_text' : get_word(0x87F51D + overworld_area * 2) if overworld_area < 128 else -1, 'music' : get_music(False), 'ambient' : get_music(True), } y['Header'] = header y['Travel'] = get_ow_travel_infos().get(overworld_area, []) y['Entrances'] = get_ow_entrance_info().get(overworld_area, []) hole_infos = get_hole_infos() if overworld_area in hole_infos: y['Holes'] = hole_infos[overworld_area] y['Exits'] = get_exit_datas().get(overworld_area, []) y['Items'] = get_items() def decode_sprites(base_addr): r = [] ea = 0x890000 + get_word(base_addr + overworld_area * 2) while get_byte(ea) != 0xff: y, x, w = get_byte(ea), get_byte(ea+1), get_byte(ea+2) r.append([x, y, tables.kSpriteNames[w]]) ea += 3 return r def get_info(stage): if overworld_area >= 128: return {} if overworld_area >= 64: stage = 3 return { 'gfx' : get_byte(0x80FA41 + (overworld_area & 63) + stage * 64), 'palette' : get_byte(0x80FB41 + (overworld_area & 63) + stage * 64), } if overworld_area < 64: y['Sprites.Beginning'] = { 'info' : get_info(0), 'sprites' : decode_sprites(0x89C881) } y['Sprites.FirstPart'] = { 'info' : get_info(1), 'sprites' : decode_sprites(0x89C901) } y['Sprites.SecondPart'] = { 'info' : get_info(2), 'sprites' : decode_sprites(0x89CA21) } elif overworld_area < 144: y['Sprites'] = { 'info' : get_info(2), 'sprites' : decode_sprites(0x89CA21) } s = yaml.dump(y, default_flow_style=None, sort_keys=False) open('overworld/overworld-%d.yaml' % overworld_area, 'w').write(s) def print_all_overworld_areas(): area_heads = get_bytes(0x82A5EC, 64) for i in range(160): if i >= 128 or area_heads[i&63] == (i&63): print_overworld_area(i) def print_dialogue(): text_compression.print_strings(open('dialogue.txt', 'w'), get_byte) def decode_room_objects(p): objs = [] j = 0 while True: p0, p1, p2 = get_byte(p), get_byte(p+1), get_byte(p+2) A = p0 | p1 << 8 if A == 0xffff: return p + 2, objs, None if A == 0xfff0: p += 2 break if (A & 0xfc) != 0xfc: index = p2 Dst = (p1 >> 2) << 7 Dst |= (p0 & 0xfc) >> 1 X = (Dst >> 1) & 0x3f Y = (Dst >> 7) & 0x3f W = p0 & 3 H = p1 & 3 if index < 0xf8: objs.append({'x' : X, 'y' : Y, 's' : '%d*%d' % (W, H), 'n': tables.kType0Names[index]}) else: index2 = (index & 7) << 4 | H << 2 | W objs.append({'x' : X, 'y' : Y, 'n': tables.kType1Names[index2]}) else: # subtype 2: 111111xx xxxxyyyy yyiiiiii X = ((p0 << 4 | p1 >> 4) & 0x3f) Y = (p1 << 2 | p2 >> 6) & 0x3f index = p2 & 0x3f objs.append({'x' : X, 'y' : Y, 'n' : tables.kType2Names[index]}) p += 3 j += 1 doors = [] while True: A = get_byte(p) | get_byte(p+1) << 8 if A == 0xffff: return p + 2, objs, doors doors.append({'type':get_byte(p + 1), 'pos' : get_byte(p) >> 4, 'dir' : A & 3}) p += 2 @cache def get_chest_info(): ea = 0x81e96e all = {} for i in range(504//3): room = get_word(ea + i * 3) data = get_byte(ea + i * 3 + 2) all.setdefault(room & 0x7fff, []).append((data, (room & 0x8000) != 0)) return all def _get_entrance_info_one(i, set): def get_exit_door(i): x = get_word((0x82D724, 0x82DC32)[set] + i * 2) if x == 0: return ['none'] if x == 0xffff: return ['none_0xffff'] return ['bombable' if x & 0x8000 else 'wooden', (x & 0x7e) >> 1, (x & 0x3f80) >> 7] kQuadrantNames = { 0 : 'upper_left', 2 : 'lower_left', 16 : 'upper_right', 18 : 'lower_right' } room = get_word((0x82C813, 0x82DB6E )[set] + i * 2) def get_se(se_base_addr, xy, quds): base_x = (room & 0xf) * 2 base_y = (room >> 4) * 2 ym = (xy[1] & 0x100) >> 8 xm = (xy[0] & 0x100) >> 8 #if room == 259: ym = 1 # possibly related to none_0xffff hu = get_byte(se_base_addr + i * 8 + 0) - base_y - ym fu = get_byte(se_base_addr + i * 8 + 1) - base_y hd = get_byte(se_base_addr + i * 8 + 2) - base_y - ym fd = get_byte(se_base_addr + i * 8 + 3) - base_y - 1 #assert fu == 0 and fd == 0 and hd == 0 and fd == 0, (room, fu, fd, hd, fd) qqq = xm if room >= 242 and quds[0] == 'single_x' else 0 hl = get_byte(se_base_addr + i * 8 + 4) - base_x - xm fl = get_byte(se_base_addr + i * 8 + 5) - base_x - qqq hr = get_byte(se_base_addr + i * 8 + 6) - base_x - xm fr = get_byte(se_base_addr + i * 8 + 7) - base_x - 1 - qqq return [hu, fu, hd, fd, hl, fl, hr, fr]# player_x = get_word((0x82D063, 0x82DBDE)[set] + i * 2) - ((room & 0x00f) << 9) player_y = get_word((0x82CF59, 0x82DBD0)[set] + i * 2) - ((room & 0x1f0) << 5) y = { ('entrance_index' if set == 0 else 'starting_point_index'): i, 'name' : tables.kEntranceNames[i] if set == 0 else 'Starting Location %d' % i, 'scroll_xy' : [ get_word((0x82CD45, 0x82DBB4)[set] + i * 2) - ((room & 0x00f) << 9), get_word((0x82CE4F, 0x82DBC2)[set] + i * 2) - ((room & 0x1f0) << 5), ], 'player_xy' : [ player_x, player_y ], 'camera_xy' : [ get_word((0x82D277, 0x82DBFA)[set] + i * 2), get_word((0x82D16D, 0x82DBEC)[set] + i * 2), ], 'blockset' : get_byte((0x82D381, 0x82DC08)[set] + i), 'music' : tables.kMusicNames[get_byte((0x82D82E, 0x82DC4E)[set] + i)], 'palace' : tables.kPalaceNames[(get_int8((0x82D48B, 0x82DC16)[set] + i) + 2) >> 1], 'doorway_orientation' : get_int8(0x82D510 + i) if set == 0 else 0, 'plane' : get_byte((0x82D595, 0x82DC1D)[set] + i) & 0xf, 'ladder_level' : get_byte((0x82D595, 0x82DC1D)[set] + i) >> 4, 'quadrants' : [ 'double_x' if (get_byte((0x82D61a, 0x82DC24)[set] + i) & 0x20) != 0 else 'single_x', 'double_y' if (get_byte((0x82D61a, 0x82DC24)[set] + i) & 0x2) else 'single_y', kQuadrantNames[get_byte((0x82D69F, 0x82DC2B)[set] + i)] ], 'floor' : get_int8((0x82D406, 0x82DC0F)[set] + i), } se = get_se((0x82C91D, 0x82DB7C)[set], y['player_xy'], y['quadrants']) if se != [0, 0, 0, 0, 0, 0, 0, 0]: y['repair_scroll_bounds'] = se y['house_exit_door'] = get_exit_door(i) # quadrant_byte = (2 if player_y & 0x100 else 0) + (16 if player_x & 0x100 else 0) # if get_byte((0x82D69F, 0x82DC2B)[set] + i) != quadrant_byte: # print('Room %d has incorrect quadrant byte' % room, y['quadrants']) if set: y['associated_entrance_index'] = get_word(0x82DC40 + i * 2) return room, y @cache def get_entrance_info(set): r = {} for i in range(133 if set == 0 else 7): room, y = _get_entrance_info_one(i, set) r.setdefault(room, []).append(y) return r @cache def pits_hurt_player(): return set(get_word(0x80990C + i * 2) for i in range(57)) def print_room(room_index): p = 0x1f8000 + room_index * 3 room_addr = get_byte(p) | get_byte(p+1) << 8 | get_byte(p+2) << 16 p = 0x40000 | get_word(0x4f502 + room_index * 2) if p == 0x4FFEF: p = 0x82EDC5 # just some place with zeros floor, layout = get_byte(room_addr), get_byte(room_addr + 1) flags = get_byte(p + 0) p7, p8 = get_byte(p + 7), get_byte(p + 8) ea = 0x890000 + get_word(0x89D62E + room_index * 2) sort_sprites_setting = get_byte(ea) header = { 'floor1': floor & 0xf, 'floor2' : floor >> 4, 'layout': layout >> 2, 'start_quadrant' : layout & 3, 'bg2' : tables.kBg2[flags >> 5], 'collision' : tables.kCollisionNames[flags >> 2 & 7], 'lights_out' : flags & 1, 'palette' : get_byte(p + 1), 'blockset' : get_byte(p + 2), 'enemyblk' : get_byte(p + 3), 'effect' : tables.kEffectNames[get_byte(p + 4)], 'tag0' : tables.kTagNames[get_byte(p + 5)], 'tag1' : tables.kTagNames[get_byte(p + 6)], 'hole0_dest' : [get_byte(p+9), p7 & 3], 'stair0_dest' : [get_byte(p+10), p7 >> 2 & 3], 'stair1_dest' : [get_byte(p+11), p7 >> 4 & 3], 'stair2_dest' : [get_byte(p+12), p7 >> 6 & 3], 'stair3_dest' : [get_byte(p+13),p8 & 3], 'tele_msg' : get_word(0x87F61D+room_index*2), 'sort_sprites' : sort_sprites_setting, 'pits_hurt_player' : room_index in pits_hurt_player() } def get_sprites(): ea = 0x890000 + get_word(0x89D62E + room_index * 2) ea += 1 r = [] while get_byte(ea) != 0xff: y, x, type = get_byte(ea), get_byte(ea+1), get_byte(ea+2) if type == 0xe4: if y == 0xfe or y == 0xfd: r[-1].append('drop_key' if y == 0xfe else 'drop_big_key') ea += 3 continue elif x >= 0xe0: floor = y >> 7 r.append([x & 0x1f, y & 0x1f, 'lower' if floor else 'upper', tables.kSpriteNames[type + 0x100]]) ea += 3 continue subtype = (x >> 5) | ((y >> 5) & 3) << 3 floor = y >> 7 name = tables.kSpriteNames[type] if subtype != 0: i = name.index('-') name = name[:i] + ('.%d' % subtype) + name[i:] r.append([x & 0x1f, y & 0x1f, 'lower' if floor else 'upper', name]) ea += 3 return r def get_secrets(): ea = 0x810000 | get_word(0x81db69 + room_index * 2) xs = [] while get_word(ea) != 0xffff: pos = get_word(ea) assert pos%2==0 x, y = pos//2%64, pos//2//64 xs.append([x, y, tables.kSecretNames[get_byte(ea+2)]]) ea += 3 return xs def get_chests(): r = [] for data, big in get_chest_info().get(room_index, []): if big: r.append('%d!' % data) else: r.append(data) return r sprites = get_sprites() secrets = get_secrets() data = {'Header' : header, 'Sprites' : sprites, 'Secrets' : secrets, 'Chests' : get_chests()} data['Entrances'] = get_entrance_info(0).get(room_index, []) if room_index in get_entrance_info(1): data['StartingPoints'] = get_entrance_info(1)[room_index] p = room_addr + 2 p, objs, doors = decode_room_objects(p) data['Layer1'] = objs if doors: data['Layer1.doors'] = doors p, objs, doors = decode_room_objects(p) data['Layer2'] = objs if doors: data['Layer2.doors'] = doors p, objs, doors = decode_room_objects(p) data['Layer3'] = objs if doors: data['Layer3.doors'] = doors return yaml.dump(data, default_flow_style=None, sort_keys=False) def print_all_dungeon_rooms(): for i in range(320): s = print_room(i) open( 'dungeon/dungeon-%d.yaml' % i, 'w').write(s) def print_default_rooms(): def print_default_room(idx): p = 0x84EF2F + idx * 3 room_addr = get_byte(p) | get_byte(p+1) << 8 | get_byte(p+2) << 16 p, objs, doors = decode_room_objects(room_addr) assert doors == None return objs default_rooms = {} for i in range(8): default_rooms['Default%d' % i] = print_default_room(i) s = yaml.dump(default_rooms, default_flow_style=None, sort_keys=False) open('dungeon/default_rooms.yaml', 'w').write(s) def print_overlay_rooms(): def print_overlay_room(idx): p = 0x84ECC0 + idx * 3 room_addr = get_byte(p) | get_byte(p+1) << 8 | get_byte(p+2) << 16 p, objs, doors = decode_room_objects(room_addr) assert doors == None return objs default_rooms = {} for i in range(19): default_rooms['Overlay%d' % i] = print_overlay_room(i) s = yaml.dump(default_rooms, default_flow_style=None, sort_keys=False) open('dungeon/overlay_rooms.yaml', 'w').write(s) def make_directories(): os.makedirs('img', exist_ok=True) os.makedirs('overworld', exist_ok=True) os.makedirs('dungeon', exist_ok=True) os.makedirs('sound', exist_ok=True) def print_all_text_stuff(): print_all_overworld_areas() print_all_dungeon_rooms() print_overlay_rooms() print_default_rooms() print_dialogue() print_map32_to_map16(open('map32_to_map16.txt', 'w')) def main(): make_directories() print_all_text_stuff() extract_music.extract_sound_data(ROM) sprite_sheets.decode_link_sprites() sprite_sheets.decode_sprite_sheets() ROM = util.load_rom(sys.argv[1] if len(sys.argv) >= 2 else None) main() #sprite_sheets.decode_sprite_sheets()