Refactor rom patching now that jsonrom patches can safely be merged
This commit is contained in:
parent
6bafdfafe6
commit
77ae96cf1b
|
@ -984,7 +984,7 @@ class Spoiler(object):
|
|||
self.medallions['Misery Mire (Player %d)' % player] = self.world.required_medallions[player][0]
|
||||
self.medallions['Turtle Rock (Player %d)' % player] = self.world.required_medallions[player][1]
|
||||
|
||||
self.startinventory = self.world.precollected_items.copy()
|
||||
self.startinventory = list(map(str, self.world.precollected_items))
|
||||
|
||||
self.locations = OrderedDict()
|
||||
listed_locations = set()
|
||||
|
@ -1133,7 +1133,7 @@ class Spoiler(object):
|
|||
outfile.write('\nMisery Mire Medallion (Player %d): %s' % (player, self.medallions['Misery Mire (Player %d)' % player]))
|
||||
outfile.write('\nTurtle Rock Medallion (Player %d): %s' % (player, self.medallions['Turtle Rock (Player %d)' % player]))
|
||||
outfile.write('\n\nStarting Inventory:\n\n')
|
||||
outfile.write('\n'.join(map(str, self.startinventory)))
|
||||
outfile.write('\n'.join(self.startinventory))
|
||||
outfile.write('\n\nLocations:\n\n')
|
||||
outfile.write('\n'.join(['%s: %s' % (location, item) for grouping in self.locations.values() for (location, item) in grouping.items()]))
|
||||
outfile.write('\n\nShops:\n\n')
|
||||
|
|
45
Main.py
45
Main.py
|
@ -13,7 +13,7 @@ from Items import ItemFactory
|
|||
from Regions import create_regions, mark_light_world_regions
|
||||
from InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
||||
from EntranceShuffle import link_entrances, link_inverted_entrances
|
||||
from Rom import patch_rom, get_race_rom_patches, get_enemizer_patch, apply_rom_settings, LocalRom, JsonRom
|
||||
from Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, JsonRom
|
||||
from Rules import set_rules
|
||||
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
|
||||
from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items, balance_multiworld_progression
|
||||
|
@ -148,32 +148,25 @@ def main(args, seed=None):
|
|||
or args.shufflepots[player] or sprite_random_on_hit)
|
||||
|
||||
rom = JsonRom() if args.jsonout or use_enemizer else LocalRom(args.rom)
|
||||
local_rom = LocalRom(args.rom) if not args.jsonout and use_enemizer else None
|
||||
|
||||
patch_rom(world, player, rom, use_enemizer)
|
||||
rom_names.append((player, list(rom.name)))
|
||||
|
||||
enemizer_patch = []
|
||||
if use_enemizer and (args.enemizercli or not args.jsonout):
|
||||
enemizer_patch = get_enemizer_patch(world, player, rom, args.rom, args.enemizercli, args.shufflepots[player], sprite_random_on_hit)
|
||||
patch_enemizer(world, player, rom, args.rom, args.enemizercli, args.shufflepots[player], sprite_random_on_hit)
|
||||
if not args.jsonout:
|
||||
patches = rom.patches
|
||||
rom = LocalRom(args.rom)
|
||||
rom.merge_enemizer_patches(patches)
|
||||
|
||||
if args.race:
|
||||
patch_race_rom(rom)
|
||||
|
||||
apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player], args.fastmenu[player], args.disablemusic[player], args.sprite[player], args.ow_palettes[player], args.uw_palettes[player], player_names)
|
||||
|
||||
if args.jsonout:
|
||||
jsonout[f'patch{player}'] = rom.patches
|
||||
if use_enemizer:
|
||||
jsonout[f'enemizer{player}'] = enemizer_patch
|
||||
if args.race:
|
||||
jsonout[f'race{player}'] = get_race_rom_patches(rom)
|
||||
else:
|
||||
if use_enemizer:
|
||||
local_rom.patch_enemizer(rom.patches, os.path.join(os.path.dirname(args.enemizercli), "enemizerBasePatch.json"), enemizer_patch)
|
||||
rom = local_rom
|
||||
|
||||
if args.race:
|
||||
for addr, values in get_race_rom_patches(rom).items():
|
||||
rom.write_bytes(int(addr), values)
|
||||
|
||||
apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player], args.fastmenu[player], args.disablemusic[player], args.sprite[player], args.ow_palettes[player], args.uw_palettes[player], player_names)
|
||||
|
||||
mcsb_name = ''
|
||||
if all([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], world.bigkeyshuffle[player]]):
|
||||
mcsb_name = '-keysanity'
|
||||
|
@ -194,11 +187,15 @@ def main(args, seed=None):
|
|||
"-nohints" if not world.hints[player] else "")) if not args.outputname else ''
|
||||
rom.write_to_file(output_path(f'{outfilebase}{playername}{outfilesuffix}.sfc'))
|
||||
|
||||
with open(output_path('%s_multidata' % outfilebase), 'wb') as f:
|
||||
jsonstr = json.dumps((world.players,
|
||||
rom_names,
|
||||
[((location.address, location.player), (location.item.code, location.item.player)) for location in world.get_filled_locations() if type(location.address) is int]))
|
||||
f.write(zlib.compress(jsonstr.encode("utf-8")))
|
||||
multidata = zlib.compress(json.dumps((world.players,
|
||||
rom_names,
|
||||
[((location.address, location.player), (location.item.code, location.item.player)) for location in world.get_filled_locations() if type(location.address) is int])
|
||||
).encode("utf-8"))
|
||||
if args.jsonout:
|
||||
jsonout["multidata"] = list(multidata)
|
||||
else:
|
||||
with open(output_path('%s_multidata' % outfilebase), 'wb') as f:
|
||||
f.write(multidata)
|
||||
|
||||
if args.create_spoiler and not args.jsonout:
|
||||
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
|
||||
|
@ -449,6 +446,6 @@ def create_playthrough(world):
|
|||
old_world.spoiler.paths[str(world.get_region('Inverted Big Bomb Shop', player))] = get_path(state, world.get_region('Inverted Big Bomb Shop', player))
|
||||
|
||||
# we can finally output our playthrough
|
||||
old_world.spoiler.playthrough = OrderedDict([("0", [item for item in world.precollected_items if item.advancement])])
|
||||
old_world.spoiler.playthrough = OrderedDict([("0", [str(item) for item in world.precollected_items if item.advancement])])
|
||||
for i, sphere in enumerate(collection_spheres):
|
||||
old_world.spoiler.playthrough[str(i + 1)] = {str(location): str(location.item) for location in sphere}
|
||||
|
|
77
Rom.py
77
Rom.py
|
@ -114,24 +114,11 @@ class LocalRom(object):
|
|||
# if RANDOMIZERBASEHASH != patchedmd5.hexdigest():
|
||||
# raise RuntimeError('Provided Base Rom unsuitable for patching. Please provide a JAP(1.0) "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc" rom to use as a base.')
|
||||
|
||||
def patch_enemizer(self, rando_patch, base_enemizer_patch_path, enemizer_patch):
|
||||
# extend to 4MB
|
||||
def merge_enemizer_patches(self, patches):
|
||||
self.buffer.extend(bytearray([0x00] * (0x400000 - len(self.buffer))))
|
||||
|
||||
# apply randomizer patches
|
||||
for address, values in rando_patch.items():
|
||||
for address, values in patches.items():
|
||||
self.write_bytes(int(address), values)
|
||||
|
||||
# load base enemizer patches
|
||||
with open(base_enemizer_patch_path, 'r') as f:
|
||||
base_enemizer_patch = json.load(f)
|
||||
for patch in base_enemizer_patch:
|
||||
self.write_bytes(patch["address"], patch["patchData"])
|
||||
|
||||
# apply enemizer patches
|
||||
for patch in enemizer_patch:
|
||||
self.write_bytes(patch["address"], patch["patchData"])
|
||||
|
||||
def write_crc(self):
|
||||
crc = (sum(self.buffer[:0x7FDC] + self.buffer[0x7FE0:]) + 0x01FE) & 0xFFFF
|
||||
inv = crc ^ 0xFFFF
|
||||
|
@ -163,9 +150,10 @@ def read_rom(stream):
|
|||
buffer = buffer[0x200:]
|
||||
return buffer
|
||||
|
||||
def get_enemizer_patch(world, player, rom, baserom_path, enemizercli, shufflepots, random_sprite_on_hit):
|
||||
def patch_enemizer(world, player, rom, baserom_path, enemizercli, shufflepots, random_sprite_on_hit):
|
||||
baserom_path = os.path.abspath(baserom_path)
|
||||
basepatch_path = os.path.abspath(local_path('data/base2current.json'))
|
||||
enemizer_basepatch_path = os.path.join(os.path.dirname(enemizercli), "enemizerBasePatch.json")
|
||||
randopatch_path = os.path.abspath(output_path('enemizer_randopatch.json'))
|
||||
options_path = os.path.abspath(output_path('enemizer_options.json'))
|
||||
enemizer_output_path = os.path.abspath(output_path('enemizer_output.json'))
|
||||
|
@ -245,20 +233,14 @@ def get_enemizer_patch(world, player, rom, baserom_path, enemizercli, shufflepot
|
|||
'IcePalace': world.get_dungeon("Ice Palace", player).boss.enemizer_name,
|
||||
'MiseryMire': world.get_dungeon("Misery Mire", player).boss.enemizer_name,
|
||||
'TurtleRock': world.get_dungeon("Turtle Rock", player).boss.enemizer_name,
|
||||
'GanonsTower1': world.get_dungeon('Ganons Tower' if world.mode[player] != 'inverted' else 'Inverted Ganons Tower', player).bosses['bottom'].enemizer_name,
|
||||
'GanonsTower2': world.get_dungeon('Ganons Tower' if world.mode[player] != 'inverted' else 'Inverted Ganons Tower', player).bosses['middle'].enemizer_name,
|
||||
'GanonsTower3': world.get_dungeon('Ganons Tower' if world.mode[player] != 'inverted' else 'Inverted Ganons Tower', player).bosses['top'].enemizer_name,
|
||||
'GanonsTower4': 'Agahnim2',
|
||||
'Ganon': 'Ganon',
|
||||
}
|
||||
}
|
||||
|
||||
if world.mode[player] != 'inverted':
|
||||
options['ManualBosses']['GanonsTower1'] = world.get_dungeon('Ganons Tower', player).bosses['bottom'].enemizer_name
|
||||
options['ManualBosses']['GanonsTower2'] = world.get_dungeon('Ganons Tower', player).bosses['middle'].enemizer_name
|
||||
options['ManualBosses']['GanonsTower3'] = world.get_dungeon('Ganons Tower', player).bosses['top'].enemizer_name
|
||||
else:
|
||||
options['ManualBosses']['GanonsTower1'] = world.get_dungeon('Inverted Ganons Tower', player).bosses['bottom'].enemizer_name
|
||||
options['ManualBosses']['GanonsTower2'] = world.get_dungeon('Inverted Ganons Tower', player).bosses['middle'].enemizer_name
|
||||
options['ManualBosses']['GanonsTower3'] = world.get_dungeon('Inverted Ganons Tower', player).bosses['top'].enemizer_name
|
||||
|
||||
rom.write_to_file(randopatch_path)
|
||||
|
||||
with open(options_path, 'w') as f:
|
||||
|
@ -273,17 +255,13 @@ def get_enemizer_patch(world, player, rom, baserom_path, enemizercli, shufflepot
|
|||
'--output', enemizer_output_path],
|
||||
cwd=os.path.dirname(enemizercli), stdout=subprocess.DEVNULL)
|
||||
|
||||
with open(enemizer_basepatch_path, 'r') as f:
|
||||
for patch in json.load(f):
|
||||
rom.write_bytes(patch["address"], patch["patchData"])
|
||||
|
||||
with open(enemizer_output_path, 'r') as f:
|
||||
ret = json.load(f)
|
||||
|
||||
if os.path.exists(randopatch_path):
|
||||
os.remove(randopatch_path)
|
||||
|
||||
if os.path.exists(options_path):
|
||||
os.remove(options_path)
|
||||
|
||||
if os.path.exists(enemizer_output_path):
|
||||
os.remove(enemizer_output_path)
|
||||
for patch in json.load(f):
|
||||
rom.write_bytes(patch["address"], patch["patchData"])
|
||||
|
||||
if random_sprite_on_hit:
|
||||
_populate_sprite_table()
|
||||
|
@ -295,11 +273,24 @@ def get_enemizer_patch(world, player, rom, baserom_path, enemizercli, shufflepot
|
|||
|
||||
for i, path in enumerate(sprites[:32]):
|
||||
sprite = Sprite(path)
|
||||
ret.append({"address": 0x300000 + (i * 0x8000), "patchData": list(sprite.sprite)})
|
||||
ret.append({"address": 0x307000 + (i * 0x8000), "patchData": list(sprite.palette)})
|
||||
ret.append({"address": 0x307078 + (i * 0x8000), "patchData": list(sprite.glove_palette)})
|
||||
rom.write_bytes(0x300000 + (i * 0x8000), sprite.sprite)
|
||||
rom.write_bytes(0x307000 + (i * 0x8000), sprite.palette)
|
||||
rom.write_bytes(0x307078 + (i * 0x8000), sprite.glove_palette)
|
||||
|
||||
return ret
|
||||
try:
|
||||
os.remove(randopatch_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
try:
|
||||
os.remove(options_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
try:
|
||||
os.remove(enemizer_output_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
_sprite_table = {}
|
||||
def _populate_sprite_table():
|
||||
|
@ -1255,13 +1246,11 @@ try:
|
|||
except ImportError:
|
||||
RaceRom = None
|
||||
|
||||
def get_race_rom_patches(rom):
|
||||
patches = {str(0x180213): [0x01, 0x00]} # Tournament Seed
|
||||
def patch_race_rom(rom):
|
||||
rom.write_bytes(0x180213, [0x01, 0x00]) # Tournament Seed
|
||||
|
||||
if 'RaceRom' in sys.modules:
|
||||
RaceRom.encrypt(rom, patches)
|
||||
|
||||
return patches
|
||||
RaceRom.encrypt(rom)
|
||||
|
||||
def write_custom_shops(rom, world, player):
|
||||
shops = [shop for shop in world.shops if shop.replaceable and shop.active and shop.region.player == player]
|
||||
|
|
Loading…
Reference in New Issue