From caa3bc8e229c2d137b1ccd346defa83f55a9e44b Mon Sep 17 00:00:00 2001 From: Simen Svale Date: Tue, 30 Dec 2025 03:01:54 +0100 Subject: [PATCH] Fix async event handling for VICE API v1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VICE API v1 sends some responses (RegisterInfo, MemoryGet) as async events with ReqID=0xff instead of matched responses. This caused timeouts because we were waiting for responses with our request ID. Fix: Match async responses (ReqID=0xff) to pending requests by expected response type instead of request ID. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/protocol/client.ts | 51 +++++++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/src/protocol/client.ts b/src/protocol/client.ts index ddc3ef0..eec00d9 100644 --- a/src/protocol/client.ts +++ b/src/protocol/client.ts @@ -55,6 +55,7 @@ export class ViceClient { { resolve: (response: ViceResponse) => void; reject: (error: ViceError) => void; + expectedResponseType?: ResponseType; // For async event matching } >(); private state: ConnectionState = { @@ -215,20 +216,48 @@ export class ViceClient { } private handleResponse(response: ViceResponse): void { - // Check for async events + debugLog(`handleResponse: type=0x${response.responseType.toString(16)}, reqId=${response.requestId}`); + + // Check for async events (state changes) if (response.responseType === ResponseType.Stopped || response.responseType === ResponseType.CheckpointHit) { this.state.running = false; this.onStopped?.(response); - return; + // Don't return - this might also be a response to a pending request } if (response.responseType === ResponseType.Resumed) { this.state.running = true; this.onResumed?.(response); + // Don't return - continue to check for pending requests + } + + // VICE API v1 sends some responses as async events (ReqID=0xff) + // For these, we match by response type to the oldest pending request expecting that type + if (response.requestId === 0xff) { + // Find a pending request that expects this response type + for (const [reqId, pending] of this.pendingRequests) { + if (pending.expectedResponseType === response.responseType) { + debugLog(`Matched async response type 0x${response.responseType.toString(16)} to request ${reqId}`); + this.pendingRequests.delete(reqId); + if (response.errorCode !== ErrorCode.Ok) { + pending.reject( + this.makeError( + `VICE_ERROR_${response.errorCode}`, + `VICE returned error code ${response.errorCode}`, + this.getErrorSuggestion(response.errorCode) + ) + ); + } else { + pending.resolve(response); + } + return; + } + } + debugLog(`No pending request matched async response type 0x${response.responseType.toString(16)}`); return; } - // Match to pending request + // Match to pending request by request ID const pending = this.pendingRequests.get(response.requestId); if (pending) { this.pendingRequests.delete(response.requestId); @@ -261,7 +290,11 @@ export class ViceClient { } } - private async sendCommand(command: Command, body: Buffer = Buffer.alloc(0)): Promise { + private async sendCommand( + command: Command, + body: Buffer = Buffer.alloc(0), + expectedResponseType?: ResponseType + ): Promise { if (!this.socket || !this.state.connected) { throw this.makeError( "NOT_CONNECTED", @@ -282,10 +315,10 @@ export class ViceClient { header[7] = command; const packet = Buffer.concat([header, body]); - debugLog(`Sending command 0x${command.toString(16)}, reqId=${requestId}`, packet); + debugLog(`Sending command 0x${command.toString(16)}, reqId=${requestId}, expectType=${expectedResponseType?.toString(16) ?? 'any'}`, packet); return new Promise((resolve, reject) => { - this.pendingRequests.set(requestId, { resolve, reject }); + this.pendingRequests.set(requestId, { resolve, reject, expectedResponseType }); this.socket!.write(packet, (err) => { if (err) { @@ -353,7 +386,8 @@ export class ViceClient { body[3] = memspace; body.writeUInt16LE(endAddress, 4); - const response = await this.sendCommand(Command.MemoryGet, body); + // VICE may send MemoryGet (0x31) as async event with ReqID=0xff + const response = await this.sendCommand(Command.MemoryGet, body, ResponseType.MemoryGet); // Response body: length(2) + data(N) const dataLength = response.body.readUInt16LE(0); @@ -405,7 +439,8 @@ export class ViceClient { async getRegisters(memspace: MemorySpace = MemorySpace.MainCPU): Promise { const body = Buffer.alloc(1); body[0] = memspace; - return this.sendCommand(Command.RegistersGet, body); + // VICE sends RegisterInfo (0x62) as async event with ReqID=0xff + return this.sendCommand(Command.RegistersGet, body, ResponseType.RegisterInfo); } async setRegisters(