Add initial SRAM class and refactor to use it

This commit is contained in:
cassidoxa
2022-02-20 00:21:33 -05:00
parent 633c221d6f
commit 8795a4b393
6 changed files with 294 additions and 30 deletions

View File

@@ -82,6 +82,7 @@ class World(object):
self.dynamic_locations = [] self.dynamic_locations = []
self.spoiler = Spoiler(self) self.spoiler = Spoiler(self)
self.lamps_needed_for_dark_rooms = 1 self.lamps_needed_for_dark_rooms = 1
self.pseudoboots = {player: False for player in range(1, players + 1)}
def intialize_regions(self): def intialize_regions(self):
for region in self.regions: for region in self.regions:

246
InitialSram.py Normal file
View File

@@ -0,0 +1,246 @@
from dataclasses import dataclass, field
from BaseClasses import CollectionState
from Utils import count_set_bits
SRAM_SIZE = 0x500
ROOM_DATA = 0x000
OVERWORLD_DATA = 0x280
def _new_default_sram():
sram_buf = [0x00] * 0x500
sram_buf[ROOM_DATA+0x20D] = 0xF0
sram_buf[ROOM_DATA+0x20F] = 0xF0
sram_buf[0x379] = 0x68
sram_buf[0x401] = 0xFF
sram_buf[0x402] = 0xFF
return sram_buf
@dataclass
class InitialSram:
_initial_sram_bytes: list[int] = field(default_factory=_new_default_sram)
def _set_value(self, idx: int, val:int):
if idx > SRAM_SIZE:
raise IndexError('SRAM index out of bounds: {idx}')
if not (-1 < val < 256):
raise ValueError('SRAM value must be between 0 and 255: {val}')
self._initial_sram_bytes[idx] = val
def _or_value(self, idx: int, val:int):
if idx > SRAM_SIZE:
raise IndexError('SRAM index out of bounds: {idx}')
if not (-1 < val < 256):
raise ValueError('SRAM value must be between 0 and 255: {val}')
self._initial_sram_bytes[idx] |= val
def pre_open_aga_curtains(self):
self._or_value(ROOM_DATA+0x61, 0x80)
def pre_open_skullwoods_curtains(self):
self._or_value(ROOM_DATA+0x93, 0x80)
def pre_open_lumberjack(self):
self._or_value(OVERWORLD_DATA+0x02, 0x20)
def pre_open_castle_gate(self):
self._or_value(OVERWORLD_DATA+0x1B, 0x20)
def pre_open_ganons_tower(self):
self._or_value(OVERWORLD_DATA+0x43, 0x20)
def pre_open_pyramid_hole(self):
self._or_value(OVERWORLD_DATA+0x5B, 0x20)
def set_starting_equipment(self, world: object, player: int):
equip = [0] * (0x340 + 0x4F)
equip[0x36C] = 0x18
equip[0x36D] = 0x18
equip[0x379] = 0x68
starting_bomb_cap_upgrades = 0
starting_arrow_cap_upgrades = 0
startingstate = CollectionState(world)
if startingstate.has('Bow', player):
equip[0x340] = 3 if startingstate.has('Silver Arrows', player) else 1
equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases
if not world.retro[player]:
equip[0x38E] |= 0x80
if startingstate.has('Silver Arrows', player):
equip[0x38E] |= 0x40
if startingstate.has('Titans Mitts', player):
equip[0x354] = 2
elif startingstate.has('Power Glove', player):
equip[0x354] = 1
if startingstate.has('Golden Sword', player):
equip[0x359] = 4
elif startingstate.has('Tempered Sword', player):
equip[0x359] = 3
elif startingstate.has('Master Sword', player):
equip[0x359] = 2
elif startingstate.has('Fighter Sword', player):
equip[0x359] = 1
if startingstate.has('Mirror Shield', player):
equip[0x35A] = 3
elif startingstate.has('Red Shield', player):
equip[0x35A] = 2
elif startingstate.has('Blue Shield', player):
equip[0x35A] = 1
if startingstate.has('Red Mail', player):
equip[0x35B] = 2
elif startingstate.has('Blue Mail', player):
equip[0x35B] = 1
if startingstate.has('Magic Upgrade (1/4)', player):
equip[0x37B] = 2
equip[0x36E] = 0x80
elif startingstate.has('Magic Upgrade (1/2)', player):
equip[0x37B] = 1
equip[0x36E] = 0x80
for item in world.precollected_items:
if item.player != player:
continue
if item.name in ['Bow', 'Silver Arrows', 'Progressive Bow', 'Progressive Bow (Alt)',
'Titans Mitts', 'Power Glove', 'Progressive Glove',
'Golden Sword', 'Tempered Sword', 'Master Sword', 'Fighter Sword', 'Progressive Sword',
'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield',
'Red Mail', 'Blue Mail', 'Progressive Armor',
'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)']:
continue
set_table = {'Book of Mudora': (0x34E, 1), 'Hammer': (0x34B, 1), 'Bug Catching Net': (0x34D, 1), 'Hookshot': (0x342, 1), 'Magic Mirror': (0x353, 2),
'Cape': (0x352, 1), 'Lamp': (0x34A, 1), 'Moon Pearl': (0x357, 1), 'Cane of Somaria': (0x350, 1), 'Cane of Byrna': (0x351, 1),
'Fire Rod': (0x345, 1), 'Ice Rod': (0x346, 1), 'Bombos': (0x347, 1), 'Ether': (0x348, 1), 'Quake': (0x349, 1)}
or_table = {'Green Pendant': (0x374, 0x04), 'Red Pendant': (0x374, 0x01), 'Blue Pendant': (0x374, 0x02),
'Crystal 1': (0x37A, 0x02), 'Crystal 2': (0x37A, 0x10), 'Crystal 3': (0x37A, 0x40), 'Crystal 4': (0x37A, 0x20),
'Crystal 5': (0x37A, 0x04), 'Crystal 6': (0x37A, 0x01), 'Crystal 7': (0x37A, 0x08),
'Big Key (Eastern Palace)': (0x367, 0x20), 'Compass (Eastern Palace)': (0x365, 0x20), 'Map (Eastern Palace)': (0x369, 0x20),
'Big Key (Desert Palace)': (0x367, 0x10), 'Compass (Desert Palace)': (0x365, 0x10), 'Map (Desert Palace)': (0x369, 0x10),
'Big Key (Tower of Hera)': (0x366, 0x20), 'Compass (Tower of Hera)': (0x364, 0x20), 'Map (Tower of Hera)': (0x368, 0x20),
'Big Key (Escape)': (0x367, 0xC0), 'Compass (Escape)': (0x365, 0xC0), 'Map (Escape)': (0x369, 0xC0),
'Big Key (Agahnims Tower)': (0x367, 0x08), 'Compass (Agahnims Tower)': (0x365, 0x08), 'Map (Agahnims Tower)': (0x369, 0x08),
'Big Key (Palace of Darkness)': (0x367, 0x02), 'Compass (Palace of Darkness)': (0x365, 0x02), 'Map (Palace of Darkness)': (0x369, 0x02),
'Big Key (Thieves Town)': (0x366, 0x10), 'Compass (Thieves Town)': (0x364, 0x10), 'Map (Thieves Town)': (0x368, 0x10),
'Big Key (Skull Woods)': (0x366, 0x80), 'Compass (Skull Woods)': (0x364, 0x80), 'Map (Skull Woods)': (0x368, 0x80),
'Big Key (Swamp Palace)': (0x367, 0x04), 'Compass (Swamp Palace)': (0x365, 0x04), 'Map (Swamp Palace)': (0x369, 0x04),
'Big Key (Ice Palace)': (0x366, 0x40), 'Compass (Ice Palace)': (0x364, 0x40), 'Map (Ice Palace)': (0x368, 0x40),
'Big Key (Misery Mire)': (0x367, 0x01), 'Compass (Misery Mire)': (0x365, 0x01), 'Map (Misery Mire)': (0x369, 0x01),
'Big Key (Turtle Rock)': (0x366, 0x08), 'Compass (Turtle Rock)': (0x364, 0x08), 'Map (Turtle Rock)': (0x368, 0x08),
'Big Key (Ganons Tower)': (0x366, 0x04), 'Compass (Ganons Tower)': (0x364, 0x04), 'Map (Ganons Tower)': (0x368, 0x04)}
set_or_table = {'Flippers': (0x356, 1, 0x379, 0x02),'Pegasus Boots': (0x355, 1, 0x379, 0x04),
'Shovel': (0x34C, 1, 0x38C, 0x04), 'Ocarina': (0x34C, 3, 0x38C, 0x01),
'Mushroom': (0x344, 1, 0x38C, 0x20 | 0x08), 'Magic Powder': (0x344, 2, 0x38C, 0x10),
'Blue Boomerang': (0x341, 1, 0x38C, 0x80), 'Red Boomerang': (0x341, 2, 0x38C, 0x40)}
keys = {'Small Key (Eastern Palace)': [0x37E], 'Small Key (Desert Palace)': [0x37F],
'Small Key (Tower of Hera)': [0x386],
'Small Key (Agahnims Tower)': [0x380], 'Small Key (Palace of Darkness)': [0x382],
'Small Key (Thieves Town)': [0x387],
'Small Key (Skull Woods)': [0x384], 'Small Key (Swamp Palace)': [0x381],
'Small Key (Ice Palace)': [0x385],
'Small Key (Misery Mire)': [0x383], 'Small Key (Turtle Rock)': [0x388],
'Small Key (Ganons Tower)': [0x389],
'Small Key (Universal)': [0x38B], 'Small Key (Escape)': [0x37C, 0x37D]}
bottles = {'Bottle': 2, 'Bottle (Red Potion)': 3, 'Bottle (Green Potion)': 4, 'Bottle (Blue Potion)': 5,
'Bottle (Fairy)': 6, 'Bottle (Bee)': 7, 'Bottle (Good Bee)': 8}
rupees = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)': 50, 'Rupees (100)': 100, 'Rupees (300)': 300}
bomb_caps = {'Bomb Upgrade (+5)': 5, 'Bomb Upgrade (+10)': 10}
arrow_caps = {'Arrow Upgrade (+5)': 5, 'Arrow Upgrade (+10)': 10}
bombs = {'Single Bomb': 1, 'Bombs (3)': 3, 'Bombs (10)': 10}
arrows = {'Single Arrow': 1, 'Arrows (10)': 10}
if item.name in set_table:
equip[set_table[item.name][0]] = set_table[item.name][1]
elif item.name in or_table:
equip[or_table[item.name][0]] |= or_table[item.name][1]
elif item.name in set_or_table:
equip[set_or_table[item.name][0]] = set_or_table[item.name][1]
equip[set_or_table[item.name][2]] |= set_or_table[item.name][3]
elif item.name in keys:
for address in keys[item.name]:
equip[address] = min(equip[address] + 1, 99)
elif item.name in bottles:
if equip[0x34F] < world.difficulty_requirements[player].progressive_bottle_limit:
equip[0x35C + equip[0x34F]] = bottles[item.name]
equip[0x34F] += 1
elif item.name in rupees:
equip[0x360:0x362] = list(min(equip[0x360] + (equip[0x361] << 8) + rupees[item.name], 9999).to_bytes(2, byteorder='little', signed=False))
equip[0x362:0x364] = list(min(equip[0x362] + (equip[0x363] << 8) + rupees[item.name], 9999).to_bytes(2, byteorder='little', signed=False))
elif item.name in bomb_caps:
starting_bomb_cap_upgrades += bomb_caps[item.name]
elif item.name in arrow_caps:
starting_arrow_cap_upgrades += arrow_caps[item.name]
elif item.name in bombs:
equip[0x343] += bombs[item.name]
elif item.name in arrows:
if world.retro[player]:
equip[0x38E] |= 0x80
equip[0x377] = 1
else:
equip[0x377] += arrows[item.name]
elif item.name in ['Piece of Heart', 'Boss Heart Container', 'Sanctuary Heart Container']:
if item.name == 'Piece of Heart':
equip[0x36B] = (equip[0x36B] + 1) % 4
if item.name != 'Piece of Heart' or equip[0x36B] == 0:
equip[0x36C] = min(equip[0x36C] + 0x08, 0xA0)
equip[0x36D] = min(equip[0x36D] + 0x08, 0xA0)
else:
raise RuntimeError(f'Unsupported item in starting equipment: {item.name}')
equip[0x370] = max(starting_bomb_cap_upgrades, 50)
equip[0x371] = max(starting_arrow_cap_upgrades, 70)
equip[0x343] = min(equip[0x343], 10)
equip[0x377] = min(equip[0x377], 30)
# Assertion and copy equip to initial_sram_bytes
assert equip[:0x340] == [0] * 0x340
self._initial_sram_bytes[0x340:0x38F] = equip[0x340:0x38F]
# Set counters and highest equipment values
self._initial_sram_bytes[0x471] = count_set_bits(self._initial_sram_bytes[0x37A])
self._initial_sram_bytes[0x429] = count_set_bits(self._initial_sram_bytes[0x374])
self._initial_sram_bytes[0x417] = self._initial_sram_bytes[0x359]
self._initial_sram_bytes[0x422] = self._initial_sram_bytes[0x35A]
self._initial_sram_bytes[0x46E] = self._initial_sram_bytes[0x35B]
if world.swords[player] == "swordless":
self.__initial_sram_bytes[0x359] = 0xFF
self.__initial_sram_bytes[0x417] = 0x00
def set_starting_rupees(self, rupees: int):
if not (-1 < rupees < 10000):
raise ValueError("Starting rupees must be between 0 and 9999")
self._initial_sram_bytes[0x362] = self._initial_sram_bytes[0x360] = rupees & 0xFF
self._initial_sram_bytes[0x363] = self._initial_sram_bytes[0x361] = rupees >> 8
def set_progress_indicator(self, indicator: int):
self._set_value(0x3C5, indicator)
def set_progress_flags(self, flags: int):
self._set_value(0x3C6, flags)
def set_starting_entrance(self, entrance: int):
self._set_value(0x3C8, entrance)
def set_starting_timer(self, seconds: int):
timer = (seconds * 60).to_bytes(4, "little")
self._initial_sram_bytes[0x454] = timer[0]
self._initial_sram_bytes[0x455] = timer[1]
self._initial_sram_bytes[0x456] = timer[2]
self._initial_sram_bytes[0x457] = timer[3]
def set_swordless_curtains(self):
self._or_value(ROOM_DATA+0x61, 0x80)
self._or_value(ROOM_DATA+0x93, 0x80)
def get_initial_sram(self):
assert len(self._initial_sram_bytes) == SRAM_SIZE
return self._initial_sram_bytes[:]

