From 87a92b09e3ee226baf24b8a03fac3841a3883f36 Mon Sep 17 00:00:00 2001 From: arekbr Date: Tue, 4 Aug 2026 22:12:38 +0200 Subject: [PATCH 1/2] feat: warp/get endpoint; accept $ in assemble, hex byte patterns in search, both register shapes in vic/write Four API round-trip gaps that make automation awkward: - warp could be set but not read, so a client could not verify its own action. Added CDebuggerApi::GetWarpSpeed() over the existing GetSettingIsWarpSpeed() and a %s/warp/get endpoint; %s/warp/set now returns the resulting state. - cpu/assemble rejected "lda #$07" with 'Not a number after #'. '$' is the canonical 6502 notation, so this is the first thing a client tries. Strip it at the caller, exactly as CViewMonitorConsole already does, rather than teaching the shared assembler grammar about '$' -- that would turn the malformed "lda #$" from a clean 400 into a silent A9 FF write. - cpu/search only accepted mnemonics, so "a9 ??" came back as unknown_mnemonic. A first token of exactly two hex digits is now read as a raw opcode byte; this cannot collide with a mnemonic, since every name in the opcode table is three characters long. - vic/read hands back [[reg, val], ...] while vic/write demanded {reg: val}, so feeding a read result straight back failed with type_error.302. vic/write now accepts both shapes and rejects anything else with 406 instead of throwing out of the handler. Verified on Debian 13: warp/get tracks warp/set, "lda #$07" assembles to a9 07 while "lda #$" still fails, "a9 ??" returns 55 matches that all start with A9, and a vic/read result can be written back unchanged. --- src/DebugInterface/CDebuggerApi.cpp | 48 +++++++++++++++++-- src/DebugInterface/CDebuggerApi.h | 1 + .../ViceInterface/CDebuggerServerApiVice.cpp | 39 +++++++++++++-- 3 files changed, 80 insertions(+), 8 deletions(-) diff --git a/src/DebugInterface/CDebuggerApi.cpp b/src/DebugInterface/CDebuggerApi.cpp index ac0e17ba..9dee4565 100644 --- a/src/DebugInterface/CDebuggerApi.cpp +++ b/src/DebugInterface/CDebuggerApi.cpp @@ -31,6 +31,8 @@ #include "CSlrTextParser.h" #include #include +#include +#include // static factory CDebuggerApi *CDebuggerApi::GetDebuggerApi(u8 emulatorType) @@ -539,6 +541,11 @@ void CDebuggerApi::SetWarpSpeed(bool isWarpSpeed) debugInterface->SetSettingIsWarpSpeed(isWarpSpeed); } +bool CDebuggerApi::GetWarpSpeed() +{ + return debugInterface->GetSettingIsWarpSpeed(); +} + bool CDebuggerApi::KeyboardDown(u32 mtKeyCode) { return debugInterface->KeyboardDown(mtKeyCode); @@ -842,8 +849,24 @@ json CDebuggerApi::AssembleCode(int startAddr, const std::string &code) strncpy(lineBuf, lines[i].c_str(), sizeof(lineBuf) - 1); lineBuf[sizeof(lineBuf) - 1] = 0; - // Strip '$' for the assembler (it expects bare hex) - // Actually the assembler handles '$' by stripping it internally via token parsing + // Remove '$' characters (assembler is hex-only), same as CViewMonitorConsole does. + // '$' is the canonical 6502 notation, so clients send "lda #$07" and used to get + // "Not a number after #" back. Stripping here keeps the shared assembler grammar + // untouched, so a malformed "lda #$" still fails instead of silently assembling. + { + char *src = lineBuf; + char *dst = lineBuf; + while (*src) + { + if (*src != '$') + { + *dst = *src; + dst++; + } + src++; + } + *dst = 0x00; + } int instructionOpcode = -1; uint16 instructionValue = 0; @@ -1040,10 +1063,25 @@ json CDebuggerApi::SearchOpcodePattern(const std::string &pattern, int startAddr // Build set of matching opcodes std::vector matchingOpcodes; - for (int op = 0; op < 256; op++) + + // A first token of exactly two hex digits is a raw opcode byte ("a9 ??"), not a + // mnemonic. Hex bytes are the most common way to write a pattern by hand, and this + // cannot collide with a mnemonic: every name in the opcode table is 3 characters + // long (ADC, BCC and DEC look hex-ish but are 3 chars, not 2). + bool isOpcodeByte = (strlen(mnemonicBuf) == 2 + && isxdigit((unsigned char)mnemonicBuf[0]) + && isxdigit((unsigned char)mnemonicBuf[1])); + if (isOpcodeByte) { - if (strcmp(opcodes[op].name, mnemonicBuf) == 0) - matchingOpcodes.push_back(op); + matchingOpcodes.push_back((u8)strtol(mnemonicBuf, NULL, 16)); + } + else + { + for (int op = 0; op < 256; op++) + { + if (strcmp(opcodes[op].name, mnemonicBuf) == 0) + matchingOpcodes.push_back(op); + } } if (matchingOpcodes.empty()) diff --git a/src/DebugInterface/CDebuggerApi.h b/src/DebugInterface/CDebuggerApi.h index ae7070b3..21c429c2 100644 --- a/src/DebugInterface/CDebuggerApi.h +++ b/src/DebugInterface/CDebuggerApi.h @@ -111,6 +111,7 @@ class CDebuggerApi // virtual void SetWarpSpeed(bool isWarpSpeed); + virtual bool GetWarpSpeed(); // input virtual bool KeyboardDown(u32 mtKeyCode); diff --git a/src/Emulators/vice/ViceInterface/CDebuggerServerApiVice.cpp b/src/Emulators/vice/ViceInterface/CDebuggerServerApiVice.cpp index 8b63cfc0..c8c49de9 100644 --- a/src/Emulators/vice/ViceInterface/CDebuggerServerApiVice.cpp +++ b/src/Emulators/vice/ViceInterface/CDebuggerServerApiVice.cpp @@ -81,11 +81,44 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) desc.description = "Write VIC-II registers"; server->AddEndpointFunction(desc, [this, server](const string token, json params, unsigned char *binaryData, int binaryDataSize) -> vector* { + // Accept both shapes: the object {"27": 0} and the list of pairs + // [[27, 0], ...] that vic/read hands back. Read-modify-write is the + // natural cycle for VIC registers, so feeding a read result straight + // back in has to work. + json registerPairs = json::array(); + { + const json &requested = params["registers"]; + if (requested.is_array()) + { + for (const auto& entry : requested) + { + if (!entry.is_array() || entry.size() != 2) + { + return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); + } + registerPairs.push_back(entry); + } + } + else if (requested.is_object()) + { + for (auto& [key, value] : requested.items()) + { + registerPairs.push_back(json::array({key, value})); + } + } + else + { + // anything else (scalar, null, missing) is a client mistake, not a + // server error -- say so instead of throwing out of the handler + return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); + } + } + { CDebugInterfaceMutexGuard lock(debugInterfaceVice); - for (auto& [key, value] : params["registers"].items()) + for (const auto& pair : registerPairs) { - u64 registerNum = FUN_DecOrHexStrWithPrefixToU64(key.c_str()); + u64 registerNum = FUN_JsonValueDecOrHexStrWithPrefixToU64(pair[0]); if (registerNum >= 0xD000 && registerNum < 0xD040) { registerNum -= 0xD000; @@ -94,7 +127,7 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) { return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); } - u64 registerValue = FUN_JsonValueDecOrHexStrWithPrefixToU64(value); + u64 registerValue = FUN_JsonValueDecOrHexStrWithPrefixToU64(pair[1]); debuggerApiVice->SetVicRegister(registerNum, registerValue); } } From 8a14318eea7547894036fb08c625b2bd97d05a8f Mon Sep 17 00:00:00 2001 From: arekbr Date: Wed, 5 Aug 2026 14:03:27 +0200 Subject: [PATCH 2/2] feat: canonical register records for chip read/write endpoints Implements the shape agreed in the PR discussion. The old state was a trap: vic/read emitted [[reg,val],...] pairs in RANDOM order (an accident of serializing std::unordered_map through nlohmann), while vic/write demanded an object -- so a read result could not be written back, and neither shape could carry what chip registers actually need. Canon: ordered list of records. read: {"registers": [17, "$D016", "0x19"]} -> {"registers": [{"reg":17,"addr":53265,"value":155}, ...]} in REQUEST order (duplicates in the request are legal) write: {"registers": [{"reg":17,"value":155}, {"addr":"$D019","value":255}, {"reg":17,"value":27}]} executed in order, duplicates included Why a list of records and not either trap branch: a JSON object carries neither ORDER nor DUPLICATES, and both are semantics on memory-mapped chips (interrupt acks, $D011/$D012 raster sequences, gate-off/gate-on in one batch); pair lists are positional and undocumentable. Records are self-describing and extensible. Round-trip holds: a read response is a valid write request. Applied consistently to vic, cia, sid and drive1541/via. Reads emit records in request order; cia/via records carry num+addr so a read written back lands on the same chip; sid keeps its burst semantics (the batch describes final state, so a later duplicate wins -- documented in the endpoint description). Backward compatible: writes still accept the legacy object and the legacy pair list; anything else is 406 instead of an exception-turned-500. Also fixed on the way: - sid register indices are now bounds-checked; previously a register number >= C64_NUM_SID_REGISTERS wrote straight past the sidRegs array - Atari antic/gtia/pokey/pia writes parsed register keys with base-10 stoi(), so "0x18" silently became register 0 -- now the same dec/hex parser the C64 endpoints use Verified behaviorally (gate script, same binary): records in request order with duplicates, all three write shapes land (read-back witness, masked for VIC's unconnected always-1 bits), read->write round-trip, junk shape -> 406, $DD02 resolves to CIA2, sid burst accepts records. All red on the previous binary, 9/9 green after. --- .../CDebuggerServerApiAtari.cpp | 21 +- .../ViceInterface/CDebuggerServerApiVice.cpp | 222 ++++++++++++------ 2 files changed, 168 insertions(+), 75 deletions(-) diff --git a/src/Emulators/atari800/AtariInterface/CDebuggerServerApiAtari.cpp b/src/Emulators/atari800/AtariInterface/CDebuggerServerApiAtari.cpp index a5fcb30c..69382f49 100644 --- a/src/Emulators/atari800/AtariInterface/CDebuggerServerApiAtari.cpp +++ b/src/Emulators/atari800/AtariInterface/CDebuggerServerApiAtari.cpp @@ -1,6 +1,7 @@ #include "CDebuggerServerApiAtari.h" #include "CDebugInterfaceAtari.h" #include "CDebuggerServer.h" +#include "SYS_Funct.h" using namespace std; using namespace nlohmann; @@ -155,7 +156,10 @@ void CDebuggerServerApiAtari::RegisterEndpoints(CDebuggerServer *server) CDebugInterfaceMutexGuard lock(debugInterfaceAtari); for (auto &[key, value] : params["registers"].items()) { - int regNum = stoi(key); + // stoi() is base-10 only: "0x18" silently parsed as register 0, + // landing the write on the wrong register with no error. Use the + // same dec/hex parser the C64 endpoints use. + int regNum = (int)FUN_DecOrHexStrWithPrefixToU64(key.c_str()); u8 val = value.get(); debugInterfaceAtari->SetAnticRegister(regNum, val); } @@ -176,7 +180,10 @@ void CDebuggerServerApiAtari::RegisterEndpoints(CDebuggerServer *server) CDebugInterfaceMutexGuard lock(debugInterfaceAtari); for (auto &[key, value] : params["registers"].items()) { - int regNum = stoi(key); + // stoi() is base-10 only: "0x18" silently parsed as register 0, + // landing the write on the wrong register with no error. Use the + // same dec/hex parser the C64 endpoints use. + int regNum = (int)FUN_DecOrHexStrWithPrefixToU64(key.c_str()); u8 val = value.get(); debugInterfaceAtari->SetGtiaRegister(regNum, val); } @@ -197,7 +204,10 @@ void CDebuggerServerApiAtari::RegisterEndpoints(CDebuggerServer *server) CDebugInterfaceMutexGuard lock(debugInterfaceAtari); for (auto &[key, value] : params["registers"].items()) { - int regNum = stoi(key); + // stoi() is base-10 only: "0x18" silently parsed as register 0, + // landing the write on the wrong register with no error. Use the + // same dec/hex parser the C64 endpoints use. + int regNum = (int)FUN_DecOrHexStrWithPrefixToU64(key.c_str()); u8 val = value.get(); debugInterfaceAtari->SetPokeyRegister(regNum, val); } @@ -218,7 +228,10 @@ void CDebuggerServerApiAtari::RegisterEndpoints(CDebuggerServer *server) CDebugInterfaceMutexGuard lock(debugInterfaceAtari); for (auto &[key, value] : params["registers"].items()) { - int regNum = stoi(key); + // stoi() is base-10 only: "0x18" silently parsed as register 0, + // landing the write on the wrong register with no error. Use the + // same dec/hex parser the C64 endpoints use. + int regNum = (int)FUN_DecOrHexStrWithPrefixToU64(key.c_str()); u8 val = value.get(); debugInterfaceAtari->SetPiaRegister(regNum, val); } diff --git a/src/Emulators/vice/ViceInterface/CDebuggerServerApiVice.cpp b/src/Emulators/vice/ViceInterface/CDebuggerServerApiVice.cpp index c8c49de9..814ecdbb 100644 --- a/src/Emulators/vice/ViceInterface/CDebuggerServerApiVice.cpp +++ b/src/Emulators/vice/ViceInterface/CDebuggerServerApiVice.cpp @@ -15,6 +15,71 @@ extern "C" { using namespace std; using namespace nlohmann; +// Canonical shape for chip register writes, agreed in the #116 discussion. +// +// A "registers" request field accepts three forms: +// 1. canonical list of records: [{"reg": 17, "value": 155}, {"addr": "$D019", "value": 255}] +// 2. legacy object: {"17": 155, "$D019": 255} +// 3. legacy list of pairs: [[17, 155], [25, 255]] (the shape reads used to emit) +// Register keys and values may be numbers or dec/hex strings ("27", "0x1B", "$D01B"). +// "reg" and "addr" go through the same per-chip address normalization, so either works; +// a record with both is rejected as ambiguous. +// +// Only the list forms guarantee ORDER and allow DUPLICATE writes to one register -- +// a JSON object carries neither, which is exactly why it could not stay the canon: +// register order is semantics on memory-mapped chips (interrupt acks, $D011/$D012 +// sequences), and writing the same register twice in one batch is a legal use case. +// +// Output: ordered (key, value) pairs; keys raw, address bases not yet subtracted. +static bool ParseRegisterWrites(const json ®isters, std::vector> &outWrites) +{ + outWrites.clear(); + + if (registers.is_array()) + { + for (const auto &entry : registers) + { + if (entry.is_array() && entry.size() == 2) + { + // legacy pair [reg, value] + outWrites.push_back({ FUN_JsonValueDecOrHexStrWithPrefixToU64(entry[0]), + FUN_JsonValueDecOrHexStrWithPrefixToU64(entry[1]) }); + } + else if (entry.is_object()) + { + // canonical record {"reg"|"addr": ..., "value": ...}; reads emit both + // fields, so when both are present "addr" wins -- it is unambiguous + // across multi-chip endpoints (CIA1/CIA2, VIA1/VIA2) + bool hasReg = entry.contains("reg"); + bool hasAddr = entry.contains("addr"); + if ((!hasReg && !hasAddr) || !entry.contains("value")) + return false; + const json &key = hasAddr ? entry.at("addr") : entry.at("reg"); + outWrites.push_back({ FUN_JsonValueDecOrHexStrWithPrefixToU64(key), + FUN_JsonValueDecOrHexStrWithPrefixToU64(entry.at("value")) }); + } + else + { + return false; + } + } + return true; + } + + if (registers.is_object()) + { + // legacy object; JSON objects have no defined order and cannot carry duplicates + for (auto &[key, value] : registers.items()) + { + outWrites.push_back({ FUN_DecOrHexStrWithPrefixToU64(key.c_str()), + FUN_JsonValueDecOrHexStrWithPrefixToU64(value) }); + } + return true; + } + + return false; +} + CDebuggerServerApiVice::CDebuggerServerApiVice(CDebugInterface *debugInterface) : CDebuggerServerApi(debugInterface) { @@ -78,47 +143,22 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) desc.fn = buf; desc.platform = plat; desc.category = "chips"; - desc.description = "Write VIC-II registers"; + desc.description = "Write VIC-II registers; ordered list of {reg|addr, value} records (also accepts the legacy object and pair-list shapes)"; server->AddEndpointFunction(desc, [this, server](const string token, json params, unsigned char *binaryData, int binaryDataSize) -> vector* { - // Accept both shapes: the object {"27": 0} and the list of pairs - // [[27, 0], ...] that vic/read hands back. Read-modify-write is the - // natural cycle for VIC registers, so feeding a read result straight - // back in has to work. - json registerPairs = json::array(); - { - const json &requested = params["registers"]; - if (requested.is_array()) - { - for (const auto& entry : requested) - { - if (!entry.is_array() || entry.size() != 2) - { - return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); - } - registerPairs.push_back(entry); - } - } - else if (requested.is_object()) - { - for (auto& [key, value] : requested.items()) - { - registerPairs.push_back(json::array({key, value})); - } - } - else - { - // anything else (scalar, null, missing) is a client mistake, not a - // server error -- say so instead of throwing out of the handler - return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); - } + std::vector> writes; + if (!params.contains("registers") || !ParseRegisterWrites(params["registers"], writes)) + { + // a client mistake, not a server error -- say so instead of throwing + return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); } { CDebugInterfaceMutexGuard lock(debugInterfaceVice); - for (const auto& pair : registerPairs) + // in request order, duplicates included -- order is semantics here + for (const auto &write : writes) { - u64 registerNum = FUN_JsonValueDecOrHexStrWithPrefixToU64(pair[0]); + u64 registerNum = write.first; if (registerNum >= 0xD000 && registerNum < 0xD040) { registerNum -= 0xD000; @@ -127,8 +167,7 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) { return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); } - u64 registerValue = FUN_JsonValueDecOrHexStrWithPrefixToU64(pair[1]); - debuggerApiVice->SetVicRegister(registerNum, registerValue); + debuggerApiVice->SetVicRegister(registerNum, write.second); } } @@ -142,14 +181,16 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) desc.fn = buf; desc.platform = plat; desc.category = "chips"; - desc.description = "Read VIC-II registers"; + desc.description = "Read VIC-II registers; returns ordered records {reg, addr, value} in request order, a valid input for vic/write"; server->AddEndpointFunction(desc, [this, server](const string token, json params, unsigned char *binaryData, int binaryDataSize) -> vector* { json j; { CDebugInterfaceMutexGuard lock(debugInterfaceVice); - std::unordered_map registers; + // records in request order -- the previous unordered_map made the + // response order random between calls, an accident of serialization + json registers = json::array(); for (const auto& reg : params["registers"]) { u64 registerNum = FUN_JsonValueDecOrHexStrWithPrefixToU64(reg); @@ -162,8 +203,11 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); } - u8 registerValue = debugInterfaceVice->GetVicRegister(registerNum); - registers[registerNum] = registerValue; + json record; + record["reg"] = registerNum; + record["addr"] = 0xD000 + registerNum; + record["value"] = debugInterfaceVice->GetVicRegister(registerNum); + registers.push_back(record); } j["registers"] = registers; @@ -225,9 +269,15 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) desc.fn = buf; desc.platform = plat; desc.category = "chips"; - desc.description = "Write CIA registers (CIA1 or CIA2)"; + desc.description = "Write CIA registers (CIA1 or CIA2); ordered list of {reg|addr, value} records (also accepts the legacy object and pair-list shapes)"; server->AddEndpointFunction(desc, [this, server](const string token, json params, unsigned char *binaryData, int binaryDataSize) -> vector* { + std::vector> writes; + if (!params.contains("registers") || !ParseRegisterWrites(params["registers"], writes)) + { + return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); + } + { CDebugInterfaceMutexGuard lock(debugInterfaceVice); @@ -241,10 +291,10 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) } } - for (auto& [key, value] : params["registers"].items()) + for (const auto &write : writes) { int ciaNum = selectedCiaNum; - u64 registerNum = FUN_DecOrHexStrWithPrefixToU64(key.c_str()); + u64 registerNum = write.first; if (registerNum >= 0xDC00 && registerNum < 0xDC10) { registerNum -= 0xDC00; @@ -259,8 +309,7 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) { return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); } - u64 registerValue = FUN_JsonValueDecOrHexStrWithPrefixToU64(value); - debuggerApiVice->SetCiaRegister(ciaNum, registerNum, registerValue); + debuggerApiVice->SetCiaRegister(ciaNum, registerNum, write.second); } } @@ -274,7 +323,7 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) desc.fn = buf; desc.platform = plat; desc.category = "chips"; - desc.description = "Read CIA registers (CIA1 or CIA2)"; + desc.description = "Read CIA registers (CIA1 or CIA2); returns ordered records {reg, num, addr, value} in request order, a valid input for cia/write"; server->AddEndpointFunction(desc, [this, server](const string token, json params, unsigned char *binaryData, int binaryDataSize) -> vector* { json j; @@ -291,7 +340,7 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) } } - std::unordered_map registers; + json registers = json::array(); for (const auto& reg : params["registers"]) { int ciaNum = selectedCiaNum; @@ -311,8 +360,14 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); } - u8 registerValue = debuggerApiVice->GetCiaRegister(ciaNum, registerNum); - registers[registerNum] = registerValue; + // "addr" makes the record unambiguous across both CIAs, so a + // read result written back lands on the same chip + json record; + record["reg"] = registerNum; + record["num"] = ciaNum; + record["addr"] = (ciaNum == 0 ? 0xDC00 : 0xDD00) + registerNum; + record["value"] = debuggerApiVice->GetCiaRegister(ciaNum, registerNum); + registers.push_back(record); } j["registers"] = registers; @@ -328,7 +383,7 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) desc.fn = buf; desc.platform = plat; desc.category = "chips"; - desc.description = "Write SID registers with burst-write to avoid side-effects"; + desc.description = "Write SID registers with burst-write to avoid side-effects; per-SID registers accept {reg, value} records, the legacy object, or pair lists (burst = final state, so a later duplicate wins)"; server->AddEndpointFunction(desc, [this, server](const string token, json params, unsigned char *binaryData, int binaryDataSize) -> vector* { { @@ -352,17 +407,28 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); } - for (auto& [key, value] : jsonSidData["registers"].items()) + std::vector> writes; + if (!jsonSidData.contains("registers") || !ParseRegisterWrites(jsonSidData["registers"], writes)) { - u64 registerNum = FUN_DecOrHexStrWithPrefixToU64(key.c_str()); + return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); + } + + for (const auto &write : writes) + { + u64 registerNum = write.first; // TODO: subtract selected sid# address --> debugInterfaceVice->GetSidStereoAddress(sidNum) // if (registerNum >= 0xD400 && registerNum < 0xDFFF) // { // registerNum -= 0xD400; // } + if (registerNum >= C64_NUM_SID_REGISTERS) + { + return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); + } - u64 registerValue = FUN_JsonValueDecOrHexStrWithPrefixToU64(value); - sidData->sidRegs[sidNum][registerNum] = registerValue; + // burst semantics: this batch describes the SID's final state, + // so duplicates resolve to the last write on purpose + sidData->sidRegs[sidNum][registerNum] = write.second; sidData->shouldSetSidReg[sidNum][registerNum] = true; } } @@ -381,7 +447,7 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) desc.fn = buf; desc.platform = plat; desc.category = "chips"; - desc.description = "Read SID registers"; + desc.description = "Read SID registers; returns ordered records {reg, num, value} in request order"; server->AddEndpointFunction(desc, [this, server](const string token, json params, unsigned char *binaryData, int binaryDataSize) -> vector* { json j; @@ -398,7 +464,7 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) } } - std::unordered_map registers; + json registers = json::array(); for (const auto& reg : params["registers"]) { u64 registerNum = FUN_JsonValueDecOrHexStrWithPrefixToU64(reg); @@ -407,13 +473,18 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) // { // registerNum -= 0xD400; // } - // if (registerNum > 0x0F) - // { - // return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); - // } + if (registerNum >= C64_NUM_SID_REGISTERS) + { + return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); + } - u8 registerValue = debuggerApiVice->GetSidRegister(sidNum, registerNum); - registers[registerNum] = registerValue; + // no "addr" here: the stereo SID base mapping is still a TODO above, + // so the record carries the SID number instead + json record; + record["reg"] = registerNum; + record["num"] = sidNum; + record["value"] = debuggerApiVice->GetSidRegister(sidNum, registerNum); + registers.push_back(record); } j["registers"] = registers; @@ -584,9 +655,15 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) desc.fn = buf; desc.platform = plat; desc.category = "chips"; - desc.description = "Write 1541 drive VIA registers (VIA1 or VIA2)"; + desc.description = "Write 1541 drive VIA registers (VIA1 or VIA2); ordered list of {reg|addr, value} records (also accepts the legacy object and pair-list shapes)"; server->AddEndpointFunction(desc, [this, server](const string token, json params, unsigned char *binaryData, int binaryDataSize) -> vector* { + std::vector> writes; + if (!params.contains("registers") || !ParseRegisterWrites(params["registers"], writes)) + { + return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); + } + { CDebugInterfaceMutexGuard lock(debugInterfaceVice); @@ -610,10 +687,10 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) } } - for (auto& [key, value] : params["registers"].items()) + for (const auto &write : writes) { int viaNum = selectedViaNum; - u64 registerNum = FUN_DecOrHexStrWithPrefixToU64(key.c_str()); + u64 registerNum = write.first; if (registerNum >= 0x1800 && registerNum < 0x1810) { registerNum -= 0x1800; @@ -628,8 +705,7 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) { return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); } - u64 registerValue = FUN_JsonValueDecOrHexStrWithPrefixToU64(value); - debuggerApiVice->SetDrive1541ViaRegister(selectedDriveNum, viaNum, registerNum, registerValue); + debuggerApiVice->SetDrive1541ViaRegister(selectedDriveNum, viaNum, registerNum, write.second); } } @@ -643,7 +719,7 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) desc.fn = buf; desc.platform = plat; desc.category = "chips"; - desc.description = "Read 1541 drive VIA registers (VIA1 or VIA2)"; + desc.description = "Read 1541 drive VIA registers (VIA1 or VIA2); returns ordered records {reg, num, addr, value} in request order, a valid input for via/write"; server->AddEndpointFunction(desc, [this, server](const string token, json params, unsigned char *binaryData, int binaryDataSize) -> vector* { json j; @@ -670,7 +746,7 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) } } - std::unordered_map registers; + json registers = json::array(); for (const auto& reg : params["registers"]) { int viaNum = selectedViaNum; @@ -690,8 +766,12 @@ void CDebuggerServerApiVice::RegisterEndpoints(CDebuggerServer *server) return server->PrepareResult(HTTP_NOT_ACCEPTABLE, token, json(), NULL, 0); } - u8 registerValue = debuggerApiVice->GetDrive1541ViaRegister(selectedDriveNum, viaNum, registerNum); - registers[registerNum] = registerValue; + json record; + record["reg"] = registerNum; + record["num"] = viaNum; + record["addr"] = (viaNum == 0 ? 0x1800 : 0x1C00) + registerNum; + record["value"] = debuggerApiVice->GetDrive1541ViaRegister(selectedDriveNum, viaNum, registerNum); + registers.push_back(record); } j["registers"] = registers;