162 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			162 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								import hashlib
							 | 
						||
| 
								 | 
							
								import math
							 | 
						||
| 
								 | 
							
								import os
							 | 
						||
| 
								 | 
							
								import struct
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from settings import get_settings
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import Utils
							 | 
						||
| 
								 | 
							
								from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from worlds.AutoWorld import World
							 | 
						||
| 
								 | 
							
								from .items import item_to_index
							 | 
						||
| 
								 | 
							
								from .rom_values import banlist_ids, function_addresses, structure_deck_selection
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								MD5Europe = "020411d3b08f5639eb8cb878283f84bf"
							 | 
						||
| 
								 | 
							
								MD5America = "b8a7c976b28172995fe9e465d654297a"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class YGO06ProcedurePatch(APProcedurePatch, APTokenMixin):
							 | 
						||
| 
								 | 
							
								    game = "Yu-Gi-Oh! 2006"
							 | 
						||
| 
								 | 
							
								    hash = MD5America
							 | 
						||
| 
								 | 
							
								    patch_file_ending = ".apygo06"
							 | 
						||
| 
								 | 
							
								    result_file_ending = ".gba"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def get_source_data(cls) -> bytes:
							 | 
						||
| 
								 | 
							
								        return get_base_rom_bytes()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def write_tokens(world: World, patch: YGO06ProcedurePatch):
							 | 
						||
| 
								 | 
							
								    structure_deck = structure_deck_selection.get(world.options.structure_deck.value)
							 | 
						||
| 
								 | 
							
								    # set structure deck
							 | 
						||
| 
								 | 
							
								    patch.write_token(APTokenTypes.WRITE, 0x000FD0AA, struct.pack("<B", structure_deck))
							 | 
						||
| 
								 | 
							
								    # set banlist
							 | 
						||
| 
								 | 
							
								    banlist = world.options.banlist
							 | 
						||
| 
								 | 
							
								    patch.write_token(APTokenTypes.WRITE, 0xF4496, struct.pack("<B", banlist_ids.get(banlist.value)))
							 | 
						||
| 
								 | 
							
								    # set items to locations map
							 | 
						||
| 
								 | 
							
								    randomizer_data_start = 0x0000F310
							 | 
						||
| 
								 | 
							
								    for location in world.multiworld.get_locations(world.player):
							 | 
						||
| 
								 | 
							
								        item = location.item.name
							 | 
						||
| 
								 | 
							
								        if location.item.player != world.player:
							 | 
						||
| 
								 | 
							
								            item = "Remote"
							 | 
						||
| 
								 | 
							
								        item_id = item_to_index.get(item)
							 | 
						||
| 
								 | 
							
								        if item_id is None:
							 | 
						||
| 
								 | 
							
								            continue
							 | 
						||
| 
								 | 
							
								        location_id = world.location_name_to_id[location.name] - 5730000
							 | 
						||
| 
								 | 
							
								        patch.write_token(APTokenTypes.WRITE, randomizer_data_start + location_id, struct.pack("<B", item_id))
							 | 
						||
| 
								 | 
							
								    # set starting inventory
							 | 
						||
| 
								 | 
							
								    inventory_map = [0 for i in range(32)]
							 | 
						||
| 
								 | 
							
								    starting_inventory = list(map(lambda i: i.name, world.multiworld.precollected_items[world.player]))
							 | 
						||
| 
								 | 
							
								    starting_inventory += world.options.start_inventory.value
							 | 
						||
| 
								 | 
							
								    for start_inventory in starting_inventory:
							 | 
						||
| 
								 | 
							
								        item_id = world.item_name_to_id[start_inventory] - 5730001
							 | 
						||
| 
								 | 
							
								        index = math.floor(item_id / 8)
							 | 
						||
| 
								 | 
							
								        bit = item_id % 8
							 | 
						||
| 
								 | 
							
								        inventory_map[index] = inventory_map[index] | (1 << bit)
							 | 
						||
| 
								 | 
							
								    for i in range(32):
							 | 
						||
| 
								 | 
							
								        patch.write_token(APTokenTypes.WRITE, 0xE9DC + i, struct.pack("<B", inventory_map[i]))
							 | 
						||
| 
								 | 
							
								    # set unlock conditions for the last 3 campaign opponents
							 | 
						||