View File

@@ -55,6 +55,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla
'Bottle (Good Bee)': (True, False, None, 0x48, 'I will sting your foes a whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has beetor again', 'a Bottle'), 'Bottle (Good Bee)': (True, False, None, 0x48, 'I will sting your foes a whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has beetor again', 'a Bottle'),
'Master Sword': (True, False, 'Sword', 0x50, 'I beat barries and pigs alike', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again', 'the Master Sword'), 'Master Sword': (True, False, 'Sword', 0x50, 'I beat barries and pigs alike', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again', 'the Master Sword'),
'Tempered Sword': (True, False, 'Sword', 0x02, 'I stole the\nblacksmith\'s\njob!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again', 'the Tempered Sword'), 'Tempered Sword': (True, False, 'Sword', 0x02, 'I stole the\nblacksmith\'s\njob!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again', 'the Tempered Sword'),
'Sword and Shield': (True, False, 'Sword', 0x00, 'An uncle\nsword rests\nhere!', 'the sword and shield', 'sword and shield-wielding kid', 'training set for sale', 'fungus for training set', 'sword and shield boy fights again', 'the small sword and shield'),
'Fighter Sword': (True, False, 'Sword', 0x49, 'A pathetic\nsword rests\nhere!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again', 'the small sword'), 'Fighter Sword': (True, False, 'Sword', 0x49, 'A pathetic\nsword rests\nhere!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again', 'the small sword'),
'Golden Sword': (True, False, 'Sword', 0x03, 'The butter\nsword rests\nhere!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again', 'the Golden Sword'), 'Golden Sword': (True, False, 'Sword', 0x03, 'The butter\nsword rests\nhere!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again', 'the Golden Sword'),
'Progressive Sword': (True, False, 'Sword', 0x5E, 'a better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a sword'), 'Progressive Sword': (True, False, 'Sword', 0x5E, 'a better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a sword'),

