802 lines
26 KiB
Python
802 lines
26 KiB
Python
#!/usr/bin/env python3
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import xml.etree.ElementTree as ET
|
|
from collections import defaultdict
|
|
from math import factorial
|
|
from itertools import count
|
|
import fileinput
|
|
|
|
import urllib.request
|
|
import urllib.parse
|
|
import yaml
|
|
from pathlib import Path
|
|
|
|
|
|
def int16_as_bytes(value):
|
|
value = value & 0xFFFF
|
|
return [value & 0xFF, (value >> 8) & 0xFF]
|
|
|
|
|
|
def int24_as_bytes(value):
|
|
value = value & 0xFFFFFF
|
|
return [value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF]
|
|
|
|
|
|
def int32_as_bytes(value):
|
|
value = value & 0xFFFFFFFF
|
|
return [value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF]
|
|
|
|
|
|
def pc_to_snes(value):
|
|
return ((value << 1) & 0x7F0000) | (value & 0x7FFF) | 0x8000
|
|
|
|
|
|
def snes_to_pc(value):
|
|
return ((value & 0x7F0000) >> 1) | (value & 0x7FFF)
|
|
|
|
|
|
def parse_player_names(names, players, teams):
|
|
names = [n for n in re.split(r'[, ]', names) if n]
|
|
ret = []
|
|
while names or len(ret) < teams:
|
|
team = [n[:16] for n in names[:players]]
|
|
while len(team) != players:
|
|
team.append(f"Player {len(team) + 1}")
|
|
ret.append(team)
|
|
|
|
names = names[players:]
|
|
return ret
|
|
|
|
|
|
def is_bundled():
|
|
return getattr(sys, 'frozen', False)
|
|
|
|
|
|
def local_path(path):
|
|
# just do stuff here and bail
|
|
return os.path.join(".", path)
|
|
|
|
if local_path.cached_path is not None:
|
|
return os.path.join(local_path.cached_path, path)
|
|
|
|
if is_bundled():
|
|
# we are running in a bundle
|
|
local_path.cached_path = sys._MEIPASS # pylint: disable=protected-access,no-member
|
|
else:
|
|
# we are running in a normal Python environment
|
|
local_path.cached_path = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
return os.path.join(local_path.cached_path, path)
|
|
|
|
|
|
local_path.cached_path = None
|
|
|
|
|
|
def output_path(path):
|
|
if output_path.cached_path is None:
|
|
output_path.cached_path = '.'
|
|
return os.path.join(output_path.cached_path, path)
|
|
|
|
|
|
output_path.cached_path = None
|
|
|
|
|
|
def open_file(filename):
|
|
if sys.platform == 'win32':
|
|
os.startfile(filename)
|
|
else:
|
|
open_command = 'open' if sys.platform == 'darwin' else 'xdg-open'
|
|
subprocess.call([open_command, filename])
|
|
|
|
|
|
def close_console():
|
|
if sys.platform == 'win32':
|
|
# windows
|
|
import ctypes.wintypes
|
|
try:
|
|
ctypes.windll.kernel32.FreeConsole()
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def make_new_base2current(old_rom='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', new_rom='working.sfc'):
|
|
from collections import OrderedDict
|
|
import json
|
|
import hashlib
|
|
with open(old_rom, 'rb') as stream:
|
|
old_rom_data = bytearray(stream.read())
|
|
with open(new_rom, 'rb') as stream:
|
|
new_rom_data = bytearray(stream.read())
|
|
# extend to 2 mb
|
|
old_rom_data.extend(bytearray([0x00] * (2097152 - len(old_rom_data))))
|
|
|
|
out_data = OrderedDict()
|
|
for idx, old in enumerate(old_rom_data):
|
|
new = new_rom_data[idx]
|
|
if old != new:
|
|
out_data[idx] = [int(new)]
|
|
for offset in reversed(list(out_data.keys())):
|
|
if offset - 1 in out_data:
|
|
out_data[offset-1].extend(out_data.pop(offset))
|
|
with open('data/base2current.json', 'wt') as outfile:
|
|
json.dump([{key: value} for key, value in out_data.items()], outfile, separators=(",", ":"))
|
|
|
|
basemd5 = hashlib.md5()
|
|
basemd5.update(new_rom_data)
|
|
return "New Rom Hash: " + basemd5.hexdigest()
|
|
|
|
|
|
def kth_combination(k, l, r):
|
|
if r == 0:
|
|
return []
|
|
elif len(l) == r:
|
|
return l
|
|
else:
|
|
i = ncr(len(l)-1, r-1)
|
|
if k < i:
|
|
return l[0:1] + kth_combination(k, l[1:], r-1)
|
|
else:
|
|
return kth_combination(k-i, l[1:], r)
|
|
|
|
|
|
def ncr(n, r):
|
|
if r == 0 or r >= n:
|
|
return 1
|
|
return factorial(n) // factorial(r) // factorial(n-r)
|
|
|
|
|
|
entrance_offsets = {
|
|
'Sanctuary': 0x2,
|
|
'HC West': 0x3,
|
|
'HC South': 0x4,
|
|
'HC East': 0x5,
|
|
'Eastern': 0x8,
|
|
'Desert West': 0x9,
|
|
'Desert South': 0xa,
|
|
'Desert East': 0xb,
|
|
'Desert Back': 0xc,
|
|
'TR Lazy Eyes': 0x15,
|
|
'TR Eye Bridge': 0x18,
|
|
'TR Chest': 0x19,
|
|
'Aga Tower': 0x24,
|
|
'Swamp': 0x25,
|
|
'Palace of Darkness': 0x26,
|
|
'Mire': 0x27,
|
|
'Skull 2 West': 0x28,
|
|
'Skull 2 East': 0x29,
|
|
'Skull 1': 0x2a,
|
|
'Skull 3': 0x2b,
|
|
'Ice': 0x2d,
|
|
'Hera': 0x33,
|
|
'Thieves': 0x34,
|
|
'TR Main': 0x35,
|
|
'GT': 0x37,
|
|
'Skull Pots': 0x76,
|
|
'Skull Left Drop': 0x77,
|
|
'Skull Pinball': 0x78,
|
|
'Skull Back Drop': 0x79,
|
|
'Sewer Drop': 0x81
|
|
}
|
|
|
|
entrance_data = {
|
|
'Room Ids': (0x14577, 2),
|
|
'Relative coords': (0x14681, 8),
|
|
'ScrollX': (0x14AA9, 2),
|
|
'ScrollY': (0x14BB3, 2),
|
|
'LinkX': (0x14CBD, 2),
|
|
'LinkY': (0x14DC7, 2),
|
|
'CameraX': (0x14ED1, 2),
|
|
'CameraY': (0x14FDB, 2),
|
|
'Blockset': (0x150e5, 1),
|
|
'FloorValues': (0x1516A, 1),
|
|
'Dungeon Value': (0x151EF, 1),
|
|
'Frame on Exit': (0x15274, 1),
|
|
'BG Setting': (0x152F9, 1),
|
|
'HV Scroll': (0x1537E, 1),
|
|
'Scroll Quad': (0x15403, 1),
|
|
'Exit Door': (0x15488, 2),
|
|
'Music': (0x15592, 1)
|
|
}
|
|
|
|
|
|
def read_layout_data(old_rom='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc'):
|
|
with open(old_rom, 'rb') as stream:
|
|
old_rom_data = bytearray(stream.read())
|
|
|
|
string = ''
|
|
for room in range(0, 0xff+1):
|
|
# print(ent)
|
|
pointer_start = 0xf8000+room*3
|
|
highbyte = old_rom_data[pointer_start+2]
|
|
midbyte = old_rom_data[pointer_start+1]
|
|
midbyte = midbyte - 0x80 if highbyte % 2 == 0 else midbyte
|
|
pointer = highbyte // 2 * 0x10000
|
|
pointer += midbyte * 0x100
|
|
pointer += old_rom_data[pointer_start]
|
|
layout_byte = old_rom_data[pointer+1]
|
|
layout = (layout_byte & 0x1c) >> 2
|
|
string += hex(room) + ':' + str(layout) + '\n'
|
|
print(string)
|
|
|
|
|
|
def read_entrance_data(old_rom='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc'):
|
|
with open(old_rom, 'rb') as stream:
|
|
old_rom_data = bytearray(stream.read())
|
|
|
|
for ent, offset in entrance_offsets.items():
|
|
# print(ent)
|
|
string = ent
|
|
for dp, data in entrance_data.items():
|
|
byte_array = []
|
|
address, size = data
|
|
for i in range(0, size):
|
|
byte_array.append(old_rom_data[address+(offset*size)+i])
|
|
some_bytes = ', '.join('0x{:02x}'.format(x) for x in byte_array)
|
|
string += '\t'+some_bytes
|
|
# print("%s: %s" % (dp, bytes))
|
|
print(string)
|
|
|
|
|
|
def room_palette_data(old_rom):
|
|
with open(old_rom, 'rb') as stream:
|
|
old_rom_data = bytearray(stream.read())
|
|
|
|
offset = defaultdict(list)
|
|
for i in range(0, 256):
|
|
pointer_offset = 0x0271e2+i*2
|
|
header_offset = old_rom_data[pointer_offset + 1] << 8
|
|
header_offset += old_rom_data[pointer_offset]
|
|
header_offset -= 0x8000
|
|
header_offset += 0x020000
|
|
offset[header_offset].append(i)
|
|
# print(f'{hex(i)}: {hex(old_rom_data[header_offset+1])}')
|
|
for header_offset, rooms in offset.items():
|
|
print(f'{hex(header_offset)}: {[hex(x) for x in rooms]}')
|
|
|
|
|
|
# Palette notes:
|
|
# HC: 0
|
|
# Sewer/Dungeon: 1
|
|
# AT: 0xc near boss, 0x0 (other) 26 (f4, f1)
|
|
# Sanc: 0x1d
|
|
# Hera: 0x6
|
|
# Desert: 0x4 (boss and near boss), 0x9 (desert tiles 1 + desert main)
|
|
# Eastern: 0xb
|
|
# Pod: 0xf, x10 (boss)
|
|
# Swamp: 0x8 (boss), 0xa (other)
|
|
# Skull: 0xe (boss), 0xd (other)
|
|
# TT: 0x17, 0x23 (attic)
|
|
# Ice: 0x13, 0x14 (boss)
|
|
# Mire: 0x11 (other) , 0x12 (boss/preroom)
|
|
# TR: 0x18, 0x19 (boss+pre)
|
|
# GT: 0x28 (entrance + B1), 0x1a (other) 0x24 (Gauntlet - Lanmo) 0x25 (conveyor-torch-wizzrode moldorm pit f5?)
|
|
# Aga2: 0x1b, 0x1b (Pre aga2)
|
|
# Caves: 0x7, 0x20
|
|
# Uncle: 0x1
|
|
# Ganon: 0x21
|
|
# Houses: 0x2
|
|
|
|
|
|
def print_wiki_doors_by_region(d_regions, world, player):
|
|
for d, region_list in d_regions.items():
|
|
tile_map = {}
|
|
for region in region_list:
|
|
tile = None
|
|
r = world.get_region(region, player)
|
|
for ext in r.exits:
|
|
door = world.check_for_door(ext.name, player)
|
|
if door is not None and door.roomIndex != -1:
|
|
tile = door.roomIndex
|
|
break
|
|
if tile is not None:
|
|
if tile not in tile_map:
|
|
tile_map[tile] = []
|
|
tile_map[tile].append(r)
|
|
toprint = ""
|
|
toprint += ('<!-- ' + d + ' -->') + "\n"
|
|
toprint += ('== Room List ==') + "\n"
|
|
toprint += "\n"
|
|
toprint += ('{| class="wikitable"') + "\n"
|
|
toprint += ('|-') + "\n"
|
|
toprint += ('! Room !! Supertile !! Doors') + "\n"
|
|
for tile, region_list in tile_map.items():
|
|
tile_done = False
|
|
for region in region_list:
|
|
toprint += ('|-') + "\n"
|
|
toprint += ('| {{Dungeon Room|{{PAGENAME}}|' + region.name + '}}') + "\n"
|
|
if not tile_done:
|
|
listlen = len(region_list)
|
|
link = '| {{UnderworldMapLink|'+str(tile)+'}}'
|
|
toprint += (link if listlen < 2 else '| rowspan = '+str(listlen)+' '+link) + "\n"
|
|
tile_done = True
|
|
strs_to_print = []
|
|
for ext in region.exits:
|
|
strs_to_print.append('{{Dungeon Door|{{PAGENAME}}|' + ext.name + '}}')
|
|
toprint += ('| '+'<br />'.join(strs_to_print))
|
|
toprint += "\n"
|
|
toprint += ('|}') + "\n"
|
|
with open(os.path.join(".", "resources", "user", "regions-" + d + ".txt"), "w+") as f:
|
|
f.write(toprint)
|
|
|
|
|
|
def update_deprecated_args(args):
|
|
if args:
|
|
argVars = vars(args)
|
|
truthy = [1, True, "True", "true"]
|
|
if "multi" in argVars:
|
|
players = int(args.multi)
|
|
else:
|
|
players = 1
|
|
# Hints default to FALSE
|
|
# Don't do: Yes
|
|
# Do: No
|
|
if "no_hints" in argVars:
|
|
if args.no_hints in truthy:
|
|
if isinstance(argVars["hints"], dict):
|
|
tmp = {}
|
|
for idx in range(1, len(argVars["hints"]) + 1):
|
|
tmp[idx] = False
|
|
args.hints = tmp
|
|
else:
|
|
args.hints = False
|
|
|
|
# Spoiler defaults to TRUE
|
|
# Don't do: Yes
|
|
# Do: No
|
|
if "suppress_spoiler" in argVars:
|
|
args.spoiler = 'none'
|
|
# Don't do: No
|
|
# Do: Yes
|
|
if "create_spoiler" in argVars:
|
|
args.spoiler = 'full'
|
|
|
|
# ROM defaults to TRUE
|
|
# Don't do: Yes
|
|
# Do: No
|
|
if "suppress_rom" in argVars:
|
|
args.create_rom = not args.suppress_rom in truthy
|
|
# Don't do: No
|
|
# Do: Yes
|
|
if "create_rom" in argVars:
|
|
args.suppress_rom = not args.create_rom in truthy
|
|
|
|
# Shuffle Ganon defaults to TRUE
|
|
# Don't do: Yes
|
|
# Do: No
|
|
if "no_shuffleganon" in argVars:
|
|
if isinstance(args.shuffleganon, dict):
|
|
for player in range(1, players + 1):
|
|
args.shuffleganon[player] = not args.no_shuffleganon in truthy
|
|
else:
|
|
args.shuffleganon = not args.no_shuffleganon in truthy
|
|
|
|
# Playthrough defaults to TRUE
|
|
# Don't do: Yes
|
|
# Do: No
|
|
if "skip_playthrough" in argVars:
|
|
args.calc_playthrough = not args.skip_playthrough in truthy
|
|
# Don't do: No
|
|
# Do: Yes
|
|
if "calc_playthrough" in argVars:
|
|
args.skip_playthrough = not args.calc_playthrough in truthy
|
|
|
|
return args
|
|
|
|
|
|
def print_wiki_doors_by_room(d_regions, world, player):
|
|
for d, region_list in d_regions.items():
|
|
tile_map = {}
|
|
for region in region_list:
|
|
tile = None
|
|
r = world.get_region(region, player)
|
|
for ext in r.exits:
|
|
door = world.check_for_door(ext.name, player)
|
|
if door is not None and door.roomIndex != -1:
|
|
tile = door.roomIndex
|
|
break
|
|
if tile is not None:
|
|
if tile not in tile_map:
|
|
tile_map[tile] = []
|
|
tile_map[tile].append(r)
|
|
toprint = ""
|
|
toprint += ('<!-- ' + d + ' -->') + "\n"
|
|
for tile, region_list in tile_map.items():
|
|
for region in region_list:
|
|
toprint += ('<!-- ' + region.name + ' -->') + "\n"
|
|
toprint += ('{{Infobox dungeon room') + "\n"
|
|
toprint += ('| dungeon = {{ROOTPAGENAME}}') + "\n"
|
|
toprint += ('| supertile = ' + str(tile)) + "\n"
|
|
toprint += ('| tile = x') + "\n"
|
|
toprint += ('}}') + "\n"
|
|
toprint += ('') + "\n"
|
|
toprint += ('== Doors ==') + "\n"
|
|
toprint += ('{| class="wikitable"') + "\n"
|
|
toprint += ('|-') + "\n"
|
|
toprint += ('! Door !! Room Side !! Requirement') + "\n"
|
|
for ext in region.exits:
|
|
ext_part = ext.name.replace(region.name, '')
|
|
ext_part = ext_part.strip()
|
|
toprint += ('{{DungeonRoomDoorList/Row|{{ROOTPAGENAME}}|{{SUBPAGENAME}}|' + ext_part + '|Side|}}') + "\n"
|
|
toprint += ('|}') + "\n"
|
|
toprint += ('') + "\n"
|
|
with open(os.path.join(".", "resources", "user", "rooms-" + d + ".txt"), "w+") as f:
|
|
f.write(toprint)
|
|
|
|
|
|
def print_xml_doors(d_regions, world, player):
|
|
root = ET.Element('root')
|
|
for d, region_list in d_regions.items():
|
|
tile_map = {}
|
|
for region in region_list:
|
|
tile = None
|
|
r = world.get_region(region, player)
|
|
for ext in r.exits:
|
|
door = world.check_for_door(ext.name, player)
|
|
if door is not None and door.roomIndex != -1:
|
|
tile = door.roomIndex
|
|
break
|
|
if tile is not None:
|
|
if tile not in tile_map:
|
|
tile_map[tile] = []
|
|
tile_map[tile].append(r)
|
|
dungeon = ET.SubElement(root, 'dungeon', {'name': d})
|
|
for tile, r_list in tile_map.items():
|
|
supertile = ET.SubElement(dungeon, 'supertile', {'id': str(tile)})
|
|
for region in r_list:
|
|
room = ET.SubElement(supertile, 'room', {'name': region.name})
|
|
for ext in region.exits:
|
|
ET.SubElement(room, 'door', {'name': ext.name})
|
|
ET.dump(root)
|
|
|
|
|
|
def print_graph(world):
|
|
root = ET.Element('root')
|
|
for region in world.regions:
|
|
r = ET.SubElement(root, 'region', {'name': region.name})
|
|
for ext in region.exits:
|
|
attribs = {'name': ext.name}
|
|
if ext.connected_region:
|
|
attribs['connected_region'] = ext.connected_region.name
|
|
if ext.door and ext.door.dest:
|
|
attribs['dest'] = ext.door.dest.name
|
|
ET.SubElement(r, 'exit', attribs)
|
|
ET.dump(root)
|
|
|
|
|
|
def extract_data_from_us_rom(rom):
|
|
with open(rom, 'rb') as stream:
|
|
rom_data = bytearray(stream.read())
|
|
|
|
rooms = [0x1c, 0x1d, 0x4e]
|
|
# rooms = [0x9a, 0x69, 0x78, 0x79, 0x7a, 0x88, 0x8a, 0xad]
|
|
for room in rooms:
|
|
b2idx = room*2
|
|
b3idx = room*3
|
|
headerptr = 0x110000 + b2idx # zscream specific
|
|
headerloc = rom_data[headerptr] + rom_data[headerptr+1]*0x100 + 0x108000 # zscream specific
|
|
header, objectdata, spritedata, secretdata = [], [], [], []
|
|
for i in range(0, 14):
|
|
header.append(rom_data[headerloc+i])
|
|
objectptr = 0xF8000 + b3idx
|
|
objectloc = rom_data[objectptr] + rom_data[objectptr+1]*0x100 + rom_data[objectptr+2]*0x10000
|
|
bank = rom_data[objectptr+2]
|
|
even = bank % 2 == 0
|
|
adjustment = ((bank // 2 if even else bank // 2 + 1) << 16) + (0x8000 if even else 0)
|
|
objectloc -= adjustment
|
|
stop, idx = False, 0
|
|
ffcnt = 0
|
|
mode = 0
|
|
# first two bytes
|
|
b1 = rom_data[objectloc+idx]
|
|
b2 = rom_data[objectloc+idx+1]
|
|
objectdata.append(b1)
|
|
objectdata.append(b2)
|
|
idx += 2
|
|
while ffcnt < 3:
|
|
b1 = rom_data[objectloc+idx]
|
|
b2 = rom_data[objectloc+idx+1]
|
|
b3 = rom_data[objectloc+idx+2]
|
|
objectdata.append(b1)
|
|
objectdata.append(b2)
|
|
if b1 == 0xff and b2 == 0xff:
|
|
ffcnt += 1
|
|
mode = 0
|
|
idx += 2
|
|
elif b1 == 0xf0 and b2 == 0xff:
|
|
mode = 1
|
|
idx += 2
|
|
elif not mode and ffcnt < 3:
|
|
objectdata.append(b3)
|
|
idx += 3
|
|
else:
|
|
idx += 2
|
|
spriteptr = 0x4d62e + b2idx
|
|
spriteloc = rom_data[spriteptr] + rom_data[spriteptr+1]*0x100 + 0x40000
|
|
done, idx = False, 0
|
|
while not done:
|
|
b1 = rom_data[spriteloc+idx]
|
|
spritedata.append(b1)
|
|
if b1 == 0xff:
|
|
done = True
|
|
idx += 1
|
|
secretptr = 0xdb69 + b2idx
|
|
secretloc = rom_data[secretptr] + rom_data[secretptr+1]*0x100
|
|
done, idx = False, 0
|
|
while not done:
|
|
b1 = rom_data[secretloc+idx]
|
|
b2 = rom_data[secretloc+idx+1]
|
|
b3 = rom_data[secretloc+idx+2]
|
|
secretdata.append(b1)
|
|
secretdata.append(b2)
|
|
if b1 == 0xff and b2 == 0xff:
|
|
done = True
|
|
else:
|
|
secretdata.append(b3)
|
|
idx += 3
|
|
|
|
print(f'Room {room:02x}')
|
|
print(f'db {",".join([f"${x:02x}" for x in header])}')
|
|
print(f'Obj Length: {len(objectdata)}')
|
|
print_data_block(objectdata)
|
|
print('Sprites')
|
|
print_data_block(spritedata)
|
|
print('Secrets')
|
|
print_data_block(secretdata)
|
|
|
|
blockdata, torchdata = [], []
|
|
blockloc = 0x271de
|
|
for i in range(0, 128):
|
|
idx = i*4
|
|
b1 = rom_data[blockloc+idx]
|
|
b2 = rom_data[blockloc+idx+1]
|
|
room_idx = b1 + b2*0x100
|
|
if room_idx in rooms:
|
|
blockdata.append(b1)
|
|
blockdata.append(b2)
|
|
blockdata.append(rom_data[blockloc+idx+2])
|
|
blockdata.append(rom_data[blockloc+idx+3])
|
|
torchloc = 0x2736A
|
|
nomatch = False
|
|
append = False
|
|
for i in range(0, 192):
|
|
idx = i*2
|
|
b1 = rom_data[torchloc+idx]
|
|
b2 = rom_data[torchloc+idx+1]
|
|
if nomatch:
|
|
if b1 == 0xff and b2 == 0xff:
|
|
nomatch = False
|
|
elif not append:
|
|
room_idx = b1 + b2*0x100
|
|
if room_idx in rooms:
|
|
append = True
|
|
else:
|
|
nomatch = True
|
|
if append:
|
|
torchdata.append(b1)
|
|
torchdata.append(b2)
|
|
if b1 == 0xff and b2 == 0xff:
|
|
append = False
|
|
print('Blocks')
|
|
print_data_block(blockdata)
|
|
print('Torches')
|
|
print_data_block(torchdata)
|
|
print()
|
|
|
|
|
|
def print_data_block(block):
|
|
for i in range(0, len(block)//16 + 1):
|
|
slice = block[i*16:i*16+16]
|
|
print(f'db {",".join([f"${x:02x}" for x in slice])}')
|
|
|
|
|
|
def extract_data_from_jp_rom(rom):
|
|
with open(rom, 'rb') as stream:
|
|
rom_data = bytearray(stream.read())
|
|
|
|
rooms = range(0, 0x128)
|
|
# rooms = [0x7b, 0x7c, 0x7d, 0x8b, 0x8c, 0x8d, 0x9b, 0x9c, 0x9d]
|
|
# rooms = [0x1a, 0x2a, 0xd1]
|
|
for room in rooms:
|
|
# print(f'Room {room:02x}')
|
|
b2idx = room*2
|
|
b3idx = room*3
|
|
headerptr = 0x271e2 + b2idx
|
|
headerloc = rom_data[headerptr] + rom_data[headerptr+1]*0x100 + 0x18000
|
|
header, objectdata, spritedata, secretdata = [], [], [], []
|
|
for i in range(0, 14):
|
|
header.append(rom_data[headerloc+i])
|
|
objectptr = 0xF8000 + b3idx
|
|
objectloc = rom_data[objectptr] + rom_data[objectptr+1]*0x100 + rom_data[objectptr+2]*0x10000
|
|
bank = rom_data[objectptr+2]
|
|
even = bank % 2 == 0
|
|
adjustment = ((bank // 2 if even else bank // 2 + 1) << 16) + (0x8000 if even else 0)
|
|
objectloc -= adjustment
|
|
stop, idx = False, 0
|
|
ffcnt = 0
|
|
mode = 0
|
|
secret_cnt = 0
|
|
# first two bytes
|
|
b1 = rom_data[objectloc+idx]
|
|
b2 = rom_data[objectloc+idx+1]
|
|
objectdata.append(b1)
|
|
objectdata.append(b2)
|
|
idx += 2
|
|
while ffcnt < 3:
|
|
b1 = rom_data[objectloc+idx]
|
|
b2 = rom_data[objectloc+idx+1]
|
|
b3 = rom_data[objectloc+idx+2]
|
|
objectdata.append(b1)
|
|
objectdata.append(b2)
|
|
if b1 == 0xff and b2 == 0xff:
|
|
ffcnt += 1
|
|
mode = 0
|
|
idx += 2
|
|
elif b1 == 0xf0 and b2 == 0xff:
|
|
mode = 1
|
|
idx += 2
|
|
elif not mode and ffcnt < 3:
|
|
objectdata.append(b3)
|
|
if b3 == 0xFA and ((b2 & 0x3) << 2) | (b1 & 0x3) in [0xf, 0xc]:
|
|
# potcalc
|
|
vram = ((b1 & 0xFC) >> 1) | ((b2 & 0xFC) << 5)
|
|
low = vram & 0xFF
|
|
high = (vram & 0xFF00) >> 8
|
|
if ffcnt == 1:
|
|
high |= 0x20
|
|
print(f'db {", ".join([f"${low:02x}", f"${high:02x}"])}')
|
|
secret_cnt += 1
|
|
idx += 3
|
|
else:
|
|
idx += 2
|
|
if secret_cnt:
|
|
print(f'Room {room:02x} {secret_cnt}')
|
|
spriteptr = 0x4d62e + b2idx
|
|
spriteloc = rom_data[spriteptr] + rom_data[spriteptr+1]*0x100 + 0x40000
|
|
secretptr = 0xdb67 + b2idx
|
|
secretloc = rom_data[secretptr] + rom_data[secretptr+1]*0x100
|
|
done, idx = False, 0
|
|
while not done:
|
|
b1 = rom_data[spriteloc+idx]
|
|
spritedata.append(b1)
|
|
if b1 == 0xff:
|
|
done = True
|
|
idx += 1
|
|
done, idx = False, 0
|
|
while not done:
|
|
b1 = rom_data[secretloc+idx]
|
|
b2 = rom_data[secretloc+idx+1]
|
|
b3 = rom_data[secretloc+idx+2]
|
|
secretdata.append(b1)
|
|
secretdata.append(b2)
|
|
if b1 == 0xff and b2 == 0xff:
|
|
done = True
|
|
else:
|
|
secretdata.append(b3)
|
|
idx += 3
|
|
# print(f'Room {room:02x}')
|
|
# print(f'HeaderPtr {headerptr:06x}')
|
|
# print(f'HeaderLoc {headerloc:06x}')
|
|
# print(f'db {",".join([f"${x:02x}" for x in header])}')
|
|
# print(f'Obj Length: {len(objectdata)}')
|
|
# print(f'ObjectPtr {objectptr:06x}')
|
|
# print(f'ObjectLoc {objectloc:06x}')
|
|
# for i in range(0, len(objectdata)//16 + 1):
|
|
# slice = objectdata[i*16:i*16+16]
|
|
# print(f'db {",".join([f"${x:02x}" for x in slice])}')
|
|
# print(f'SpritePtr {spriteptr:06x}')
|
|
# print(f'SpriteLoc {spriteloc:06x}')
|
|
# print_data_block(spritedata)
|
|
# print(f'SecretPtr {secretptr:06x}')
|
|
# print(f'SecretLoc {secretloc:06x}')
|
|
# print_data_block(secretdata)
|
|
# print()
|
|
|
|
def count_set_bits(val):
|
|
if val == 0:
|
|
return 0
|
|
else:
|
|
return (val & 1) + count_set_bits(val >> 1)
|
|
|
|
def check_pots():
|
|
from PotShuffle import vanilla_pots
|
|
for supertile, pot_list in vanilla_pots.items():
|
|
for i,pot in enumerate(pot_list):
|
|
if pot.obj_ref:
|
|
r = pot.obj_ref
|
|
secret_vram = pot.x | (pot.y << 8)
|
|
tile_vram = ((r.data[1] & 0xFC) << 5) | ((r.data[0] & 0xFC) >> 1)
|
|
if secret_vram != tile_vram:
|
|
print(f'{pot.room}#{i+1} secret: {hex(secret_vram)} tile: {hex(tile_vram)}')
|
|
|
|
|
|
def stack_size3a(size=2):
|
|
# See reference: https://stackoverflow.com/questions/34115298/how-do-i-get-the-current-depth-of-the-python-interpreter-stack
|
|
"""Get stack size for caller's frame."""
|
|
frame = sys._getframe(size)
|
|
try:
|
|
for size in count(size, 8):
|
|
frame = frame.f_back.f_back.f_back.f_back.\
|
|
f_back.f_back.f_back.f_back
|
|
except AttributeError:
|
|
while frame:
|
|
frame = frame.f_back
|
|
size += 1
|
|
return size - 1
|
|
|
|
|
|
def find_and_replace():
|
|
for data_line in fileinput.input('scratch.txt', inplace=True):
|
|
if '=' in data_line:
|
|
one, two = data_line.split(' = ')
|
|
number = int(two.strip())
|
|
print(data_line.replace(two, hex(number)))
|
|
|
|
|
|
def load_yaml(path_list):
|
|
path = os.path.join(*path_list)
|
|
if os.path.exists(Path(path)):
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
return yaml.load(f, Loader=yaml.SafeLoader)
|
|
elif urllib.parse.urlparse(path).scheme in ['http', 'https']:
|
|
return yaml.load(urllib.request.urlopen(path), Loader=yaml.FullLoader)
|
|
|
|
|
|
yaml_cache = {}
|
|
|
|
|
|
def load_cached_yaml(path_list):
|
|
path = os.path.join(*path_list)
|
|
if path in yaml_cache:
|
|
return yaml_cache[path]
|
|
else:
|
|
if os.path.exists(Path(path)):
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
data = yaml.load(f, Loader=yaml.SafeLoader)
|
|
yaml_cache[path] = data
|
|
return data
|
|
elif urllib.parse.urlparse(path).scheme in ['http', 'https']:
|
|
data = yaml.load(urllib.request.urlopen(path), Loader=yaml.FullLoader)
|
|
yaml_cache[path] = data
|
|
return data
|
|
|
|
|
|
class bidict(dict):
|
|
def __init__(self, *args, **kwargs):
|
|
super(bidict, self).__init__(*args, **kwargs)
|
|
self.inverse = {}
|
|
for key, value in self.items():
|
|
self.inverse.setdefault(value,[]).append(key)
|
|
|
|
def __setitem__(self, key, value):
|
|
if key in self:
|
|
self.inverse[self[key]].remove(key)
|
|
super(bidict, self).__setitem__(key, value)
|
|
self.inverse.setdefault(value,[]).append(key)
|
|
|
|
def __delitem__(self, key):
|
|
value = self[key]
|
|
self.inverse.setdefault(value,[]).remove(key)
|
|
if value in self.inverse and not self.inverse[value]:
|
|
del self.inverse[value]
|
|
super(bidict, self).__delitem__(key)
|
|
|
|
|
|
class HexInt(int): pass
|
|
|
|
def hex_representer(dumper, data):
|
|
import yaml
|
|
return yaml.ScalarNode('tag:yaml.org,2002:int', f"{data:#0{4}x}")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
print(make_new_base2current())
|
|
# read_entrance_data(old_rom=sys.argv[1])
|
|
# room_palette_data(old_rom=sys.argv[1])
|
|
# extract_data_from_us_rom(sys.argv[1])
|
|
# extract_data_from_jp_rom(sys.argv[1])
|
|
# check_pots()
|
|
# find_and_replace()
|