def _GetPatchForOverworldCaveData(self) -> Patch: patch = Patch() for cave_type in Range.VALID_CAVE_TYPES: cave_num = int(cave_type) - self.CAVE_TYPE_CAVE_NUM_OFFSET if cave_type == CaveType.ARMOS_ITEM_VIRTUAL_CAVE: patch.AddData( self.ARMOS_ITEM_ADDRESS, [self.overworld_caves[cave_num].GetItemAtPosition(2)]) continue if cave_type == CaveType.COAST_ITEM_VIRTUAL_CAVE: patch.AddData( self.COAST_ITEM_ADDRESS, [self.overworld_caves[cave_num].GetItemAtPosition(2)]) continue # Note that the Cave class is responsible for protecting bits 6 and 7 in its item data patch.AddData( self.OVERWORLD_DATA_START_ADDRESS + self._GetOverworldCaveDataIndex( cave_type, 0, is_second_byte=False), self.overworld_caves[cave_num].GetItemData()) patch.AddData( self.OVERWORLD_DATA_START_ADDRESS + self._GetOverworldCaveDataIndex( cave_type, 0, is_second_byte=True), self.overworld_caves[cave_num].GetPriceData()) return patch
def _GetPatchForLevelGrid(self, start_address: int, rooms: List[Room]) -> Patch: patch = Patch() for room_num in Range.VALID_ROOM_NUMBERS: room_data = rooms[room_num].GetRomData() assert len(room_data) == self.NUM_BYTES_OF_DATA_PER_ROOM for table_num in range(0, self.NUM_BYTES_OF_DATA_PER_ROOM): patch.AddData( start_address + table_num * self.LEVEL_TABLE_SIZE + room_num, [room_data[table_num]]) return patch
def _AddExtras(self, patch: Patch) -> None: if self.settings.progressive_items: patch.AddData(0x6B49, [0x11, 0x12, 0x13]) # Swords patch.AddData(0x6B4E, [0x11, 0x12]) # Candles patch.AddData(0x6B50, [0x11, 0x12]) # Arrows patch.AddData(0x6B5A, [0x11, 0x12]) # Rings # Change "no item" code from 0x03 (Mags) to 0x0E (Triforce of Power) patch.AddData(0x1785F, [0x0E]) # Include everything above in the hash code. hash_code = patch.GetHashCode() patch.AddData(0xAFD0, hash_code) patch.AddData(0xA4CD, [0x4C, 0x90, 0xAF]) patch.AddData(0xAFA0, [ 0xA2, 0x0A, 0xA9, 0xFF, 0x95, 0xAC, 0xCA, 0xD0, 0xFB, 0xA2, 0x04, 0xA0, 0x60, 0xBD, 0xBF, 0xAF, 0x9D, 0x44, 0x04, 0x98, 0x69, 0x1B, 0xA8, 0x95, 0x70, 0xA9, 0x20, 0x95, 0x84, 0xA9, 0x00, 0x95, 0xAC, 0xCA, 0xD0, 0xE9, 0x20, 0x9D, 0x97, 0xA9, 0x14, 0x85, 0x14, 0xE6, 0x13, 0x60, 0xFF, 0xFF, 0x1E, 0x0A, 0x06, 0x01 ]) # What does this do? patch.AddData( 0x1A129, [0x0C, 0x18, 0x0D, 0x0E, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24]) if self.settings.select_swap: patch.AddData(0x1EC4C, [0x4C, 0xC0, 0xFF]) patch.AddData(0x1FFD0, [ 0xA9, 0x05, 0x20, 0xAC, 0xFF, 0xAD, 0x56, 0x06, 0xC9, 0x0F, 0xD0, 0x02, 0xA9, 0x07, 0xA8, 0xA9, 0x01, 0x20, 0xC8, 0xB7, 0x4C, 0x58, 0xEC ]) if self.settings.randomize_level_text or self.settings.speed_up_text: random_level_text = random.choice( ['palace', 'house-', 'block-', 'random', 'cage_-', 'home_-', 'castle']) text_data_table = TextDataTable( "very_fast" if self.settings.speed_up_text else "normal", random_level_text if self.settings.randomize_level_text else "level-") patch += text_data_table.GetPatch()
def _GetPatchForOverworldData(self) -> Patch: patch = Patch() for screen_num in Range.VALID_ROOM_NUMBERS: addr = 0x80 + int(screen_num) patch.AddData(0x18410 + addr, [self.overworld_raw_data[addr]]) return patch
def _GetPatchForLevelMetadata(self) -> Patch: patch = Patch() patch.AddData(self.LEVEL_METADATA_ADDRESS, self.level_metadata) return patch
class DataTable(): NES_FILE_OFFSET = 0x10 OVERWORLD_DATA_START_ADDRESS = 0x18400 + NES_FILE_OFFSET LEVEL_1_TO_6_DATA_START_ADDRESS = 0x18700 + NES_FILE_OFFSET LEVEL_7_TO_9_DATA_START_ADDRESS = 0x18A00 + NES_FILE_OFFSET LEVEL_TABLE_SIZE = 0x80 NUM_BYTES_OF_DATA_PER_ROOM = 6 ARMOS_ITEM_ADDRESS = 0x10CF5 + NES_FILE_OFFSET COAST_ITEM_ADDRESS = 0x1788A + NES_FILE_OFFSET CAVE_TYPE_CAVE_NUM_OFFSET = 0x10 BOMB_UPGRADE_PRICE_ADDRESS = 0x4B72 + NES_FILE_OFFSET BOMB_UPGRADE_QUANTITY_ADDRESS = 0x4B8A + NES_FILE_OFFSET LEVEL_METADATA_ADDRESS = 0x19300 + NES_FILE_OFFSET LEVEL_METADATA_OFFSET = 0xFC GATEWAY_OFFSET = 0x23 ENEMY_QUANTITIES_OFFSET = 0x24 ITEM_POSITIONS_OFFSET = 0x29 OFFSET_OFFSET = 0x2D START_ROOM_OFFSET = 0x2F TRIFORCE_LOCATION_OFFSET = 0x30 STAIRCASE_LIST_OFFSET = 0x34 ENTRANCE_DIRECTION_OFFSET = 0x3D MAP_BYTES_OFFSET = 0x3F MAP_THINGIES_OFFSET = 0x4F DARK_PALETTE_COLOR_OFFSETS = [ 0x0C, 0x20, 0x7D, 0x85, 0x86, 0x8A, 0x8E, 0x8F, 0x92, 0x93, 0xBD, 0xC5, 0xC6, 0xCA, 0xCE, 0xCF, 0xD2, 0xD3, 0xD7 ] MEDIUM_PALETTE_COLOR_OFFSETS = [ 0x0D, 0x11, 0x21, 0x7E, 0x82, 0x87, 0x8B, 0xBE, 0xC2, 0xC7, 0xCB ] LIGHT_PALETTE_COLOR_OFFSETS = [0x0E, 0x12, 0x22, 0x7F, 0x83, 0xBF, 0xC3] WATER_PALETTE_COLOR_OFFSETS = [0x10, 0x81, 0x89, 0xC1, 0xC9] SPRITE_SET_ADDRESS = 0xC010 BOSS_SPRITE_SET_ADDRESS = 0xC024 SPRITE_SET_VALUE_LOOKUP: Dict[SpriteSet, List[int]] = { SpriteSet.GORIYA_SPRITE_SET: [0xBB, 0x9D], SpriteSet.DARKNUT_SPRITE_SET: [0x7B, 0x98], SpriteSet.WIZZROBE_SPRITE_SET: [0x9B, 0x9A], SpriteSet.DODONGO_SPRITE_SET: [0xDB, 0x9F], SpriteSet.GLEEOK_SPRITE_SET: [0xDB, 0xA3], SpriteSet.PATRA_SPRITE_SET: [0xDB, 0xA7] } def __init__(self) -> None: self.overworld_raw_data = list( open("data/overworld-data.bin", 'rb').read(0x300)) self.level_1_to_6_raw_data = list( open("data/level-1-6-data.bin", 'rb').read(0x300)) self.level_7_to_9_raw_data = list( open("data/level-7-9-data.bin", 'rb').read(0x300)) self.level_metadata = list( open("data/level-metadata.bin", 'rb').read(0x9D8)) self.overworld_caves: List[Cave] = [] self.level_1_to_6_rooms: List[Room] = [] self.level_7_to_9_rooms: List[Room] = [] self.sprite_set_patch = Patch() self.misc_data_patch = Patch() def GetCaveDestination(self, screen_num: int) -> int: print("Reading byte %x " % (screen_num)) print("Which we think is 0x%x " % (0x80 + screen_num + 0x18410)) foo = self.overworld_raw_data[0x80 + screen_num] print("Data is 0x%x" % foo) # foo = foo 4 # print ("without bottom 2 bits is %x" % foo ) bar = foo >> 2 print("Bar is 0x%x" % bar) bar -= 0x10 print("Cave type 0x%x?" % bar) return bar def SetCaveDestination(self, screen_num: int, level_num_or_cave_type: int) -> None: foo = self.overworld_raw_data[0x80 + screen_num] bits_to_keep = foo & 0x03 foo = level_num_or_cave_type + 0x10 bits_to_write = foo << 2 self.overworld_raw_data[0x80 + screen_num] = bits_to_keep + bits_to_write def SetLevelGrid(self, grid_id: GridId, level_grid: List[Room]) -> None: if grid_id == GridId.GRID_A: self.level_1_to_6_rooms = level_grid else: self.level_7_to_9_rooms = level_grid def ResetToVanilla(self) -> None: self._ReadOverworldData() self.level_1_to_6_rooms = self._ReadDataForLevelGrid( self.level_1_to_6_raw_data) self.level_7_to_9_rooms = self._ReadDataForLevelGrid( self.level_7_to_9_raw_data) self.level_metadata = list( open("data/level-metadata.bin", 'rb').read(0x9D8)) self.sprite_set_patch = Patch() def _ReadDataForLevelGrid(self, level_data: List[int]) -> List[Room]: rooms: List[Room] = [] for room_num in Range.VALID_ROOM_NUMBERS: room_data: List[int] = [] for byte_num in range(0, self.NUM_BYTES_OF_DATA_PER_ROOM): room_data.append(level_data[byte_num * self.LEVEL_TABLE_SIZE + room_num]) rooms.append(Room(room_data)) return rooms def GetLevelNumberOrCaveType(self, screen_num: int) -> int: return (self.overworld_raw_data[0x80 + screen_num] & 0xFC) >> 2 def _GetOverworldCaveDataIndex(self, cave_type: CaveType, position_num: int, is_second_byte: bool) -> int: cave_index = int(cave_type - 0x10) assert cave_index in range(0, 0x16) second_byte_index = 0x3C if is_second_byte else 0x00 return 0x200 + second_byte_index + 3 * cave_index + position_num def _ReadOverworldData(self) -> None: self.overworld_caves = [] for cave_type in Range.VALID_CAVE_TYPES: cave_num = cave_type - 0x10 if cave_type == CaveType.ARMOS_ITEM_VIRTUAL_CAVE: self.overworld_caves.append( Cave([0x3F, Item.POWER_BRACELET, 0x7F, 0x00, 0x00, 0x00])) elif cave_type == CaveType.COAST_ITEM_VIRTUAL_CAVE: self.overworld_caves.append( Cave([0x3F, Item.HEART_CONTAINER, 0x7F, 0x00, 0x00, 0x00])) else: assert cave_type in Range.VALID_CAVE_TYPES # Not needed? cave_data: List[int] = [] for position_num in range(0, 3): cave_data.append(self.overworld_raw_data[ self._GetOverworldCaveDataIndex(cave_type, position_num, is_second_byte=False)]) for position_num in range(0, 3): cave_data.append(self.overworld_raw_data[ self._GetOverworldCaveDataIndex(cave_type, position_num, is_second_byte=True)]) self.overworld_caves.append(Cave(cave_data)) assert len( self.overworld_caves ) == 22 # 0-19 are actual caves, 20-21 are for the armos/coast def GetRoom(self, level_num: LevelNum, room_num: RoomNum) -> Room: assert level_num in Range.VALID_LEVEL_NUMBERS assert room_num in Range.VALID_ROOM_NUMBERS if level_num in [7, 8, 9]: return self.level_7_to_9_rooms[room_num] return self.level_1_to_6_rooms[room_num] def GetRoomItem(self, location: Location) -> Item: assert location.IsLevelRoom() if location.GetLevelNum() in [7, 8, 9]: return self.level_7_to_9_rooms[location.GetRoomNum()].GetItem() return self.level_1_to_6_rooms[location.GetRoomNum()].GetItem() def SetRoomItem(self, item: Item, location: Location) -> None: assert location.IsLevelRoom() if location.GetLevelNum() in [7, 8, 9]: self.level_7_to_9_rooms[location.GetRoomNum()].SetItem(item) else: self.level_1_to_6_rooms[location.GetRoomNum()].SetItem(item) def GetCaveItem(self, location: Location) -> Item: assert location.IsCavePosition() return self.overworld_caves[location.GetCaveNum()].GetItemAtPosition( location.GetPositionNum()) def SetCaveItem(self, item: Item, location: Location) -> None: assert location.IsCavePosition() self.overworld_caves[location.GetCaveNum()].SetItemAtPosition( item, location.GetPositionNum()) def SetCavePrice(self, price: int, location: Location) -> None: assert location.IsCavePosition() self.overworld_caves[location.GetCaveNum()].SetPriceAtPosition( price, location.GetPositionNum()) def RandomizeBombUpgrades(self) -> None: done = False while not done: price = random.randrange(75, 125) quantity = random.randrange(2, 6) if price not in range(110, 126) or quantity not in [2, 3]: done = True self.misc_data_patch.AddData(self.BOMB_UPGRADE_PRICE_ADDRESS, [price]) self.misc_data_patch.AddData(self.BOMB_UPGRADE_QUANTITY_ADDRESS, [quantity]) def ClearAllVisitMarkers(self) -> None: logging.debug("Clearing Visit markers") for room in self.level_1_to_6_rooms: room.ClearVisitMark() for room in self.level_7_to_9_rooms: room.ClearVisitMark() def ClearStaircaseRoomNumbersForLevel(self, level_num: LevelNum) -> None: assert level_num in range(1, 10) offset = level_num * self.LEVEL_METADATA_OFFSET + self.STAIRCASE_LIST_OFFSET for counter in range(0, 9): self.level_metadata[offset + counter] = 0xFF def AddStaircaseRoomNumberForLevel(self, level_num: LevelNum, room_num: RoomNum) -> None: offset = level_num * self.LEVEL_METADATA_OFFSET + self.STAIRCASE_LIST_OFFSET assert room_num in range(0, 0x80) for counter in range(0, 9): if self.level_metadata[offset + counter] == 0xFF: self.level_metadata[offset + counter] = room_num return print("This should never happen! (AddStaircaseRoomNumberForLevel)") sys.exit() def UpdateCompassPointer(self, location: Location) -> None: assert location.IsLevelRoom() (level_num, room_num) = (location.GetLevelNum(), location.GetRoomNum()) #room = self.GetRoom(location.GetLevelNum(), room_num) assert room_num in range(0, 0x100) self.level_metadata[level_num * self.LEVEL_METADATA_OFFSET + self.TRIFORCE_LOCATION_OFFSET] = room_num def WriteDungeonPalette(self, level_num: LevelNum, palette: Tuple[int, int, int, int]) -> None: (dark, medium, light, water) = palette assert dark in range(0, 0x100) assert medium in range(0, 0x100) assert light in range(0, 0x100) assert water in range(0, 0x100) for offset in self.DARK_PALETTE_COLOR_OFFSETS: self.level_metadata[level_num * self.LEVEL_METADATA_OFFSET + offset] = dark for offset in self.MEDIUM_PALETTE_COLOR_OFFSETS: self.level_metadata[level_num * self.LEVEL_METADATA_OFFSET + offset] = medium for offset in self.LIGHT_PALETTE_COLOR_OFFSETS: self.level_metadata[level_num * self.LEVEL_METADATA_OFFSET + offset] = light for offset in self.WATER_PALETTE_COLOR_OFFSETS: self.level_metadata[level_num * self.LEVEL_METADATA_OFFSET + offset] = water # Gets a list of staircase rooms for a level. # # Note that this will include not just passage staircases between two # dungeon rooms but also item rooms with only one passage two and # from a dungeon room. def GetLevelStaircaseRoomNumberList(self, level_num: LevelNum) -> List[RoomNum]: assert level_num in Range.VALID_LEVEL_NUMBERS offset = level_num * self.LEVEL_METADATA_OFFSET + self.STAIRCASE_LIST_OFFSET tbr: List[RoomNum] = [] for offset in range(0, 9): tbr.append(RoomNum(self.level_metadata[offset])) return tbr def SetMapData(self, level_num: LevelNum, map_bytes: List[int], thingies: List[int], offset: int) -> None: assert level_num in Range.VALID_LEVEL_NUMBERS level_offset = level_num * self.LEVEL_METADATA_OFFSET for num in range(0, 0x10): self.level_metadata[level_offset + self.MAP_BYTES_OFFSET + num] = map_bytes[num] for num in range(0, 44): self.level_metadata[level_offset + self.MAP_THINGIES_OFFSET + num] = thingies[num] self.level_metadata[level_offset + self.OFFSET_OFFSET] = (4 - offset) % 16 self.level_metadata[level_offset + self.OFFSET_OFFSET + 1] = ((32 - offset) % 32) * 8 def SetStartRoomDataForLevel(self, level_num: LevelNum, start_room: RoomNum, entrance_direction: Direction) -> None: level_offset = level_num * self.LEVEL_METADATA_OFFSET if start_room: self.level_metadata[level_offset + self.START_ROOM_OFFSET] = start_room if entrance_direction: self.level_metadata[ level_offset + self. ENTRANCE_DIRECTION_OFFSET] = entrance_direction.GetRomValue() if start_room and entrance_direction: formatted_gateway = (start_room + int(entrance_direction) + 0x80) % 0x100 self.level_metadata[level_offset + self.GATEWAY_OFFSET] = formatted_gateway # Gets the Room number of the start screen for a level. def GetLevelStartRoomNumber(self, level_num: LevelNum) -> RoomNum: assert level_num in Range.VALID_LEVEL_NUMBERS return RoomNum( self.level_metadata[level_num * self.LEVEL_METADATA_OFFSET + self.START_ROOM_OFFSET]) def GetLevelEntranceDirection(self, level_num: LevelNum) -> Direction: assert level_num in Range.VALID_LEVEL_NUMBERS offset = level_num * self.LEVEL_METADATA_OFFSET + self.ENTRANCE_DIRECTION_OFFSET return Direction.FromRomValue(self.level_metadata[offset]) def SetSpriteSetsForLevel(self, level_num: LevelNum, sprite_set: SpriteSet, boss_sprite_set: SpriteSet) -> None: self.sprite_set_patch.AddData(self.SPRITE_SET_ADDRESS + 2 * level_num, self.SPRITE_SET_VALUE_LOOKUP[sprite_set]) self.sprite_set_patch.AddData( self.BOSS_SPRITE_SET_ADDRESS + 2 * level_num, self.SPRITE_SET_VALUE_LOOKUP[boss_sprite_set]) def SetItemPositionsForLevel(self, level_num: LevelNum, item_positions: List[int]) -> None: enemy_quantities = [] enemy_quantities.append(random.randrange(1, 5)) enemy_quantities.append(random.randrange(2, 6)) enemy_quantities.append(random.randrange(3, 7)) enemy_quantities.append(random.randrange(4, 8)) enemy_quantities.sort() assert item_positions[0] == 0x89 for counter in range(0, 4): offset = level_num * self.LEVEL_METADATA_OFFSET + counter assert item_positions[counter] in range(0, 0x100) assert enemy_quantities[counter] in range(0, 0x100) self.level_metadata[ offset + self.ITEM_POSITIONS_OFFSET] = item_positions[counter] self.level_metadata[ offset + self.ENEMY_QUANTITIES_OFFSET] = enemy_quantities[counter] def GetPatch(self) -> Patch: patch = Patch() patch += self._GetPatchForLevelGrid( self.LEVEL_1_TO_6_DATA_START_ADDRESS, self.level_1_to_6_rooms) patch += self._GetPatchForLevelGrid( self.LEVEL_7_TO_9_DATA_START_ADDRESS, self.level_7_to_9_rooms) patch += self._GetPatchForLevelMetadata() patch += self._GetPatchForOverworldCaveData() patch += self._GetPatchForOverworldData() patch += self.sprite_set_patch return patch def _GetPatchForLevelGrid(self, start_address: int, rooms: List[Room]) -> Patch: patch = Patch() for room_num in Range.VALID_ROOM_NUMBERS: room_data = rooms[room_num].GetRomData() assert len(room_data) == self.NUM_BYTES_OF_DATA_PER_ROOM for table_num in range(0, self.NUM_BYTES_OF_DATA_PER_ROOM): patch.AddData( start_address + table_num * self.LEVEL_TABLE_SIZE + room_num, [room_data[table_num]]) return patch def _GetPatchForLevelMetadata(self) -> Patch: patch = Patch() patch.AddData(self.LEVEL_METADATA_ADDRESS, self.level_metadata) return patch def _GetPatchForOverworldData(self) -> Patch: patch = Patch() for screen_num in Range.VALID_ROOM_NUMBERS: addr = 0x80 + int(screen_num) patch.AddData(0x18410 + addr, [self.overworld_raw_data[addr]]) return patch def _GetPatchForOverworldCaveData(self) -> Patch: patch = Patch() for cave_type in Range.VALID_CAVE_TYPES: cave_num = int(cave_type) - self.CAVE_TYPE_CAVE_NUM_OFFSET if cave_type == CaveType.ARMOS_ITEM_VIRTUAL_CAVE: patch.AddData( self.ARMOS_ITEM_ADDRESS, [self.overworld_caves[cave_num].GetItemAtPosition(2)]) continue if cave_type == CaveType.COAST_ITEM_VIRTUAL_CAVE: patch.AddData( self.COAST_ITEM_ADDRESS, [self.overworld_caves[cave_num].GetItemAtPosition(2)]) continue # Note that the Cave class is responsible for protecting bits 6 and 7 in its item data patch.AddData( self.OVERWORLD_DATA_START_ADDRESS + self._GetOverworldCaveDataIndex( cave_type, 0, is_second_byte=False), self.overworld_caves[cave_num].GetItemData()) patch.AddData( self.OVERWORLD_DATA_START_ADDRESS + self._GetOverworldCaveDataIndex( cave_type, 0, is_second_byte=True), self.overworld_caves[cave_num].GetPriceData()) return patch
class TextDataTable(): TEXT_SPEED_ADDRESS = 0x482D TEXT_LEVEL_ADDRESS = 0x19D17 def __init__(self, text_speed: str, phrase: str) -> None: self.patch = Patch() self.text_speed = text_speed self.phrase = phrase def GetPatch(self) -> Patch: self._AddTextSpeedToPatchIfNeeded() self._AddLevelNameToPatchIfNeeded() return self.patch def _AddTextSpeedToPatchIfNeeded(self) -> None: logging.debug("Updating text speed.") converted_text_speed = TextSpeed.NORMAL if self.text_speed == 'normal': return if self.text_speed == 'random': converted_text_speed = random.choice(list(TextSpeed)) else: converted_text_speed = TextSpeed[self.text_speed.upper()] self.patch.AddData(self.TEXT_SPEED_ADDRESS, [int(converted_text_speed)]) def _AddLevelNameToPatchIfNeeded(self) -> None: assert len( self.phrase) == 6, "The level prefix must be six characters long." if self.phrase.lower() == 'level-': return # No need to replace the existing text with the same text. self.patch.AddData(self.TEXT_LEVEL_ADDRESS, self.__ascii_string_to_bytes(self.phrase)) def __ascii_string_to_bytes(self, phrase: str) -> List[int]: """Convert the string to a form the game can understand.""" return list(map(self._ascii_char_to_bytes, phrase)) @staticmethod def _ascii_char_to_bytes(char: str) -> int: if ord(char) >= 48 and ord(char) <= 57: # 0-9 return ord(char) - 48 if ord(char) >= 65 and ord(char) <= 90: # A-Z return ord(char) - 55 if ord(char) >= 97 and ord(char) <= 122: # a-z return ord(char) - 87 misc_char_map = { '_': 0x24, # Meant to represent a space. '~': 0x25, # Meant to represent leading space. ',': 0x28, '!': 0x29, "'": 0x2A, '&': 0x2B, '.': 0x2C, '"': 0x2D, '?': 0x2E, '-': 0x2F } return misc_char_map[char] or misc_char_map['_']