Introduce classes for Rom patching to enable json patch output for VT integration. Remove halfassed door frame overlay fixes (solved in randomizer rom now), make bonk prize shuffle optional.

This commit is contained in:
LLCoolDave 2017-07-14 14:37:34 +02:00
parent c3854f4d2d
commit 83c448f14d
4 changed files with 230 additions and 205 deletions

View File

@ -34,7 +34,6 @@ class World(object):
self.clock_mode = 'off'
self.aga_randomness = False
self.lock_aga_door_in_escape = False
self.fix_door_frames = self.shuffle not in ['vanilla', 'dungeonssimple', 'dungeonsfull']
self.fix_trock_doors = self.shuffle != 'vanilla'
self.save_and_quite_from_boss = False
self.check_beatable_only = check_beatable_only

13
Main.py
View File

@ -1,7 +1,7 @@
from BaseClasses import World, CollectionState, Item
from Regions import create_regions
from EntranceShuffle import link_entrances
from Rom import patch_rom, patch_base_rom
from Rom import patch_rom, LocalRom, JsonRom
from Rules import set_rules
from Dungeons import fill_dungeons
from Items import ItemFactory
@ -87,11 +87,12 @@ def main(args, seed=None):
outfilebase = 'ER_%s_%s_%s_%s_%s_%s' % (world.mode, world.goal, world.shuffle, world.difficulty, world.algorithm, world.seed)
if not args.suppress_rom:
rom = bytearray(open(args.rom, 'rb').read())
patch_base_rom(rom)
patched_rom = patch_rom(world, rom, bytearray(logic_hash), args.quickswap, args.heartbeep, sprite)
with open('%s.sfc' % outfilebase, 'wb') as outfile:
outfile.write(patched_rom)
if args.jsonout:
rom = JsonRom()
else:
rom = LocalRom(args.rom)
patch_rom(world, rom, bytearray(logic_hash), args.quickswap, args.heartbeep, sprite)
rom.write_to_file(args.jsonout or '%s.sfc' % outfilebase)
if args.create_spoiler:
with open('%s_Spoiler.txt' % outfilebase, 'w') as outfile:

View File

@ -1,7 +1,7 @@
from BaseClasses import World
from Regions import create_regions
from EntranceShuffle import link_entrances, connect_entrance, connect_two_way, connect_exit
from Rom import patch_rom, patch_base_rom, write_string_to_rom, write_credits_string_to_rom
from Rom import patch_rom, LocalRom, write_string_to_rom, write_credits_string_to_rom
from Rules import set_rules
from Items import ItemFactory
from Main import create_playthrough
@ -79,9 +79,8 @@ def main(args, seed=None):
else:
sprite = None
rom = bytearray(open(args.rom, 'rb').read())
patch_base_rom(rom)
patched_rom = patch_rom(world, rom, logic_hash, args.quickswap, args.heartbeep, sprite)
rom = LocalRom(args.rom)
patch_rom(world, rom, logic_hash, args.quickswap, args.heartbeep, sprite)
for textname, texttype, text in text_patches:
if texttype == 'text':
@ -91,8 +90,7 @@ def main(args, seed=None):
outfilebase = 'Plando_%s_%s' % (os.path.splitext(os.path.basename(args.plando))[0], world.seed)
with open('%s.sfc' % outfilebase, 'wb') as outfile:
outfile.write(patched_rom)
rom.write_to_file('%s.sfc' % outfilebase)
if args.create_spoiler:
with open('%s_Spoiler.txt' % outfilebase, 'w') as outfile:
outfile.write(world.spoiler)
@ -140,9 +138,6 @@ def fill_world(world, plando, text_patches):
elif line.startswith('!light_cone_dw'):
_, dwconestr = line.split(':', 1)
world.dark_world_light_cone = dwconestr.strip().lower() == 'true'
elif line.startswith('!fix_door_frames'):
_, dfstring = line.split(':', 1)
world.fix_door_frames = dfstring.strip().lower() == 'true'
elif line.startswith('!fix_trock_doors'):
_, trdstr = line.split(':', 1)
world.fix_trock_doors = trdstr.strip().lower() == 'true'

408
Rom.py
View File

