import logging import asyncio from NetUtils import ClientStatus, color from SNIClient import Context, snes_buffered_write, snes_flush_writes, snes_read from Patch import GAME_DKC3 snes_logger = logging.getLogger("SNES") # DKC3 - DKC3_TODO: Check these values ROM_START = 0x000000 WRAM_START = 0xF50000 WRAM_SIZE = 0x20000 SRAM_START = 0xE00000 SAVEDATA_START = WRAM_START + 0xF000 SAVEDATA_SIZE = 0x500 DKC3_ROMNAME_START = 0x00FFC0 DKC3_ROMHASH_START = 0x7FC0 ROMNAME_SIZE = 0x15 ROMHASH_SIZE = 0x15 DKC3_RECV_PROGRESS_ADDR = WRAM_START + 0x632 # DKC3_TODO: Find a permanent home for this DKC3_FILE_NAME_ADDR = WRAM_START + 0x5D9 DEATH_LINK_ACTIVE_ADDR = DKC3_ROMNAME_START + 0x15 # DKC3_TODO: Find a permanent home for this async def deathlink_kill_player(ctx: Context): pass #if ctx.game == GAME_DKC3: # DKC3_TODO: Handle Receiving Deathlink async def dkc3_rom_init(ctx: Context): if not ctx.rom: ctx.finished_game = False ctx.death_link_allow_survive = False game_name = await snes_read(ctx, DKC3_ROMNAME_START, 0x15) if game_name is None or game_name != b"DONKEY KONG COUNTRY 3": return False else: ctx.game = GAME_DKC3 ctx.items_handling = 0b111 # remote items rom = await snes_read(ctx, DKC3_ROMHASH_START, ROMHASH_SIZE) if rom is None or rom == bytes([0] * ROMHASH_SIZE): return False ctx.rom = rom #death_link = await snes_read(ctx, DEATH_LINK_ACTIVE_ADDR, 1) ## DKC3_TODO: Handle Deathlink #if death_link: # ctx.allow_collect = bool(death_link[0] & 0b100) # await ctx.update_death_link(bool(death_link[0] & 0b1)) return True async def dkc3_game_watcher(ctx: Context): if ctx.game == GAME_DKC3: # DKC3_TODO: Handle Deathlink save_file_name = await snes_read(ctx, DKC3_FILE_NAME_ADDR, 0x5) if save_file_name is None or save_file_name[0] == 0x00: # We haven't loaded a save file return new_checks = [] from worlds.dkc3.Rom import location_rom_data, item_rom_data, boss_location_ids, level_unlock_map for loc_id, loc_data in location_rom_data.items(): if loc_id not in ctx.locations_checked: data = await snes_read(ctx, WRAM_START + loc_data[0], 1) masked_data = data[0] & (1 << loc_data[1]) bit_set = (masked_data != 0) invert_bit = ((len(loc_data) >= 3) and loc_data[2]) if bit_set != invert_bit: # DKC3_TODO: Handle non-included checks new_checks.append(loc_id) verify_save_file_name = await snes_read(ctx, DKC3_FILE_NAME_ADDR, 0x5) if verify_save_file_name is None or verify_save_file_name[0] == 0x00 or verify_save_file_name != save_file_name: # We have somehow exited the save file (or worse) return rom = await snes_read(ctx, DKC3_ROMHASH_START, ROMHASH_SIZE) if rom != ctx.rom: ctx.rom = None # We have somehow loaded a different ROM return for new_check_id in new_checks: ctx.locations_checked.add(new_check_id) location = ctx.location_names[new_check_id] snes_logger.info( f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})') await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}]) # DKC3_TODO: Make this actually visually display new things received (ASM Hook required) recv_count = await snes_read(ctx, DKC3_RECV_PROGRESS_ADDR, 1) recv_index = recv_count[0] if recv_index < len(ctx.items_received): item = ctx.items_received[recv_index] recv_index += 1 logging.info('Received %s from %s (%s) (%d/%d in list)' % ( color(ctx.item_names[item.item], 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'), ctx.location_names[item.location], recv_index, len(ctx.items_received))) snes_buffered_write(ctx, DKC3_RECV_PROGRESS_ADDR, bytes([recv_index])) if item.item in item_rom_data: item_count = await snes_read(ctx, WRAM_START + item_rom_data[item.item][0], 0x1) new_item_count = item_count[0] + 1 for address in item_rom_data[item.item]: snes_buffered_write(ctx, WRAM_START + address, bytes([new_item_count])) # Handle Coin Displays current_level = await snes_read(ctx, WRAM_START + 0x5E3, 0x5) overworld_locked = ((await snes_read(ctx, WRAM_START + 0x5FC, 0x1))[0] == 0x01) if item.item == 0xDC3002 and not overworld_locked and (current_level[0] == 0x0A and current_level[2] == 0x00 and current_level[4] == 0x03): # Bazaar and Barter item_count = await snes_read(ctx, WRAM_START + 0xB02, 0x1) new_item_count = item_count[0] + 1 snes_buffered_write(ctx, WRAM_START + 0xB02, bytes([new_item_count])) elif item.item == 0xDC3002 and not overworld_locked and current_level[0] == 0x04: # Swanky item_count = await snes_read(ctx, WRAM_START + 0xA26, 0x1) new_item_count = item_count[0] + 1 snes_buffered_write(ctx, WRAM_START + 0xA26, bytes([new_item_count])) elif item.item == 0xDC3003 and not overworld_locked and (current_level[0] == 0x0A and current_level[2] == 0x08 and current_level[4] == 0x01): # Boomer item_count = await snes_read(ctx, WRAM_START + 0xB02, 0x1) new_item_count = item_count[0] + 1 snes_buffered_write(ctx, WRAM_START + 0xB02, bytes([new_item_count])) else: # Handle Patch and Skis if item.item == 0xDC3007: num_upgrades = 1 inventory = await snes_read(ctx, WRAM_START + 0x605, 0xF) if (inventory[0] & 0x02): num_upgrades = 3 elif (inventory[13] & 0x08) or (inventory[0] & 0x01): num_upgrades = 2 if num_upgrades == 1: snes_buffered_write(ctx, WRAM_START + 0x605, bytes([inventory[0] | 0x01])) if inventory[4] == 0: snes_buffered_write(ctx, WRAM_START + 0x609, bytes([0x01])) elif inventory[6] == 0: snes_buffered_write(ctx, WRAM_START + 0x60B, bytes([0x01])) elif inventory[8] == 0: snes_buffered_write(ctx, WRAM_START + 0x60D, bytes([0x01])) elif inventory[10] == 0: snes_buffered_write(ctx, WRAM_START + 0x60F, bytes([0x01])) cove_mekanos_progress = await snes_read(ctx, WRAM_START + 0x691, 0x2) snes_buffered_write(ctx, WRAM_START + 0x691, bytes([cove_mekanos_progress[0] | 0x01])) snes_buffered_write(ctx, WRAM_START + 0x692, bytes([cove_mekanos_progress[1] | 0x01])) elif num_upgrades == 2: snes_buffered_write(ctx, WRAM_START + 0x605, bytes([inventory[0] | 0x02])) if inventory[4] == 0: snes_buffered_write(ctx, WRAM_START + 0x609, bytes([0x02])) elif inventory[6] == 0: snes_buffered_write(ctx, WRAM_START + 0x60B, bytes([0x02])) elif inventory[8] == 0: snes_buffered_write(ctx, WRAM_START + 0x60D, bytes([0x02])) elif inventory[10] == 0: snes_buffered_write(ctx, WRAM_START + 0x60F, bytes([0x02])) elif num_upgrades == 3: snes_buffered_write(ctx, WRAM_START + 0x606, bytes([inventory[1] | 0x20])) k3_ridge_progress = await snes_read(ctx, WRAM_START + 0x693, 0x2) snes_buffered_write(ctx, WRAM_START + 0x693, bytes([k3_ridge_progress[0] | 0x01])) snes_buffered_write(ctx, WRAM_START + 0x694, bytes([k3_ridge_progress[1] | 0x01])) elif item.item == 0xDC3000: # Handle Victory if not ctx.finished_game: await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) ctx.finished_game = True else: print("Item Not Recognized: ", item.item) pass await snes_flush_writes(ctx) # DKC3_TODO: This method of collect should work, however it does not unlock the next level correctly when previous is flagged # Handle Collected Locations for loc_id in ctx.checked_locations: if loc_id not in ctx.locations_checked and loc_id not in boss_location_ids: loc_data = location_rom_data[loc_id] data = await snes_read(ctx, WRAM_START + loc_data[0], 1) invert_bit = ((len(loc_data) >= 3) and loc_data[2]) if not invert_bit: masked_data = data[0] | (1 << loc_data[1]) #print("Collected Location: ", hex(loc_data[0]), " | ", loc_data[1]) snes_buffered_write(ctx, WRAM_START + loc_data[0], bytes([masked_data])) if (loc_data[1] == 1): # Make the next levels accessible level_id = loc_data[0] - 0x632 levels_to_tiles = await snes_read(ctx, ROM_START + 0x3FF800, 0x60) tiles_to_levels = await snes_read(ctx, ROM_START + 0x3FF860, 0x60) tile_id = levels_to_tiles[level_id] if levels_to_tiles[level_id] != 0xFF else level_id tile_id = tile_id + 0x632 #print("Tile ID: ", hex(tile_id)) if tile_id in level_unlock_map: for next_level_address in level_unlock_map[tile_id]: next_level_id = next_level_address - 0x632 next_tile_id = tiles_to_levels[next_level_id] if tiles_to_levels[next_level_id] != 0xFF else next_level_id next_tile_id = next_tile_id + 0x632 #print("Next Level ID: ", hex(next_tile_id)) next_data = await snes_read(ctx, WRAM_START + next_tile_id, 1) snes_buffered_write(ctx, WRAM_START + next_tile_id, bytes([next_data[0] | 0x01])) await snes_flush_writes(ctx) else: masked_data = data[0] & ~(1 << loc_data[1]) print("Collected Inverted Location: ", hex(loc_data[0]), " | ", loc_data[1]) snes_buffered_write(ctx, WRAM_START + loc_data[0], bytes([masked_data])) await snes_flush_writes(ctx) ctx.locations_checked.add(loc_id) # Calculate Boomer Cost Text boomer_cost_text = await snes_read(ctx, WRAM_START + 0xAAFD, 2) if boomer_cost_text[0] == 0x31 and boomer_cost_text[1] == 0x35: boomer_cost = await snes_read(ctx, ROM_START + 0x349857, 1) boomer_cost_tens = int(boomer_cost[0]) // 10 boomer_cost_ones = int(boomer_cost[0]) % 10 snes_buffered_write(ctx, WRAM_START + 0xAAFD, bytes([0x30 + boomer_cost_tens, 0x30 + boomer_cost_ones])) await snes_flush_writes(ctx) boomer_final_cost_text = await snes_read(ctx, WRAM_START + 0xAB9B, 2) if boomer_final_cost_text[0] == 0x32 and boomer_final_cost_text[1] == 0x35: boomer_cost = await snes_read(ctx, ROM_START + 0x349857, 1) boomer_cost_tens = boomer_cost[0] // 10 boomer_cost_ones = boomer_cost[0] % 10 snes_buffered_write(ctx, WRAM_START + 0xAB9B, bytes([0x30 + boomer_cost_tens, 0x30 + boomer_cost_ones])) await snes_flush_writes(ctx)