diff --git a/SNIClient.py b/SNIClient.py index aad23169..c97d0d6c 100644 --- a/SNIClient.py +++ b/SNIClient.py @@ -149,8 +149,8 @@ class Context(CommonContext): def event_invalid_slot(self): if self.snes_socket is not None and not self.snes_socket.closed: asyncio.create_task(self.snes_socket.close()) - raise Exception('Invalid ROM detected, ' - 'please verify that you have loaded the correct rom and reconnect your snes (/snes)') + raise Exception("Invalid ROM detected, " + "please verify that you have loaded the correct rom and reconnect your snes (/snes)") async def server_auth(self, password_requested: bool = False): if password_requested and not self.password: @@ -158,7 +158,7 @@ class Context(CommonContext): if self.rom is None: self.awaiting_rom = True snes_logger.info( - 'No ROM detected, awaiting snes connection to authenticate to the multiworld server (/snes)') + "No ROM detected, awaiting snes connection to authenticate to the multiworld server (/snes)") return self.awaiting_rom = False self.auth = self.rom @@ -262,7 +262,7 @@ async def deathlink_kill_player(ctx: Context): SNES_RECONNECT_DELAY = 5 -# LttP +# FXPAK Pro protocol memory mapping used by SNI ROM_START = 0x000000 WRAM_START = 0xF50000 WRAM_SIZE = 0x20000 @@ -293,21 +293,24 @@ SHOP_LEN = (len(Shops.shop_table) * 3) + 5 DEATH_LINK_ACTIVE_ADDR = ROMNAME_START + 0x15 # 1 byte # SM -SM_ROMNAME_START = 0x007FC0 +SM_ROMNAME_START = ROM_START + 0x007FC0 SM_INGAME_MODES = {0x07, 0x09, 0x0b} SM_ENDGAME_MODES = {0x26, 0x27} SM_DEATH_MODES = {0x15, 0x17, 0x18, 0x19, 0x1A} -SM_RECV_PROGRESS_ADDR = SRAM_START + 0x2000 # 2 bytes -SM_RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte -SM_RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte +# RECV and SEND are from the gameplay's perspective: SNIClient writes to RECV queue and reads from SEND queue +SM_RECV_QUEUE_START = SRAM_START + 0x2000 +SM_RECV_QUEUE_WCOUNT = SRAM_START + 0x2602 +SM_SEND_QUEUE_START = SRAM_START + 0x2700 +SM_SEND_QUEUE_RCOUNT = SRAM_START + 0x2680 +SM_SEND_QUEUE_WCOUNT = SRAM_START + 0x2682 SM_DEATH_LINK_ACTIVE_ADDR = ROM_START + 0x277f04 # 1 byte SM_REMOTE_ITEM_FLAG_ADDR = ROM_START + 0x277f06 # 1 byte # SMZ3 -SMZ3_ROMNAME_START = 0x00FFC0 +SMZ3_ROMNAME_START = ROM_START + 0x00FFC0 SMZ3_INGAME_MODES = {0x07, 0x09, 0x0b} SMZ3_ENDGAME_MODES = {0x26, 0x27} @@ -1083,6 +1086,9 @@ async def game_watcher(ctx: Context): if ctx.awaiting_rom: await ctx.server_auth(False) + elif ctx.server is None: + snes_logger.warning("ROM detected but no active multiworld server connection. " + + "Connect using command: /connect server:port") if ctx.auth and ctx.auth != ctx.rom: snes_logger.warning("ROM change detected, please reconnect to the multiworld server") @@ -1159,6 +1165,9 @@ async def game_watcher(ctx: Context): await ctx.send_msgs([{"cmd": "LocationScouts", "locations": [scout_location]}]) await track_locations(ctx, roomid, roomdata) elif ctx.game == GAME_SM: + if ctx.server is None or ctx.slot is None: + # not successfully connected to a multiworld server, cannot process the game sending items + continue gamemode = await snes_read(ctx, WRAM_START + 0x0998, 1) if "DeathLink" in ctx.tags and gamemode and ctx.last_death_link + 1 < time.time(): currently_dead = gamemode[0] in SM_DEATH_MODES @@ -1169,22 +1178,22 @@ async def game_watcher(ctx: Context): ctx.finished_game = True continue - data = await snes_read(ctx, SM_RECV_PROGRESS_ADDR + 0x680, 4) + data = await snes_read(ctx, SM_SEND_QUEUE_RCOUNT, 4) if data is None: continue recv_index = data[0] | (data[1] << 8) - recv_item = data[2] | (data[3] << 8) + recv_item = data[2] | (data[3] << 8) # this is actually SM_SEND_QUEUE_WCOUNT while (recv_index < recv_item): itemAdress = recv_index * 8 - message = await snes_read(ctx, SM_RECV_PROGRESS_ADDR + 0x700 + itemAdress, 8) + message = await snes_read(ctx, SM_SEND_QUEUE_START + itemAdress, 8) # worldId = message[0] | (message[1] << 8) # unused # itemId = message[2] | (message[3] << 8) # unused itemIndex = (message[4] | (message[5] << 8)) >> 3 recv_index += 1 - snes_buffered_write(ctx, SM_RECV_PROGRESS_ADDR + 0x680, + snes_buffered_write(ctx, SM_SEND_QUEUE_RCOUNT, bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF])) from worlds.sm.Locations import locations_start_id @@ -1196,12 +1205,11 @@ async def game_watcher(ctx: Context): f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})') await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [location_id]}]) - data = await snes_read(ctx, SM_RECV_PROGRESS_ADDR + 0x600, 4) + data = await snes_read(ctx, SM_RECV_QUEUE_WCOUNT, 2) if data is None: continue - # recv_itemOutPtr = data[0] | (data[1] << 8) # unused - itemOutPtr = data[2] | (data[3] << 8) + itemOutPtr = data[0] | (data[1] << 8) from worlds.sm.Items import items_start_id from worlds.sm.Locations import locations_start_id @@ -1214,10 +1222,10 @@ async def game_watcher(ctx: Context): locationId = 0x00 #backward compat playerID = item.player if item.player <= SM_ROM_PLAYER_LIMIT else 0 - snes_buffered_write(ctx, SM_RECV_PROGRESS_ADDR + itemOutPtr * 4, bytes( + snes_buffered_write(ctx, SM_RECV_QUEUE_START + itemOutPtr * 4, bytes( [playerID & 0xFF, (playerID >> 8) & 0xFF, itemId & 0xFF, locationId & 0xFF])) itemOutPtr += 1 - snes_buffered_write(ctx, SM_RECV_PROGRESS_ADDR + 0x602, + snes_buffered_write(ctx, SM_RECV_QUEUE_WCOUNT, bytes([itemOutPtr & 0xFF, (itemOutPtr >> 8) & 0xFF])) logging.info('Received %s from %s (%s) (%d/%d in list)' % ( color(ctx.item_names[item.item], 'red', 'bold'), @@ -1225,6 +1233,9 @@ async def game_watcher(ctx: Context): ctx.location_names[item.location], itemOutPtr, len(ctx.items_received))) await snes_flush_writes(ctx) elif ctx.game == GAME_SMZ3: + if ctx.server is None or ctx.slot is None: + # not successfully connected to a multiworld server, cannot process the game sending items + continue currentGame = await snes_read(ctx, SRAM_START + 0x33FE, 2) if (currentGame is not None): if (currentGame[0] != 0):