From 77ae96cf1b7a84a0e6d2b37a7f0e1b8bf042fb7c Mon Sep 17 00:00:00 2001 From: Bonta-kun <40473493+Bonta0@users.noreply.github.com> Date: Fri, 10 Jan 2020 07:02:44 +0100 Subject: [PATCH] Refactor rom patching now that jsonrom patches can safely be merged --- BaseClasses.py | 4 +-- Main.py | 45 ++++++++++++++--------------- Rom.py | 77 ++++++++++++++++++++++---------------------------- 3 files changed, 56 insertions(+), 70 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 3438fa20..ffdf060e 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -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') diff --git a/Main.py b/Main.py index 8315569c..3b21c512 100644 --- a/Main.py +++ b/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} diff --git a/Rom.py b/Rom.py index 9d942511..ec71854c 100644 --- a/Rom.py +++ b/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]