Add initial SRAM class and refactor to use it
This commit is contained in:
@@ -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
246
InitialSram.py
Normal 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[:]
|
||||||
1
Items.py
1
Items.py
@@ -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
68
Rom.py
@@ -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)
|
||||||
|
|||||||
6
Utils.py
6
Utils.py
@@ -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
Reference in New Issue
Block a user