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 |