| 
								 | 
							
								    third_tier_5 = (
							 | 
						||
| 
								 | 
							
								        world.options.third_tier_5_campaign_boss_challenges.value
							 | 
						||
| 
								 | 
							
								        if world.options.third_tier_5_campaign_boss_unlock_condition.value == 1
							 | 
						||
| 
								 | 
							
								        else world.options.third_tier_5_campaign_boss_campaign_opponents.value
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								    patch.write_token(APTokenTypes.WRITE, 0xEEFA, struct.pack("<B", third_tier_5))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    fourth_tier_5 = (
							 | 
						||
| 
								 | 
							
								        world.options.fourth_tier_5_campaign_boss_challenges.value
							 | 
						||
| 
								 | 
							
								        if world.options.fourth_tier_5_campaign_boss_unlock_condition.value == 1
							 | 
						||
| 
								 | 
							
								        else world.options.fourth_tier_5_campaign_boss_campaign_opponents.value
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								    patch.write_token(APTokenTypes.WRITE, 0xEF10, struct.pack("<B", fourth_tier_5))
							 | 
						||
| 
								 | 
							
								    final = (
							 | 
						||
| 
								 | 
							
								        world.options.final_campaign_boss_challenges.value
							 | 
						||
| 
								 | 
							
								        if world.options.final_campaign_boss_unlock_condition.value == 1
							 | 
						||
| 
								 | 
							
								        else world.options.final_campaign_boss_campaign_opponents.value
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								    patch.write_token(APTokenTypes.WRITE, 0xEF22, struct.pack("<B", final))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    patch.write_token(
							 | 
						||
| 
								 | 
							
								        APTokenTypes.WRITE,
							 | 
						||
| 
								 | 
							
								        0xEEF8,
							 | 
						||
| 
								 | 
							
								        struct.pack(
							 | 
						||
| 
								 | 
							
								            "<B",
							 | 
						||
| 
								 | 
							
								            int((function_addresses.get(world.options.third_tier_5_campaign_boss_unlock_condition.value) - 0xEEFA) / 2),
							 | 
						||
| 
								 | 
							
								        ),
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								    patch.write_token(
							 | 
						||
| 
								 | 
							
								        APTokenTypes.WRITE,
							 | 
						||
| 
								 | 
							
								        0xEF0E,
							 | 
						||
| 
								 | 
							
								        struct.pack(
							 | 
						||
| 
								 | 
							
								            "<B",
							 | 
						||
| 
								 | 
							
								            int(
							 | 
						||
| 
								 | 
							
								                (function_addresses.get(world.options.fourth_tier_5_campaign_boss_unlock_condition.value) - 0xEF10) / 2
							 | 
						||
| 
								 | 
							
								            ),
							 | 
						||
| 
								 | 
							
								        ),
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								    patch.write_token(
							 | 
						||
| 
								 | 
							
								        APTokenTypes.WRITE,
							 | 
						||
| 
								 | 
							
								        0xEF20,
							 | 
						||
| 
								 | 
							
								        struct.pack(
							 | 
						||
| 
								 | 
							
								            "<B", int((function_addresses.get(world.options.final_campaign_boss_unlock_condition.value) - 0xEF22) / 2)
							 | 
						||
| 
								 | 
							
								        ),
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								    # set starting money
							 | 
						||
| 
								 | 
							
								    patch.write_token(APTokenTypes.WRITE, 0xF4734, struct.pack("<I", world.options.starting_money))
							 | 
						||
| 
								 | 
							
								    patch.write_token(APTokenTypes.WRITE, 0xE70C, struct.pack("<B", world.options.money_reward_multiplier.value))
							 | 
						||
| 
								 | 
							
								    patch.write_token(APTokenTypes.WRITE, 0xE6E4, struct.pack("<B", world.options.money_reward_multiplier.value))
							 | 
						||
| 
								 | 
							
								    # normalize booster packs if option is set
							 | 
						||
| 
								 | 
							
								    if world.options.normalize_boosters_packs.value:
							 | 
						||
| 
								 | 
							
								        booster_pack_price = world.options.booster_pack_prices.value.to_bytes(2, "little")
							 | 
						||
| 
								 | 
							
								        for booster in range(51):
							 | 
						||
| 
								 | 
							
								            space = booster * 16
							 | 
						||
| 
								 | 
							
								            patch.write_token(APTokenTypes.WRITE, 0x1E5E2E8 + space, struct.pack("<B", booster_pack_price[0]))
							 | 
						||
| 
								 | 
							
								            patch.write_token(APTokenTypes.WRITE, 0x1E5E2E9 + space, struct.pack("<B", booster_pack_price[1]))
							 | 
						||
| 
								 | 
							
								            patch.write_token(APTokenTypes.WRITE, 0x1E5E2EA + space, struct.pack("<B", 5))
							 | 
						||
| 
								 | 
							
								    # set shuffled campaign opponents if option is set
							 | 
						||
| 
								 | 
							
								    if world.options.campaign_opponents_shuffle.value:
							 | 
						||
| 
								 | 
							
								        i = 0
							 | 
						||
| 
								 | 
							
								        for opp in world.campaign_opponents:
							 | 
						||
| 
								 | 
							
								            space = i * 32
							 | 
						||
| 
								 | 
							
								            patch.write_token(APTokenTypes.WRITE, 0x000F3BA + i, struct.pack("<B", opp.id))
							 | 
						||
| 
								 | 
							
								            patch.write_token(APTokenTypes.WRITE, 0x1E58D0E + space, struct.pack("<H", opp.card_id))
							 | 
						||
| 
								 | 
							
								            patch.write_token(APTokenTypes.WRITE, 0x1E58D10 + space, struct.pack("<H", opp.deck_name_id))
							 | 
						||
| 
								 | 
							
								            for j, b in enumerate(opp.deck_file.encode("ascii")):
							 | 
						||
| 
								 | 
							
								                patch.write_token(APTokenTypes.WRITE, 0x1E58D12 + space + j, struct.pack("<B", b))
							 | 
						||
| 
								 | 
							
								            i = i + 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for j, b in enumerate(world.romName):
							 | 
						||
| 
								 | 
							
								        patch.write_token(APTokenTypes.WRITE, 0x10 + j, struct.pack("<B", b))
							 | 
						||
| 
								 | 
							
								    for j, b in enumerate(world.playerName):
							 | 
						||
| 
								 | 
							
								        patch.write_token(APTokenTypes.WRITE, 0x30 + j, struct.pack("<B", b))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    patch.write_file("token_data.bin", patch.get_token_binary())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def get_base_rom_bytes(file_name: str = "") -> bytes:
							 | 
						||
| 
								 | 
							
								    base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None)
							 | 
						||
| 
								 | 
							
								    if not base_rom_bytes:
							 | 
						||
| 
								 | 
							
								        file_name = get_base_rom_path(file_name)
							 | 
						||
| 
								 | 
							
								        base_rom_bytes = bytes(Utils.read_snes_rom(open(file_name, "rb")))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        basemd5 = hashlib.md5()
							 | 
						||
| 
								 | 
							
								        basemd5.update(base_rom_bytes)
							 | 
						||
| 
								 | 
							
								        md5hash = basemd5.hexdigest()
							 | 
						||
| 
								 | 
							
								        if MD5Europe != md5hash and MD5America != md5hash:
							 | 
						||
| 
								 | 
							
								            raise Exception(
							 | 
						||
| 
								 | 
							
								                "Supplied Base Rom does not match known MD5 for"
							 | 
						||
| 
								 | 
							
								                "Yu-Gi-Oh! World Championship 2006 America or Europe "
							 | 
						||
| 
								 | 
							
								                "Get the correct game and version, then dump it"
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								        get_base_rom_bytes.base_rom_bytes = base_rom_bytes
							 | 
						||
| 
								 | 
							
								    return base_rom_bytes
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def get_base_rom_path(file_name: str = "") -> str:
							 | 
						||
| 
								 | 
							
								    if not file_name:
							 | 
						||
| 
								 | 
							
								        file_name = get_settings().yugioh06_settings.rom_file
							 | 
						||
| 
								 | 
							
								    if not os.path.exists(file_name):
							 | 
						||
| 
								 | 
							
								        file_name = Utils.user_path(file_name)
							 | 
						||
| 
								 | 
							
								    return file_name
							 |