68
Rom.py
View File

@@ -14,17 +14,19 @@ from Text import Uncle_texts, Ganon1_texts, TavernMan_texts, Sahasrahla2_texts,
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
from Utils import output_path, local_path, int16_as_bytes, int32_as_bytes, snes_to_pc from Utils import output_path, local_path, int16_as_bytes, int32_as_bytes, snes_to_pc
from Items import ItemFactory, item_table from Items import ItemFactory, item_table
from InitialSram import InitialSram
from EntranceShuffle import door_addresses from EntranceShuffle import door_addresses
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = 'ac23ab12e7c442515d51370642772c3b' RANDOMIZERBASEHASH = '2f55b2be3691b962cf609749263ee447'
class JsonRom(object): class JsonRom(object):
def __init__(self): def __init__(self):
self.name = None self.name = None
self.initial_sram = InitialSram()
self.patches = {} self.patches = {}
def write_byte(self, address, value): def write_byte(self, address, value):
@@ -45,6 +47,9 @@ class JsonRom(object):
def write_int32(self, address, value): def write_int32(self, address, value):
self.write_bytes(address, int32_as_bytes(value)) self.write_bytes(address, int32_as_bytes(value))
def write_initial_sram(self):
self.write_bytes(0x183000, self.initial_sram.get_initial_sram())
def write_to_file(self, file): def write_to_file(self, file):
with open(file, 'w') as stream: with open(file, 'w') as stream:
json.dump([self.patches], stream) json.dump([self.patches], stream)
@@ -60,6 +65,7 @@ class LocalRom(object):
def __init__(self, file, patch=True): def __init__(self, file, patch=True):
self.name = None self.name = None
self.initial_sram = InitialSram()
with open(file, 'rb') as stream: with open(file, 'rb') as stream:
self.buffer = read_rom(stream) self.buffer = read_rom(stream)
if patch: if patch:
@@ -86,6 +92,9 @@ class LocalRom(object):
for i, value in enumerate(values): for i, value in enumerate(values):
self.write_int32(startaddress + (i * 2), value) self.write_int32(startaddress + (i * 2), value)
def write_initial_sram(self):
self.write_bytes(0x183000, self.initial_sram.get_initial_sram())
def write_to_file(self, file): def write_to_file(self, file):
with open(file, 'wb') as outfile: with open(file, 'wb') as outfile:
outfile.write(self.buffer) outfile.write(self.buffer)
@@ -558,11 +567,11 @@ def patch_rom(world, player, rom):
# set open mode: # set open mode:
if world.mode in ['open', 'inverted']: if world.mode in ['open', 'inverted']:
rom.write_byte(0x180032, 0x01) # open mode init_open_mode_sram(rom)
if world.mode == 'inverted': if world.mode == 'inverted':
set_inverted_mode(world, rom) set_inverted_mode(world, rom)
elif world.mode == 'standard': elif world.mode == 'standard':
rom.write_byte(0x180032, 0x00) # standard mode init_standard_mode_sram(rom)
uncle_location = world.get_location('Link\'s Uncle', player) uncle_location = world.get_location('Link\'s Uncle', player)
if uncle_location.item is None or uncle_location.item.name not in ['Master Sword', 'Tempered Sword', 'Fighter Sword', 'Golden Sword', 'Progressive Sword']: if uncle_location.item is None or uncle_location.item.name not in ['Master Sword', 'Tempered Sword', 'Fighter Sword', 'Golden Sword', 'Progressive Sword']:
@@ -792,10 +801,9 @@ def patch_rom(world, player, rom):
# set swordless mode settings # set swordless mode settings
rom.write_byte(0x18003F, 0x01 if world.swords == 'swordless' else 0x00) # hammer can harm ganon rom.write_byte(0x18003F, 0x01 if world.swords == 'swordless' else 0x00) # hammer can harm ganon
rom.write_byte(0x180040, 0x01 if world.swords == 'swordless' else 0x00) # open curtains
rom.write_byte(0x180041, 0x01 if world.swords == 'swordless' else 0x00) # swordless medallions rom.write_byte(0x180041, 0x01 if world.swords == 'swordless' else 0x00) # swordless medallions
rom.write_byte(0x180043, 0xFF if world.swords == 'swordless' else 0x00) # starting sword for link
rom.write_byte(0x180044, 0x01 if world.swords == 'swordless' else 0x00) # hammer activates tablets rom.write_byte(0x180044, 0x01 if world.swords == 'swordless' else 0x00) # hammer activates tablets
rom.initial_sram.set_swordless_curtains() # open curtains
# set up clocks for timed modes # set up clocks for timed modes
if world.shuffle == 'vanilla': if world.shuffle == 'vanilla':
@@ -811,34 +819,34 @@ def patch_rom(world, player, rom):
rom.write_int32(0x180200, 0) # red clock adjustment time (in frames, sint32) rom.write_int32(0x180200, 0) # red clock adjustment time (in frames, sint32)
rom.write_int32(0x180204, 0) # blue clock adjustment time (in frames, sint32) rom.write_int32(0x180204, 0) # blue clock adjustment time (in frames, sint32)
rom.write_int32(0x180208, 0) # green clock adjustment time (in frames, sint32) rom.write_int32(0x180208, 0) # green clock adjustment time (in frames, sint32)
rom.write_int32(0x18020C, 0) # starting time (in frames, sint32) rom.initial_sram.set_starting_timer(0) # starting time (in frames, sint32)
elif world.clock_mode == 'ohko': elif world.clock_mode == 'ohko':
rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality
rom.write_int32(0x180200, 0) # red clock adjustment time (in frames, sint32) rom.write_int32(0x180200, 0) # red clock adjustment time (in frames, sint32)
rom.write_int32(0x180204, 0) # blue clock adjustment time (in frames, sint32) rom.write_int32(0x180204, 0) # blue clock adjustment time (in frames, sint32)
rom.write_int32(0x180208, 0) # green clock adjustment time (in frames, sint32) rom.write_int32(0x180208, 0) # green clock adjustment time (in frames, sint32)
rom.write_int32(0x18020C, 0) # starting time (in frames, sint32) rom.initial_sram.set_starting_timer(0) # starting time (in frames, sint32)
elif world.clock_mode == 'countdown-ohko': elif world.clock_mode == 'countdown-ohko':
rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality
rom.write_int32(0x180200, -100 * 60 * 60 * 60) # red clock adjustment time (in frames, sint32) rom.write_int32(0x180200, -100 * 60 * 60 * 60) # red clock adjustment time (in frames, sint32)
rom.write_int32(0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32) rom.write_int32(0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
rom.write_int32(0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32) rom.write_int32(0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
if world.difficulty_adjustments == 'normal': if world.difficulty_adjustments == 'normal':
rom.write_int32(0x18020C, (10 + ERtimeincrease) * 60 * 60) # starting time (in frames, sint32) rom.initial_sram.set_starting_timer((10 + ERtimeincrease) * 60) # starting time (in seconds)
else: else:
rom.write_int32(0x18020C, int((5 + ERtimeincrease / 2) * 60 * 60)) # starting time (in frames, sint32) rom.initial_sram.set_starting_timer(int((5 + ERtimeincrease / 2) * 60)) # starting time (in seconds)
if world.clock_mode == 'stopwatch': if world.clock_mode == 'stopwatch':
rom.write_bytes(0x180190, [0x02, 0x01, 0x00]) # set stopwatch mode rom.write_bytes(0x180190, [0x02, 0x01, 0x00]) # set stopwatch mode
rom.write_int32(0x180200, -2 * 60 * 60) # red clock adjustment time (in frames, sint32) rom.write_int32(0x180200, -2 * 60 * 60) # red clock adjustment time (in frames, sint32)
rom.write_int32(0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32) rom.write_int32(0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
rom.write_int32(0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32) rom.write_int32(0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
rom.write_int32(0x18020C, 0) # starting time (in frames, sint32) rom.initial_sram.set_starting_timer(0) # starting time (in frames, sint32)
if world.clock_mode == 'countdown': if world.clock_mode == 'countdown':
rom.write_bytes(0x180190, [0x01, 0x01, 0x00]) # set countdown, with no reset available rom.write_bytes(0x180190, [0x01, 0x01, 0x00]) # set countdown, with no reset available
rom.write_int32(0x180200, -2 * 60 * 60) # red clock adjustment time (in frames, sint32) rom.write_int32(0x180200, -2 * 60 * 60) # red clock adjustment time (in frames, sint32)
rom.write_int32(0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32) rom.write_int32(0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
rom.write_int32(0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32) rom.write_int32(0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
rom.write_int32(0x18020C, (40 + ERtimeincrease) * 60 * 60) # starting time (in frames, sint32) rom.initial_sram.set_starting_timer((40 + ERtimeincrease) * 60) # starting time (in seconds)
# set up goals for treasure hunt # set up goals for treasure hunt
rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon == 'Triforce Piece' else [0x0D, 0x28]) rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon == 'Triforce Piece' else [0x0D, 0x28])
@@ -857,14 +865,15 @@ def patch_rom(world, player, rom):
rom.write_byte(0x180169, 0x02) # lock aga/ganon tower door with crystals in inverted rom.write_byte(0x180169, 0x02) # lock aga/ganon tower door with crystals in inverted
rom.write_byte(0x180171, 0x01 if world.ganon_at_pyramid[player] else 0x00) # Enable respawning on pyramid after ganon death rom.write_byte(0x180171, 0x01 if world.ganon_at_pyramid[player] else 0x00) # Enable respawning on pyramid after ganon death
rom.write_byte(0x180173, 0x01) # Bob is enabled rom.write_byte(0x180173, 0x01) # Bob is enabled
rom.write_byte(0x180168, 0x08) # Spike Cave Damage rom.write_byte(0x180195, 0x08) # Spike Cave Damage
rom.write_bytes(0x18016B, [0x04, 0x02, 0x01]) #Set spike cave and MM spike room Cape usage rom.write_bytes(0x18016B, [0x04, 0x02, 0x01]) #Set spike cave and MM spike room Cape usage
rom.write_bytes(0x18016E, [0x04, 0x08, 0x10]) #Set spike cave and MM spike room Cape usage rom.write_bytes(0x18016E, [0x04, 0x08, 0x10]) #Set spike cave and MM spike room Cape usage
rom.write_bytes(0x50563, [0x3F, 0x14]) # disable below ganon chest rom.write_bytes(0x50563, [0x3F, 0x14]) # disable below ganon chest
rom.write_byte(0x50599, 0x00) # disable below ganon chest rom.write_byte(0x50599, 0x00) # disable below ganon chest
rom.write_bytes(0xE9A5, [0x7E, 0x00, 0x24]) # disable below ganon chest rom.write_bytes(0xE9A5, [0x7E, 0x00, 0x24]) # disable below ganon chest
rom.write_byte(0x18008B, 0x00) # Pyramid Hole not pre-opened rom.write_byte(0x18008B, 0x00) # Pyramid Hole not pre-opened
rom.write_byte(0x18008C, 0x01 if world.crystals_needed_for_gt == 0 else 0x00) # Pyramid Hole pre-opened if crystal requirement is 0 if world.crystals_needed_for_gt == 0:
rom.initial_sram.pre_open_ganons_tower()
rom.write_byte(0xF5D73, 0xF0) # bees are catchable rom.write_byte(0xF5D73, 0xF0) # bees are catchable
rom.write_byte(0xF5F10, 0xF0) # bees are catchable rom.write_byte(0xF5F10, 0xF0) # bees are catchable
rom.write_byte(0x180086, 0x00 if world.aga_randomness else 0x01) # set blue ball and ganon warp randomness rom.write_byte(0x180086, 0x00 if world.aga_randomness else 0x01) # set blue ball and ganon warp randomness
@@ -874,22 +883,9 @@ def patch_rom(world, player, rom):
rom.write_byte(0x18017E, 0x01) # Fairy fountains only trade in bottles rom.write_byte(0x18017E, 0x01) # Fairy fountains only trade in bottles
rom.write_byte(0x180034, 0x0A) # starting max bombs rom.write_byte(0x180034, 0x0A) # starting max bombs
rom.write_byte(0x180035, 30) # starting max arrows rom.write_byte(0x180035, 30) # starting max arrows
for x in range(0x183000, 0x18304F): if world.pseudoboots[player]:
rom.write_byte(x, 0) # Zero the initial equipment array rom.write_byte(0x18008E, 0x01)
rom.write_byte(0x18302C, 0x18) # starting max health rom.initial_sram.set_starting_equipment(world, player)
rom.write_byte(0x18302D, 0x18) # starting current health
rom.write_byte(0x183039, 0x68) # starting abilities, bit array
for item in world.precollected_items:
if item.player != player:
continue
if item.name == 'Fighter Sword':
rom.write_byte(0x183000+0x19, 0x01)
rom.write_byte(0x0271A6+0x19, 0x01)
rom.write_byte(0x180043, 0x01) # special starting sword byte
else:
raise RuntimeError("Unsupported pre-collected item: {}".format(item))
rom.write_byte(0x18004A, 0x00 if world.mode != 'inverted' else 0x01) # Inverted mode rom.write_byte(0x18004A, 0x00 if world.mode != 'inverted' else 0x01) # Inverted mode
rom.write_byte(0x18005D, 0x00) # Hammer always breaks barrier rom.write_byte(0x18005D, 0x00) # Hammer always breaks barrier
@@ -1044,6 +1040,9 @@ def patch_rom(world, player, rom):
write_strings(rom, world, player) write_strings(rom, world, player)
# write initial sram
rom.write_initial_sram()
# set rom name # set rom name
# 21 bytes # 21 bytes
from Main import __version__ from Main import __version__
@@ -1425,6 +1424,17 @@ def write_strings(rom, world, player):
rom.write_bytes(0x181500, data) rom.write_bytes(0x181500, data)
rom.write_bytes(0x76CC0, [byte for p in pointers for byte in [p & 0xFF, p >> 8 & 0xFF]]) rom.write_bytes(0x76CC0, [byte for p in pointers for byte in [p & 0xFF, p >> 8 & 0xFF]])
def init_open_mode_sram(rom):
rom.initial_sram.pre_open_castle_gate();
rom.initial_sram.set_progress_indicator(0x02);
rom.initial_sram.set_progress_flags(0x14);
rom.initial_sram.set_starting_entrance(0x01);
def init_standard_mode_sram(rom):
rom.initial_sram.set_progress_indicator(0x00);
rom.initial_sram.set_progress_flags(0x0);
rom.initial_sram.set_starting_entrance(0x00);
def set_inverted_mode(world, rom): def set_inverted_mode(world, rom):
rom.write_byte(snes_to_pc(0x0283E0), 0xF0) # residual portals rom.write_byte(snes_to_pc(0x0283E0), 0xF0) # residual portals
rom.write_byte(snes_to_pc(0x02B34D), 0xF0) rom.write_byte(snes_to_pc(0x02B34D), 0xF0)

View File

@@ -19,6 +19,12 @@ def snes_to_pc(value):
def is_bundled(): def is_bundled():
return getattr(sys, 'frozen', False) return getattr(sys, 'frozen', False)
def count_set_bits(val):
if val == 0:
return 0
else:
return (val & 1) + count_set_bits(val >> 1)
def local_path(path): def local_path(path):
if local_path.cached_path is not None: if local_path.cached_path is not None:
return os.path.join(local_path.cached_path, path) return os.path.join(local_path.cached_path, path)

File diff suppressed because one or more lines are too long