@ -11,6 +11,68 @@ JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '89fcdb48446bd858878f14e8a994d0b8'
class JsonRom(object):
def __init__(self):
self.patches = {}
def write_byte(self, address, value):
self.patches[str(address)] = [value]
def write_bytes(self, startaddress, values):
self.patches[str(startaddress)] = list(values)
def write_to_file(self, file):
json.dump([self.patches], open(file, 'w'))
class LocalRom(object):
def __init__(self, file):
self.buffer = bytearray(open(file, 'rb').read())
self.patch_base_rom()
def write_byte(self, address, value):
self.buffer[address] = value
def write_bytes(self, startaddress, values):
for i, value in enumerate(values):
self.write_byte(startaddress + i, value)
def write_to_file(self, file):
with open(file, 'wb') as outfile:
outfile.write(self.buffer)
def patch_base_rom(self):
# verify correct checksum of baserom
basemd5 = hashlib.md5()
basemd5.update(self.buffer)
if not JAP10HASH == basemd5.hexdigest():
logging.getLogger('').warning('Supplied Base Rom does not match known MD5 for JAP(1.0) release. Will try to patch anyway.')
# extend to 2MB
self.buffer.extend(bytearray([0x00] * (2097152 - len(self.buffer))))
# load randomizer patches
patches = json.load(open('base2current.json', 'r'))
for patch in patches:
if isinstance(patch, dict):
for baseaddress, values in patch.items():
self.write_bytes(int(baseaddress), values)
# verify md5
patchedmd5 = hashlib.md5()
patchedmd5.update(self.buffer)
if not 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 write_crc(self):
# this does not seem to work
crc = sum(self.buffer[:0x7FEB] + self.buffer[0x7FF5:]) % 0xFFFF
inv = crc ^ 0xFFFF
self.write_bytes(0x7FDC, [inv & 0xFF, (inv >> 8) & 0xFF, crc & 0xFF, (crc >> 8) & 0xFF])
def patch_rom(world, rom, hashtable, quickswap=False, beep='normal', sprite=None):
# patch items
for location in world.get_locations():
@ -22,20 +84,17 @@ def patch_rom(world, rom, hashtable, quickswap=False, beep='normal', sprite=None
locationaddress = location.address
if not location.crystal:
# regular items
write_byte(rom, locationaddress, itemid)
rom.write_byte(locationaddress, itemid)
else:
# crystals
for address, value in zip(locationaddress, itemid):
write_byte(rom, address, value)
rom.write_byte(address, value)
# patch music
music_addresses = dungeon_music_addresses[location.name]
music = 0x11 if 'Pendant' in location.item.name else 0x16
for music_address in music_addresses:
write_byte(rom, music_address, music)
# store old door overlay table
door_overlays = bytearray(rom[0x15488:0x15488+0x10A])
rom.write_byte(music_address, music)
# patch entrances
for region in world.regions:
@ -43,87 +102,76 @@ def patch_rom(world, rom, hashtable, quickswap=False, beep='normal', sprite=None
if exit.target is not None:
addresses = [exit.addresses] if isinstance(exit.addresses, int) else exit.addresses
for address in addresses:
write_byte(rom, address, exit.target)
# this does not yet seem to fix our door headaches ...
if world.fix_door_frames and exit.vanilla is not None:
# patch door overlay table. The value of where this now leads is patched into the location where this entrance would lead in vanilla
write_byte(rom, 0x15488 + (2*exit.vanilla), door_overlays[2*exit.target])
write_byte(rom, 0x15489 + (2 * exit.vanilla), door_overlays[(2 * exit.target) + 1])
rom.write_byte(address, exit.target)
# patch medallion requirements
if world.required_medallions[0] == 'Bombos':
write_byte(rom, 0x180022, 0x00) # requirement
write_byte(rom, 0x4FF2, 0x31) # sprite
write_byte(rom, 0x50D1, 0x80)
write_byte(rom, 0x51B0, 0x00)
rom.write_byte(0x180022, 0x00) # requirement
rom.write_byte(0x4FF2, 0x31) # sprite
rom.write_byte(0x50D1, 0x80)
rom.write_byte(0x51B0, 0x00)
elif world.required_medallions[0] == 'Quake':
write_byte(rom, 0x180022, 0x02) # requirement
write_byte(rom, 0x4FF2, 0x31) # sprite
write_byte(rom, 0x50D1, 0x88)
write_byte(rom, 0x51B0, 0x00)
rom.write_byte(0x180022, 0x02) # requirement
rom.write_byte(0x4FF2, 0x31) # sprite
rom.write_byte(0x50D1, 0x88)
rom.write_byte(0x51B0, 0x00)
if world.required_medallions[1] == 'Bombos':
write_byte(rom, 0x180023, 0x00) # requirement
write_byte(rom, 0x5020, 0x31) # sprite
write_byte(rom, 0x50FF, 0x90)
write_byte(rom, 0x51DE, 0x00)
rom.write_byte(0x180023, 0x00) # requirement
rom.write_byte(0x5020, 0x31) # sprite
rom.write_byte(0x50FF, 0x90)
rom.write_byte(0x51DE, 0x00)
elif world.required_medallions[1] == 'Ether':
write_byte(rom, 0x180023, 0x01) # requirement
write_byte(rom, 0x5020, 0x31) # sprite
write_byte(rom, 0x50FF, 0x98)
write_byte(rom, 0x51DE, 0x00)
rom.write_byte(0x180023, 0x01) # requirement
rom.write_byte(0x5020, 0x31) # sprite
rom.write_byte(0x50FF, 0x98)
rom.write_byte(0x51DE, 0x00)
# set open mode:
if world.mode in ['open', 'swordless']:
write_byte(rom, 0x180032, 0x01) # open mode
rom.write_byte(0x180032, 0x01) # open mode
# disable sword sprite from uncle
write_bytes(rom, 0x6D263, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
write_bytes(rom, 0x6D26B, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
write_bytes(rom, 0x6D293, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
write_bytes(rom, 0x6D29B, [0x00, 0x00, 0xf7, 0xff, 0x00, 0x0E])
write_bytes(rom, 0x6D2B3, [0x00, 0x00, 0xf6, 0xff, 0x02, 0x0E])
write_bytes(rom, 0x6D2BB, [0x00, 0x00, 0xf6, 0xff, 0x02, 0x0E])
write_bytes(rom, 0x6D2E3, [0x00, 0x00, 0xf7, 0xff, 0x02, 0x0E])
write_bytes(rom, 0x6D2EB, [0x00, 0x00, 0xf7, 0xff, 0x02, 0x0E])
write_bytes(rom, 0x6D31B, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E])
write_bytes(rom, 0x6D323, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E])
rom.write_bytes(0x6D263, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
rom.write_bytes(0x6D26B, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
rom.write_bytes(0x6D293, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
rom.write_bytes(0x6D29B, [0x00, 0x00, 0xf7, 0xff, 0x00, 0x0E])
rom.write_bytes(0x6D2B3, [0x00, 0x00, 0xf6, 0xff, 0x02, 0x0E])
rom.write_bytes(0x6D2BB, [0x00, 0x00, 0xf6, 0xff, 0x02, 0x0E])
rom.write_bytes(0x6D2E3, [0x00, 0x00, 0xf7, 0xff, 0x02, 0x0E])
rom.write_bytes(0x6D2EB, [0x00, 0x00, 0xf7, 0xff, 0x02, 0x0E])
rom.write_bytes(0x6D31B, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E])
rom.write_bytes(0x6D323, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E])
else:
write_byte(rom, 0x180032, 0x00) # standard mode
rom.write_byte(0x180032, 0x00) # standard mode
# set light cones
write_byte(rom, 0x180038, 0x01 if world.sewer_light_cone else 0x00)
write_byte(rom, 0x180039, 0x01 if world.light_world_light_cone else 0x00)
write_byte(rom, 0x18003A, 0x01 if world.dark_world_light_cone else 0x00)
# disable light world cane in minor glitches
if world.logic == 'minorglitches':
write_byte(rom, 0x180039, 0x00) # light world light cone disable
write_byte(rom, 0x18003A, 0x00) # dark world light cone disable
rom.write_byte(0x180038, 0x01 if world.sewer_light_cone else 0x00)
rom.write_byte(0x180039, 0x01 if world.light_world_light_cone else 0x00)
rom.write_byte(0x18003A, 0x01 if world.dark_world_light_cone else 0x00)
# handle difficulty
if world.difficulty == 'hard':
# Spike Cave Damage
write_byte(rom, 0x180168, 0x02)
rom.write_byte(0x180168, 0x02)
# Powdered Fairies Prize
write_byte(rom, 0x36DD0, 0x79) # Bee
rom.write_byte(0x36DD0, 0x79) # Bee
# potion heal amount
write_byte(rom, 0x180084, 0x08) # One Heart
rom.write_byte(0x180084, 0x08) # One Heart
# potion magic restore amount
write_byte(rom, 0x180085, 0x20) # Quarter Magic
rom.write_byte(0x180085, 0x20) # Quarter Magic
else:
# Spike Cave Damage
write_byte(rom, 0x180168, 0x08)
rom.write_byte(0x180168, 0x08)
# Powdered Fairies Prize
write_byte(rom, 0x36DD0, 0xE3) # fairy
rom.write_byte(0x36DD0, 0xE3) # fairy
# potion heal amount
write_byte(rom, 0x180084, 0xA0) # full
rom.write_byte(0x180084, 0xA0) # full
# potion magic restore amount
write_byte(rom, 0x180085, 0x80) # full
rom.write_byte(0x180085, 0x80) # full
# set up game internal RNG seed
for i in range(1024):
write_byte(rom, 0x178000 + i, random.randint(0, 255))
rom.write_byte(0x178000 + i, random.randint(0, 255))
# shuffle prize packs
prizes = [0xD8, 0xD8, 0xD8, 0xD8, 0xD9, 0xD8, 0xD8, 0xD9, 0xDA, 0xD9, 0xDA, 0xDB, 0xDA, 0xD9, 0xDA, 0xDA, 0xE0, 0xDF, 0xDF, 0xDA, 0xE0, 0xDF, 0xD8, 0xDF,
@ -132,22 +180,22 @@ def patch_rom(world, rom, hashtable, quickswap=False, beep='normal', sprite=None
random.shuffle(prizes)
# write tree pull prizes
write_byte(rom, 0xEFBD4, prizes.pop())
write_byte(rom, 0xEFBD5, prizes.pop())
write_byte(rom, 0xEFBD6, prizes.pop())
rom.write_byte(0xEFBD4, prizes.pop())
rom.write_byte(0xEFBD5, prizes.pop())
rom.write_byte(0xEFBD6, prizes.pop())
# rupee crab prizes
write_byte(rom, 0x329C8, prizes.pop()) # first prize
write_byte(rom, 0x329C4, prizes.pop()) # final prize
rom.write_byte(0x329C8, prizes.pop()) # first prize
rom.write_byte(0x329C4, prizes.pop()) # final prize
# stunned enemy prize
write_byte(rom, 0x37993, prizes.pop())
rom.write_byte(0x37993, prizes.pop())
# saved fish prize
write_byte(rom, 0xE82CC, prizes.pop())
rom.write_byte(0xE82CC, prizes.pop())
# fill enemy prize packs
write_bytes(rom, 0x37A78, prizes)
rom.write_bytes(0x37A78, prizes)
# prize pack drop chances
if world.difficulty == 'hard':
@ -156,140 +204,138 @@ def patch_rom(world, rom, hashtable, quickswap=False, beep='normal', sprite=None
droprates = [0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01] # 50%
random.shuffle(droprates)
write_bytes(rom, 0x37A62, droprates)
rom.write_bytes(0x37A62, droprates)
vanilla_prize_pack_assignment = [131, 150, 132, 128, 128, 128, 128, 128, 2, 0, 2, 128, 160, 131, 151, 128, 128, 148, 145, 7, 0, 128, 0, 128, 146, 150, 128, 160, 0, 0, 0, 128, 4, 128,
130, 6, 6, 0, 0, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 128, 128, 144, 128, 145, 145,
145, 151, 145, 149, 149, 147, 151, 20, 145, 146, 129, 130, 130, 128, 133, 128, 128, 128, 4, 4, 128, 145, 128, 128, 128, 128, 128, 128, 128, 128, 0, 128,
128, 130, 138, 128, 128, 128, 128, 146, 145, 128, 130, 129, 129, 128, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 151, 128, 128, 128, 128, 194,
128, 21, 21, 23, 6, 0, 128, 0, 192, 19, 64, 0, 2, 6, 16, 20, 0, 0, 64, 0, 0, 0, 0, 19, 70, 17, 128, 128, 0, 0, 0, 16, 0, 0, 0, 22, 22, 22, 129, 135, 130,
0, 128, 128, 0, 0, 0, 0, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 23, 0, 18, 0, 0, 0, 0, 0, 16, 23, 0, 64, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0]
# shuffle enemies to prize packs
for i in range(243):
if rom[0x6B632 + i] & 0x0F != 0x00:
rom[0x6B632 + i] = (rom[0x6B632 + i] & 0xF0) | random.randint(1, 7)
if vanilla_prize_pack_assignment[i] & 0x0F != 0x00:
rom.write_byte(0x6B632 + i, (vanilla_prize_pack_assignment[i] & 0xF0) | random.randint(1, 7))
# set bonk prizes
bonk_prizes = [0x79, 0xE3, 0x79, 0xAC, 0xAC, 0xE0, 0xDC, 0xAC, 0xE3, 0xE3, 0xDA, 0xE3, 0xDA, 0xD8, 0xAC, 0xAC, 0xE3, 0xD8, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xDC, 0xDB, 0xE3, 0xDA, 0x79, 0x79, 0xE3, 0xE3,
0xDA, 0x79, 0xAC, 0xAC, 0x79, 0xE3, 0x79, 0xAC, 0xAC, 0xE0, 0xDC, 0xE3, 0x79, 0xDE, 0xE3, 0xAC, 0xDB, 0x79, 0xE3, 0xD8, 0xAC, 0x79, 0xE3, 0xDB, 0xDB, 0xE3, 0xE3, 0x79, 0xD8, 0xDD]
bonk_addresses = [0x4CF6C, 0x4CFBA, 0x4CFE0, 0x4CFFB, 0x4D018, 0x4D01B, 0x4D028, 0x4D03C, 0x4D059, 0x4D07A, 0x4D09E, 0x4D0A8, 0x4D0AB, 0x4D0AE, 0x4D0BE, 0x4D0DD,
0x4D16A, 0x4D1E5, 0x4D1EE, 0x4D20B, 0x4CBBF, 0x4CBBF, 0x4CC17, 0x4CC1A, 0x4CC4A, 0x4CC4D, 0x4CC53, 0x4CC69, 0x4CC6F, 0x4CC7C, 0x4CCEF, 0x4CD51,
0x4CDC0, 0x4CDC3, 0x4CDC6, 0x4CE37, 0x4D2DE, 0x4D32F, 0x4D355, 0x4D367, 0x4D384, 0x4D387, 0x4D397, 0x4D39E, 0x4D3AB, 0x4D3AE, 0x4D3D1, 0x4D3D7,
0x4D3F8, 0x4D416, 0x4D420, 0x4D423, 0x4D42D, 0x4D449, 0x4D48C, 0x4D4D9, 0x4D4DC, 0x4D4E3, 0x4D504, 0x4D507, 0x4D55E, 0x4D56A]
random.shuffle(bonk_prizes)
for prize, address in zip(bonk_prizes, bonk_addresses):
write_byte(rom, address, prize)
if world.shuffle_bonk_prizes:
bonk_prizes = [0x79, 0xE3, 0x79, 0xAC, 0xAC, 0xE0, 0xDC, 0xAC, 0xE3, 0xE3, 0xDA, 0xE3, 0xDA, 0xD8, 0xAC, 0xAC, 0xE3, 0xD8, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xDC, 0xDB, 0xE3, 0xDA, 0x79, 0x79, 0xE3, 0xE3,
0xDA, 0x79, 0xAC, 0xAC, 0x79, 0xE3, 0x79, 0xAC, 0xAC, 0xE0, 0xDC, 0xE3, 0x79, 0xDE, 0xE3, 0xAC, 0xDB, 0x79, 0xE3, 0xD8, 0xAC, 0x79, 0xE3, 0xDB, 0xDB, 0xE3, 0xE3, 0x79, 0xD8, 0xDD]
bonk_addresses = [0x4CF6C, 0x4CFBA, 0x4CFE0, 0x4CFFB, 0x4D018, 0x4D01B, 0x4D028, 0x4D03C, 0x4D059, 0x4D07A, 0x4D09E, 0x4D0A8, 0x4D0AB, 0x4D0AE, 0x4D0BE, 0x4D0DD,
0x4D16A, 0x4D1E5, 0x4D1EE, 0x4D20B, 0x4CBBF, 0x4CBBF, 0x4CC17, 0x4CC1A, 0x4CC4A, 0x4CC4D, 0x4CC53, 0x4CC69, 0x4CC6F, 0x4CC7C, 0x4CCEF, 0x4CD51,
0x4CDC0, 0x4CDC3, 0x4CDC6, 0x4CE37, 0x4D2DE, 0x4D32F, 0x4D355, 0x4D367, 0x4D384, 0x4D387, 0x4D397, 0x4D39E, 0x4D3AB, 0x4D3AE, 0x4D3D1, 0x4D3D7,
0x4D3F8, 0x4D416, 0x4D420, 0x4D423, 0x4D42D, 0x4D449, 0x4D48C, 0x4D4D9, 0x4D4DC, 0x4D4E3, 0x4D504, 0x4D507, 0x4D55E, 0x4D56A]
random.shuffle(bonk_prizes)
for prize, address in zip(bonk_prizes, bonk_addresses):
rom.write_byte(address, prize)
# set Fountain bottle exchange items
write_byte(rom, 0x348FF, [0x16, 0x2B, 0x2C, 0x2D, 0x3C, 0x3D, 0x48][random.randint(0, 6)])
write_byte(rom, 0x3493B, [0x16, 0x2B, 0x2C, 0x2D, 0x3C, 0x3D, 0x48][random.randint(0, 6)])
rom.write_byte(0x348FF, [0x16, 0x2B, 0x2C, 0x2D, 0x3C, 0x3D, 0x48][random.randint(0, 6)])
rom.write_byte(0x3493B, [0x16, 0x2B, 0x2C, 0x2D, 0x3C, 0x3D, 0x48][random.randint(0, 6)])
# set Fat Fairy Bow/Sword prizes to be disappointing
write_byte(rom, 0x34914, 0x3A) # Bow and Arrow
write_byte(rom, 0x180028, 0x49) # Fighter Sword
rom.write_byte(0x34914, 0x3A) # Bow and Arrow
rom.write_byte(0x180028, 0x49) # Fighter Sword
# set swordless mode settings
write_byte(rom, 0x18003F, 0x01 if world.mode == 'swordless' else 0x00) # hammer can harm ganon
write_byte(rom, 0x180040, 0x01 if world.mode == 'swordless' else 0x00) # open curtains
write_byte(rom, 0x180041, 0x01 if world.mode == 'swordless' else 0x00) # swordless medallions
write_byte(rom, 0x180042, 0xFF if world.mode == 'swordless' else 0x00) # starting sword for link
rom.write_byte(0x18003F, 0x01 if world.mode == 'swordless' else 0x00) # hammer can harm ganon
rom.write_byte(0x180040, 0x01 if world.mode == 'swordless' else 0x00) # open curtains
rom.write_byte(0x180041, 0x01 if world.mode == 'swordless' else 0x00) # swordless medallions
rom.write_byte(0x180042, 0xFF if world.mode == 'swordless' else 0x00) # starting sword for link
# set up clocks for timed modes
if world.clock_mode == 'off':
write_bytes(rom, 0x180190, [0x00, 0x00, 0x00]) # turn off clock mode
write_bytes(rom, 0x180200, [0x00, 0x00, 0x00, 0x00]) # red clock adjustment time (in frames, sint32)
write_bytes(rom, 0x180204, [0x00, 0x00, 0x00, 0x00]) # blue clock adjustment time (in frames, sint32)
write_bytes(rom, 0x180208, [0x00, 0x00, 0x00, 0x00]) # green clock adjustment time (in frames, sint32)
write_bytes(rom, 0x18020C, [0x00, 0x00, 0x00, 0x00]) # starting time (in frames, sint32)
rom.write_bytes(0x180190, [0x00, 0x00, 0x00]) # turn off clock mode
rom.write_bytes(0x180200, [0x00, 0x00, 0x00, 0x00]) # red clock adjustment time (in frames, sint32)
rom.write_bytes(0x180204, [0x00, 0x00, 0x00, 0x00]) # blue clock adjustment time (in frames, sint32)
rom.write_bytes(0x180208, [0x00, 0x00, 0x00, 0x00]) # green clock adjustment time (in frames, sint32)
rom.write_bytes(0x18020C, [0x00, 0x00, 0x00, 0x00]) # starting time (in frames, sint32)
elif world.clock_mode == 'ohko':
write_bytes(rom, 0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality
write_bytes(rom, 0x180200, [0x00, 0x00, 0x00, 0x00]) # red clock adjustment time (in frames, sint32)
write_bytes(rom, 0x180204, [0x00, 0x00, 0x00, 0x00]) # blue clock adjustment time (in frames, sint32)
write_bytes(rom, 0x180208, [0x50, 0x46, 0x00, 0x00]) # green clock adjustment time (in frames, sint32)
write_bytes(rom, 0x18020C, [0xA0, 0x8C, 0x00, 0x00]) # starting time (in frames, sint32)
rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality
rom.write_bytes(0x180200, [0x00, 0x00, 0x00, 0x00]) # red clock adjustment time (in frames, sint32)
rom.write_bytes(0x180204, [0x00, 0x00, 0x00, 0x00]) # blue clock adjustment time (in frames, sint32)
rom.write_bytes(0x180208, [0x50, 0x46, 0x00, 0x00]) # green clock adjustment time (in frames, sint32)
rom.write_bytes(0x18020C, [0xA0, 0x8C, 0x00, 0x00]) # starting time (in frames, sint32)
if world.clock_mode == 'stopwatch':
write_bytes(rom, 0x180190, [0x02, 0x01, 0x00]) # set stopwatch mode
write_bytes(rom, 0x180200, [0xE0, 0xE3, 0xFF, 0xFF]) # red clock adjustment time (in frames, sint32)
write_bytes(rom, 0x180204, [0x20, 0x1C, 0x00, 0x00]) # blue clock adjustment time (in frames, sint32)
write_bytes(rom, 0x180208, [0x40, 0x38, 0x00, 0x00]) # green clock adjustment time (in frames, sint32)
write_bytes(rom, 0x18020C, [0x00, 0x00, 0x00, 0x00]) # starting time (in frames, sint32)
rom.write_bytes(0x180190, [0x02, 0x01, 0x00]) # set stopwatch mode
rom.write_bytes(0x180200, [0xE0, 0xE3, 0xFF, 0xFF]) # red clock adjustment time (in frames, sint32)
rom.write_bytes(0x180204, [0x20, 0x1C, 0x00, 0x00]) # blue clock adjustment time (in frames, sint32)
rom.write_bytes(0x180208, [0x40, 0x38, 0x00, 0x00]) # green clock adjustment time (in frames, sint32)
rom.write_bytes(0x18020C, [0x00, 0x00, 0x00, 0x00]) # starting time (in frames, sint32)
if world.clock_mode == 'countdown':
write_bytes(rom, 0x180190, [0x01, 0x01, 0x00]) # set countdown, with no reset available
write_bytes(rom, 0x180200, [0xE0, 0xE3, 0xFF, 0xFF]) # red clock adjustment time (in frames, sint32)
write_bytes(rom, 0x180204, [0x20, 0x1C, 0x00, 0x00]) # blue clock adjustment time (in frames, sint32)
write_bytes(rom, 0x180208, [0x40, 0x38, 0x00, 0x00]) # green clock adjustment time (in frames, sint32)
write_bytes(rom, 0x18020C, [0x80, 0x32, 0x02, 0x00]) # starting time (in frames, sint32)
rom.write_bytes(0x180190, [0x01, 0x01, 0x00]) # set countdown, with no reset available
rom.write_bytes(0x180200, [0xE0, 0xE3, 0xFF, 0xFF]) # red clock adjustment time (in frames, sint32)
rom.write_bytes(0x180204, [0x20, 0x1C, 0x00, 0x00]) # blue clock adjustment time (in frames, sint32)
rom.write_bytes(0x180208, [0x40, 0x38, 0x00, 0x00]) # green clock adjustment time (in frames, sint32)
rom.write_bytes(0x18020C, [0x80, 0x32, 0x02, 0x00]) # starting time (in frames, sint32)
# set up goals for treasure hunt
write_bytes(rom, 0x180165, [0x0E, 0x28] if world.treasure_hunt_icon == 'Triforce Piece' else [0x0D, 0x28])
write_byte(rom, 0x180167, world.treasure_hunt_count % 256)
rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon == 'Triforce Piece' else [0x0D, 0x28])
rom.write_byte(0x180167, world.treasure_hunt_count % 256)
# assorted fixes
write_byte(rom, 0x180030, 0x00) # Disable SRAM trace
write_byte(rom, 0x180036, 0x0A) # Rupoor negative value
write_byte(rom, 0x180169, 0x01 if world.lock_aga_door_in_escape else 0x00) # Lock or unlock aga tower door during escape sequence.
write_byte(rom, 0x180086, 0x00 if world.aga_randomness else 0x01) # set blue ball and ganon warp randomness
write_byte(rom, 0x1800A1, 0x01) # enable overworld screen transition draining for water level inside swamp
rom.write_byte(0x180030, 0x00) # Disable SRAM trace
rom.write_byte(0x180036, 0x0A) # Rupoor negative value
rom.write_byte(0x180169, 0x01 if world.lock_aga_door_in_escape else 0x00) # Lock or unlock aga tower door during escape sequence.
rom.write_byte(0x180086, 0x00 if world.aga_randomness else 0x01) # set blue ball and ganon warp randomness
rom.write_byte(0x1800A1, 0x01) # enable overworld screen transition draining for water level inside swamp
if world.goal in ['pedestal', 'starhunt', 'triforcehunt']:
write_byte(rom, 0x18003E, 0x01) # make ganon invincible
rom.write_byte(0x18003E, 0x01) # make ganon invincible
elif world.goal in ['dungeons']:
write_byte(rom, 0x18003E, 0x02) # make ganon invincible until all dungeons are beat
write_byte(rom, 0x18016A, 0x00) # disable free roaming item text boxes
write_byte(rom, 0x18003B, 0x00) # disable maps showing crystals on overworld
write_byte(rom, 0x18003C, 0x00) # disable compasses showing dungeon count
rom.write_byte(0x18003E, 0x02) # make ganon invincible until all dungeons are beat
rom.write_byte(0x18016A, 0x00) # disable free roaming item text boxes
rom.write_byte(0x18003B, 0x00) # disable maps showing crystals on overworld
rom.write_byte(0x18003C, 0x00) # disable compasses showing dungeon count
digging_game_rng = random.randint(1, 30) # set rng for digging game
write_byte(rom, 0x180020, digging_game_rng)
write_byte(rom, 0xEFD95, digging_game_rng)
write_byte(rom, 0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills
write_byte(rom, 0x180042, 0x01 if world.save_and_quite_from_boss else 0x00) # Allow Save and Quite after boss kill
rom.write_byte(0x180020, digging_game_rng)
rom.write_byte(0xEFD95, digging_game_rng)
rom.write_byte(0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills
rom.write_byte(0x180042, 0x01 if world.save_and_quite_from_boss else 0x00) # Allow Save and Quite after boss kill
# remove shield from uncle
write_bytes(rom, 0x6D253, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
write_bytes(rom, 0x6D25B, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
write_bytes(rom, 0x6D283, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
write_bytes(rom, 0x6D28B, [0x00, 0x00, 0xf7, 0xff, 0x00, 0x0E])
write_bytes(rom, 0x6D2CB, [0x00, 0x00, 0xf6, 0xff, 0x02, 0x0E])
write_bytes(rom, 0x6D2FB, [0x00, 0x00, 0xf7, 0xff, 0x02, 0x0E])
write_bytes(rom, 0x6D313, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E])
rom.write_bytes(0x6D253, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
rom.write_bytes(0x6D25B, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
rom.write_bytes(0x6D283, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
rom.write_bytes(0x6D28B, [0x00, 0x00, 0xf7, 0xff, 0x00, 0x0E])
rom.write_bytes(0x6D2CB, [0x00, 0x00, 0xf6, 0xff, 0x02, 0x0E])
rom.write_bytes(0x6D2FB, [0x00, 0x00, 0xf7, 0xff, 0x02, 0x0E])
rom.write_bytes(0x6D313, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E])
if world.swamp_patch_required:
# patch swamp: Need to enable permanent drain of water as dam or swamp were moved
write_byte(rom, 0x18003D, 0x01)
rom.write_byte(0x18003D, 0x01)
# set correct flag for hera basement item
if world.get_location('[dungeon-L3-1F] Tower of Hera - Freestanding Key').item is not None and world.get_location('[dungeon-L3-1F] Tower of Hera - Freestanding Key').item.name == 'Small Key (Tower of Hera)':
write_byte(rom, 0x4E3BB, 0xE4)
rom.write_byte(0x4E3BB, 0xE4)
else:
write_byte(rom, 0x4E3BB, 0xEB)
# disable open door sprites when exiting caves
# this does not seem to work completely yet
if world.fix_door_frames:
for i in range(0x85):
write_byte(rom, 0x15274 + i, 0x00)
for i in range(0x86):
write_byte(rom, 0x15488 + i, 0x00)
# leave the entry marking tavern north a north facing exit
for i in range(0x82):
write_byte(rom, 0x15510 + i, 0x00)
rom.write_byte(0x4E3BB, 0xEB)
# fix trock doors for reverse entrances
if world.fix_trock_doors:
write_byte(rom, 0xFED31, 0x0E) # preopen bombable exit
write_byte(rom, 0xFEE41, 0x0E) # preopen bombable exit
write_byte(rom, 0xFE465, 0x1E) # remove small key door on backside of big key door
rom.write_byte(0xFED31, 0x0E) # preopen bombable exit
rom.write_byte(0xFEE41, 0x0E) # preopen bombable exit
rom.write_byte(0xFE465, 0x1E) # remove small key door on backside of big key door
# Thanks to Zarby89 for finding these values
# fix skull woods exit point
if world.fix_skullwoods_exit:
write_byte(rom, 0x15E0D, 0xF8)
rom.write_byte(0x15E0D, 0xF8)
# fix palace of darkness exit point
if world.fix_palaceofdarkness_exit:
write_byte(rom, 0x15E03, 0x40)
rom.write_byte(0x15E03, 0x40)
# fix turtle rock exit point
if world.fix_trock_exit:
write_byte(rom, 0x15E1D, 0x34)
rom.write_byte(0x15E1D, 0x34)
# enable quick item swapping with L and R (ported by Amazing Ampharos)
if quickswap:
write_bytes(rom, 0x107fb, [0x22, 0x50, 0xFF, 0x1F])
write_bytes(rom, 0x12451, [0x22, 0x50, 0xFF, 0x1F])
write_bytes(rom, 0xfff50, [0x20, 0x58, 0xFF, 0xA5, 0xF6, 0x29, 0x40, 0x6B, 0xA5, 0xF6, 0x89, 0x10, 0xF0, 0x03, 0x4C, 0x69,
rom.write_bytes(0x107fb, [0x22, 0x50, 0xFF, 0x1F])
rom.write_bytes(0x12451, [0x22, 0x50, 0xFF, 0x1F])
rom.write_bytes(0xfff50, [0x20, 0x58, 0xFF, 0xA5, 0xF6, 0x29, 0x40, 0x6B, 0xA5, 0xF6, 0x89, 0x10, 0xF0, 0x03, 0x4C, 0x69,
0xFF, 0x89, 0x20, 0xF0, 0x03, 0x4C, 0xAA, 0xFF, 0x60, 0xAD, 0x02, 0x02, 0xF0, 0x3B, 0xDA, 0xAA,
0xE0, 0x0F, 0xF0, 0x14, 0xE0, 0x10, 0xF0, 0x14, 0xE0, 0x14, 0xD0, 0x02, 0xA2, 0x00, 0xE8, 0xBF,
0x3F, 0xF3, 0x7E, 0xF0, 0xEB, 0x4C, 0xEB, 0xFF, 0xA2, 0x01, 0x80, 0x0A, 0xAF, 0x4F, 0xF3, 0x7E,
@ -305,38 +351,46 @@ def patch_rom(world, rom, hashtable, quickswap=False, beep='normal', sprite=None
# set rom name
# 21 bytes
write_bytes(rom, 0x7FC0, bytearray('ER_041_%09d_' % world.seed, 'utf8') + world.option_identifier.to_bytes(4, 'big'))
rom.write_bytes(0x7FC0, bytearray('ER_041_%09d_' % world.seed, 'utf8') + world.option_identifier.to_bytes(4, 'big'))
# set heart beep rate
write_byte(rom, 0x180033, {'off': 0x00, 'half': 0x40, 'quarter': 0x80, 'normal': 0x20}[beep])
rom.write_byte(0x180033, {'off': 0x00, 'half': 0x40, 'quarter': 0x80, 'normal': 0x20}[beep])
# store hash table for main menu hash
write_bytes(rom, 0x181000, hashtable)
rom.write_bytes(0x181000, hashtable)
# write link sprite if required
if sprite is not None:
write_bytes(rom, 0x80000, sprite)
write_sprite(rom, sprite)
if isinstance(rom, LocalRom):
rom.write_crc()
return rom
def write_byte(rom, address, value):
rom[address] = value
def write_bytes(rom, startaddress, values):
for i, value in enumerate(values):
write_byte(rom, startaddress + i, value)
def write_sprite(rom, sprite):
if len(sprite) == 0x7000:
# sprite file with graphics and without palette data
rom.write_bytes(0x80000, sprite[:0x7000])
elif len(sprite) == 0x7078:
# sprite file with graphics and palette data
rom.write_bytes(0x80000, sprite[:0x7000])
rom.write_bytes(0xDD308, sprite[0x7000:])
elif len(sprite) in [0x100000, 0x200000]:
# full rom with patched sprite, extract it
rom.write_bytes(0x80000, sprite[0x80000:0x87000])
rom.write_bytes(0xDD308, sprite[0xDD308:0xDD380])
def write_string_to_rom(rom, target, string):
address, maxbytes = text_addresses[target]
write_bytes(rom, address, string_to_alttp_text(string, maxbytes))
rom.write_bytes(address, string_to_alttp_text(string, maxbytes))
def write_credits_string_to_rom(rom, target, string):
address, length = credits_addresses[target]
write_bytes(rom, address, string_to_credits(string, length))
rom.write_bytes(address, string_to_credits(string, length))
def write_strings(rom, world):
@ -396,27 +450,3 @@ def write_strings(rom, world):
fluteboyitem = world.get_location('Flute Boy').item
fluteboyitem_text = FluteBoy_texts[random.randint(0, len(FluteBoy_texts) - 1)] if fluteboyitem is None or fluteboyitem.fluteboy_credit_text is None else fluteboyitem.fluteboy_credit_text
write_credits_string_to_rom(rom, 'FluteBoy', fluteboyitem_text)
def patch_base_rom(rom):
# verify correct checksum of baserom
basemd5 = hashlib.md5()
basemd5.update(rom)
if not JAP10HASH == basemd5.hexdigest():
logging.getLogger('').warning('Supplied Base Rom does not match known MD5 for JAP(1.0) release. Will try to patch anyway.')
# extend to 2MB
rom.extend(bytearray([0x00]*(2097152 - len(rom))))
# load randomizer patches
patches = json.load(open('base2current.json', 'r'))
for patch in patches:
if isinstance(patch, dict):
for baseaddress, values in patch.items():
write_bytes(rom, int(baseaddress), values)
# verify md5
patchedmd5 = hashlib.md5()
patchedmd5.update(rom)
if not 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.')