OXYGENHUB
# 9120e87480a6 enhanced-x16-emulator / src / debug_server.c
146.5 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
// Commander X16 Emulator — Debug Adapter Protocol (DAP) Server
2
// Speaks DAP over TCP for direct IDE integration (Visual Studio, VS Code, etc.)
3
// Transport: Content-Length framed JSON over TCP, default port 9009
4
5
#include <SDL.h>
6
#include "debug_server.h"
7
#include "dbg_info.h"
8
#include "debugger.h"
9
#include "disasm.h"
10
#include "code_map.h"
11
#include "perf_budget.h"
12
#include "vera_bandwidth.h"
13
#include "glue.h"
14
#include "memory.h"
15
#include "source_view.h"
16
#ifdef _WIN32
17
#include <ws2tcpip.h>
18
#else
19
#include <netinet/tcp.h>
20
#endif
21
#include "video.h"
22
#include "keyboard.h"
23
#include "joystick.h"
24
25
#include "cpu/fake6502.h"
26
27
#include <stdio.h>
28
#include <stdlib.h>
29
#include <string.h>
30
#include <ctype.h>
31
#include <cjson/cJSON.h>
32
33
// Platform socket abstraction
34
#ifdef _WIN32
35
  #include <winsock2.h>
36
  #include <ws2tcpip.h>
37
  #pragma comment(lib, "ws2_32.lib")
38
  typedef SOCKET socket_t;
39
  #define SOCKET_INVALID INVALID_SOCKET
40
  #define CLOSE_SOCKET closesocket
41
  #define SOCK_ERRNO WSAGetLastError()
42
  #define WOULD_BLOCK (SOCK_ERRNO == WSAEWOULDBLOCK)
43
  static bool wsa_initialized = false;
44
#else
45
  #include <sys/socket.h>
46
  #include <netinet/in.h>
47
  #include <arpa/inet.h>
48
  #include <fcntl.h>
49
  #include <unistd.h>
50
  #include <errno.h>
51
  typedef int socket_t;
52
  #define SOCKET_INVALID (-1)
53
  #define CLOSE_SOCKET close
54
  #define SOCK_ERRNO errno
55
  #define WOULD_BLOCK (SOCK_ERRNO == EAGAIN || SOCK_ERRNO == EWOULDBLOCK)
56
#endif
57
58
// Receive buffer (large enough for DAP messages with headers)
59
#define RECV_BUF_SIZE 65536
60
static char recv_buf[RECV_BUF_SIZE];
61
static int  recv_buf_len = 0;
62
63
// Send buffer
64
#define SEND_BUF_SIZE 65536
65
static char send_buf[SEND_BUF_SIZE];
66
67
// Server state
68
static socket_t listen_sock = SOCKET_INVALID;
69
static socket_t client_sock = SOCKET_INVALID;
70
static bool     server_enabled = false;
71
static int      server_port = 0;
72
73
// DAP sequence counter (for events we send)
74
static int dap_seq = 1;
75
76
// DAP variable reference IDs
77
#define VARREF_REGISTERS  1
78
#define VARREF_ZEROPAGE   2
79
#define VARREF_STACK      3
80
#define VARREF_VREGS      4   // X16 16-bit virtual registers R0-R15 (ZP $02-$21)
81
82
// Breakpoint tracking for DAP (maps DAP bp IDs to addresses)
83
#define MAX_DAP_BREAKPOINTS 256
84
static struct {
85
    int  dap_id;       // DAP breakpoint ID
86
    uint16_t addr;     // resolved address
87
    char file[256];    // source file
88
    int  line;         // source line
89
    bool verified;
90
    uint8_t bank;      // program bank (65816); addresses are 24-bit
91
    int  x16Bank;      // the selector it was armed with; a removal that passes
92
                       // DEBUG_BANK_ANY will not match a bank-pinned entry
93
    // Kept so the breakpoint can be armed again exactly as the client asked for
94
    // it when an overlay reload forces it to be re-resolved. Without these, a
95
    // reload quietly turned a bank-pinned conditional breakpoint into an
96
    // unconditional one that fires in every bank.
97
    char cond[192];
98
    char hit_cond[64];
99
} dap_bps[MAX_DAP_BREAKPOINTS];
100
static int num_dap_bps = 0;
101
static int next_dap_bp_id = 1;
102
103
// Function/instruction breakpoints tracked separately so each re-set clears its
104
// own set. All three add into the same core table, which now records which of
105
// them asked -- so each can clear what it asked for without knowing or caring
106
// what anyone else wanted at the same address.
107
static uint16_t func_bp_addrs[128];
108
static uint8_t  func_bp_banks[128];
109
static int      num_func_bps = 0;
110
static uint16_t instr_bp_addrs[128];
111
static uint8_t  instr_bp_banks[128];
112
static int      num_instr_bps = 0;
113
114
static bool     dap_session_active = false;
115
// Whether the stop the machine is currently sitting in has been announced. A
116
// breakpoint can hit while the client is still configuring, and configurationDone
117
// would then report the same halt a second time under a different reason.
118
static bool     dap_stop_announced = false;
119
120
// Is there a local debugger UI whose user owns the run state?
121
//
122
// When there is, a client connecting or going away must not resume the machine:
123
// somebody is sitting at a breakpoint looking at it. When there is not -- a
124
// headless -debugport run -- nothing else can ever resume it, so a session
125
// ending has to.
126
//
127
// Both front ends count, and both need their own test. -imgui does not set
128
// debug_window_enabled, and -debugport sets debugger_enabled by itself, so
129
// neither flag alone answers the question: testing only the first ignored the
130
// graphical debugger entirely and resumed the machine out from under it.
131
static bool
132
dap_local_ui_owns_run_state(void)
133
{
134
	return (debug_window_enabled && debugger_enabled)
135
	       || (imgui_debugger_enabled && video_debug_ui_available());
136
}
137
138
// Forward declarations
139
static void send_dap_event(const char *event_name, cJSON *body);
140
static void send_dap_message(cJSON *json);
141
static int dap_apply_bp_condition(dbg_addr_t addr, const char *cond_str, const char *hit_str);
142
143
// Re-resolve unverified breakpoints after new .dbg file loads
144
static void retry_unverified_breakpoints(void) {
145
    for (int i = 0; i < num_dap_bps; i++) {
146
        if (!dap_bps[i].verified) {
147
            dbg_addr_t addr = 0;
148
            if (dbg_info_source_to_addr(dap_bps[i].file, dap_bps[i].line, &addr)) {
149
                // Re-arm it exactly as the client asked for it. Re-applying the
150
                // condition also recovers the bank pin, which decides the
151
                // selector the core entry is keyed on -- arming with ANY here
152
                // turned a bank-pinned conditional breakpoint into one that
153
                // fires in every bank on its first arrival.
154
                int bank_pin = dap_apply_bp_condition(addr,
155
                                                      dap_bps[i].cond,
156
                                                      dap_bps[i].hit_cond);
157
                dap_bps[i].addr = (uint16_t)(addr & 0xFFFF);
158
                dap_bps[i].bank = (uint8_t)(addr >> 16);
159
                dap_bps[i].verified = true;
160
                dap_bps[i].x16Bank = bank_pin;
161
                struct breakpoint hw_bp;
162
                hw_bp.pc = (int)(addr & 0xFFFF);
163
                hw_bp.bank = (uint8_t)(addr >> 16);
164
                hw_bp.x16Bank = bank_pin;
165
                debug_bp_add_for(hw_bp, DEBUG_OWNER_DAP_SOURCE);
166
                printf("[dap] Resolved pending breakpoint: %s:%d -> $%04X\n",
167
                       dap_bps[i].file, dap_bps[i].line, (unsigned)addr);
168
                // Say so. This is reached after invalidate_breakpoints_in_range()
169
                // has told the client the breakpoint is unverified, so staying
170
                // quiet would leave it showing one that is in fact armed.
171
                if (client_sock != SOCKET_INVALID) {
172
                    cJSON *body = cJSON_CreateObject();
173
                    cJSON *bp = cJSON_CreateObject();
174
                    cJSON_AddNumberToObject(bp, "id", dap_bps[i].dap_id);
175
                    cJSON_AddBoolToObject(bp, "verified", true);
176
                    cJSON_AddNumberToObject(bp, "line", dap_bps[i].line);
177
                    cJSON_AddItemToObject(body, "breakpoint", bp);
178
                    cJSON_AddStringToObject(body, "reason", "changed");
179
                    send_dap_event("breakpoint", body);
180
                }
181
            }
182
        }
183
    }
184
}
185
186
// Invalidate breakpoints whose addresses fall within a range being unloaded
187
static void invalidate_breakpoints_in_range(uint32_t start, uint32_t end) {
188
    // Compared as full 24-bit addresses, program bank included. Clamping the
189
    // range to 16 bits and comparing only the low half meant reloading a
190
    // bank-1 module left its breakpoints armed and stale, while reloading a
191
    // bank-0 range invalidated unrelated breakpoints in every other bank.
192
    for (int i = 0; i < num_dap_bps; i++) {
193
        const uint32_t bp_addr =
194
            ((uint32_t)dap_bps[i].bank << 16) | dap_bps[i].addr;
195
        if (dap_bps[i].verified && bp_addr >= start && bp_addr <= end) {
196
            // Drop this session's claim on the core entry. It goes only if
197
            // nothing else wants it, so a -bp or an F9 breakpoint at the same
198
            // address survives a module reload that has nothing to do with it.
199
            debug_bp_remove_for(dap_bps[i].addr, dap_bps[i].bank, dap_bps[i].x16Bank,
200
                                DEBUG_OWNER_DAP_SOURCE);
201
202
            dap_bps[i].addr = 0;
203
            dap_bps[i].bank = 0;
204
            printf("[dap] Invalidated breakpoint: %s:%d (range $%06X-$%06X unloaded)\n",
205
                   dap_bps[i].file, dap_bps[i].line, start, end);
206
207
            // Notify VS that breakpoint is no longer verified
208
            if (client_sock != SOCKET_INVALID) {
209
                cJSON *body = cJSON_CreateObject();
210
                cJSON *bp = cJSON_CreateObject();
211
                cJSON_AddNumberToObject(bp, "id", dap_bps[i].dap_id);
212
                cJSON_AddBoolToObject(bp, "verified", false);
213
                cJSON_AddStringToObject(bp, "message", "Module unloaded");
214
                cJSON_AddNumberToObject(bp, "line", dap_bps[i].line);
215
                cJSON_AddItemToObject(body, "breakpoint", bp);
216
                cJSON_AddStringToObject(body, "reason", "changed");
217
                send_dap_event("breakpoint", body);
218
            }
219
        }
220
    }
221
}
222
223
// Public wrappers so the shared runtime-load handler (debugger.c) can drive the
224
// DAP-only breakpoint bookkeeping around a module swap. Both are no-ops when no
225
// DAP client is connected, so the native source window works without DAP.
226
void debug_server_invalidate_breakpoints_in_range(uint32_t start, uint32_t end) {
227
    if (!server_enabled || client_sock == SOCKET_INVALID) return;
228
    invalidate_breakpoints_in_range(start, end);
229
}
230
231
void debug_server_retry_unverified_breakpoints(void) {
232
    if (!server_enabled || client_sock == SOCKET_INVALID) return;
233
    retry_unverified_breakpoints();
234
}
235
236
// ─── Platform helpers ───────────────────────────────────────────────
237
238
static bool set_nonblocking(socket_t sock) {
239
#ifdef _WIN32
240
    u_long mode = 1;
241
    return ioctlsocket(sock, FIONBIO, &mode) == 0;
242
#else
243
    int flags = fcntl(sock, F_GETFL, 0);
244
    if (flags < 0) return false;
245
    return fcntl(sock, F_SETFL, flags | O_NONBLOCK) == 0;
246
#endif
247
}
248
249
static bool platform_init(void) {
250
#ifdef _WIN32
251
    if (!wsa_initialized) {
252
        WSADATA wsa;
253
        if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
254
            fprintf(stderr, "[dap] WSAStartup failed: %d\n", WSAGetLastError());
255
            return false;
256
        }
257
        wsa_initialized = true;
258
    }
259
#endif
260
    return true;
261
}
262
263
static void platform_cleanup(void) {
264
#ifdef _WIN32
265
    if (wsa_initialized) {
266
        WSACleanup();
267
        wsa_initialized = false;
268
    }
269
#endif
270
}
271
272
// ─── DAP message sending ────────────────────────────────────────────
273
274
static void disconnect_client(void);
275
static void dap_release_session_state(void);
276
277
static void send_dap_message(cJSON *json) {
278
    if (client_sock == SOCKET_INVALID) {
279
        cJSON_Delete(json);
280
        return;
281
    }
282
    char *body = cJSON_PrintUnformatted(json);
283
    if (body) {
284
        int body_len = (int)strlen(body);
285
        int header_len = snprintf(send_buf, sizeof(send_buf),
286
                                  "Content-Length: %d\r\n\r\n", body_len);
287
        if (header_len + body_len >= SEND_BUF_SIZE) {
288
            // Too large to frame. Dropping it silently left the client waiting
289
            // on a request_seq that would never be answered -- a source file
290
            // over 64K is enough to do it, and the editor shows a tab that
291
            // never finishes loading. The receive side already drops a client
292
            // it cannot buffer; do the same rather than hang.
293
            fprintf(stderr, "[dap] response of %d bytes exceeds the send buffer; dropping client\n",
294
                    header_len + body_len);
295
            cJSON_free(body);
296
            cJSON_Delete(json);
297
            disconnect_client();
298
            return;
299
        }
300
        {
301
            memcpy(send_buf + header_len, body, body_len);
302
            int total = header_len + body_len;
303
            int offset = 0;
304
            // A client that stops reading fills the socket buffer, and this is
305
            // the emulator's only thread. Retrying forever parks the machine on
306
            // a peer that may never come back, so give up after a bounded wait
307
            // and drop the client instead: a dead session is recoverable, a
308
            // frozen emulator is not.
309
            int stalls = 0;
310
            const int max_stalls = 500;          // ~500ms of a peer not reading
311
            while (offset < total) {
312
                int n = send(client_sock, send_buf + offset, total - offset, 0);
313
                if (n > 0) {
314
                    offset += n;
315
                    stalls = 0;
316
                } else if (n < 0 && WOULD_BLOCK) {
317
                    if (++stalls > max_stalls) {
318
                        fprintf(stderr, "[dap] client is not reading; dropping it\n");
319
                        disconnect_client();
320
                        break;
321
                    }
322
                    // SDL's, rather than Sleep/usleep behind a platform split:
323
                    // usleep needs a feature-test macro this file does not set,
324
                    // which only the -Werror Linux builds noticed.
325
                    SDL_Delay(1);
326
                } else {
327
                    break; // real error
328
                }
329
            }
330
        }
331
        cJSON_free(body);
332
    }
333
    cJSON_Delete(json);
334
}
335
336
static void send_dap_response(int request_seq, const char *command, bool success, cJSON *body) {
337
    cJSON *resp = cJSON_CreateObject();
338
    cJSON_AddNumberToObject(resp, "seq", dap_seq++);
339
    cJSON_AddStringToObject(resp, "type", "response");
340
    cJSON_AddNumberToObject(resp, "request_seq", request_seq);
341
    cJSON_AddBoolToObject(resp, "success", success);
342
    cJSON_AddStringToObject(resp, "command", command);
343
    if (body) {
344
        cJSON_AddItemToObject(resp, "body", body);
345
    }
346
    send_dap_message(resp);
347
}
348
349
static void send_dap_error_response(int request_seq, const char *command, const char *message) {
350
    cJSON *body = cJSON_CreateObject();
351
    cJSON *error_obj = cJSON_CreateObject();
352
    cJSON_AddNumberToObject(error_obj, "id", 1);
353
    cJSON_AddStringToObject(error_obj, "format", message);
354
    cJSON_AddItemToObject(body, "error", error_obj);
355
356
    cJSON *resp = cJSON_CreateObject();
357
    cJSON_AddNumberToObject(resp, "seq", dap_seq++);
358
    cJSON_AddStringToObject(resp, "type", "response");
359
    cJSON_AddNumberToObject(resp, "request_seq", request_seq);
360
    cJSON_AddBoolToObject(resp, "success", false);
361
    cJSON_AddStringToObject(resp, "command", command);
362
    cJSON_AddStringToObject(resp, "message", message);
363
    cJSON_AddItemToObject(resp, "body", body);
364
    send_dap_message(resp);
365
}
366
367
static void send_dap_event(const char *event_name, cJSON *body) {
368
    cJSON *event = cJSON_CreateObject();
369
    cJSON_AddNumberToObject(event, "seq", dap_seq++);
370
    cJSON_AddStringToObject(event, "type", "event");
371
    cJSON_AddStringToObject(event, "event", event_name);
372
    if (body) {
373
        cJSON_AddItemToObject(event, "body", body);
374
    }
375
    send_dap_message(event);
376
}
377
378
// ContinuedEvent — tell the client execution resumed (all threads).
379
static void send_continued_event(void) {
380
    dap_stop_announced = false;   // whatever we were stopped in is over
381
    if (client_sock == SOCKET_INVALID) return;
382
    cJSON *body = cJSON_CreateObject();
383
    cJSON_AddNumberToObject(body, "threadId", 1);
384
    cJSON_AddBoolToObject(body, "allThreadsContinued", true);
385
    send_dap_event("continued", body);
386
}
387
388
// OutputEvent — forward text to the client's debug console. Public API so the
389
// emulator can pipe program/console output to an attached DAP client.
390
void debug_server_output(const char *category, const char *text) {
391
    if (!server_enabled || client_sock == SOCKET_INVALID || !text) return;
392
    cJSON *body = cJSON_CreateObject();
393
    cJSON_AddStringToObject(body, "category", category ? category : "console");
394
    cJSON_AddStringToObject(body, "output", text);
395
    send_dap_event("output", body);
396
}
397
398
// Pending events to send on next poll (avoid back-to-back sends)
399
// Announces the single emulated CPU as a DAP thread. Sent after launch and
400
// after attach, which are the two ways a session starts.
401
static void send_dap_event_thread_started(void);
402
403
// Set by launch when the client asked to be given control before anything runs.
404
static bool dap_stop_on_entry = false;
405
406
// ─── DAP command handlers ───────────────────────────────────────────
407
408
static int handle_dap_initialize(int seq, cJSON *args) {
409
    (void)args;
410
    cJSON *caps = cJSON_CreateObject();
411
    cJSON_AddBoolToObject(caps, "supportsConfigurationDoneRequest", true);
412
    cJSON_AddBoolToObject(caps, "supportsSetVariable", true);
413
    cJSON_AddBoolToObject(caps, "supportsRestartRequest", true);
414
    cJSON_AddBoolToObject(caps, "supportsTerminateRequest", true);
415
    cJSON_AddBoolToObject(caps, "supportsReadMemoryRequest", true);
416
    cJSON_AddBoolToObject(caps, "supportsWriteMemoryRequest", true);
417
    cJSON_AddBoolToObject(caps, "supportsDisassembleRequest", true);
418
    cJSON_AddBoolToObject(caps, "supportTerminateDebuggee", true);
419
    cJSON_AddBoolToObject(caps, "supportsEvaluateForHovers", true);
420
    cJSON_AddBoolToObject(caps, "supportsSteppingGranularity", true);
421
    cJSON_AddBoolToObject(caps, "supportsGotoTargetsRequest", true);
422
    cJSON_AddBoolToObject(caps, "supportsSetExpression", false);
423
    cJSON_AddBoolToObject(caps, "supportsValueFormattingOptions", true);
424
    cJSON_AddBoolToObject(caps, "supportsFunctionBreakpoints", true);
425
    // Newly implemented in this build:
426
    cJSON_AddBoolToObject(caps, "supportsDataBreakpoints", true);
427
    cJSON_AddBoolToObject(caps, "supportsInstructionBreakpoints", true);
428
    cJSON_AddBoolToObject(caps, "supportsLoadedSourcesRequest", true);
429
    cJSON_AddBoolToObject(caps, "supportsBreakpointLocationsRequest", true);
430
    cJSON_AddBoolToObject(caps, "supportsStepInTargetsRequest", true);
431
    cJSON_AddBoolToObject(caps, "supportsConditionalBreakpoints", true);
432
    cJSON_AddBoolToObject(caps, "supportsHitConditionalBreakpoints", true);
433
    // Non-standard, so that a client can tell this emulator's performance
434
    // budget surface from one that would reject the requests.
435
    cJSON_AddBoolToObject(caps, "supportsX16PerfStats", true);
436
    send_dap_response(seq, "initialize", true, caps);
437
438
    dap_session_active = true;
439
440
    // Per the DAP spec this follows the initialize response immediately: a
441
    // client is entitled to wait for it before sending configurationDone, so
442
    // deferring it to the next request deadlocks anything strict about that.
443
    send_dap_event("initialized", NULL);
444
    return 0;
445
}
446
447
static int handle_dap_launch(int seq, cJSON *args) {
448
    // Load .dbg file if specified in launch arguments (additive, don't clear existing)
449
    if (args) {
450
        cJSON *dbg_file = cJSON_GetObjectItemCaseSensitive(args, "dbgFile");
451
        if (dbg_file && cJSON_IsString(dbg_file)) {
452
            if (dbg_info_load(dbg_file->valuestring) == 0) {
453
                printf("[dap] Loaded debug info: %s\n", dbg_file->valuestring);
454
            } else {
455
                fprintf(stderr, "[dap] Warning: failed to load debug info: %s\n",
456
                        dbg_file->valuestring);
457
            }
458
        }
459
        cJSON *soe = cJSON_GetObjectItemCaseSensitive(args, "stopOnEntry");
460
        dap_stop_on_entry = (soe && cJSON_IsTrue(soe));
461
    }
462
    send_dap_response(seq, "launch", true, NULL);
463
464
    send_dap_event_thread_started();
465
466
    return 0;
467
}
468
469
static int handle_dap_configuration_done(int seq) {
470
    send_dap_response(seq, "configurationDone", true, NULL);
471
472
    // Sending can drop the client -- a peer that stops reading is disconnected
473
    // rather than allowed to freeze the emulator -- so there may be no session
474
    // left to configure. Carrying on would halt a headless machine with nobody
475
    // able to resume it.
476
    if (client_sock == SOCKET_INVALID) {
477
        dap_stop_on_entry = false;
478
        return 0;
479
    }
480
481
    // Already halted, because a breakpoint set moments ago has hit or a
482
    // previous client left it that way. Report where we actually are; resuming
483
    // would drive past a stop the client should see, and saying nothing leaves
484
    // it showing a running target it cannot pause. Checked before stopOnEntry
485
    // so the same halt is not announced twice under two different reasons.
486
    if (currentMode == DMODE_STOP) {
487
        dap_stop_on_entry = false;
488
        // Unless it has already been reported -- a breakpoint that hit while
489
        // the client was still configuring emits its own stopped event, and
490
        // announcing the same halt again under a different reason confuses a
491
        // client into thinking it stopped twice.
492
        if (!dap_stop_announced) debug_server_notify_stopped("breakpoint");
493
        return 1;
494
    }
495
496
    // The client wants control before anything executes, and is waiting for the
497
    // stopped event to know it has it.
498
    if (dap_stop_on_entry) {
499
        dap_stop_on_entry = false;
500
        currentMode = DMODE_STOP;
501
        debug_server_notify_stopped("entry");
502
        return 1;
503
    }
504
505
    currentMode = DMODE_RUN;
506
    return 1;
507
}
508
509
static int handle_dap_threads(int seq) {
510
    cJSON *body = cJSON_CreateObject();
511
    cJSON *threads = cJSON_CreateArray();
512
    cJSON *thread = cJSON_CreateObject();
513
    cJSON_AddNumberToObject(thread, "id", 1);
514
    cJSON_AddStringToObject(thread, "name", "6502 CPU");
515
    cJSON_AddItemToArray(threads, thread);
516
    cJSON_AddItemToObject(body, "threads", threads);
517
    send_dap_response(seq, "threads", true, body);
518
    return 0;
519
}
520
521
// Build a DAP stack frame object for a code address. `bank` is the 65C816
522
// program bank (0 on the 65C02); it makes the instructionPointerReference a
523
// full 24-bit address so disassembly opens in the right bank on GS.
524
static cJSON *build_stack_frame(int id, uint16_t addr, uint8_t bank, bool is_current) {
525
    cJSON *frame = cJSON_CreateObject();
526
    cJSON_AddNumberToObject(frame, "id", id);
527
528
    const char *src_file = NULL;
529
    int         src_line = 0;
530
    bool        has_source = dbg_info_addr_to_source_banked(addr, (int)memory_get_ram_bank(),
531
                                                            &src_file, &src_line);
532
533
    const char *label = NULL;
534
    char        name_buf[80];
535
    const char *afmt = (bank != 0 || is_gen2) ? "%s ($%02X:%04X)" : "%s ($%04X)";
536
    const char *nfmt = (bank != 0 || is_gen2) ? "$%02X:%04X" : "$%04X";
537
    if (dbg_info_addr_to_label(addr, &label) && label) {
538
        if (bank != 0 || is_gen2) snprintf(name_buf, sizeof(name_buf), afmt, label, bank, addr);
539
        else                      snprintf(name_buf, sizeof(name_buf), afmt, label, addr);
540
    } else {
541
        if (bank != 0 || is_gen2) snprintf(name_buf, sizeof(name_buf), nfmt, bank, addr);
542
        else                      snprintf(name_buf, sizeof(name_buf), nfmt, addr);
543
    }
544
    cJSON_AddStringToObject(frame, "name", name_buf);
545
    cJSON_AddNumberToObject(frame, "line", has_source ? src_line : 0);
546
    cJSON_AddNumberToObject(frame, "column", 1);
547
548
    if (has_source) {
549
        cJSON *source = cJSON_CreateObject();
550
        cJSON_AddStringToObject(source, "path", src_file);
551
        const char *basename = strrchr(src_file, '/');
552
        if (!basename) basename = strrchr(src_file, '\\');
553
        cJSON_AddStringToObject(source, "name", basename ? basename + 1 : src_file);
554
        cJSON_AddItemToObject(frame, "source", source);
555
    } else {
556
        cJSON *hint = cJSON_CreateObject();
557
        cJSON_AddStringToObject(hint, "kind", is_current ? "normal" : "subtle");
558
        cJSON_AddItemToObject(frame, "presentationHint", hint);
559
        cJSON *source = cJSON_CreateObject();
560
        cJSON_AddStringToObject(source, "name", name_buf);
561
        cJSON_AddNumberToObject(source, "sourceReference", 0);
562
        cJSON_AddStringToObject(source, "origin", "disassembly");
563
        cJSON_AddItemToObject(frame, "source", source);
564
    }
565
566
    char instr_ref[16];
567
    if (bank != 0 || is_gen2)
568
        snprintf(instr_ref, sizeof(instr_ref), "0x%06X", ((uint32_t)bank << 16) | addr);
569
    else
570
        snprintf(instr_ref, sizeof(instr_ref), "0x%04X", addr);
571
    cJSON_AddStringToObject(frame, "instructionPointerReference", instr_ref);
572
    return frame;
573
}
574
575
static int handle_dap_stack_trace(int seq, cJSON *args) {
576
    int startFrame = 0, levels = 0;
577
    if (args) {
578
        cJSON *sf = cJSON_GetObjectItemCaseSensitive(args, "startFrame");
579
        if (sf && cJSON_IsNumber(sf)) startFrame = (int)sf->valuedouble;
580
        cJSON *lv = cJSON_GetObjectItemCaseSensitive(args, "levels");
581
        if (lv && cJSON_IsNumber(lv)) levels = (int)lv->valuedouble;
582
    }
583
584
    // Heuristically unwind the hardware stack (no frame pointer on 6502): frame 0
585
    // is the current PC, then scan for return-address pairs preceded by a JSR.
586
    uint16_t addrs[64];
587
    int      nf = 0;
588
    addrs[nf++] = regs.pc;
589
590
    bool     native = regs.is65c816 && !regs.e;
591
    uint32_t sp     = regs.sp;
592
    uint32_t start  = native ? (sp + 1) : (0x100 + (sp & 0xFF) + 1);
593
    uint32_t end    = native ? (sp + 1 + 256) : 0x200;
594
    for (uint32_t a = start; a + 1 < end && nf < 64;) {
595
        uint8_t  lo     = debug_read6502((uint16_t)a, 0, USE_CURRENT_X16_BANK);
596
        uint8_t  hi     = debug_read6502((uint16_t)(a + 1), 0, USE_CURRENT_X16_BANK);
597
        uint16_t pushed = (uint16_t)(lo | (hi << 8));
598
        uint8_t  opc    = debug_read6502((uint16_t)(pushed - 2), regs.k, USE_CURRENT_X16_BANK);
599
        if (opc == 0x20) { addrs[nf++] = (uint16_t)(pushed + 1); a += 2; }
600
        else             { a += 1; }
601
    }
602
603
    cJSON *body   = cJSON_CreateObject();
604
    cJSON *frames = cJSON_CreateArray();
605
    int    total  = nf;
606
    if (startFrame < 0) startFrame = 0;
607
    if (startFrame > nf) startFrame = nf;
608
    int    last   = (levels > 0) ? (startFrame + levels) : nf;
609
    if (last > nf) last = nf;
610
    for (int i = startFrame; i < last; i++) {
611
        cJSON_AddItemToArray(frames, build_stack_frame(i + 1, addrs[i], regs.k, i == 0));
612
    }
613
    cJSON_AddItemToObject(body, "stackFrames", frames);
614
    cJSON_AddNumberToObject(body, "totalFrames", total);
615
    send_dap_response(seq, "stackTrace", true, body);
616
    return 0;
617
}
618
619
static int handle_dap_scopes(int seq, cJSON *args) {
620
    (void)args;
621
    cJSON *body = cJSON_CreateObject();
622
    cJSON *scopes = cJSON_CreateArray();
623
624
    // Registers scope
625
    cJSON *reg_scope = cJSON_CreateObject();
626
    cJSON_AddStringToObject(reg_scope, "name", "Registers");
627
    cJSON_AddNumberToObject(reg_scope, "variablesReference", VARREF_REGISTERS);
628
    cJSON_AddBoolToObject(reg_scope, "expensive", false);
629
    cJSON_AddStringToObject(reg_scope, "presentationHint", "registers");
630
    cJSON_AddItemToArray(scopes, reg_scope);
631
632
    // Virtual registers scope — the X16's sixteen 16-bit pseudo-registers R0-R15
633
    // (a software convention shadowed in zero page $02-$21; used by the KERNAL/
634
    // VERA/GRAPH APIs and cc65). Presented like CPU registers so they can be
635
    // watched/edited as first-class 16-bit registers.
636
    cJSON *vreg_scope = cJSON_CreateObject();
637
    cJSON_AddStringToObject(vreg_scope, "name", "Virtual Regs (R0-R15)");
638
    cJSON_AddNumberToObject(vreg_scope, "variablesReference", VARREF_VREGS);
639
    cJSON_AddBoolToObject(vreg_scope, "expensive", false);
640
    cJSON_AddStringToObject(vreg_scope, "presentationHint", "registers");
641
    cJSON_AddItemToArray(scopes, vreg_scope);
642
643
    // Zero Page scope
644
    cJSON *zp_scope = cJSON_CreateObject();
645
    cJSON_AddStringToObject(zp_scope, "name", "Zero Page");
646
    cJSON_AddNumberToObject(zp_scope, "variablesReference", VARREF_ZEROPAGE);
647
    cJSON_AddBoolToObject(zp_scope, "expensive", false);
648
    cJSON_AddItemToArray(scopes, zp_scope);
649
650
    // Stack scope
651
    cJSON *stk_scope = cJSON_CreateObject();
652
    cJSON_AddStringToObject(stk_scope, "name", "Stack");
653
    cJSON_AddNumberToObject(stk_scope, "variablesReference", VARREF_STACK);
654
    cJSON_AddBoolToObject(stk_scope, "expensive", false);
655
    cJSON_AddItemToArray(scopes, stk_scope);
656
657
    cJSON_AddItemToObject(body, "scopes", scopes);
658
    send_dap_response(seq, "scopes", true, body);
659
    return 0;
660
}
661
662
static void add_variable(cJSON *vars, const char *name, const char *value, const char *type) {
663
    cJSON *var = cJSON_CreateObject();
664
    cJSON_AddStringToObject(var, "name", name);
665
    cJSON_AddStringToObject(var, "value", value);
666
    if (type) cJSON_AddStringToObject(var, "type", type);
667
    cJSON_AddNumberToObject(var, "variablesReference", 0);
668
    cJSON_AddItemToArray(vars, var);
669
}
670
671
// Parse a number from a DAP value string: "$hex", "0xhex", or decimal.
672
static uint32_t dap_parse_num(const char *s) {
673
    if (!s) return 0;
674
    while (*s == ' ') s++;
675
    if (s[0] == '$')
676
        return (uint32_t)strtoul(s + 1, NULL, 16);
677
    if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
678
        return (uint32_t)strtoul(s + 2, NULL, 16);
679
    return (uint32_t)strtoul(s, NULL, 0);
680
}
681
682
static bool dap_has_prefix_i(const char *s, const char *prefix) {
683
    while (*prefix) {
684
        if (tolower((unsigned char)*s) != tolower((unsigned char)*prefix)) return false;
685
        s++; prefix++;
686
    }
687
    return true;
688
}
689
690
// Parse a DAP memoryReference. A bare number is a CPU address (24-bit
691
// bank:addr); a "vram:" prefix selects VERA's 17-bit video address space, which
692
// is not in the CPU map and so cannot be reached through a CPU address at all.
693
// VRAM references default to hex, since every VRAM address in the docs and the
694
// debugger is written that way.
695
//
696
// Fails rather than falling back to zero on a reference it cannot read. The old
697
// behaviour ran the string through strtoul and used whatever came out, so a
698
// typo, a "$C000" and a "vram:1B000" all quietly returned the bytes at CPU
699
// address 0 with success:true -- the wrong memory, with nothing in the response
700
// to say so.
701
static bool dap_parse_mem_ref(const char *s, bool *is_vram, uint32_t *out) {
702
    if (!s) return false;
703
    while (*s == ' ') s++;
704
705
    *is_vram = false;
706
    if (dap_has_prefix_i(s, "vram:")) {
707
        *is_vram = true;
708
        s += 5;
709
        while (*s == ' ') s++;
710
    }
711
712
    int base = *is_vram ? 16 : 0;
713
    if (s[0] == '$') {
714
        s++;
715
        base = 16;
716
    } else if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
717
        s += 2;
718
        base = 16;
719
    }
720
721
    char *endp = NULL;
722
    unsigned long v = strtoul(s, &endp, base);
723
    if (!endp || endp == s) return false;
724
    while (*endp == ' ') endp++;
725
    if (*endp) return false;
726
727
    *out = (uint32_t)v;
728
    return true;
729
}
730
731
// Non-intrusive VERA register read for debug views. AUDIO_CTRL (0x1B) runs
732
// audio_render() and SPI_DATA (0x1E) starts a real SPI transfer *on read*
733
// (video_read ignores debugOn for them), so never poll those — return 0.
734
static uint8_t vera_read_dbg(uint8_t reg) {
735
    reg &= 0x1F;
736
    if (reg == 0x1B || reg == 0x1E) return 0;
737
    return video_read(reg, true);
738
}
739
740
// Direct-page base + offset, matching the CPU's direct_page_add() (support.h).
741
// The X16 r0-r15 pseudo-registers live at D+$02..D+$21; D is 0 under the KERNAL
742
// ABI but can be non-zero in 65C816 native (GS) code, so honor it.
743
static uint16_t dap_dp_add(uint16_t offset) {
744
    if (regs.e && (regs.dp & 0x00FF) == 0)
745
        return (uint16_t)((regs.dp & 0xFF00) | (uint8_t)((uint8_t)(regs.dp & 0xFF) + (offset & 0xFF)));
746
    return (uint16_t)(regs.dp + offset);
747
}
748
749
static int handle_dap_variables(int seq, cJSON *args) {
750
    int ref = 0;
751
    if (args) {
752
        cJSON *ref_item = cJSON_GetObjectItemCaseSensitive(args, "variablesReference");
753
        if (ref_item && cJSON_IsNumber(ref_item)) ref = (int)ref_item->valuedouble;
754
    }
755
756
    cJSON *body = cJSON_CreateObject();
757
    cJSON *vars = cJSON_CreateArray();
758
    char buf[32];
759
760
    if (ref == VARREF_REGISTERS) {
761
        // PC is 24-bit in gen2/GS (program bank K : 16-bit PC).
762
        if (is_gen2 || (regs.is65c816 && regs.k != 0))
763
            snprintf(buf, sizeof(buf), "$%02X:%04X", regs.k, regs.pc);
764
        else
765
            snprintf(buf, sizeof(buf), "$%04X (%d)", regs.pc, regs.pc);
766
        add_variable(vars, "PC", buf, "uint16");
767
768
        // A/X/Y: show 16-bit in native mode, 8-bit in emulation mode
769
        if (regs.is65c816 && !regs.e) {
770
            // Native mode: A width depends on M flag, X/Y on X flag
771
            if (regs.status & FLAG_MEMORY_WIDTH) {
772
                snprintf(buf, sizeof(buf), "$%02X (%d)", regs.a, regs.a);
773
                add_variable(vars, "A", buf, "uint8");
774
            } else {
775
                snprintf(buf, sizeof(buf), "$%04X (%d)", regs.c, regs.c);
776
                add_variable(vars, "A", buf, "uint16");
777
            }
778
            if (regs.status & FLAG_INDEX_WIDTH) {
779
                snprintf(buf, sizeof(buf), "$%02X (%d)", (uint8_t)regs.x, (uint8_t)regs.x);
780
                add_variable(vars, "X", buf, "uint8");
781
                snprintf(buf, sizeof(buf), "$%02X (%d)", (uint8_t)regs.y, (uint8_t)regs.y);
782
                add_variable(vars, "Y", buf, "uint8");
783
            } else {
784
                snprintf(buf, sizeof(buf), "$%04X (%d)", regs.x, regs.x);
785
                add_variable(vars, "X", buf, "uint16");
786
                snprintf(buf, sizeof(buf), "$%04X (%d)", regs.y, regs.y);
787
                add_variable(vars, "Y", buf, "uint16");
788
            }
789
            snprintf(buf, sizeof(buf), "$%04X (%d)", regs.sp, regs.sp);
790
            add_variable(vars, "SP", buf, "uint16");
791
            snprintf(buf, sizeof(buf), "$%04X (%d)", regs.dp, regs.dp);
792
            add_variable(vars, "D", buf, "uint16");
793
            snprintf(buf, sizeof(buf), "$%02X (%d)", regs.db, regs.db);
794
            add_variable(vars, "DBR", buf, "uint8");
795
            snprintf(buf, sizeof(buf), "$%02X (%d)", regs.k, regs.k);
796
            add_variable(vars, "PBR", buf, "uint8");
797
        } else {
798
            // Emulation mode: all 8-bit
799
            snprintf(buf, sizeof(buf), "$%02X (%d)", regs.a, regs.a);
800
            add_variable(vars, "A", buf, "uint8");
801
            snprintf(buf, sizeof(buf), "$%02X (%d)", (uint8_t)regs.x, (uint8_t)regs.x);
802
            add_variable(vars, "X", buf, "uint8");
803
            snprintf(buf, sizeof(buf), "$%02X (%d)", (uint8_t)regs.y, (uint8_t)regs.y);
804
            add_variable(vars, "Y", buf, "uint8");
805
            snprintf(buf, sizeof(buf), "$%02X (%d)", (uint8_t)regs.sp, (uint8_t)regs.sp);
806
            add_variable(vars, "SP", buf, "uint8");
807
        }
808
809
        // Status flags
810
        uint8_t p = regs.status;
811
        if (regs.is65c816 && !regs.e) {
812
            snprintf(buf, sizeof(buf), "$%02X [%c%c%c%c%c%c%c%c]", p,
813
                     (p & 0x80) ? 'N' : 'n', (p & 0x40) ? 'V' : 'v',
814
                     (p & 0x20) ? 'M' : 'm', (p & 0x10) ? 'X' : 'x',
815
                     (p & 0x08) ? 'D' : 'd', (p & 0x04) ? 'I' : 'i',
816
                     (p & 0x02) ? 'Z' : 'z', (p & 0x01) ? 'C' : 'c');
817
        } else {
818
            snprintf(buf, sizeof(buf), "$%02X [%c%c-%c%c%c%c%c]", p,
819
                     (p & 0x80) ? 'N' : 'n', (p & 0x40) ? 'V' : 'v',
820
                     (p & 0x10) ? 'B' : 'b',
821
                     (p & 0x08) ? 'D' : 'd', (p & 0x04) ? 'I' : 'i',
822
                     (p & 0x02) ? 'Z' : 'z', (p & 0x01) ? 'C' : 'c');
823
        }
824
        add_variable(vars, "P (Status)", buf, "uint8");
825
826
        snprintf(buf, sizeof(buf), "$%02X (%d)", memory_get_ram_bank(), memory_get_ram_bank());
827
        add_variable(vars, "RAM Bank", buf, "uint8");
828
829
        snprintf(buf, sizeof(buf), "$%02X (%d)", memory_get_rom_bank(), memory_get_rom_bank());
830
        add_variable(vars, "ROM Bank", buf, "uint8");
831
832
    } else if (ref == VARREF_ZEROPAGE) {
833
        // Show zero page in groups of 16 bytes
834
        for (int row = 0; row < 16; row++) {
835
            char name[16], value[80];
836
            int base = row * 16;
837
            snprintf(name, sizeof(name), "$%02X-$%02X", base, base + 15);
838
            char *p = value;
839
            for (int col = 0; col < 16; col++) {
840
                uint8_t b = debug_read6502((uint16_t)(base + col), 0, USE_CURRENT_X16_BANK);
841
                p += snprintf(p, sizeof(value) - (p - value), "%02X ", b);
842
            }
843
            add_variable(vars, name, value, NULL);
844
        }
845
846
    } else if (ref == VARREF_STACK) {
847
        // Show stack contents from SP+1 to $FF (what's currently on the stack)
848
        uint8_t sp = (uint8_t)regs.sp;
849
        int depth = 0xFF - sp;
850
        if (depth > 32) depth = 32; // cap display
851
        for (int i = 0; i < depth; i++) {
852
            char name[16], value[16];
853
            uint16_t addr = 0x0100 + sp + 1 + i;
854
            uint8_t b = debug_read6502(addr, 0, USE_CURRENT_X16_BANK);
855
            snprintf(name, sizeof(name), "$%04X", addr);
856
            snprintf(value, sizeof(value), "$%02X (%d)", b, b);
857
            add_variable(vars, name, value, "uint8");
858
        }
859
860
    } else if (ref == VARREF_VREGS) {
861
        // X16 virtual registers R0-R15: 16-bit little-endian at direct-page +
862
        // $02 + 2*n (D is 0 under the KERNAL ABI, non-zero possible in GS native).
863
        for (int n = 0; n < 16; n++) {
864
            char name[8], value[48];
865
            uint16_t a  = dap_dp_add((uint16_t)(0x02 + 2 * n));
866
            uint8_t  lo = debug_read6502(a, 0, USE_CURRENT_X16_BANK);
867
            uint8_t  hi = debug_read6502((uint16_t)(a + 1), 0, USE_CURRENT_X16_BANK);
868
            uint16_t v  = (uint16_t)(lo | (hi << 8));
869
            snprintf(name, sizeof(name), "R%d", n);
870
            snprintf(value, sizeof(value), "$%04X (%d)  @$%04X", v, v, a);
871
            add_variable(vars, name, value, "uint16");
872
        }
873
    }
874
875
    cJSON_AddItemToObject(body, "variables", vars);
876
    send_dap_response(seq, "variables", true, body);
877
    return 0;
878
}
879
880
// Case-insensitive prefix test (pfx must be lowercase).
881
static bool dap_ci_prefix(const char *s, const char *pfx) {
882
    for (; *pfx; s++, pfx++) {
883
        char a = (*s >= 'A' && *s <= 'Z') ? (char)(*s + 32) : *s;
884
        if (a != *pfx) return false;
885
    }
886
    return true;
887
}
888
889
// Parse a single "OPERAND OP VALUE" term: OPERAND is A/X/Y/SP/P, byte[$addr],
890
// word[$addr] or a bare $addr (byte); OP is == != < <= > >= (or =). Fills the
891
// outputs and returns true on success. Codes match debugger.c's bp_cond enums
892
// (A=0..WORD=6; EQ=0..GE=5).
893
static bool dap_parse_value_term(const char *s, int *out_operand, uint16_t *out_oaddr,
894
                                 int *out_op, uint32_t *out_value) {
895
    while (*s == ' ') s++;
896
    int      operand = -1;
897
    uint16_t oaddr   = 0;
898
    #define ISALNUM(c) (((c) >= '0' && (c) <= '9') || ((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z'))
899
    if (!strncmp(s, "byte[", 5)) { operand = 5; oaddr = (uint16_t)dap_parse_num(s + 5); const char *b = strchr(s, ']'); s = b ? b + 1 : s + strlen(s); }
900
    else if (!strncmp(s, "word[", 5)) { operand = 6; oaddr = (uint16_t)dap_parse_num(s + 5); const char *b = strchr(s, ']'); s = b ? b + 1 : s + strlen(s); }
901
    else if (*s == '$' || (*s == '0' && (s[1] == 'x' || s[1] == 'X'))) { operand = 5; oaddr = (uint16_t)dap_parse_num(s); while (*s && *s != ' ' && *s != '=' && *s != '!' && *s != '<' && *s != '>') s++; }
902
    else if ((s[0] == 'A' || s[0] == 'a') && !ISALNUM(s[1])) { operand = 0; s++; }
903
    else if ((s[0] == 'X' || s[0] == 'x') && !ISALNUM(s[1])) { operand = 1; s++; }
904
    else if ((s[0] == 'Y' || s[0] == 'y') && !ISALNUM(s[1])) { operand = 2; s++; }
905
    else if ((s[0] == 'S' || s[0] == 's') && (s[1] == 'P' || s[1] == 'p')) { operand = 3; s += 2; }
906
    else if ((s[0] == 'P' || s[0] == 'p') && !ISALNUM(s[1])) { operand = 4; s++; }
907
    #undef ISALNUM
908
    if (operand < 0) return false;
909
    while (*s == ' ') s++;
910
    int op = -1;
911
    if (!strncmp(s, "==", 2)) { op = 0; s += 2; }
912
    else if (!strncmp(s, "!=", 2)) { op = 1; s += 2; }
913
    else if (!strncmp(s, "<=", 2)) { op = 3; s += 2; }
914
    else if (!strncmp(s, ">=", 2)) { op = 5; s += 2; }
915
    else if (*s == '=') { op = 0; s++; }
916
    else if (*s == '<') { op = 2; s++; }
917
    else if (*s == '>') { op = 4; s++; }
918
    if (op < 0) return false;
919
    while (*s == ' ') s++;
920
    *out_operand = operand;
921
    *out_oaddr   = oaddr;
922
    *out_op      = op;
923
    *out_value   = dap_parse_num(s);
924
    return true;
925
}
926
927
// If a term is "bank[==]N" or "rombank[==]N" (case-insensitive), return the bank
928
// number; else -1. Used to pin a breakpoint to a specific RAM/ROM bank.
929
static int dap_parse_bank_term(const char *s) {
930
    while (*s == ' ') s++;
931
    if (dap_ci_prefix(s, "rombank")) s += 7;
932
    else if (dap_ci_prefix(s, "bank")) s += 4;
933
    else return -1;
934
    // The keyword must be followed by a separator / number / end — reject longer
935
    // identifiers like "banker" so they don't silently parse as bank 0.
936
    if ((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z')) return -1;
937
    while (*s == ' ' || *s == '=' || *s == '#') s++;
938
    if (!*s) return -1;
939
    return (int)(dap_parse_num(s) & 0xFF);
940
}
941
942
// Parse an optional leading comparison then a number ("== $05", "!=0", "$05",
943
// "5"). Default op is EQ. Returns false if no number is present.
944
static bool dap_parse_simple_cmp(const char *s, int *out_op, uint32_t *out_val) {
945
    while (*s == ' ') s++;
946
    int op = 0; // default EQ
947
    if (!strncmp(s, "==", 2)) { op = 0; s += 2; }
948
    else if (!strncmp(s, "!=", 2)) { op = 1; s += 2; }
949
    else if (!strncmp(s, "<=", 2)) { op = 3; s += 2; }
950
    else if (!strncmp(s, ">=", 2)) { op = 5; s += 2; }
951
    else if (*s == '=') { op = 0; s++; }
952
    else if (*s == '<') { op = 2; s++; }
953
    else if (*s == '>') { op = 4; s++; }
954
    while (*s == ' ') s++;
955
    if (!*s) return false;
956
    *out_op  = op;
957
    *out_val = dap_parse_num(s);
958
    return true;
959
}
960
961
// Parse a DAP source-breakpoint "condition"/"hitCondition" and apply it to the
962
// conditional-breakpoint core for the breakpoint at (addr, bank 0). The condition
963
// may combine a bank pin and a value test with "&&", e.g.
964
//   "bank == 0 && byte[$1234] != 0"  or  "A == $05"  or  "rombank==3".
965
// The value term (A/X/Y/SP/P, byte[$a], word[$a], $a  OP  value; only one is
966
// applied) drives the conditional-BP core; the bank term is returned so the
967
// caller can pin the breakpoint's x16Bank. hitCondition's number N means "stop
968
// on the Nth hit". Returns the bank pin (0..255) or -1 if none was given.
969
static int dap_apply_bp_condition(dbg_addr_t addr, const char *cond_str, const char *hit_str) {
970
    // The condition record is keyed on the same (pc, bank, x16Bank) triple as
971
    // the breakpoint. Applying it at bank 0 while the breakpoint is armed at
972
    // addr >> 16 wrote it under a key nothing looks up, and arrival then built
973
    // a fresh empty record -- so the breakpoint fired unconditionally on its
974
    // first hit.
975
    const uint16_t pc   = (uint16_t)(addr & 0xFFFF);
976
    const uint8_t  bank = (uint8_t)(addr >> 16);
977
    // Work out the bank pin FIRST, across the whole expression, before applying
978
    // anything keyed on it. The terms are applied in the order written, so
979
    // "A == 5 && bank == 3" used to store the value condition against
980
    // x16Bank = ANY while the breakpoint itself was armed for bank 3; arrival
981
    // then found no matching condition record and the breakpoint fired
982
    // unconditionally. Writing the same two terms the other way round worked,
983
    // which is not a distinction the user should be able to feel.
984
    int bank_pin = -1;
985
    char buf[192];
986
    buf[0] = '\0';
987
    if (cond_str && cond_str[0]) {
988
        strncpy(buf, cond_str, sizeof(buf) - 1);
989
        buf[sizeof(buf) - 1] = '\0';
990
        char *scan = buf;
991
        while (scan && *scan) {
992
            char *amp = strstr(scan, "&&");
993
            char saved = '\0';
994
            if (amp) { saved = *amp; *amp = '\0'; }
995
            int b = dap_parse_bank_term(scan);
996
            if (b >= 0) bank_pin = b;
997
            if (amp) { *amp = saved; scan = amp + 2; } else { scan = NULL; }
998
        }
999
    }
1000
    // Only the banked window has a bank to pin. Applied here rather than by the
1001
    // caller so that the conditions below are keyed the same way the breakpoint
1002
    // will be.
1003
    // Normalised exactly as the core will store it. Testing pc < $A000 alone
1004
    // disagreed on a gen2 machine with a non-zero program bank, where an
1005
    // address above $A000 is not banked either -- so the core stored ANY while
1006
    // we recorded the raw pin, and the two keys stopped matching.
1007
    bank_pin = debug_normalise_bank(bank_pin, pc, bank);
1008
1009
    debug_bp_clear_condition(pc, bank, bank_pin);
1010
    debug_bp_set_ignore(pc, bank, bank_pin, 0);
1011
    // Stated in full every time, deliberately. Removing a breakpoint leaves its
1012
    // condition record behind -- that is how a hit count survives an
1013
    // enable/disable toggle -- so a client re-sending a breakpoint without a
1014
    // condition would otherwise inherit the one it had before. The hit count
1015
    // itself is left alone: an editor re-sends a whole file's breakpoints on
1016
    // every keystroke, and a count that restarted each time could not drive a
1017
    // hit condition at all.
1018
1019
    if (buf[0]) {
1020
        bool value_set = false;
1021
        char *p = buf;
1022
        while (p && *p) {
1023
            char *amp = strstr(p, "&&");
1024
            if (amp) *amp = '\0';                 // terminate this term
1025
            if (dap_parse_bank_term(p) < 0 && !value_set) {
1026
                int operand, op; uint16_t oaddr; uint32_t val;
1027
                if (dap_parse_value_term(p, &operand, &oaddr, &op, &val)) {
1028
                    debug_bp_set_condition(pc, bank, bank_pin, operand, oaddr, op, val);
1029
                    value_set = true;
1030
                }
1031
            }
1032
            p = amp ? amp + 2 : NULL;
1033
        }
1034
    }
1035
1036
    if (hit_str && hit_str[0]) {
1037
        const char *h = hit_str;
1038
        while (*h && !(*h >= '0' && *h <= '9')) h++;
1039
        uint32_t n = (uint32_t)strtoul(h, NULL, 0);
1040
        if (n > 0) debug_bp_set_ignore(pc, bank, bank_pin, n - 1); // stop on the Nth hit
1041
    }
1042
    return bank_pin;
1043
}
1044
1045
static int handle_dap_set_breakpoints(int seq, cJSON *args) {
1046
    if (!args) {
1047
        send_dap_error_response(seq, "setBreakpoints", "missing arguments");
1048
        return 0;
1049
    }
1050
1051
    cJSON *source = cJSON_GetObjectItemCaseSensitive(args, "source");
1052
    cJSON *bps = cJSON_GetObjectItemCaseSensitive(args, "breakpoints");
1053
    if (!source || !bps) {
1054
        send_dap_error_response(seq, "setBreakpoints", "missing source or breakpoints");
1055
        return 0;
1056
    }
1057
1058
    cJSON *path_item = cJSON_GetObjectItemCaseSensitive(source, "path");
1059
    const char *src_path = (path_item && cJSON_IsString(path_item)) ? path_item->valuestring : "";
1060
1061
    // Drop this file's breakpoints, then restate what this owner still wants.
1062
    //
1063
    // Removing them one at a time looks simpler and is wrong. Source files are
1064
    // matched to addresses by basename, so src/main.s:10 and overlay/main.s:10
1065
    // resolve to the SAME core entry -- while this loop matches on the full
1066
    // path. One owner's two claims on an address are one bit, so removing the
1067
    // first would disarm a breakpoint the other file still wants, leaving the
1068
    // client showing a verified breakpoint that no longer stops anything.
1069
    //
1070
    // So: forget every source claim and re-assert the ones that survive. Other
1071
    // owners are untouched throughout, and conditions and hit counts live on
1072
    // the address rather than the claim, so nothing is lost by re-adding.
1073
    for (int i = num_dap_bps - 1; i >= 0; i--) {
1074
        if (strcmp(dap_bps[i].file, src_path) == 0) {
1075
            for (int j = i; j < num_dap_bps - 1; j++) {
1076
                dap_bps[j] = dap_bps[j + 1];
1077
            }
1078
            num_dap_bps--;
1079
        }
1080
    }
1081
1082
    debug_bp_clear_owner(DEBUG_OWNER_DAP_SOURCE);
1083
    for (int i = 0; i < num_dap_bps; i++) {
1084
        // A pending entry holds address 0 and was never armed; re-adding it
1085
        // would arm an unrelated breakpoint at $0000.
1086
        if (!dap_bps[i].verified)
1087
            continue;
1088
        struct breakpoint keep = { dap_bps[i].addr, dap_bps[i].bank, dap_bps[i].x16Bank, 0, false };
1089
        debug_bp_add_for(keep, DEBUG_OWNER_DAP_SOURCE);
1090
    }
1091
1092
    // Set new breakpoints
1093
    cJSON *result_body = cJSON_CreateObject();
1094
    cJSON *result_bps = cJSON_CreateArray();
1095
1096
    int bp_count = cJSON_GetArraySize(bps);
1097
    for (int i = 0; i < bp_count && num_dap_bps < MAX_DAP_BREAKPOINTS; i++) {
1098
        cJSON *bp_item = cJSON_GetArrayItem(bps, i);
1099
        cJSON *line_item = cJSON_GetObjectItemCaseSensitive(bp_item, "line");
1100
        int line = (line_item && cJSON_IsNumber(line_item)) ? (int)line_item->valuedouble : 0;
1101
1102
        cJSON *result_bp = cJSON_CreateObject();
1103
        int bp_id = next_dap_bp_id++;
1104
1105
        // Kept verbatim so the breakpoint can be re-armed exactly as asked for
1106
        // if an overlay reload forces it to be resolved again.
1107
        const char *cond_str = "";
1108
        const char *hit_str  = "";
1109
        {
1110
            cJSON *c = cJSON_GetObjectItemCaseSensitive(bp_item, "condition");
1111
            if (c && cJSON_IsString(c) && c->valuestring) cond_str = c->valuestring;
1112
            cJSON *h = cJSON_GetObjectItemCaseSensitive(bp_item, "hitCondition");
1113
            if (h && cJSON_IsString(h) && h->valuestring) hit_str = h->valuestring;
1114
        }
1115
1116
        // Try to resolve source:line to address
1117
        dbg_addr_t addr = 0;
1118
        bool resolved = dbg_info_source_to_addr(src_path, line, &addr);
1119
1120
        if (resolved) {
1121
            // The condition is parsed first so a "bank==N" term pins x16Bank at
1122
            // add time; the value term and hit count go to the core as well.
1123
            int bank_pin = dap_apply_bp_condition(addr, cond_str, hit_str);
1124
            struct breakpoint hw_bp;
1125
            hw_bp.pc = (int)(addr & 0xFFFF);
1126
            hw_bp.bank = (uint8_t)(addr >> 16);
1127
            hw_bp.x16Bank = bank_pin;
1128
            hw_bp.owners = 0;
1129
            hw_bp.enabled = false;
1130
            const debug_add_result_t added = debug_bp_add_for(hw_bp, DEBUG_OWNER_DAP_SOURCE);
1131
1132
            if (added == DEBUG_ADD_FULL) {
1133
                // Nothing was armed, so say so rather than reporting a verified
1134
                // breakpoint the machine will never stop on.
1135
                cJSON_AddNumberToObject(result_bp, "id", bp_id);
1136
                cJSON_AddBoolToObject(result_bp, "verified", false);
1137
                cJSON_AddStringToObject(result_bp, "message",
1138
                                        "Out of memory; breakpoint not armed");
1139
                cJSON_AddNumberToObject(result_bp, "line", line);
1140
                cJSON *bp_source = cJSON_CreateObject();
1141
                cJSON_AddStringToObject(bp_source, "path", src_path);
1142
                cJSON_AddItemToObject(result_bp, "source", bp_source);
1143
                cJSON_AddItemToArray(result_bps, result_bp);
1144
                continue;
1145
            }
1146
1147
            dap_bps[num_dap_bps].dap_id = bp_id;
1148
            dap_bps[num_dap_bps].addr = (uint16_t)(addr & 0xFFFF);
1149
            dap_bps[num_dap_bps].bank = (uint8_t)(addr >> 16);
1150
            dap_bps[num_dap_bps].x16Bank = bank_pin;
1151
            strncpy(dap_bps[num_dap_bps].file, src_path, sizeof(dap_bps[num_dap_bps].file) - 1);
1152
            dap_bps[num_dap_bps].line = line;
1153
            dap_bps[num_dap_bps].verified = true;
1154
            strncpy(dap_bps[num_dap_bps].cond, cond_str, sizeof(dap_bps[num_dap_bps].cond) - 1);
1155
            dap_bps[num_dap_bps].cond[sizeof(dap_bps[num_dap_bps].cond) - 1] = '\0';
1156
            strncpy(dap_bps[num_dap_bps].hit_cond, hit_str, sizeof(dap_bps[num_dap_bps].hit_cond) - 1);
1157
            dap_bps[num_dap_bps].hit_cond[sizeof(dap_bps[num_dap_bps].hit_cond) - 1] = '\0';
1158
            num_dap_bps++;
1159
1160
1161
            cJSON_AddNumberToObject(result_bp, "id", bp_id);
1162
            cJSON_AddBoolToObject(result_bp, "verified", true);
1163
            cJSON_AddNumberToObject(result_bp, "line", line);
1164
1165
            // Add source info
1166
            cJSON *bp_source = cJSON_CreateObject();
1167
            cJSON_AddStringToObject(bp_source, "path", src_path);
1168
            cJSON_AddItemToObject(result_bp, "source", bp_source);
1169
        } else {
1170
            // Nothing describes this line yet -- typically the guest has not
1171
            // loaded the program. Say so rather than claiming it is verified:
1172
            // retry_unverified_breakpoints() arms it when the .dbg arrives and
1173
            // sends a verified event then, so the client's view catches up on
1174
            // its own. Reporting it verified here made an unarmable breakpoint
1175
            // look identical to a working one.
1176
            cJSON_AddNumberToObject(result_bp, "id", bp_id);
1177
            cJSON_AddBoolToObject(result_bp, "verified", false);
1178
            cJSON_AddStringToObject(result_bp, "message",
1179
                                    "No debug info for this line yet; will arm when it loads");
1180
            cJSON_AddNumberToObject(result_bp, "line", line);
1181
            cJSON *bp_source = cJSON_CreateObject();
1182
            cJSON_AddStringToObject(bp_source, "path", src_path);
1183
            cJSON_AddItemToObject(result_bp, "source", bp_source);
1184
1185
            // Store as pending — will set hardware BP when matching .dbg loads
1186
            dap_bps[num_dap_bps].dap_id = bp_id;
1187
            dap_bps[num_dap_bps].addr = 0;
1188
            dap_bps[num_dap_bps].bank = 0;
1189
            dap_bps[num_dap_bps].x16Bank = DEBUG_BANK_ANY;
1190
            strncpy(dap_bps[num_dap_bps].file, src_path, sizeof(dap_bps[num_dap_bps].file) - 1);
1191
            dap_bps[num_dap_bps].line = line;
1192
            dap_bps[num_dap_bps].verified = false;
1193
            strncpy(dap_bps[num_dap_bps].cond, cond_str, sizeof(dap_bps[num_dap_bps].cond) - 1);
1194
            dap_bps[num_dap_bps].cond[sizeof(dap_bps[num_dap_bps].cond) - 1] = '\0';
1195
            strncpy(dap_bps[num_dap_bps].hit_cond, hit_str, sizeof(dap_bps[num_dap_bps].hit_cond) - 1);
1196
            dap_bps[num_dap_bps].hit_cond[sizeof(dap_bps[num_dap_bps].hit_cond) - 1] = '\0';
1197
            num_dap_bps++;
1198
        }
1199
        cJSON_AddItemToArray(result_bps, result_bp);
1200
    }
1201
1202
    cJSON_AddItemToObject(result_body, "breakpoints", result_bps);
1203
    send_dap_response(seq, "setBreakpoints", true, result_body);
1204
    return 0;
1205
}
1206
1207
static int handle_dap_continue(int seq, cJSON *args) {
1208
    (void)args;
1209
    // Cleared first so the resume hook stays quiet: this request sends its own
1210
    // continued event after the response, and the hook exists for resumes the
1211
    // client did not ask for.
1212
    dap_stop_announced = false;
1213
    DEBUGContinue();
1214
    cJSON *body = cJSON_CreateObject();
1215
    cJSON_AddBoolToObject(body, "allThreadsContinued", true);
1216
    send_dap_response(seq, "continue", true, body);
1217
    send_continued_event();
1218
    return 1;
1219
}
1220
1221
// Stepping is delegated to the debugger's own execution control rather than
1222
// reimplemented here. Its step-over knows the call opcodes of every CPU variant
1223
// the emulator supports -- the version this replaces recognised only JSR $20,
1224
// so on a 65816 it stepped INTO a JSL -- and its step-out finds the return
1225
// address by unwinding rather than assuming it is at SP+1, which is wrong the
1226
// moment the routine has pushed anything. It also parks its breakpoint in
1227
// `stepBreakPoint`, separate from the user's table, so stepping can no longer
1228
// collide with a breakpoint the user set at the same address. That target
1229
// carries its owner, so a session teardown can retract a step it asked for
1230
// without touching one the user started at the keyboard.
1231
static int handle_dap_next(int seq, cJSON *args) {
1232
    (void)args;
1233
    dap_stop_announced = false;   // this request implies execution continues
1234
    DEBUGStepOverAuto(DEBUG_OWNER_DAP_SOURCE);
1235
    send_dap_response(seq, "next", true, NULL);
1236
    return 1;
1237
}
1238
1239
static int handle_dap_step_in(int seq, cJSON *args) {
1240
    (void)args;
1241
    dap_stop_announced = false;   // this request implies execution continues
1242
    DEBUGStepIntoAuto();
1243
    send_dap_response(seq, "stepIn", true, NULL);
1244
    return 1;
1245
}
1246
1247
static int handle_dap_step_out(int seq, cJSON *args) {
1248
    (void)args;
1249
    dap_stop_announced = false;   // this request implies execution continues
1250
    DEBUGStepOut(DEBUG_OWNER_DAP_SOURCE);
1251
    send_dap_response(seq, "stepOut", true, NULL);
1252
    return 1;
1253
}
1254
1255
static int handle_dap_pause(int seq, cJSON *args) {
1256
    (void)args;
1257
    const bool wasRunning = (currentMode != DMODE_STOP);
1258
    // Emits the stopped event itself on the RUN->STOP transition.
1259
    DEBUGBreakToDebugger();
1260
    send_dap_response(seq, "pause", true, NULL);
1261
    // Already halted -- a previous client left it that way, or a breakpoint got
1262
    // there first. There is no transition for DEBUGBreakToDebugger() to report,
1263
    // and the protocol still owes this request a stopped event; without one the
1264
    // client waits forever for a pause that has in fact already happened.
1265
    if (!wasRunning && !dap_stop_announced) {
1266
        debug_server_notify_stopped("pause");
1267
    }
1268
    return 1;
1269
}
1270
1271
static int handle_dap_disconnect(int seq, cJSON *args) {
1272
    // Default to terminating since the emulator is always launched for debugging
1273
    bool terminate = true;
1274
    if (args) {
1275
        cJSON *term_item = cJSON_GetObjectItemCaseSensitive(args, "terminateDebuggee");
1276
        if (term_item && cJSON_IsBool(term_item)) {
1277
            terminate = cJSON_IsTrue(term_item);
1278
        }
1279
    }
1280
    send_dap_response(seq, "disconnect", true, NULL);
1281
1282
    if (terminate) {
1283
        SDL_Event quit_event;
1284
        quit_event.type = SDL_QUIT;
1285
        SDL_PushEvent(&quit_event);
1286
    } else {
1287
        // Keep emulator running — prevent disconnect_client from killing it
1288
        dap_session_active = false;
1289
        // Release what the session installed BEFORE resuming. Resuming first
1290
        // meant the machine ran on with this client's breakpoints still armed
1291
        // until the socket actually closed, and stopping at one of them then
1292
        // left it halted with the session already marked inactive -- so the
1293
        // socket cleanup would not resume it either.
1294
        dap_release_session_state();
1295
        // Resume only when there is no interactive debugger UI to drive the run
1296
        // state; otherwise leave it as the user left it (see disconnect_client).
1297
        if (currentMode == DMODE_STOP && !dap_local_ui_owns_run_state()) {
1298
            currentMode = DMODE_RUN;
1299
        }
1300
    }
1301
    return 0;
1302
}
1303
1304
static int handle_dap_restart(int seq, cJSON *args) {
1305
    (void)args;
1306
    machine_reset();
1307
    send_dap_response(seq, "restart", true, NULL);
1308
    return 1;
1309
}
1310
1311
// ─── Run to Cursor / Set Next Statement ────────────────────────────
1312
1313
static int handle_dap_goto_targets(int seq, cJSON *args) {
1314
    // Returns possible goto targets for a source location
1315
    if (!args) {
1316
        send_dap_error_response(seq, "gotoTargets", "missing arguments");
1317
        return 0;
1318
    }
1319
1320
    cJSON *source = cJSON_GetObjectItemCaseSensitive(args, "source");
1321
    cJSON *line_item = cJSON_GetObjectItemCaseSensitive(args, "line");
1322
    if (!source || !line_item) {
1323
        send_dap_error_response(seq, "gotoTargets", "missing source or line");
1324
        return 0;
1325
    }
1326
1327
    cJSON *path = cJSON_GetObjectItemCaseSensitive(source, "path");
1328
    int line = line_item->valueint;
1329
1330
    // Look up the address for this source line using debug info
1331
    dbg_addr_t addr = 0;
1332
    bool found = false;
1333
    if (path && cJSON_IsString(path)) {
1334
        found = dbg_info_source_to_addr(path->valuestring, line, &addr);
1335
    }
1336
1337
    cJSON *body = cJSON_CreateObject();
1338
    cJSON *targets = cJSON_CreateArray();
1339
1340
    if (found) {
1341
        cJSON *target = cJSON_CreateObject();
1342
        cJSON_AddNumberToObject(target, "id", (int)addr);
1343
        char label[32];
1344
        snprintf(label, sizeof(label), "$%04X", addr);
1345
        cJSON_AddStringToObject(target, "label", label);
1346
        cJSON_AddNumberToObject(target, "line", line);
1347
        cJSON_AddItemToArray(targets, target);
1348
    }
1349
1350
    cJSON_AddItemToObject(body, "targets", targets);
1351
    send_dap_response(seq, "gotoTargets", true, body);
1352
    return 0;
1353
}
1354
1355
static int handle_dap_goto(int seq, cJSON *args) {
1356
    // Set Next Statement: move PC to target address
1357
    if (!args) {
1358
        send_dap_error_response(seq, "goto", "missing arguments");
1359
        return 0;
1360
    }
1361
1362
    cJSON *target_id = cJSON_GetObjectItemCaseSensitive(args, "targetId");
1363
    if (!target_id || !cJSON_IsNumber(target_id)) {
1364
        send_dap_error_response(seq, "goto", "missing targetId");
1365
        return 0;
1366
    }
1367
1368
    uint16_t addr = (uint16_t)target_id->valueint;
1369
    regs.pc = addr;
1370
1371
    send_dap_response(seq, "goto", true, NULL);
1372
1373
    // Send stopped event at new location
1374
    cJSON *body = cJSON_CreateObject();
1375
    cJSON_AddStringToObject(body, "reason", "goto");
1376
    cJSON_AddNumberToObject(body, "threadId", 1);
1377
    cJSON_AddBoolToObject(body, "allThreadsStopped", true);
1378
    send_dap_event("stopped", body);
1379
1380
    return 0;
1381
}
1382
1383
static int handle_dap_evaluate(int seq, cJSON *args) {
1384
    if (!args) {
1385
        send_dap_error_response(seq, "evaluate", "missing arguments");
1386
        return 0;
1387
    }
1388
    cJSON *expr_item = cJSON_GetObjectItemCaseSensitive(args, "expression");
1389
    if (!expr_item || !cJSON_IsString(expr_item)) {
1390
        send_dap_error_response(seq, "evaluate", "missing expression");
1391
        return 0;
1392
    }
1393
    const char *expr = expr_item->valuestring;
1394
1395
    // Parse expression: $XXXX = memory peek, register names, bp_* commands
1396
    char result[1024] = "???";
1397
1398
    // Breakpoint management via evaluate
1399
    if (strncmp(expr, "bp_add ", 7) == 0) {
1400
        uint16_t addr = (uint16_t)strtoul(expr + 7, NULL, 16);
1401
        struct breakpoint bp;
1402
        bp.pc = addr;
1403
        bp.bank = 0;
1404
        bp.x16Bank = -1;
1405
        bp.owners = 0;
1406
        bp.enabled = false;
1407
        // Owned by the console, so it goes away with the session even when the
1408
        // table below is full and nothing here remembers it. An untracked one
1409
        // used to outlive the client that asked for it and could halt a
1410
        // headless machine with nobody able to resume it.
1411
        const debug_add_result_t added = debug_bp_add_for(bp, DEBUG_OWNER_DAP_CONSOLE);
1412
        if (added == DEBUG_ADD_FULL) {
1413
            snprintf(result, sizeof(result), "out of memory; no breakpoint at $%04X", addr);
1414
        } else {
1415
            // Also recorded here, when there is room, so the breakpoint has an
1416
            // id to report hits against.
1417
            if (num_dap_bps < MAX_DAP_BREAKPOINTS) {
1418
                dap_bps[num_dap_bps].dap_id  = next_dap_bp_id++;
1419
                dap_bps[num_dap_bps].addr    = addr;
1420
                dap_bps[num_dap_bps].bank    = 0;
1421
                dap_bps[num_dap_bps].x16Bank = -1;
1422
                dap_bps[num_dap_bps].line    = 0;
1423
                dap_bps[num_dap_bps].file[0] = '\0';
1424
                dap_bps[num_dap_bps].cond[0] = '\0';
1425
                dap_bps[num_dap_bps].hit_cond[0] = '\0';
1426
                dap_bps[num_dap_bps].verified = true;
1427
                num_dap_bps++;
1428
            }
1429
            snprintf(result, sizeof(result), "breakpoint added at $%04X", addr);
1430
        }
1431
    } else if (strncmp(expr, "bp_remove ", 10) == 0) {
1432
        uint16_t addr = (uint16_t)strtoul(expr + 10, NULL, 16);
1433
        for (int i = num_dap_bps - 1; i >= 0; i--) {
1434
            if (dap_bps[i].addr == addr && dap_bps[i].bank == 0) {
1435
                for (int j = i; j < num_dap_bps - 1; j++) dap_bps[j] = dap_bps[j + 1];
1436
                num_dap_bps--;
1437
            }
1438
        }
1439
        // Typed by a person, so it takes the breakpoint away whoever asked for
1440
        // it -- the same rule as F9 in the debug window. Being told "removed"
1441
        // and finding it still armed because something else also wanted it is
1442
        // not an answer anyone can act on.
1443
        bool gone = debug_bp_delete(addr, 0, DEBUG_BANK_ANY);
1444
        if (gone)
1445
            snprintf(result, sizeof(result), "breakpoint removed at $%04X", addr);
1446
        else
1447
            snprintf(result, sizeof(result), "no breakpoint at $%04X", addr);
1448
    } else if (strcmp(expr, "bp_list") == 0) {
1449
        int len = 0;
1450
        const int n = debug_bp_count();
1451
        len += snprintf(result + len, sizeof(result) - len, "%d breakpoints:", n);
1452
        for (int i = 0; i < n && len < (int)sizeof(result) - 12; i++) {
1453
            const struct breakpoint *b = debug_bp_at(i);
1454
            len += snprintf(result + len, sizeof(result) - len, " $%04X%s",
1455
                            (unsigned)b->pc, b->enabled ? "" : "(off)");
1456
        }
1457
    } else if (strcmp(expr, "bp_clear") == 0) {
1458
        // Typed by a person, and "clear all breakpoints" means all of them --
1459
        // including any set with -bp or F9. The core's own clear takes the
1460
        // condition and hit-count records with them; zeroing numBreakpoints
1461
        // behind its back left those behind, and a breakpoint later re-created
1462
        // at the same address inherited a stale hit count -- an "ignore 5" that
1463
        // had already been reached fired on the second arrival, not the sixth.
1464
        debug_bp_clear_all();
1465
        num_dap_bps   = 0;
1466
        num_func_bps  = 0;
1467
        num_instr_bps = 0;
1468
        snprintf(result, sizeof(result), "all breakpoints cleared");
1469
    } else if (strcmp(expr, "reset") == 0) {
1470
        machine_reset();
1471
        snprintf(result, sizeof(result), "CPU reset");
1472
    } else if (expr[0] == '$' || expr[0] == '0') {
1473
        // Memory address (24-bit: high byte is the 65C816 bank byte).
1474
        uint32_t addr;
1475
        if (expr[0] == '$') {
1476
            addr = (uint32_t)strtoul(expr + 1, NULL, 16);
1477
        } else {
1478
            addr = (uint32_t)strtoul(expr, NULL, 16);
1479
        }
1480
        addr &= 0xFFFFFF;
1481
        uint8_t val = debug_read6502((uint16_t)(addr & 0xFFFF), (uint8_t)(addr >> 16), USE_CURRENT_X16_BANK);
1482
        snprintf(result, sizeof(result), "$%02X (%d)", val, val);
1483
    } else if (!strcmp(expr, "A") || !strcmp(expr, "a")) {
1484
        if (regs.is65c816 && !regs.e && !(regs.status & FLAG_MEMORY_WIDTH))
1485
            snprintf(result, sizeof(result), "$%04X (%d)", regs.c, regs.c);
1486
        else
1487
            snprintf(result, sizeof(result), "$%02X (%d)", regs.a, regs.a);
1488
    } else if (!strcmp(expr, "X") || !strcmp(expr, "x")) {
1489
        if (regs.is65c816 && !regs.e && !(regs.status & FLAG_INDEX_WIDTH))
1490
            snprintf(result, sizeof(result), "$%04X (%d)", regs.x, regs.x);
1491
        else
1492
            snprintf(result, sizeof(result), "$%02X (%d)", (uint8_t)regs.x, (uint8_t)regs.x);
1493
    } else if (!strcmp(expr, "Y") || !strcmp(expr, "y")) {
1494
        if (regs.is65c816 && !regs.e && !(regs.status & FLAG_INDEX_WIDTH))
1495
            snprintf(result, sizeof(result), "$%04X (%d)", regs.y, regs.y);
1496
        else
1497
            snprintf(result, sizeof(result), "$%02X (%d)", (uint8_t)regs.y, (uint8_t)regs.y);
1498
    } else if (!strcmp(expr, "SP") || !strcmp(expr, "sp")) {
1499
        snprintf(result, sizeof(result), "$%02X (%d)", (uint8_t)regs.sp, (uint8_t)regs.sp);
1500
    } else if (!strcmp(expr, "PC") || !strcmp(expr, "pc")) {
1501
        if (is_gen2 || (regs.is65c816 && regs.k != 0))
1502
            snprintf(result, sizeof(result), "$%02X:%04X", regs.k, regs.pc);
1503
        else
1504
            snprintf(result, sizeof(result), "$%04X (%d)", regs.pc, regs.pc);
1505
    } else if (!strcmp(expr, "P") || !strcmp(expr, "p")) {
1506
        snprintf(result, sizeof(result), "$%02X", regs.status);
1507
    } else if (!strcmp(expr, "regs_all")) {
1508
        snprintf(result, sizeof(result), "PC=$%04X A=$%02X X=$%02X Y=$%02X SP=$%02X P=$%02X",
1509
                 regs.pc, regs.a, (uint8_t)regs.x, (uint8_t)regs.y, (uint8_t)regs.sp, regs.status);
1510
    } else if (strncmp(expr, "watch_add ", 10) == 0) {
1511
        // watch_add ADDR [LEN]  — add memory write watchpoint
1512
        char *endp;
1513
        uint16_t addr = (uint16_t)strtoul(expr + 10, &endp, 16);
1514
        uint16_t len = 1;
1515
        if (*endp == ' ') len = (uint16_t)strtoul(endp + 1, NULL, 16);
1516
        if (len == 0) len = 1;
1517
        // Owned by the console, so it goes away with the session. An untracked
1518
        // one outlived the client that made it and could halt a headless
1519
        // machine with nobody able to resume it.
1520
        const debug_add_result_t added =
1521
            debug_wp_add_for(addr, len, DEBUG_BANK_ANY, DEBUG_OWNER_DAP_CONSOLE);
1522
        if (added == DEBUG_ADD_FULL)
1523
            snprintf(result, sizeof(result), "watchpoint table full (max %d)", MAX_WATCHPOINTS);
1524
        else
1525
            snprintf(result, sizeof(result), "watchpoint added at $%04X len %d", addr, len);
1526
    } else if (strncmp(expr, "watch_remove ", 13) == 0) {
1527
        uint16_t addr = (uint16_t)strtoul(expr + 13, NULL, 16);
1528
        // Typed by a person, so it takes the watch away whoever asked for it.
1529
        if (debug_wp_delete(addr, DEBUG_BANK_ANY))
1530
            snprintf(result, sizeof(result), "watchpoint removed at $%04X", addr);
1531
        else
1532
            snprintf(result, sizeof(result), "no watchpoint at $%04X", addr);
1533
    } else if (!strcmp(expr, "watch_list")) {
1534
        int len = 0;
1535
        int n   = debug_wp_count();
1536
        len += snprintf(result + len, sizeof(result) - len, "%d watchpoints:", n);
1537
        for (int i = 0; i < n && len < (int)sizeof(result) - 20; i++) {
1538
            const struct watchpoint *wp = debug_wp_at(i);
1539
            if (!wp) break;
1540
            len += snprintf(result + len, sizeof(result) - len, " $%04X(%d)",
1541
                           wp->addr, wp->len);
1542
        }
1543
    } else if (!strcmp(expr, "watch_clear")) {
1544
        debug_wp_clear_all();   // the console command clears everyone's, ours included
1545
        snprintf(result, sizeof(result), "all watchpoints cleared");
1546
    } else if (strncmp(expr, "vram ", 5) == 0) {
1547
        // vram ADDR [COUNT] — read VERA VRAM bytes (17-bit address)
1548
        char *endp;
1549
        uint32_t addr = (uint32_t)strtoul(expr + 5, &endp, 16);
1550
        int count = 1;
1551
        if (*endp == ' ') count = (int)strtoul(endp + 1, NULL, 10);
1552
        if (count < 1) count = 1;
1553
        if (count > 128) count = 128;
1554
        int len = 0;
1555
        len += snprintf(result + len, sizeof(result) - len, "$%05X:", addr);
1556
        for (int i = 0; i < count && len < (int)sizeof(result) - 5; i++) {
1557
            uint8_t val = video_space_read((addr + i) & 0x1FFFF);
1558
            len += snprintf(result + len, sizeof(result) - len, " %02X", val);
1559
        }
1560
    } else if (strncmp(expr, "vera_reg", 8) == 0) {
1561
        // vera_reg — dump all VERA registers ($9F20-$9F3F)
1562
        int len = 0;
1563
        for (int i = 0; i < 32 && len < (int)sizeof(result) - 10; i++) {
1564
            uint8_t val = vera_read_dbg((uint8_t)i);
1565
            len += snprintf(result + len, sizeof(result) - len, "%02X ", val);
1566
        }
1567
    }
1568
1569
    cJSON *body = cJSON_CreateObject();
1570
    cJSON_AddStringToObject(body, "result", result);
1571
    cJSON_AddNumberToObject(body, "variablesReference", 0);
1572
    send_dap_response(seq, "evaluate", true, body);
1573
    return 0;
1574
}
1575
1576
static int handle_dap_read_memory(int seq, cJSON *args) {
1577
    if (!args) {
1578
        send_dap_error_response(seq, "readMemory", "missing arguments");
1579
        return 0;
1580
    }
1581
    cJSON *ref_item = cJSON_GetObjectItemCaseSensitive(args, "memoryReference");
1582
    cJSON *count_item = cJSON_GetObjectItemCaseSensitive(args, "count");
1583
    cJSON *offset_item = cJSON_GetObjectItemCaseSensitive(args, "offset");
1584
1585
    if (!ref_item || !cJSON_IsString(ref_item) || !count_item || !cJSON_IsNumber(count_item)) {
1586
        send_dap_error_response(seq, "readMemory", "missing memoryReference or count");
1587
        return 0;
1588
    }
1589
1590
    uint32_t addr = 0;
1591
    bool is_vram = false;
1592
    if (!dap_parse_mem_ref(ref_item->valuestring, &is_vram, &addr)) {
1593
        send_dap_error_response(seq, "readMemory", "unparseable memoryReference (expected a CPU address like 0xC000, or vram:1B000)");
1594
        return 0;
1595
    }
1596
    int offset = (offset_item && cJSON_IsNumber(offset_item)) ? (int)offset_item->valuedouble : 0;
1597
    int count = (int)count_item->valuedouble;
1598
    const uint32_t addr_mask = is_vram ? 0x1FFFF : 0xFFFFFF;
1599
    addr = (uint32_t)((int)addr + offset) & addr_mask;
1600
    if (count > 4096) count = 4096;
1601
    if (count < 0) count = 0;
1602
1603
    // Build base64 data (DAP readMemory uses base64)
1604
    // For simplicity, send as hex-encoded data with unreadableBytes=0
1605
    static const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1606
    unsigned char *raw = (unsigned char *)malloc(count);
1607
    char *b64 = (char *)malloc((count * 4 / 3) + 8);
1608
    if (!raw || !b64) {
1609
        free(raw); free(b64);
1610
        send_dap_error_response(seq, "readMemory", "memory allocation failed");
1611
        return 0;
1612
    }
1613
1614
    // 24-bit linear read: the high byte of the address is the 65C816 bank byte.
1615
    // For bank 0 this is the classic X16 map (I/O + windowed banks following the
1616
    // live mapping); banks 1-255 are flat RAM in gen2/GS. Spans bank boundaries.
1617
    // VRAM is a separate 17-bit space behind VERA, so it wraps within itself.
1618
    for (int i = 0; i < count; i++) {
1619
        uint32_t cur = (addr + (uint32_t)i) & addr_mask;
1620
        raw[i] = is_vram ? video_space_read(cur)
1621
                         : debug_read6502((uint16_t)(cur & 0xFFFF), (uint8_t)(cur >> 16), USE_CURRENT_X16_BANK);
1622
    }
1623
1624
    // Base64 encode
1625
    int b64_len = 0;
1626
    for (int i = 0; i < count; i += 3) {
1627
        int remaining = count - i;
1628
        unsigned int triplet = raw[i] << 16;
1629
        if (remaining > 1) triplet |= raw[i + 1] << 8;
1630
        if (remaining > 2) triplet |= raw[i + 2];
1631
1632
        b64[b64_len++] = b64chars[(triplet >> 18) & 0x3F];
1633
        b64[b64_len++] = b64chars[(triplet >> 12) & 0x3F];
1634
        b64[b64_len++] = (remaining > 1) ? b64chars[(triplet >> 6) & 0x3F] : '=';
1635
        b64[b64_len++] = (remaining > 2) ? b64chars[triplet & 0x3F] : '=';
1636
    }
1637
    b64[b64_len] = '\0';
1638
1639
    cJSON *body = cJSON_CreateObject();
1640
    char addr_str[16];
1641
    // Echoed in the space it was read from, so a client can page through VRAM by
1642
    // feeding this straight back with an offset.
1643
    if (is_vram)
1644
        snprintf(addr_str, sizeof(addr_str), "vram:0x%05X", addr & 0x1FFFF);
1645
    else if (is_gen2)
1646
        snprintf(addr_str, sizeof(addr_str), "0x%06X", addr & 0xFFFFFF);
1647
    else
1648
        snprintf(addr_str, sizeof(addr_str), "0x%04X", addr & 0xFFFF);
1649
    cJSON_AddStringToObject(body, "address", addr_str);
1650
    cJSON_AddNumberToObject(body, "unreadableBytes", 0);
1651
    cJSON_AddStringToObject(body, "data", b64);
1652
1653
    free(raw);
1654
    free(b64);
1655
    send_dap_response(seq, "readMemory", true, body);
1656
    return 0;
1657
}
1658
1659
// Decode a single base64 character to its 6-bit value, or -1 if not a data char.
1660
static int dap_b64_decode_char(char ch) {
1661
    if (ch >= 'A' && ch <= 'Z') return ch - 'A';
1662
    if (ch >= 'a' && ch <= 'z') return ch - 'a' + 26;
1663
    if (ch >= '0' && ch <= '9') return ch - '0' + 52;
1664
    if (ch == '+') return 62;
1665
    if (ch == '/') return 63;
1666
    return -1; // '=', whitespace, or anything else
1667
}
1668
1669
static int handle_dap_write_memory(int seq, cJSON *args) {
1670
    if (!args) {
1671
        send_dap_error_response(seq, "writeMemory", "missing arguments");
1672
        return 0;
1673
    }
1674
    cJSON *ref_item = cJSON_GetObjectItemCaseSensitive(args, "memoryReference");
1675
    cJSON *data_item = cJSON_GetObjectItemCaseSensitive(args, "data");
1676
    cJSON *offset_item = cJSON_GetObjectItemCaseSensitive(args, "offset");
1677
1678
    if (!ref_item || !cJSON_IsString(ref_item) || !data_item || !cJSON_IsString(data_item)) {
1679
        send_dap_error_response(seq, "writeMemory", "missing memoryReference or data");
1680
        return 0;
1681
    }
1682
1683
    uint32_t addr = 0;
1684
    bool is_vram = false;
1685
    if (!dap_parse_mem_ref(ref_item->valuestring, &is_vram, &addr)) {
1686
        send_dap_error_response(seq, "writeMemory", "unparseable memoryReference (expected a CPU address like 0xC000, or vram:1B000)");
1687
        return 0;
1688
    }
1689
    int offset = (offset_item && cJSON_IsNumber(offset_item)) ? (int)offset_item->valuedouble : 0;
1690
    const uint32_t addr_mask = is_vram ? 0x1FFFF : 0xFFFFFF;
1691
    addr = (uint32_t)((int)addr + offset) & addr_mask;
1692
1693
    const char *b64 = data_item->valuestring;
1694
    int b64_len = (int)strlen(b64);
1695
    unsigned char *raw = (unsigned char *)malloc((b64_len / 4) * 3 + 4);
1696
    if (!raw) {
1697
        send_dap_error_response(seq, "writeMemory", "memory allocation failed");
1698
        return 0;
1699
    }
1700
1701
    // Base64 decode
1702
    int out = 0;
1703
    int acc = 0, bits = -8;
1704
    for (int i = 0; i < b64_len; i++) {
1705
        int d = dap_b64_decode_char(b64[i]);
1706
        if (d < 0) continue; // skip padding/whitespace
1707
        acc = (acc << 6) | d;
1708
        bits += 6;
1709
        if (bits >= 0) {
1710
            raw[out++] = (unsigned char)((acc >> bits) & 0xFF);
1711
            bits -= 8;
1712
        }
1713
    }
1714
1715
    // Write into emulated RAM using 24-bit linear addressing: the high byte of
1716
    // the address is the 65C816 bank byte (bank 0 = classic X16 map incl. the
1717
    // windowed banks + I/O side effects; banks 1-255 = flat RAM in gen2/GS).
1718
    // A vram: reference goes straight into VERA's 17-bit space instead, which is
1719
    // the only way to write it -- the CPU reaches VRAM only through the data
1720
    // port, one auto-incrementing byte at a time.
1721
    for (int i = 0; i < out; i++) {
1722
        uint32_t cur = (addr + (uint32_t)i) & addr_mask;
1723
        if (is_vram) {
1724
            video_space_write(cur, raw[i]);
1725
        } else {
1726
            debug_write6502((uint16_t)(cur & 0xFFFF), (uint8_t)(cur >> 16), raw[i]);
1727
        }
1728
    }
1729
1730
    free(raw);
1731
1732
    cJSON *body = cJSON_CreateObject();
1733
    cJSON_AddNumberToObject(body, "bytesWritten", out);
1734
    cJSON_AddNumberToObject(body, "offset", 0);
1735
    send_dap_response(seq, "writeMemory", true, body);
1736
    return 0;
1737
}
1738
1739
// ─── Custom: x16/sendKey — inject a key press for automated testing ──
1740
//
1741
// Arguments:
1742
//   key:       SDL key name (e.g. "Return", "A", "Space", "Left", "F1") or a
1743
//              numeric SDL scancode.
1744
//   action:    "press" (default) sends down+up, "down" holds, "up" releases.
1745
//   modifiers: optional array of key names/scancodes held around the key
1746
//              (e.g. ["Left Shift"]). Applied as down before / up after.
1747
static SDL_Scancode dap_resolve_scancode(cJSON *item) {
1748
    if (!item) return SDL_SCANCODE_UNKNOWN;
1749
    if (cJSON_IsNumber(item)) {
1750
        int sc = (int)item->valuedouble;
1751
        if (sc < 0 || sc >= SDL_NUM_SCANCODES) return SDL_SCANCODE_UNKNOWN;
1752
        return (SDL_Scancode)sc;
1753
    }
1754
    if (cJSON_IsString(item) && item->valuestring && item->valuestring[0]) {
1755
        return SDL_GetScancodeFromName(item->valuestring);
1756
    }
1757
    return SDL_SCANCODE_UNKNOWN;
1758
}
1759
1760
static void dap_key_event(SDL_Scancode sc, bool down) {
1761
    handle_keyboard(down, SDL_GetKeyFromScancode(sc), sc);
1762
}
1763
1764
static int handle_x16_send_key(int seq, cJSON *args) {
1765
    if (!args) {
1766
        send_dap_error_response(seq, "x16/sendKey", "missing arguments");
1767
        return 0;
1768
    }
1769
    SDL_Scancode sc = dap_resolve_scancode(cJSON_GetObjectItemCaseSensitive(args, "key"));
1770
    if (sc == SDL_SCANCODE_UNKNOWN) {
1771
        send_dap_error_response(seq, "x16/sendKey", "unknown or missing 'key'");
1772
        return 0;
1773
    }
1774
1775
    const char *action = "press";
1776
    cJSON *action_item = cJSON_GetObjectItemCaseSensitive(args, "action");
1777
    if (action_item && cJSON_IsString(action_item)) action = action_item->valuestring;
1778
1779
    // Resolve modifiers (held down around the key for press/down).
1780
    SDL_Scancode mods[8];
1781
    int num_mods = 0;
1782
    cJSON *mod_arr = cJSON_GetObjectItemCaseSensitive(args, "modifiers");
1783
    if (mod_arr && cJSON_IsArray(mod_arr)) {
1784
        cJSON *m;
1785
        cJSON_ArrayForEach(m, mod_arr) {
1786
            if (num_mods >= 8) break;
1787
            SDL_Scancode msc = dap_resolve_scancode(m);
1788
            if (msc != SDL_SCANCODE_UNKNOWN) mods[num_mods++] = msc;
1789
        }
1790
    }
1791
1792
    bool do_down = (!strcmp(action, "press") || !strcmp(action, "down"));
1793
    bool do_up   = (!strcmp(action, "press") || !strcmp(action, "up"));
1794
1795
    if (do_down) {
1796
        for (int i = 0; i < num_mods; i++) dap_key_event(mods[i], true);
1797
        dap_key_event(sc, true);
1798
    }
1799
    if (do_up) {
1800
        dap_key_event(sc, false);
1801
        for (int i = num_mods - 1; i >= 0; i--) dap_key_event(mods[i], false);
1802
    }
1803
1804
    cJSON *body = cJSON_CreateObject();
1805
    cJSON_AddNumberToObject(body, "scancode", sc);
1806
    cJSON_AddStringToObject(body, "action", action);
1807
    send_dap_response(seq, "x16/sendKey", true, body);
1808
    return 0;
1809
}
1810
1811
// ─── Custom: x16/type — type a UTF-8/PETSCII string into the keyboard ──
1812
//
1813
// Arguments: text — the string to type. Routed through the same path as a
1814
// clipboard paste, so it lands in the KERNAL keyboard buffer (ideal for BASIC
1815
// lines, filenames, etc.). Supports the "\Xnn" hex escape the paste path does.
1816
static int handle_x16_type(int seq, cJSON *args) {
1817
    cJSON *text_item = args ? cJSON_GetObjectItemCaseSensitive(args, "text") : NULL;
1818
    if (!text_item || !cJSON_IsString(text_item)) {
1819
        send_dap_error_response(seq, "x16/type", "missing 'text'");
1820
        return 0;
1821
    }
1822
    // machine_paste takes ownership and frees with SDL_free when handle_free=true.
1823
    char *copy = SDL_strdup(text_item->valuestring);
1824
    if (!copy) {
1825
        send_dap_error_response(seq, "x16/type", "out of memory");
1826
        return 0;
1827
    }
1828
    machine_paste(copy, true);
1829
1830
    cJSON *body = cJSON_CreateObject();
1831
    cJSON_AddNumberToObject(body, "length", (double)strlen(text_item->valuestring));
1832
    send_dap_response(seq, "x16/type", true, body);
1833
    return 0;
1834
}
1835
1836
// ─── Custom: x16/joystick — drive a SNES controller port from a script ──
1837
//
1838
// Arguments:
1839
//   index:   slot 0-3 (required).
1840
//   buttons: array of names held down. The array is the complete held set, so
1841
//            anything absent is released -- that is what lets a script hold a
1842
//            direction across frames and then let go in a single request.
1843
//   mask:    raw 16-bit active-low mask (0 = pressed), an alternative to
1844
//            `buttons` for replaying a captured state verbatim.
1845
//   enabled: false releases the slot back to a physical controller.
1846
//
1847
// A request carrying neither `buttons` nor `mask` reports the slot's state
1848
// without changing it, so a test can assert what it just pressed.
1849
1850
static const struct {
1851
    const char *name;
1852
    uint16_t    bit;
1853
    bool        alias;
1854
} dap_joy_buttons[] = {
1855
    {"up",     JOY_BIT_DPAD_UP,        false},
1856
    {"down",   JOY_BIT_DPAD_DOWN,      false},
1857
    {"left",   JOY_BIT_DPAD_LEFT,      false},
1858
    {"right",  JOY_BIT_DPAD_RIGHT,     false},
1859
    {"a",      JOY_BIT_A,              false},
1860
    {"b",      JOY_BIT_B,              false},
1861
    {"x",      JOY_BIT_X,              false},
1862
    {"y",      JOY_BIT_Y,              false},
1863
    {"start",  JOY_BIT_START,          false},
1864
    {"select", JOY_BIT_SELECT,         false},
1865
    {"l",      JOY_BIT_LEFT_SHOULDER,  false},
1866
    {"r",      JOY_BIT_RIGHT_SHOULDER, false},
1867
    // Aliases: accepted on input, never used to name a button on output.
1868
    {"back",   JOY_BIT_SELECT,         true},
1869
    {"lshoulder", JOY_BIT_LEFT_SHOULDER,  true},
1870
    {"rshoulder", JOY_BIT_RIGHT_SHOULDER, true},
1871
};
1872
1873
#define DAP_JOY_BUTTON_COUNT ((int)(sizeof(dap_joy_buttons) / sizeof(dap_joy_buttons[0])))
1874
1875
static bool dap_str_ieq(const char *a, const char *b) {
1876
    if (!a || !b) return false;
1877
    while (*a && *b) {
1878
        if (tolower((unsigned char)*a) != tolower((unsigned char)*b)) return false;
1879
        a++; b++;
1880
    }
1881
    return *a == *b;
1882
}
1883
1884
static uint16_t dap_joy_bit_from_name(const char *name) {
1885
    for (int i = 0; i < DAP_JOY_BUTTON_COUNT; i++) {
1886
        if (dap_str_ieq(name, dap_joy_buttons[i].name)) return dap_joy_buttons[i].bit;
1887
    }
1888
    return 0;
1889
}
1890
1891
// The held set, named. Active-low, so a cleared bit is a pressed button.
1892
static cJSON *dap_joy_buttons_array(uint16_t mask) {
1893
    cJSON *arr = cJSON_CreateArray();
1894
    for (int i = 0; i < DAP_JOY_BUTTON_COUNT; i++) {
1895
        if (dap_joy_buttons[i].alias) continue;
1896
        if (!(mask & dap_joy_buttons[i].bit)) {
1897
            cJSON_AddItemToArray(arr, cJSON_CreateString(dap_joy_buttons[i].name));
1898
        }
1899
    }
1900
    return arr;
1901
}
1902
1903
static int handle_x16_joystick(int seq, cJSON *args) {
1904
    if (!args) {
1905
        send_dap_error_response(seq, "x16/joystick", "missing arguments");
1906
        return 0;
1907
    }
1908
1909
    cJSON *index_item = cJSON_GetObjectItemCaseSensitive(args, "index");
1910
    if (!index_item || !cJSON_IsNumber(index_item)) {
1911
        send_dap_error_response(seq, "x16/joystick", "missing or non-numeric 'index'");
1912
        return 0;
1913
    }
1914
    int index = (int)index_item->valuedouble;
1915
    if (index < 0 || index >= NUM_JOYSTICKS) {
1916
        send_dap_error_response(seq, "x16/joystick", "'index' out of range (expected 0-3)");
1917
        return 0;
1918
    }
1919
1920
    cJSON *enabled_item = cJSON_GetObjectItemCaseSensitive(args, "enabled");
1921
    cJSON *mask_item    = cJSON_GetObjectItemCaseSensitive(args, "mask");
1922
    cJSON *buttons_item = cJSON_GetObjectItemCaseSensitive(args, "buttons");
1923
1924
    if (buttons_item && !cJSON_IsArray(buttons_item)) {
1925
        send_dap_error_response(seq, "x16/joystick", "'buttons' must be an array of names");
1926
        return 0;
1927
    }
1928
    if (mask_item && !cJSON_IsNumber(mask_item)) {
1929
        send_dap_error_response(seq, "x16/joystick", "'mask' must be a number");
1930
        return 0;
1931
    }
1932
1933
    if (enabled_item && cJSON_IsBool(enabled_item) && !cJSON_IsTrue(enabled_item)) {
1934
        joystick_clear_virtual(index);
1935
    } else if (buttons_item || mask_item) {
1936
        // Start from "nothing pressed" so an absent button is a released one.
1937
        uint16_t mask = 0xFFFF;
1938
        if (mask_item) {
1939
            mask = (uint16_t)(unsigned)mask_item->valuedouble;
1940
        }
1941
        if (buttons_item) {
1942
            cJSON *b;
1943
            cJSON_ArrayForEach(b, buttons_item) {
1944
                if (!cJSON_IsString(b) || !b->valuestring) continue;
1945
                uint16_t bit = dap_joy_bit_from_name(b->valuestring);
1946
                if (bit == 0) {
1947
                    char err[96];
1948
                    snprintf(err, sizeof(err), "unknown button '%s'", b->valuestring);
1949
                    send_dap_error_response(seq, "x16/joystick", err);
1950
                    return 0;
1951
                }
1952
                mask &= (uint16_t)~bit;
1953
            }
1954
        }
1955
        joystick_set_virtual(index, mask);
1956
    } else if (enabled_item && cJSON_IsBool(enabled_item)) {
1957
        // enabled:true on its own plugs a controller in with nothing held. That
1958
        // is a distinct state from an empty port -- a port being driven runs its
1959
        // shift register out, so the KERNAL reports a controller present -- and
1960
        // it is what a game that waits for one to be connected needs to see.
1961
        joystick_debug_state_t cur;
1962
        joystick_debug_get_state(&cur);
1963
        joystick_set_virtual(index,
1964
                             joystick_virtual_active(index) ? cur.slots[index].button_mask : 0xFFFF);
1965
    }
1966
    // Otherwise: no enabled/buttons/mask, so this is a query and changes nothing.
1967
1968
    joystick_debug_state_t js;
1969
    joystick_debug_get_state(&js);
1970
1971
    cJSON *body = cJSON_CreateObject();
1972
    cJSON_AddNumberToObject(body, "index", index);
1973
    cJSON_AddNumberToObject(body, "mask", js.slots[index].button_mask);
1974
    cJSON_AddItemToObject(body, "buttons", dap_joy_buttons_array(js.slots[index].button_mask));
1975
    cJSON_AddBoolToObject(body, "enabled", js.slots[index].virtual_active);
1976
    cJSON_AddBoolToObject(body, "slotEnabled", js.slots[index].enabled);
1977
    cJSON_AddBoolToObject(body, "controllerBound", js.slots[index].controller_bound);
1978
    cJSON_AddNumberToObject(body, "shift", js.slots[index].shift_mask);
1979
    cJSON_AddNumberToObject(body, "data", js.data);
1980
    cJSON_AddBoolToObject(body, "latch", js.latch);
1981
    send_dap_response(seq, "x16/joystick", true, body);
1982
    return 0;
1983
}
1984
1985
// ─── Custom: x16/registers — all CPU + KERNAL + VERA state ─────────
1986
1987
static int handle_x16_registers(int seq, cJSON *args) {
1988
    (void)args;
1989
    cJSON *body = cJSON_CreateObject();
1990
1991
    // CPU registers
1992
    cJSON *cpu = cJSON_CreateObject();
1993
    cJSON_AddNumberToObject(cpu, "pc", regs.pc);
1994
    cJSON_AddNumberToObject(cpu, "a", regs.c);       // full 16-bit accumulator
1995
    cJSON_AddNumberToObject(cpu, "x", regs.x);       // full 16-bit X
1996
    cJSON_AddNumberToObject(cpu, "y", regs.y);       // full 16-bit Y
1997
    cJSON_AddNumberToObject(cpu, "sp", regs.sp);
1998
    cJSON_AddNumberToObject(cpu, "dp", regs.dp);     // direct page
1999
    cJSON_AddNumberToObject(cpu, "dbr", regs.db);    // data bank
2000
    cJSON_AddNumberToObject(cpu, "pbr", regs.k);     // program bank
2001
    cJSON_AddNumberToObject(cpu, "status", regs.status);
2002
    cJSON_AddNumberToObject(cpu, "e", regs.e);       // emulation flag
2003
    cJSON_AddBoolToObject(cpu, "is65c816", regs.is65c816);
2004
    cJSON_AddNumberToObject(cpu, "ramBank", memory_get_ram_bank());
2005
    cJSON_AddNumberToObject(cpu, "romBank", memory_get_rom_bank());
2006
    cJSON_AddItemToObject(body, "cpu", cpu);
2007
2008
    // KERNAL r0-r15 pseudo-registers (ZP $02-$21, 16-bit little-endian)
2009
    cJSON *kregs = cJSON_CreateArray();
2010
    for (int i = 0; i < 16; i++) {
2011
        uint8_t lo = debug_read6502(0x02 + i * 2, 0, USE_CURRENT_X16_BANK);
2012
        uint8_t hi = debug_read6502(0x03 + i * 2, 0, USE_CURRENT_X16_BANK);
2013
        cJSON_AddItemToArray(kregs, cJSON_CreateNumber(lo | (hi << 8)));
2014
    }
2015
    cJSON_AddItemToObject(body, "kernal", kregs);
2016
2017
    // VERA registers ($9F20-$9F3F, read via video_read)
2018
    cJSON *vera = cJSON_CreateObject();
2019
    uint8_t addr0_l = video_read(0, true);
2020
    uint8_t addr0_m = video_read(1, true);
2021
    uint8_t addr0_h = video_read(2, true);
2022
    uint32_t addr0 = addr0_l | (addr0_m << 8) | ((addr0_h & 0x01) << 16);
2023
    uint8_t addr0_inc = (addr0_h >> 3) & 0x0F;
2024
    cJSON_AddNumberToObject(vera, "addr0", addr0);
2025
    cJSON_AddNumberToObject(vera, "addr0Inc", addr0_inc);
2026
2027
    uint8_t ctrl = video_read(5, true);
2028
    cJSON_AddNumberToObject(vera, "ctrl", ctrl);
2029
2030
    // If ADDRSEL=1 in CTRL, read addr1 via the alt port
2031
    // VERA ADDR1 is accessible when ADDRSEL bit is set
2032
    // We read the raw register values for display
2033
    cJSON_AddNumberToObject(vera, "ien", video_read(6, true));
2034
    cJSON_AddNumberToObject(vera, "isr", video_read(7, true));
2035
    cJSON_AddNumberToObject(vera, "irqLineL", video_read(8, true));
2036
2037
    // Display composer (DCSEL-banked in HW; read via video_get_dc_value so the
2038
    // values are correct regardless of the guest's current DCSEL). These index
2039
    // reg_composer[]: 0-3 = VIDEO/HSCALE/VSCALE/BORDER, 4-7 = HSTART/HSTOP/VSTART/VSTOP.
2040
    cJSON_AddNumberToObject(vera, "dcVideo", video_get_dc_value(0));
2041
    cJSON_AddNumberToObject(vera, "dcHScale", video_get_dc_value(1));
2042
    cJSON_AddNumberToObject(vera, "dcVScale", video_get_dc_value(2));
2043
    cJSON_AddNumberToObject(vera, "dcBorder", video_get_dc_value(3));
2044
    cJSON_AddNumberToObject(vera, "dcHStartL", video_get_dc_value(4));
2045
    cJSON_AddNumberToObject(vera, "dcHStopL", video_get_dc_value(5));
2046
    cJSON_AddNumberToObject(vera, "dcVStartL", video_get_dc_value(6));
2047
    cJSON_AddNumberToObject(vera, "dcVStopL", video_get_dc_value(7));
2048
2049
    // Layer 0 registers live at VERA $0D-$13 (reg_layer[0][0..6]); no side effects.
2050
    cJSON *l0 = cJSON_CreateObject();
2051
    cJSON_AddNumberToObject(l0, "config", video_read(0x0D, true));
2052
    cJSON_AddNumberToObject(l0, "mapBase", video_read(0x0E, true));
2053
    cJSON_AddNumberToObject(l0, "tileBase", video_read(0x0F, true));
2054
    cJSON_AddNumberToObject(l0, "hScroll", video_read(0x10, true) | (video_read(0x11, true) << 8));
2055
    cJSON_AddNumberToObject(l0, "vScroll", video_read(0x12, true) | (video_read(0x13, true) << 8));
2056
    cJSON_AddItemToObject(vera, "l0", l0);
2057
2058
    // Layer 1 registers live at VERA $14-$1A (reg_layer[1][0..6]); no side effects.
2059
    cJSON *l1 = cJSON_CreateObject();
2060
    cJSON_AddNumberToObject(l1, "config", video_read(0x14, true));
2061
    cJSON_AddNumberToObject(l1, "mapBase", video_read(0x15, true));
2062
    cJSON_AddNumberToObject(l1, "tileBase", video_read(0x16, true));
2063
    cJSON_AddNumberToObject(l1, "hScroll", video_read(0x17, true) | (video_read(0x18, true) << 8));
2064
    cJSON_AddNumberToObject(l1, "vScroll", video_read(0x19, true) | (video_read(0x1A, true) << 8));
2065
    cJSON_AddItemToObject(vera, "l1", l1);
2066
2067
    // Audio: AUDIO_CTRL ($1B) runs audio_render() on read, so suppress it
2068
    // (reads back 0); AUDIO_RATE ($1C) is side-effect-free.
2069
    cJSON_AddNumberToObject(vera, "audioCtrl", vera_read_dbg(0x1B));
2070
    cJSON_AddNumberToObject(vera, "audioRate", video_read(0x1C, true));
2071
2072
    cJSON_AddItemToObject(body, "vera", vera);
2073
2074
    send_dap_response(seq, "x16/registers", true, body);
2075
    return 0;
2076
}
2077
2078
// ─── Performance budget (x16/perfStats, x16/perfConfig) ─────────────────────
2079
// Exposes the guest cycle budget accounting from perf_budget.c so that tooling
2080
// -- a VS Code extension, a CI job, a profiling script -- can watch for
2081
// regressions without a human reading a panel.
2082
//
2083
// Statistics are answered while the machine runs free: nothing here stops it,
2084
// and the server is polled from the emulation thread, so the numbers cannot be
2085
// torn by a concurrent update.
2086
2087
static cJSON *perf_stat_json(const perf_stat_t *s) {
2088
    cJSON *o = cJSON_CreateObject();
2089
    cJSON_AddNumberToObject(o, "min", s->min);
2090
    cJSON_AddNumberToObject(o, "mean", s->mean);
2091
    cJSON_AddNumberToObject(o, "p50", s->p50);
2092
    cJSON_AddNumberToObject(o, "p95", s->p95);
2093
    cJSON_AddNumberToObject(o, "p99", s->p99);
2094
    cJSON_AddNumberToObject(o, "max", s->max);
2095
    return o;
2096
}
2097
2098
static void perf_window_name(float seconds, char *out, size_t outsz) {
2099
    if (seconds <= 0.0f) snprintf(out, outsz, "session");
2100
    else                 snprintf(out, outsz, "%gs", (double)seconds);
2101
}
2102
2103
// Accepts either a number of seconds or a name ("30s", "session"), so a client
2104
// can send back the same window labels it was given.
2105
static float perf_window_seconds(const cJSON *item) {
2106
    if (cJSON_IsNumber(item)) return (float)item->valuedouble;
2107
    if (cJSON_IsString(item) && item->valuestring) {
2108
        if (!strcmp(item->valuestring, "session")) return 0.0f;
2109
        return (float)atof(item->valuestring); // "30s" -> 30
2110
    }
2111
    return 0.0f;
2112
}
2113
2114
static cJSON *perf_budget_json(void) {
2115
    cJSON *b = cJSON_CreateObject();
2116
    cJSON_AddBoolToObject(b, "enabled", perf_budget_is_enabled());
2117
    cJSON_AddNumberToObject(b, "targetFps", perf_budget_get_target_fps());
2118
    cJSON_AddNumberToObject(b, "vsyncHz", perf_budget_vsync_hz());
2119
    cJSON_AddNumberToObject(b, "machineKhz", (double)MHZ * 1000.0);
2120
    cJSON_AddNumberToObject(b, "cyclesPerFrame", perf_budget_cycles_per_frame());
2121
    cJSON_AddNumberToObject(b, "cyclesPerScanline", perf_budget_cycles_per_scanline());
2122
    cJSON_AddNumberToObject(b, "budgetCycles", perf_budget_budget_cycles());
2123
    cJSON_AddNumberToObject(b, "framesPerBudget", perf_budget_frames_per_budget());
2124
    cJSON_AddNumberToObject(b, "historyFrames", perf_budget_capacity());
2125
    cJSON_AddNumberToObject(b, "totalFrames", perf_budget_total_frames());
2126
    const char *mode = "auto";
2127
    switch (perf_budget_get_idle_mode()) {
2128
        case PERF_IDLE_MARKERS: mode = "markers"; break;
2129
        case PERF_IDLE_NONE:    mode = "none";    break;
2130
        default:                mode = "auto";    break;
2131
    }
2132
    cJSON_AddStringToObject(b, "idleMode", mode);
2133
    cJSON_AddBoolToObject(b, "warpMode", warp_mode);
2134
    return b;
2135
}
2136
2137
static cJSON *perf_frame_json(const perf_frame_t *f) {
2138
    cJSON *o = cJSON_CreateObject();
2139
    const uint32_t budget = perf_budget_budget_cycles();
2140
    cJSON_AddNumberToObject(o, "frame", f->frame);
2141
    cJSON_AddNumberToObject(o, "totalCycles", f->total);
2142
    cJSON_AddNumberToObject(o, "workCycles", perf_frame_work(f));
2143
    cJSON_AddNumberToObject(o, "idleCycles", f->idle);
2144
    cJSON_AddNumberToObject(o, "irqCycles", f->irq);
2145
    cJSON_AddNumberToObject(o, "mainCycles",
2146
                            perf_frame_work(f) > f->irq ? perf_frame_work(f) - f->irq : 0);
2147
    cJSON_AddNumberToObject(o, "budgetWorkCycles", f->period_work);
2148
    cJSON_AddNumberToObject(o, "instructions", f->instructions);
2149
    cJSON_AddNumberToObject(o, "hostUs", f->host_us);
2150
    cJSON_AddNumberToObject(o, "utilizationPct",
2151
                            budget ? 100.0 * (double)f->period_work / (double)budget : 0.0);
2152
    cJSON_AddBoolToObject(o, "overBudget", budget && f->period_work > budget);
2153
    return o;
2154
}
2155
2156
// VERA's side of the same question. Deliberately a sub-object of perfStats
2157
// rather than a request of its own: a developer asking "what is eating my
2158
// frame?" should get the CPU answer and the bus answer in one round trip,
2159
// because the interesting cases are the ones where the two disagree.
2160
//
2161
// The headline is peakLineClocks against lineClocks (800), NOT a percentage of
2162
// VERA's total bandwidth. A frame uses maybe a fifth of the chip's 100 MB/s and
2163
// reporting that would be true and useless -- the scanline is the window that
2164
// actually runs out. See src/vera_bandwidth.h.
2165
static cJSON *vera_bandwidth_json(void) {
2166
    cJSON *o = cJSON_CreateObject();
2167
    cJSON_AddBoolToObject(o, "enabled", vera_bandwidth_is_enabled());
2168
    cJSON_AddNumberToObject(o, "lineClocks", VERA_BW_LINE_CLOCKS);
2169
    cJSON_AddNumberToObject(o, "bytesPerAccess", VERA_BW_BYTES_PER_ACCESS);
2170
    cJSON_AddNumberToObject(o, "accessClocks", VERA_BW_ACCESS_CLOCKS);
2171
    cJSON_AddNumberToObject(o, "scanlinesPerFrame", vera_bandwidth_scanlines_per_frame());
2172
2173
    vera_bw_frame_t f;
2174
    if (!vera_bandwidth_last_frame(&f)) {
2175
        return o;
2176
    }
2177
2178
    cJSON_AddNumberToObject(o, "frame", f.frame);
2179
    cJSON_AddNumberToObject(o, "linesActive", f.lines_active);
2180
2181
    cJSON *layers = cJSON_CreateArray();
2182
    for (int i = 0; i < 2; i++) {
2183
        cJSON *l = cJSON_CreateObject();
2184
        cJSON_AddNumberToObject(l, "fetches", f.layer_fetches[i]);
2185
        cJSON_AddNumberToObject(l, "bytes", f.layer_bytes[i]);
2186
        cJSON_AddNumberToObject(l, "peakLine", f.layer_peak_line[i]);
2187
        cJSON_AddNumberToObject(l, "peakFetches", f.layer_peak_fetches[i]);
2188
        cJSON_AddItemToArray(layers, l);
2189
    }
2190
    cJSON_AddItemToObject(o, "layers", layers);
2191
2192
    // Per-scanline pressure: the headline.
2193
    cJSON_AddNumberToObject(o, "peakLine", f.peak_line);
2194
    cJSON_AddNumberToObject(o, "peakLineClocks", f.peak_clocks);
2195
    cJSON_AddNumberToObject(o, "p95LineClocks", f.p95_clocks);
2196
    cJSON_AddNumberToObject(o, "meanLineClocks", f.mean_clocks);
2197
    cJSON_AddNumberToObject(o, "linesOverBudget", f.lines_over);
2198
    cJSON_AddNumberToObject(o, "peakLinePct",
2199
                            100.0 * (double)f.peak_clocks / (double)VERA_BW_LINE_CLOCKS);
2200
    cJSON_AddNumberToObject(o, "totalFetches", f.total_fetches);
2201
    cJSON_AddNumberToObject(o, "totalClocks", f.total_clocks);
2202
2203
    // How much of each scanline was still free when the layers and the CPU had
2204
    // taken their share. Sprites are last on the bus (vram_if.v:142-157), so
2205
    // this is what they were really competing for.
2206
    //
2207
    // Named "bus" deliberately. Every clock figure in this object is BUS
2208
    // OCCUPANCY, which is not the clock the sprite views report: sprite render
2209
    // time is wall clock and runs about five times higher, so a client that
2210
    // subtracted one from the other would conclude a comfortable line was
2211
    // oversubscribed. Convert sprite width and colour depth to fetches first.
2212
    cJSON_AddNumberToObject(o, "spriteBusHeadroomClocksAtPeak",
2213
                            f.peak_clocks < VERA_BW_LINE_CLOCKS
2214
                                ? VERA_BW_LINE_CLOCKS - f.peak_clocks : 0);
2215
    cJSON_AddStringToObject(o, "clockUnits", "bus");
2216
2217
    cJSON *port = cJSON_CreateObject();
2218
    cJSON_AddNumberToObject(port, "readBytes", f.port_reads);
2219
    cJSON_AddNumberToObject(port, "writeBytes", f.port_writes);
2220
    cJSON_AddNumberToObject(port, "accesses", f.port_accesses);
2221
    cJSON_AddNumberToObject(port, "vramBytes", f.port_vram_bytes);
2222
    cJSON_AddNumberToObject(port, "fxCacheWrites", f.fx_cache_writes);
2223
    cJSON_AddNumberToObject(port, "fxAffineFetches", f.fx_affine_fetches);
2224
    // Bytes that reached VRAM per byte the CPU pushed. Above 1.0 means FX is
2225
    // doing work the CPU did not pay for, which is the point of using it.
2226
    const uint32_t stores = f.port_reads + f.port_writes;
2227
    cJSON_AddNumberToObject(port, "amplification",
2228
                            stores ? (double)f.port_vram_bytes / (double)stores : 0.0);
2229
    cJSON_AddItemToObject(o, "port", port);
2230
2231
    return o;
2232
}
2233
2234
static int handle_x16_perf_stats(int seq, cJSON *args) {
2235
    // A client asking for statistics is a client that wants them collected.
2236
    // Arming here rather than making it a separate round trip means the common
2237
    // case -- attach, poll, read -- just works.
2238
    if (!perf_budget_owner_wants(PERF_OWNER_DAP) && !perf_budget_is_enabled()) {
2239
        perf_budget_arm(PERF_OWNER_DAP, true);
2240
    }
2241
    if (!vera_bandwidth_owner_wants(VERA_BW_OWNER_DAP) && !vera_bandwidth_is_enabled()) {
2242
        vera_bandwidth_arm(VERA_BW_OWNER_DAP, true);
2243
    }
2244
2245
    cJSON *body = cJSON_CreateObject();
2246
    cJSON_AddItemToObject(body, "budget", perf_budget_json());
2247
    cJSON_AddItemToObject(body, "bandwidth", vera_bandwidth_json());
2248
2249
    perf_frame_t last;
2250
    if (perf_budget_last_frame(&last)) {
2251
        cJSON_AddItemToObject(body, "current", perf_frame_json(&last));
2252
    }
2253
2254
    // Windows: whatever the client asked for, else a useful default spread.
2255
    static const float defaults[] = {1.0f, 10.0f, 30.0f, 60.0f, 0.0f};
2256
    cJSON *req = args ? cJSON_GetObjectItemCaseSensitive(args, "windows") : NULL;
2257
    cJSON *windows = cJSON_CreateArray();
2258
    const int nreq = (req && cJSON_IsArray(req)) ? cJSON_GetArraySize(req) : 0;
2259
    const int nwin = nreq > 0 ? nreq : (int)(sizeof defaults / sizeof defaults[0]);
2260
    for (int i = 0; i < nwin; i++) {
2261
        const float secs = nreq > 0 ? perf_window_seconds(cJSON_GetArrayItem(req, i))
2262
                                    : defaults[i];
2263
        perf_window_t w;
2264
        if (!perf_budget_window(secs, &w)) continue;
2265
2266
        char name[32];
2267
        perf_window_name(secs, name, sizeof name);
2268
        cJSON *o = cJSON_CreateObject();
2269
        cJSON_AddStringToObject(o, "name", name);
2270
        cJSON_AddNumberToObject(o, "seconds", secs);
2271
        cJSON_AddNumberToObject(o, "frames", w.frames);
2272
        cJSON_AddNumberToObject(o, "budgetCycles", w.budget_cycles);
2273
        cJSON_AddItemToObject(o, "work", perf_stat_json(&w.work));
2274
        cJSON_AddItemToObject(o, "budgetWork", perf_stat_json(&w.budget_work));
2275
        cJSON_AddItemToObject(o, "total", perf_stat_json(&w.total));
2276
        cJSON_AddItemToObject(o, "irq", perf_stat_json(&w.irq));
2277
        cJSON_AddItemToObject(o, "hostUs", perf_stat_json(&w.host_us));
2278
        cJSON_AddNumberToObject(o, "overruns", w.overruns);
2279
        cJSON_AddNumberToObject(o, "overrunPct", w.overrun_pct);
2280
        cJSON_AddNumberToObject(o, "worstOverrunCycles", w.worst_overrun);
2281
        cJSON_AddNumberToObject(o, "worstFrame", w.worst_frame);
2282
        cJSON_AddNumberToObject(o, "meanUtilizationPct", w.mean_utilization);
2283
        cJSON_AddNumberToObject(o, "p95UtilizationPct", w.p95_utilization);
2284
        cJSON_AddNumberToObject(o, "maxUtilizationPct", w.max_utilization);
2285
        cJSON_AddItemToArray(windows, o);
2286
    }
2287
    cJSON_AddItemToObject(body, "windows", windows);
2288
2289
    cJSON *incz = args ? cJSON_GetObjectItemCaseSensitive(args, "includeZones") : NULL;
2290
    if (!incz || !cJSON_IsFalse(incz)) {
2291
        cJSON *zones = cJSON_CreateArray();
2292
        const int nz = perf_budget_zone_count();
2293
        for (int i = 0; i < nz; i++) {
2294
            perf_zone_t z;
2295
            if (!perf_budget_zone_get(i, &z)) continue;
2296
            cJSON *o = cJSON_CreateObject();
2297
            cJSON_AddNumberToObject(o, "id", i);
2298
            cJSON_AddStringToObject(o, "name", z.name);
2299
            cJSON_AddBoolToObject(o, "isMarker", z.is_marker);
2300
            cJSON_AddBoolToObject(o, "isIdle", z.is_idle);
2301
            if (z.is_marker) {
2302
                cJSON_AddNumberToObject(o, "markerId", z.marker_id);
2303
            } else {
2304
                cJSON_AddNumberToObject(o, "start", z.start);
2305
                cJSON_AddNumberToObject(o, "end", z.end);
2306
                cJSON_AddNumberToObject(o, "bank", z.bank);
2307
            }
2308
            if (i < PERF_RING_ZONES) {
2309
                perf_frame_t lf;
2310
                if (perf_budget_last_frame(&lf))
2311
                    cJSON_AddNumberToObject(o, "current", lf.zone[i]);
2312
            }
2313
            perf_stat_t st;
2314
            if (perf_budget_zone_window(i, 30.0f, &st))
2315
                cJSON_AddItemToObject(o, "window30s", perf_stat_json(&st));
2316
            cJSON_AddItemToArray(zones, o);
2317
        }
2318
        cJSON_AddItemToObject(body, "zones", zones);
2319
    }
2320
2321
    cJSON *incf = args ? cJSON_GetObjectItemCaseSensitive(args, "includeFrames") : NULL;
2322
    if (incf && cJSON_IsNumber(incf) && incf->valuedouble > 0) {
2323
        int want = (int)incf->valuedouble;
2324
        const int have = perf_budget_frame_count();
2325
        if (want > have) want = have;
2326
        cJSON *frames = cJSON_CreateArray();
2327
        // Oldest first, so a client can append them to a series it is keeping.
2328
        for (int i = want - 1; i >= 0; i--) {
2329
            perf_frame_t f;
2330
            if (perf_budget_frame_at(i, &f))
2331
                cJSON_AddItemToArray(frames, perf_frame_json(&f));
2332
        }
2333
        cJSON_AddItemToObject(body, "frames", frames);
2334
    }
2335
2336
    send_dap_response(seq, "x16/perfStats", true, body);
2337
    return 0;
2338
}
2339
2340
// Whether the client wants pushed overrun notifications, and when the last one
2341
// went out. Coalescing is the point: a program that misses its budget usually
2342
// misses it for many frames running, and one event per frame would be a flood
2343
// that says nothing the first one did not.
2344
static bool     perf_overrun_events = false;
2345
static uint32_t perf_overrun_last_ms = 0;
2346
#define PERF_OVERRUN_EVENT_INTERVAL_MS 1000
2347
2348
static int handle_x16_perf_config(int seq, cJSON *args) {
2349
    if (!args) {
2350
        send_dap_error_response(seq, "x16/perfConfig", "missing arguments");
2351
        return 0;
2352
    }
2353
2354
    cJSON *item = cJSON_GetObjectItemCaseSensitive(args, "enabled");
2355
    if (cJSON_IsBool(item)) {
2356
        const bool want = cJSON_IsTrue(item);
2357
        if (!perf_budget_arm(PERF_OWNER_DAP, want)) {
2358
            send_dap_error_response(seq, "x16/perfConfig",
2359
                                    "could not allocate the history ring");
2360
            return 0;
2361
        }
2362
        // Turning profiling on is as clear a statement of intent as there is,
2363
        // so overrun events follow it unless the client says otherwise below.
2364
        perf_overrun_events = want;
2365
        // The bandwidth accounting follows the same switch. Two requests to
2366
        // answer one question ("profile this machine") would be a surface
2367
        // nobody wants to remember.
2368
        vera_bandwidth_arm(VERA_BW_OWNER_DAP, want);
2369
    }
2370
2371
    item = cJSON_GetObjectItemCaseSensitive(args, "targetFps");
2372
    if (cJSON_IsNumber(item) && item->valuedouble > 0)
2373
        perf_budget_set_target_fps((float)item->valuedouble);
2374
2375
    item = cJSON_GetObjectItemCaseSensitive(args, "idleMode");
2376
    if (cJSON_IsString(item) && item->valuestring) {
2377
        if (!strcmp(item->valuestring, "auto"))         perf_budget_set_idle_mode(PERF_IDLE_AUTO);
2378
        else if (!strcmp(item->valuestring, "markers")) perf_budget_set_idle_mode(PERF_IDLE_MARKERS);
2379
        else if (!strcmp(item->valuestring, "none"))    perf_budget_set_idle_mode(PERF_IDLE_NONE);
2380
    }
2381
2382
    item = cJSON_GetObjectItemCaseSensitive(args, "historyFrames");
2383
    if (cJSON_IsNumber(item) && item->valuedouble >= 2)
2384
        perf_budget_set_capacity((int)item->valuedouble);
2385
2386
    item = cJSON_GetObjectItemCaseSensitive(args, "overrunEvents");
2387
    if (cJSON_IsBool(item))
2388
        perf_overrun_events = cJSON_IsTrue(item);
2389
2390
    // Zones, when given, REPLACE the address zones wholesale: a client that
2391
    // owns the zone list wants to declare it, not diff it. Marker regions the
2392
    // guest opened are not the client's to remove, but zone_clear takes them
2393
    // too -- they are re-created by the next marker write.
2394
    item = cJSON_GetObjectItemCaseSensitive(args, "zones");
2395
    if (cJSON_IsArray(item)) {
2396
        perf_budget_zone_clear();
2397
        const int n = cJSON_GetArraySize(item);
2398
        for (int i = 0; i < n; i++) {
2399
            cJSON *z = cJSON_GetArrayItem(item, i);
2400
            if (!cJSON_IsObject(z)) continue;
2401
            cJSON *jn = cJSON_GetObjectItemCaseSensitive(z, "name");
2402
            cJSON *js = cJSON_GetObjectItemCaseSensitive(z, "start");
2403
            cJSON *je = cJSON_GetObjectItemCaseSensitive(z, "end");
2404
            cJSON *jb = cJSON_GetObjectItemCaseSensitive(z, "bank");
2405
            cJSON *ji = cJSON_GetObjectItemCaseSensitive(z, "idle");
2406
            if (!cJSON_IsNumber(js) || !cJSON_IsNumber(je)) continue;
2407
            perf_budget_zone_add(cJSON_IsString(jn) ? jn->valuestring : "zone",
2408
                                 (uint16_t)js->valuedouble, (uint16_t)je->valuedouble,
2409
                                 cJSON_IsNumber(jb) ? (int16_t)jb->valuedouble : (int16_t)-1,
2410
                                 cJSON_IsTrue(ji));
2411
        }
2412
    }
2413
2414
    item = cJSON_GetObjectItemCaseSensitive(args, "reset");
2415
    if (cJSON_IsTrue(item)) {
2416
        perf_budget_reset();
2417
        vera_bandwidth_reset();
2418
    }
2419
2420
    cJSON *body = cJSON_CreateObject();
2421
    cJSON_AddItemToObject(body, "budget", perf_budget_json());
2422
    cJSON_AddItemToObject(body, "bandwidth", vera_bandwidth_json());
2423
    cJSON_AddBoolToObject(body, "overrunEvents", perf_overrun_events);
2424
    send_dap_response(seq, "x16/perfConfig", true, body);
2425
    return 0;
2426
}
2427
2428
// Push accumulated budget overruns to the client. Called once per poll, which
2429
// is once per emulated frame.
2430
static void perf_poll_overruns(void) {
2431
    if (!perf_overrun_events || client_sock == SOCKET_INVALID) return;
2432
    if (!perf_budget_is_enabled()) return;
2433
2434
    const uint32_t now = SDL_GetTicks();
2435
    if (now - perf_overrun_last_ms < PERF_OVERRUN_EVENT_INTERVAL_MS) return;
2436
2437
    perf_overrun_report_t r;
2438
    if (!perf_budget_drain_overruns(&r)) {
2439
        // Nothing to report. The clock is still advanced so that a run of clean
2440
        // seconds does not make the first bad one fire instantly.
2441
        perf_overrun_last_ms = now;
2442
        return;
2443
    }
2444
    perf_overrun_last_ms = now;
2445
2446
    cJSON *body = cJSON_CreateObject();
2447
    cJSON_AddNumberToObject(body, "sinceLastEventFrames", r.frames);
2448
    cJSON_AddNumberToObject(body, "overruns", r.overruns);
2449
    cJSON_AddNumberToObject(body, "worstOverrunCycles", r.worst_overrun);
2450
    cJSON_AddNumberToObject(body, "worstFrame", r.worst_frame);
2451
    cJSON_AddNumberToObject(body, "worstUtilizationPct", r.worst_utilization);
2452
    cJSON_AddNumberToObject(body, "budgetCycles", perf_budget_budget_cycles());
2453
    send_dap_event("x16/perfBudgetOverrun", body);
2454
}
2455
2456
static int handle_dap_disassemble(int seq, cJSON *args) {
2457
    if (!args) {
2458
        send_dap_error_response(seq, "disassemble", "missing arguments");
2459
        return 0;
2460
    }
2461
    cJSON *ref_item = cJSON_GetObjectItemCaseSensitive(args, "memoryReference");
2462
    cJSON *count_item = cJSON_GetObjectItemCaseSensitive(args, "instructionCount");
2463
    cJSON *offset_item = cJSON_GetObjectItemCaseSensitive(args, "offset");
2464
2465
    if (!ref_item || !cJSON_IsString(ref_item)) {
2466
        send_dap_error_response(seq, "disassemble", "missing memoryReference");
2467
        return 0;
2468
    }
2469
2470
    uint32_t ref = (uint32_t)strtoul(ref_item->valuestring, NULL, 0);
2471
    int offset = (offset_item && cJSON_IsNumber(offset_item)) ? (int)offset_item->valuedouble : 0;
2472
    int count = (count_item && cJSON_IsNumber(count_item)) ? (int)count_item->valuedouble : 10;
2473
    uint32_t ref24 = (uint32_t)(((int)ref + offset)) & 0xFFFFFF;
2474
    uint16_t addr = (uint16_t)(ref24 & 0xFFFF);
2475
    uint8_t  bank = (uint8_t)(ref24 >> 16); // 65C816 program bank
2476
    if (count > 200) count = 200;
2477
    if (count < 0) count = 0;
2478
2479
    // Decode via the anchored code map so operand widths follow the recorded
2480
    // 65C816 M/X/E flag state (the naive path assumed a fixed width and drifted).
2481
    uint8_t rambank = memory_get_ram_bank();
2482
    uint8_t rombank = memory_get_rom_bank();
2483
    static code_map_line_t lines[200];
2484
    int n = code_map_disasm_forward(addr, bank, rambank, rombank, count, lines, 200, NULL);
2485
2486
    cJSON *instructions = cJSON_CreateArray();
2487
    for (int i = 0; i < n; i++) {
2488
        const code_map_line_t *ln = &lines[i];
2489
2490
        cJSON *inst = cJSON_CreateObject();
2491
        char addr_str[16];
2492
        if (bank != 0 || is_gen2)
2493
            snprintf(addr_str, sizeof(addr_str), "0x%06X", ((uint32_t)bank << 16) | ln->addr);
2494
        else
2495
            snprintf(addr_str, sizeof(addr_str), "0x%04X", ln->addr);
2496
        cJSON_AddStringToObject(inst, "address", addr_str);
2497
        cJSON_AddStringToObject(inst, "instruction", ln->text);
2498
2499
        // Build instructionBytes hex string
2500
        char bytes_str[32] = "";
2501
        int blen = 0;
2502
        for (int b = 0; b < ln->size && b < 4; b++) {
2503
            blen += snprintf(bytes_str + blen, sizeof(bytes_str) - blen, "%02X ", ln->bytes[b]);
2504
        }
2505
        if (blen > 0) bytes_str[blen - 1] = '\0'; // trim trailing space
2506
        cJSON_AddStringToObject(inst, "instructionBytes", bytes_str);
2507
2508
        // Add source location if dbg info is available
2509
        const char *src_file = NULL;
2510
        int src_line = 0;
2511
        if (dbg_info_addr_to_source(ln->addr, &src_file, &src_line)) {
2512
            cJSON *source = cJSON_CreateObject();
2513
            cJSON_AddStringToObject(source, "path", src_file);
2514
            cJSON_AddItemToObject(inst, "source", source);
2515
            cJSON_AddNumberToObject(inst, "line", src_line);
2516
        }
2517
2518
        cJSON_AddItemToArray(instructions, inst);
2519
    }
2520
2521
    cJSON *body = cJSON_CreateObject();
2522
    cJSON_AddItemToObject(body, "instructions", instructions);
2523
    send_dap_response(seq, "disassemble", true, body);
2524
    return 0;
2525
}
2526
2527
// ─── DAP command dispatch ───────────────────────────────────────────
2528
2529
// ─── setVariable ─────────────────────────────────────────────────────
2530
static int handle_dap_set_variable(int seq, cJSON *args) {
2531
    if (!args) { send_dap_error_response(seq, "setVariable", "missing arguments"); return 0; }
2532
    cJSON *ref_i  = cJSON_GetObjectItemCaseSensitive(args, "variablesReference");
2533
    cJSON *name_i = cJSON_GetObjectItemCaseSensitive(args, "name");
2534
    cJSON *val_i  = cJSON_GetObjectItemCaseSensitive(args, "value");
2535
    int ref = (ref_i && cJSON_IsNumber(ref_i)) ? (int)ref_i->valuedouble : 0;
2536
    const char *name   = (name_i && cJSON_IsString(name_i)) ? name_i->valuestring : "";
2537
    const char *valstr = (val_i && cJSON_IsString(val_i)) ? val_i->valuestring : "";
2538
    uint32_t v = dap_parse_num(valstr);
2539
    bool ok = true;
2540
    bool a16 = regs.is65c816 && !regs.e && !(regs.status & FLAG_MEMORY_WIDTH);
2541
    bool x16 = regs.is65c816 && !regs.e && !(regs.status & FLAG_INDEX_WIDTH);
2542
2543
    if (ref == VARREF_REGISTERS) {
2544
        if      (!strcmp(name, "PC")) regs.pc = (uint16_t)v;
2545
        else if (!strcmp(name, "A"))  { if (a16) regs.c = (uint16_t)v; else regs.a = (uint8_t)v; }
2546
        else if (!strcmp(name, "X"))  { if (x16) regs.x = (uint16_t)v; else regs.xl = (uint8_t)v; }
2547
        else if (!strcmp(name, "Y"))  { if (x16) regs.y = (uint16_t)v; else regs.yl = (uint8_t)v; }
2548
        else if (!strcmp(name, "SP")) regs.sp = regs.is65c816 ? (uint16_t)v : (uint16_t)((regs.sp & 0xFF00) | (v & 0xFF));
2549
        else if (!strcmp(name, "D"))  regs.dp = (uint16_t)v;
2550
        else if (!strcmp(name, "DBR")) regs.db = (uint8_t)v;
2551
        else if (!strcmp(name, "PBR")) regs.k = (uint8_t)v;
2552
        else if (name[0] == 'P')      regs.status = (uint8_t)v; // "P (Status)"
2553
        else if (!strcmp(name, "RAM Bank")) memory_set_ram_bank((uint8_t)v);
2554
        else if (!strcmp(name, "ROM Bank")) memory_set_rom_bank((uint8_t)v);
2555
        else ok = false;
2556
    } else if (ref == VARREF_STACK) {
2557
        debug_write6502((uint16_t)dap_parse_num(name), 0, (uint8_t)v); // name is "$XXXX"
2558
    } else if (ref == VARREF_VREGS) {
2559
        // name is "Rn" (n = 0..15) → 16-bit little-endian at direct-page + $02 + 2*n.
2560
        if ((name[0] == 'R' || name[0] == 'r') && name[1]) {
2561
            int n = atoi(name + 1);
2562
            if (n >= 0 && n <= 15) {
2563
                uint16_t a = dap_dp_add((uint16_t)(0x02 + 2 * n));
2564
                debug_write6502(a, 0, (uint8_t)(v & 0xFF));
2565
                debug_write6502((uint16_t)(a + 1), 0, (uint8_t)((v >> 8) & 0xFF));
2566
            } else ok = false;
2567
        } else ok = false;
2568
    } else {
2569
        ok = false; // zero-page rows are 16-byte groups — not individually settable
2570
    }
2571
2572
    if (!ok) { send_dap_error_response(seq, "setVariable", "cannot set this variable"); return 0; }
2573
    char result[40];
2574
    snprintf(result, sizeof result, "$%X (%u)", v, v);
2575
    cJSON *body = cJSON_CreateObject();
2576
    cJSON_AddStringToObject(body, "value", result);
2577
    cJSON_AddNumberToObject(body, "variablesReference", 0);
2578
    send_dap_response(seq, "setVariable", true, body);
2579
    return 0;
2580
}
2581
2582
// ─── terminate (end debugging; keep the emulator running) ────────────
2583
static int handle_dap_terminate(int seq, cJSON *args) {
2584
    (void)args;
2585
    send_dap_response(seq, "terminate", true, NULL);
2586
    dap_session_active = false;
2587
    // Released before resuming, for the same reason as the disconnect request:
2588
    // running on with this client's breakpoints still armed halts the machine
2589
    // again moments later, and by then the session is marked inactive so the
2590
    // socket cleanup will not resume it.
2591
    dap_release_session_state();
2592
    // Same rule as a client disconnecting: only take the machine out of a stop
2593
    // when there is no local debugger whose user owns the run state. Otherwise
2594
    // "Stop Debugging" in the editor runs the emulator out from under someone
2595
    // who is halted in the SDL debug window inspecting it.
2596
    if (!dap_local_ui_owns_run_state()) {
2597
        currentMode = DMODE_RUN;
2598
    }
2599
    return 1;
2600
}
2601
2602
// ─── attach (the emulator is already running; mirror launch) ─────────
2603
static int handle_dap_attach(int seq, cJSON *args) {
2604
    if (args) {
2605
        cJSON *dbg_file = cJSON_GetObjectItemCaseSensitive(args, "dbgFile");
2606
        if (dbg_file && cJSON_IsString(dbg_file)) dbg_info_load(dbg_file->valuestring);
2607
    }
2608
    send_dap_response(seq, "attach", true, NULL);
2609
    send_dap_event_thread_started();
2610
    return 0;
2611
}
2612
2613
// ─── setFunctionBreakpoints (label breakpoints via .dbg) ─────────────
2614
static int handle_dap_set_function_breakpoints(int seq, cJSON *args) {
2615
    // "Here is the complete list" -- so let go of everything this kind of
2616
    // breakpoint asked for, and start again. Anything another owner also wants
2617
    // stays armed for them; nothing here has to know who they are.
2618
    debug_bp_clear_owner(DEBUG_OWNER_DAP_FUNCTION);
2619
    num_func_bps = 0;
2620
2621
    cJSON *body = cJSON_CreateObject();
2622
    cJSON *arr  = cJSON_CreateArray();
2623
    cJSON *bps  = args ? cJSON_GetObjectItemCaseSensitive(args, "breakpoints") : NULL;
2624
    int    n    = (bps && cJSON_IsArray(bps)) ? cJSON_GetArraySize(bps) : 0;
2625
    for (int i = 0; i < n; i++) {
2626
        cJSON *fb = cJSON_GetArrayItem(bps, i);
2627
        cJSON *nm = cJSON_GetObjectItemCaseSensitive(fb, "name");
2628
        cJSON *rb = cJSON_CreateObject();
2629
        dbg_addr_t addr;
2630
        bool verified = false;
2631
        if (nm && cJSON_IsString(nm) && dbg_info_label_to_addr(nm->valuestring, &addr) && num_func_bps < 128) {
2632
            struct breakpoint bp = { (int)(addr & 0xFFFF), (uint8_t)(addr >> 16), -1, 0, false };
2633
            if (debug_bp_add_for(bp, DEBUG_OWNER_DAP_FUNCTION) != DEBUG_ADD_FULL) {
2634
                func_bp_banks[num_func_bps] = (uint8_t)(addr >> 16);
2635
                func_bp_addrs[num_func_bps++] = (uint16_t)(addr & 0xFFFF);
2636
                cJSON_AddNumberToObject(rb, "id", next_dap_bp_id++);
2637
                cJSON_AddNumberToObject(rb, "instructionReference", addr);
2638
                verified = true;
2639
            }
2640
        }
2641
        cJSON_AddBoolToObject(rb, "verified", verified);
2642
        cJSON_AddItemToArray(arr, rb);
2643
    }
2644
    cJSON_AddItemToObject(body, "breakpoints", arr);
2645
    send_dap_response(seq, "setFunctionBreakpoints", true, body);
2646
    return 0;
2647
}
2648
2649
// ─── setInstructionBreakpoints ───────────────────────────────────────
2650
2651
// One field of a "bb:aaaa" reference. Unprefixed digits are hex, which is what
2652
// -bp means by the same notation; dap_parse_num()'s base-0 rule would read the
2653
// bank in "33:A555" as decimal.
2654
static uint32_t dap_parse_hex_field(const char *s) {
2655
    while (*s == ' ') s++;
2656
    if (s[0] == '$')
2657
        return (uint32_t)strtoul(s + 1, NULL, 16);
2658
    if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
2659
        return (uint32_t)strtoul(s + 2, NULL, 16);
2660
    return (uint32_t)strtoul(s, NULL, 16);
2661
}
2662
2663
// Resolve an instructionReference into a core breakpoint, or fail.
2664
//
2665
// The X16 bank was the dimension this path did not have. A reference was read as
2666
// `pc | program_bank << 16` with x16Bank left at ANY, but read6502 forces the
2667
// program bank to zero off a Gen2, so any non-zero top byte built an entry whose
2668
// bank could never equal the running one: it was rejected on every instruction
2669
// while the client had been told "verified": true. Breakpoints in banked RAM --
2670
// exactly the ones worth setting from an editor -- silently did nothing.
2671
//
2672
// Which field a bank byte belongs in is debug_bp_from_ref()'s to decide, so that
2673
// this and -bp cannot drift apart on it. Only the syntax is here: "bb:aaaa" is
2674
// accepted and means what -bp means by it.
2675
static bool dap_parse_instruction_ref(const char *s, long offset, struct breakpoint *out) {
2676
    if (!s) return false;
2677
    while (*s == ' ') s++;
2678
2679
    const char *colon = strchr(s, ':');
2680
    if (colon) {
2681
        unsigned long bank = dap_parse_hex_field(s);
2682
        unsigned long a    = dap_parse_hex_field(colon + 1);
2683
        if (bank > 0xFF || a > 0xFFFF) return false;
2684
        return debug_bp_from_ref((long)a, (int)bank, offset, out);
2685
    }
2686
    return debug_bp_from_ref((long)dap_parse_num(s), DEBUG_BANK_ANY, offset, out);
2687
}
2688
2689
static int handle_dap_set_instruction_breakpoints(int seq, cJSON *args) {
2690
    debug_bp_clear_owner(DEBUG_OWNER_DAP_INSTRUCTION);
2691
    num_instr_bps = 0;
2692
2693
    cJSON *body = cJSON_CreateObject();
2694
    cJSON *arr  = cJSON_CreateArray();
2695
    cJSON *bps  = args ? cJSON_GetObjectItemCaseSensitive(args, "breakpoints") : NULL;
2696
    int    n    = (bps && cJSON_IsArray(bps)) ? cJSON_GetArraySize(bps) : 0;
2697
    for (int i = 0; i < n; i++) {
2698
        cJSON *ib  = cJSON_GetArrayItem(bps, i);
2699
        cJSON *ir  = cJSON_GetObjectItemCaseSensitive(ib, "instructionReference");
2700
        cJSON *off = cJSON_GetObjectItemCaseSensitive(ib, "offset");
2701
        cJSON *rb  = cJSON_CreateObject();
2702
        long   offset = (off && cJSON_IsNumber(off)) ? (long)off->valuedouble : 0;
2703
        struct breakpoint bp;
2704
        bool verified = false;
2705
        if (ir && cJSON_IsString(ir)
2706
            && dap_parse_instruction_ref(ir->valuestring, offset, &bp)
2707
            && num_instr_bps < 128) {
2708
            if (debug_bp_add_for(bp, DEBUG_OWNER_DAP_INSTRUCTION) != DEBUG_ADD_FULL) {
2709
                instr_bp_banks[num_instr_bps] = bp.bank;
2710
                instr_bp_addrs[num_instr_bps++] = (uint16_t)bp.pc;
2711
                cJSON_AddNumberToObject(rb, "id", next_dap_bp_id++);
2712
                verified = true;
2713
            }
2714
        }
2715
        cJSON_AddBoolToObject(rb, "verified", verified);
2716
        // Verified used to be the only thing said about a breakpoint, which made
2717
        // "armed" and "accepted but inert" look identical from the client side.
2718
        if (!verified)
2719
            cJSON_AddStringToObject(rb, "message",
2720
                                    "address could not be resolved to an armable breakpoint");
2721
        cJSON_AddItemToArray(arr, rb);
2722
    }
2723
    cJSON_AddItemToObject(body, "breakpoints", arr);
2724
    send_dap_response(seq, "setInstructionBreakpoints", true, body);
2725
    return 0;
2726
}
2727
2728
// ─── dataBreakpointInfo (memory write watchpoint on an address) ──────
2729
static int handle_dap_data_breakpoint_info(int seq, cJSON *args) {
2730
    cJSON *name_i = args ? cJSON_GetObjectItemCaseSensitive(args, "name") : NULL;
2731
    cJSON *body = cJSON_CreateObject();
2732
    if (name_i && cJSON_IsString(name_i)) {
2733
        uint16_t addr = (uint16_t)dap_parse_num(name_i->valuestring);
2734
        char dataId[16]; snprintf(dataId, sizeof dataId, "%u", addr);
2735
        char desc[64];   snprintf(desc, sizeof desc, "write $%04X", addr);
2736
        cJSON_AddStringToObject(body, "dataId", dataId);
2737
        cJSON_AddStringToObject(body, "description", desc);
2738
        cJSON *at = cJSON_CreateArray();
2739
        cJSON_AddItemToArray(at, cJSON_CreateString("write"));
2740
        cJSON_AddItemToObject(body, "accessTypes", at);
2741
        cJSON_AddBoolToObject(body, "canPersist", false);
2742
    } else {
2743
        cJSON_AddNullToObject(body, "dataId");
2744
        cJSON_AddStringToObject(body, "description", "not available");
2745
    }
2746
    send_dap_response(seq, "dataBreakpointInfo", true, body);
2747
    return 0;
2748
}
2749
2750
// ─── setDataBreakpoints (maps to emulator write watchpoints) ─────────
2751
static int handle_dap_set_data_breakpoints(int seq, cJSON *args) {
2752
    // The client's complete list, so let go of the previous one. Watches the
2753
    // console made are owned separately and are left alone, as are any from -wp.
2754
    debug_wp_clear_owner(DEBUG_OWNER_DAP_SOURCE);
2755
2756
    cJSON *body = cJSON_CreateObject();
2757
    cJSON *arr  = cJSON_CreateArray();
2758
    cJSON *bps  = args ? cJSON_GetObjectItemCaseSensitive(args, "breakpoints") : NULL;
2759
    int    n    = (bps && cJSON_IsArray(bps)) ? cJSON_GetArraySize(bps) : 0;
2760
    for (int i = 0; i < n; i++) {
2761
        cJSON *db = cJSON_GetArrayItem(bps, i);
2762
        cJSON *id = cJSON_GetObjectItemCaseSensitive(db, "dataId");
2763
        cJSON *rb = cJSON_CreateObject();
2764
        bool verified = false;
2765
        if (id && cJSON_IsString(id)) {
2766
            uint16_t addr = (uint16_t)dap_parse_num(id->valuestring);
2767
            if (debug_wp_add_for(addr, 1, DEBUG_BANK_ANY, DEBUG_OWNER_DAP_SOURCE)
2768
                != DEBUG_ADD_FULL) {
2769
                verified = true;
2770
                // Optional written-value filter via the DAP dataBreakpoint's
2771
                // `condition` (e.g. "== $05", "!=0", "$FF"): only break when the
2772
                // byte written to addr compares true.
2773
                cJSON *c = cJSON_GetObjectItemCaseSensitive(db, "condition");
2774
                if (c && cJSON_IsString(c) && c->valuestring[0]) {
2775
                    int op; uint32_t val;
2776
                    if (dap_parse_simple_cmp(c->valuestring, &op, &val))
2777
                        debug_wp_set_value(addr, DEBUG_BANK_ANY, op, (uint8_t)val);
2778
                }
2779
            }
2780
        }
2781
        cJSON_AddBoolToObject(rb, "verified", verified);
2782
        cJSON_AddItemToArray(arr, rb);
2783
    }
2784
    cJSON_AddItemToObject(body, "breakpoints", arr);
2785
    send_dap_response(seq, "setDataBreakpoints", true, body);
2786
    return 0;
2787
}
2788
2789
// ─── loadedSources (files from the loaded .dbg) ──────────────────────
2790
static int handle_dap_loaded_sources(int seq, cJSON *args) {
2791
    (void)args;
2792
    cJSON *body = cJSON_CreateObject();
2793
    cJSON *arr  = cJSON_CreateArray();
2794
    int n = dbg_info_file_count();
2795
    for (int i = 0; i < n; i++) {
2796
        const char *nm = NULL;
2797
        if (dbg_info_file_at(i, &nm) && nm) {
2798
            const char *base = nm;
2799
            for (const char *p = nm; *p; ++p) if (*p == '/' || *p == '\\') base = p + 1;
2800
            cJSON *src = cJSON_CreateObject();
2801
            cJSON_AddStringToObject(src, "name", base);
2802
            cJSON_AddStringToObject(src, "path", nm);
2803
            cJSON_AddItemToArray(arr, src);
2804
        }
2805
    }
2806
    cJSON_AddItemToObject(body, "sources", arr);
2807
    send_dap_response(seq, "loadedSources", true, body);
2808
    return 0;
2809
}
2810
2811
// ─── source (return file content by path) ────────────────────────────
2812
static int handle_dap_source(int seq, cJSON *args) {
2813
    const char *path = NULL;
2814
    if (args) {
2815
        cJSON *src = cJSON_GetObjectItemCaseSensitive(args, "source");
2816
        if (src) {
2817
            cJSON *p = cJSON_GetObjectItemCaseSensitive(src, "path");
2818
            if (!p) p = cJSON_GetObjectItemCaseSensitive(src, "name");
2819
            if (p && cJSON_IsString(p)) path = p->valuestring;
2820
        }
2821
    }
2822
    const source_file_t *sf = path ? source_view_get(path) : NULL;
2823
    if (sf && sf->found) {
2824
        size_t total = 1;
2825
        for (int i = 0; i < sf->count; i++) total += strlen(sf->lines[i]) + 1;
2826
        char *buf = (char *)malloc(total);
2827
        cJSON *body = cJSON_CreateObject();
2828
        if (buf) {
2829
            size_t o = 0;
2830
            for (int i = 0; i < sf->count; i++) {
2831
                size_t l = strlen(sf->lines[i]);
2832
                memcpy(buf + o, sf->lines[i], l); o += l;
2833
                buf[o++] = '\n';
2834
            }
2835
            buf[o] = '\0';
2836
            cJSON_AddStringToObject(body, "content", buf);
2837
            free(buf);
2838
        } else {
2839
            cJSON_AddStringToObject(body, "content", "");
2840
        }
2841
        send_dap_response(seq, "source", true, body);
2842
    } else {
2843
        send_dap_error_response(seq, "source", "source not available");
2844
    }
2845
    return 0;
2846
}
2847
2848
// ─── breakpointLocations (which lines in a range map to code) ────────
2849
static int handle_dap_breakpoint_locations(int seq, cJSON *args) {
2850
    const char *path = NULL; int line = 0, endLine = 0;
2851
    if (args) {
2852
        cJSON *src = cJSON_GetObjectItemCaseSensitive(args, "source");
2853
        if (src) { cJSON *p = cJSON_GetObjectItemCaseSensitive(src, "path"); if (p && cJSON_IsString(p)) path = p->valuestring; }
2854
        cJSON *l  = cJSON_GetObjectItemCaseSensitive(args, "line");    if (l && cJSON_IsNumber(l))  line = (int)l->valuedouble;
2855
        cJSON *el = cJSON_GetObjectItemCaseSensitive(args, "endLine"); if (el && cJSON_IsNumber(el)) endLine = (int)el->valuedouble;
2856
    }
2857
    if (endLine < line) endLine = line;
2858
    cJSON *body = cJSON_CreateObject();
2859
    cJSON *arr  = cJSON_CreateArray();
2860
    for (int ln = line; path && ln <= endLine; ln++) {
2861
        dbg_addr_t addr;
2862
        if (dbg_info_source_to_addr(path, ln, &addr)) {
2863
            cJSON *loc = cJSON_CreateObject();
2864
            cJSON_AddNumberToObject(loc, "line", ln);
2865
            cJSON_AddItemToArray(arr, loc);
2866
        }
2867
    }
2868
    cJSON_AddItemToObject(body, "breakpoints", arr);
2869
    send_dap_response(seq, "breakpointLocations", true, body);
2870
    return 0;
2871
}
2872
2873
// ─── stepInTargets (the call destination at a JSR/JSL) ───────────────
2874
static int handle_dap_step_in_targets(int seq, cJSON *args) {
2875
    (void)args;
2876
    cJSON *body = cJSON_CreateObject();
2877
    cJSON *arr  = cJSON_CreateArray();
2878
    uint8_t opc = debug_read6502(regs.pc, regs.k, USE_CURRENT_X16_BANK);
2879
    if (opc == 0x20 || opc == 0x22) { // JSR abs / JSL
2880
        uint16_t target = debug_read6502((uint16_t)(regs.pc + 1), regs.k, USE_CURRENT_X16_BANK) |
2881
                          (debug_read6502((uint16_t)(regs.pc + 2), regs.k, USE_CURRENT_X16_BANK) << 8);
2882
        const char *label = NULL;
2883
        char lbl[64];
2884
        if (dbg_info_addr_to_label(target, &label) && label)
2885
            snprintf(lbl, sizeof lbl, "%s ($%04X)", label, target);
2886
        else
2887
            snprintf(lbl, sizeof lbl, "$%04X", target);
2888
        cJSON *t = cJSON_CreateObject();
2889
        cJSON_AddNumberToObject(t, "id", 1);
2890
        cJSON_AddStringToObject(t, "label", lbl);
2891
        cJSON_AddItemToArray(arr, t);
2892
    }
2893
    cJSON_AddItemToObject(body, "targets", arr);
2894
    send_dap_response(seq, "stepInTargets", true, body);
2895
    return 0;
2896
}
2897
2898
// Process a single DAP message. Returns 1 if execution mode was changed.
2899
static int dispatch_dap(const char *json_body) {
2900
    cJSON *root = cJSON_Parse(json_body);
2901
    if (!root) {
2902
        fprintf(stderr, "[dap] Failed to parse JSON: %.80s\n", json_body);
2903
        return 0;
2904
    }
2905
2906
    cJSON *type_item = cJSON_GetObjectItemCaseSensitive(root, "type");
2907
    cJSON *seq_item = cJSON_GetObjectItemCaseSensitive(root, "seq");
2908
    cJSON *cmd_item = cJSON_GetObjectItemCaseSensitive(root, "command");
2909
    cJSON *args = cJSON_GetObjectItemCaseSensitive(root, "arguments");
2910
2911
    if (!type_item || !cJSON_IsString(type_item) || strcmp(type_item->valuestring, "request") != 0) {
2912
        // Only handle requests
2913
        cJSON_Delete(root);
2914
        return 0;
2915
    }
2916
2917
    int seq = (seq_item && cJSON_IsNumber(seq_item)) ? (int)seq_item->valuedouble : 0;
2918
    const char *cmd = (cmd_item && cJSON_IsString(cmd_item)) ? cmd_item->valuestring : "";
2919
2920
    int result = 0;
2921
2922
    printf("[dap] << %s (seq=%d)\n", cmd, seq);
2923
2924
    if (!strcmp(cmd, "initialize"))         result = handle_dap_initialize(seq, args);
2925
    else if (!strcmp(cmd, "launch"))        result = handle_dap_launch(seq, args);
2926
    else if (!strcmp(cmd, "configurationDone")) result = handle_dap_configuration_done(seq);
2927
    else if (!strcmp(cmd, "threads"))       result = handle_dap_threads(seq);
2928
    else if (!strcmp(cmd, "stackTrace"))    result = handle_dap_stack_trace(seq, args);
2929
    else if (!strcmp(cmd, "scopes"))        result = handle_dap_scopes(seq, args);
2930
    else if (!strcmp(cmd, "variables"))     result = handle_dap_variables(seq, args);
2931
    else if (!strcmp(cmd, "setBreakpoints"))result = handle_dap_set_breakpoints(seq, args);
2932
    else if (!strcmp(cmd, "continue"))      result = handle_dap_continue(seq, args);
2933
    else if (!strcmp(cmd, "next"))          result = handle_dap_next(seq, args);
2934
    else if (!strcmp(cmd, "stepIn"))        result = handle_dap_step_in(seq, args);
2935
    else if (!strcmp(cmd, "stepOut"))       result = handle_dap_step_out(seq, args);
2936
    else if (!strcmp(cmd, "pause"))         result = handle_dap_pause(seq, args);
2937
    else if (!strcmp(cmd, "disconnect"))    result = handle_dap_disconnect(seq, args);
2938
    else if (!strcmp(cmd, "restart"))       result = handle_dap_restart(seq, args);
2939
    else if (!strcmp(cmd, "gotoTargets"))   result = handle_dap_goto_targets(seq, args);
2940
    else if (!strcmp(cmd, "goto"))          result = handle_dap_goto(seq, args);
2941
    else if (!strcmp(cmd, "evaluate"))      result = handle_dap_evaluate(seq, args);
2942
    else if (!strcmp(cmd, "readMemory"))    result = handle_dap_read_memory(seq, args);
2943
    else if (!strcmp(cmd, "writeMemory"))   result = handle_dap_write_memory(seq, args);
2944
    else if (!strcmp(cmd, "x16/registers")) result = handle_x16_registers(seq, args);
2945
    else if (!strcmp(cmd, "x16/sendKey"))   result = handle_x16_send_key(seq, args);
2946
    else if (!strcmp(cmd, "x16/type"))      result = handle_x16_type(seq, args);
2947
    else if (!strcmp(cmd, "x16/joystick"))  result = handle_x16_joystick(seq, args);
2948
    else if (!strcmp(cmd, "x16/perfStats")) result = handle_x16_perf_stats(seq, args);
2949
    else if (!strcmp(cmd, "x16/perfConfig")) result = handle_x16_perf_config(seq, args);
2950
2951
    else if (!strcmp(cmd, "disassemble"))   result = handle_dap_disassemble(seq, args);
2952
    else if (!strcmp(cmd, "setVariable"))   result = handle_dap_set_variable(seq, args);
2953
    else if (!strcmp(cmd, "terminate"))     result = handle_dap_terminate(seq, args);
2954
    else if (!strcmp(cmd, "attach"))        result = handle_dap_attach(seq, args);
2955
    else if (!strcmp(cmd, "setInstructionBreakpoints")) result = handle_dap_set_instruction_breakpoints(seq, args);
2956
    else if (!strcmp(cmd, "dataBreakpointInfo")) result = handle_dap_data_breakpoint_info(seq, args);
2957
    else if (!strcmp(cmd, "setDataBreakpoints")) result = handle_dap_set_data_breakpoints(seq, args);
2958
    else if (!strcmp(cmd, "loadedSources")) result = handle_dap_loaded_sources(seq, args);
2959
    else if (!strcmp(cmd, "source"))        result = handle_dap_source(seq, args);
2960
    else if (!strcmp(cmd, "breakpointLocations")) result = handle_dap_breakpoint_locations(seq, args);
2961
    else if (!strcmp(cmd, "stepInTargets")) result = handle_dap_step_in_targets(seq, args);
2962
    else if (!strcmp(cmd, "setExceptionBreakpoints")) {
2963
        // VS always sends this — just acknowledge
2964
        send_dap_response(seq, "setExceptionBreakpoints", true, NULL);
2965
    }
2966
    else if (!strcmp(cmd, "setFunctionBreakpoints")) {
2967
        result = handle_dap_set_function_breakpoints(seq, args);
2968
    }
2969
    else {
2970
        fprintf(stderr, "[dap] Unhandled command: %s\n", cmd);
2971
        send_dap_error_response(seq, cmd, "command not supported");
2972
    }
2973
2974
    cJSON_Delete(root);
2975
    return result;
2976
}
2977
2978
// ─── Connection management ──────────────────────────────────────────
2979
2980
// Everything the session installed in the machine. Run before resuming, so a
2981
// breakpoint set by a client that has gone cannot stop a headless emulator with
2982
// nobody left to notice.
2983
static void dap_release_session_state(void) {
2984
    // Everything this session asked for, by name. Each entry goes only if no
2985
    // other owner still wants it, so a -bp breakpoint or one the user set with
2986
    // F9 at the same address survives -- without this file having to know that
2987
    // they exist.
2988
    debug_bp_clear_owner(DEBUG_OWNER_DAP_SOURCE);
2989
    debug_bp_clear_owner(DEBUG_OWNER_DAP_FUNCTION);
2990
    debug_bp_clear_owner(DEBUG_OWNER_DAP_INSTRUCTION);
2991
    debug_bp_clear_owner(DEBUG_OWNER_DAP_CONSOLE);
2992
    debug_wp_clear_owner(DEBUG_OWNER_DAP_SOURCE);
2993
    debug_wp_clear_owner(DEBUG_OWNER_DAP_CONSOLE);
2994
2995
    num_dap_bps   = 0;
2996
    num_func_bps  = 0;
2997
    num_instr_bps = 0;
2998
2999
    // A step-over or step-out THIS session started has a breakpoint of its own,
3000
    // and nothing else can retract it. One the local debugger started belongs to
3001
    // the user at the keyboard and is left alone; the debugger decides which is
3002
    // which, since it is the one holding the step.
3003
    DEBUGCancelStepFor(DEBUG_OWNER_DAP_SOURCE);
3004
3005
    dap_stop_on_entry = false;
3006
    dap_stop_announced = false;
3007
}
3008
3009
static void disconnect_client(void) {
3010
    if (client_sock != SOCKET_INVALID) {
3011
        CLOSE_SOCKET(client_sock);
3012
        client_sock = SOCKET_INVALID;
3013
        // Clear the contents, not just the length. A dropped oversized frame
3014
        // otherwise stayed in the buffer as a NUL-terminated string, and the
3015
        // next client -- accepted before it has written anything -- had that
3016
        // header re-parsed and was dropped on connect, for the life of the
3017
        // process.
3018
        recv_buf_len = 0;
3019
        recv_buf[0] = '\0';
3020
        // Everything the session installed goes before anything else, so a
3021
        // breakpoint it set cannot stop a machine it is no longer watching.
3022
        dap_release_session_state();
3023
        dap_seq = 1;
3024
        printf("[dap] Client disconnected\n");
3025
3026
        // Client dropped without sending a proper "disconnect" request. Never
3027
        // kill the emulator — just end the DAP session.
3028
        //
3029
        // Only auto-resume when there is NO interactive debugger UI: in that
3030
        // case a headless, DAP-only session would otherwise stay frozen forever
3031
        // with nothing able to resume it. When the ImGui/SDL debugger is up the
3032
        // user owns the run state (they may have deliberately paused, or be
3033
        // mid-step), so a stray client connect/disconnect must not resume the
3034
        // machine out from under them.
3035
        if (dap_session_active) {
3036
            dap_session_active = false;
3037
            // A UI that was asked for AND is still running: the guest can clear
3038
            // debugger_enabled by writing $9FB0, which suppresses the overlay,
3039
            // and leaving the machine stopped for a window nobody can see would
3040
            // strand it.
3041
            bool has_ui = dap_local_ui_owns_run_state();
3042
            if (currentMode == DMODE_STOP && !has_ui) {
3043
                printf("[dap] Session ended, resuming emulator\n");
3044
                currentMode = DMODE_RUN;
3045
            } else {
3046
                printf("[dap] Session ended (run state left unchanged)\n");
3047
            }
3048
        }
3049
    }
3050
}
3051
3052
static void try_accept(void) {
3053
    if (client_sock != SOCKET_INVALID) return;
3054
3055
    struct sockaddr_in client_addr;
3056
    int addr_len = sizeof(client_addr);
3057
#ifdef _WIN32
3058
    socket_t new_sock = accept(listen_sock, (struct sockaddr *)&client_addr, &addr_len);
3059
#else
3060
    socket_t new_sock = accept(listen_sock, (struct sockaddr *)&client_addr, (socklen_t *)&addr_len);
3061
#endif
3062
    if (new_sock == SOCKET_INVALID) return;
3063
3064
    set_nonblocking(new_sock);
3065
    client_sock = new_sock;
3066
    recv_buf_len = 0;
3067
    dap_seq = 1;
3068
    printf("[dap] Client connected from %s:%d\n",
3069
           inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
3070
}
3071
3072
// ─── DAP framing: Content-Length parser ─────────────────────────────
3073
3074
// Try to extract a complete DAP message from the receive buffer.
3075
// Copies the JSON body into `out` (NUL-terminated) and sets *consumed.
3076
// Returns false if no complete message is available yet.
3077
//
3078
// The body is copied rather than terminated in place. Writing the NUL at
3079
// header_size + content_length lands on the first byte of the NEXT message
3080
// whenever two arrive in one read -- which is the normal case, since a client
3081
// sends setBreakpoints/setFunctionBreakpoints/configurationDone as a burst
3082
// without waiting for each response. That byte then survived compaction as a
3083
// leading NUL, so every later strstr() for the header terminator saw an empty
3084
// string and the connection stopped dispatching until it overflowed.
3085
static bool try_extract_dap_message(char *out, size_t out_size, int *consumed) {
3086
    // Look for Content-Length header
3087
    char *header_end = strstr(recv_buf, "\r\n\r\n");
3088
    if (!header_end) return false;
3089
3090
    int header_size = (int)(header_end - recv_buf) + 4; // include \r\n\r\n
3091
3092
    // Parse Content-Length
3093
    int content_length = -1;
3094
    char *cl = recv_buf;
3095
    while (cl < header_end) {
3096
        if (strncmp(cl, "Content-Length:", 15) == 0) {
3097
            cl += 15;
3098
            while (*cl == ' ' || *cl == '\t') cl++;
3099
            content_length = atoi(cl);
3100
            break;
3101
        }
3102
        // Skip to next line
3103
        char *nl = strchr(cl, '\n');
3104
        if (!nl || nl >= header_end) break;
3105
        cl = nl + 1;
3106
    }
3107
3108
    if (content_length < 0) {
3109
        fprintf(stderr, "[dap] Missing Content-Length header\n");
3110
        *consumed = header_size;
3111
        return false;
3112
    }
3113
    if (content_length >= (int)out_size) {
3114
        // Too large to be one of ours. The body may not even have arrived yet,
3115
        // so consuming header + length here would skip bytes that are not in
3116
        // the buffer and then parse the tail of the body as a fresh header.
3117
        // There is no way to resynchronise on a stream we cannot buffer.
3118
        fprintf(stderr, "[dap] message of %d bytes is too large; dropping client\n",
3119
                content_length);
3120
        disconnect_client();
3121
        *consumed = 0;
3122
        return false;
3123
    }
3124
3125
    // Check if we have the complete body
3126
    if (recv_buf_len < header_size + content_length) return false;
3127
3128
    memcpy(out, recv_buf + header_size, (size_t)content_length);
3129
    out[content_length] = '\0';
3130
    *consumed = header_size + content_length;
3131
    return true;
3132
}
3133
3134
// ─── Public API ─────────────────────────────────────────────────────
3135
3136
int debug_server_init(int port) {
3137
    if (!platform_init()) return -1;
3138
3139
    listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
3140
    if (listen_sock == SOCKET_INVALID) {
3141
        fprintf(stderr, "[dap] Failed to create socket\n");
3142
        return -1;
3143
    }
3144
3145
    int optval = 1;
3146
    setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof(optval));
3147
3148
    struct sockaddr_in addr;
3149
    memset(&addr, 0, sizeof(addr));
3150
    addr.sin_family = AF_INET;
3151
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
3152
    addr.sin_port = htons((uint16_t)port);
3153
3154
    if (bind(listen_sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
3155
        fprintf(stderr, "[dap] Failed to bind to port %d: error %d\n", port, SOCK_ERRNO);
3156
        CLOSE_SOCKET(listen_sock);
3157
        listen_sock = SOCKET_INVALID;
3158
        return -1;
3159
    }
3160
3161
    if (listen(listen_sock, 1) != 0) {
3162
        fprintf(stderr, "[dap] Failed to listen: error %d\n", SOCK_ERRNO);
3163
        CLOSE_SOCKET(listen_sock);
3164
        listen_sock = SOCKET_INVALID;
3165
        return -1;
3166
    }
3167
3168
    set_nonblocking(listen_sock);
3169
    server_enabled = true;
3170
    server_port = port;
3171
    printf("[dap] Debug Adapter Protocol server listening on 127.0.0.1:%d\n", port);
3172
    return 0;
3173
}
3174
3175
static void send_dap_event_thread_started(void) {
3176
    cJSON *tb = cJSON_CreateObject();
3177
    cJSON_AddStringToObject(tb, "reason", "started");
3178
    cJSON_AddNumberToObject(tb, "threadId", 1);
3179
    send_dap_event("thread", tb);
3180
}
3181
3182
int debug_server_poll(void) {
3183
    if (!server_enabled) return 0;
3184
3185
    try_accept();
3186
    if (client_sock == SOCKET_INVALID) return 0;
3187
3188
    // Push any budget overruns before reading: a client that never polls still
3189
    // learns that the guest is missing its frame.
3190
    perf_poll_overruns();
3191
3192
    // Receive data (non-blocking)
3193
    int space = RECV_BUF_SIZE - recv_buf_len - 2; // -2 for null terminator space
3194
    if (space <= 0) {
3195
        fprintf(stderr, "[dap] Receive buffer overflow, disconnecting\n");
3196
        disconnect_client();
3197
        return 0;
3198
    }
3199
3200
    int n = recv(client_sock, recv_buf + recv_buf_len, space, 0);
3201
    if (n > 0) {
3202
        recv_buf_len += n;
3203
        recv_buf[recv_buf_len] = '\0';
3204
    } else if (n == 0) {
3205
        disconnect_client();
3206
        return 0;
3207
    } else {
3208
        if (!WOULD_BLOCK) {
3209
            disconnect_client();
3210
            return 0;
3211
        }
3212
    }
3213
3214
    // Process all complete DAP messages in the buffer
3215
    int mode_changed = 0;
3216
    for (;;) {
3217
        static char body[RECV_BUF_SIZE];
3218
        int consumed = 0;
3219
        if (!try_extract_dap_message(body, sizeof body, &consumed)) {
3220
            if (consumed > 0) {
3221
                int remaining = recv_buf_len - consumed;
3222
                if (remaining > 0) memmove(recv_buf, recv_buf + consumed, remaining);
3223
                if (remaining < 0) remaining = 0;
3224
                recv_buf_len = remaining;
3225
                recv_buf[recv_buf_len] = '\0';
3226
                continue;
3227
            }
3228
            break;
3229
        }
3230
3231
        // Compact before dispatching. A handler can call back into the socket
3232
        // (an event, or an error response), and leaving a consumed message in
3233
        // the buffer would let it be seen twice.
3234
        int remaining = recv_buf_len - consumed;
3235
        if (remaining > 0) {
3236
            memmove(recv_buf, recv_buf + consumed, remaining);
3237
        }
3238
        recv_buf_len = remaining > 0 ? remaining : 0;
3239
        recv_buf[recv_buf_len] = '\0';
3240
3241
        if (dispatch_dap(body)) {
3242
            mode_changed = 1;
3243
        }
3244
3245
    }
3246
3247
    return mode_changed;
3248
}
3249
3250
void debug_server_note_resumed(void) {
3251
    // A resume the client did not ask for -- someone pressed F5 or F10 in the
3252
    // SDL debug window while a session was attached. It is still watching a
3253
    // machine it believes is halted, so tell it. Resumes that came from a DAP
3254
    // request have already sent their own event and cleared the flag.
3255
    if (dap_stop_announced && server_enabled && client_sock != SOCKET_INVALID) {
3256
        send_continued_event();
3257
        return;   // send_continued_event() clears the flag
3258
    }
3259
    dap_stop_announced = false;
3260
}
3261
3262
void debug_server_notify_stopped(const char *reason) {
3263
    if (!server_enabled || client_sock == SOCKET_INVALID) return;
3264
    dap_stop_announced = true;
3265
3266
    cJSON *body = cJSON_CreateObject();
3267
3268
    // Map our reason strings to DAP reason strings
3269
    if (!strcmp(reason, "breakpoint")) {
3270
        cJSON_AddStringToObject(body, "reason", "breakpoint");
3271
        // Find which DAP breakpoint was hit
3272
        cJSON *hit_ids = cJSON_CreateArray();
3273
        for (int i = 0; i < num_dap_bps; i++) {
3274
            // Both halves of the key, and only breakpoints that are actually
3275
            // armed: pending ones hold addr 0 and were reported as hit every
3276
            // time the machine stopped at $0000.
3277
            if (dap_bps[i].verified && dap_bps[i].addr == regs.pc &&
3278
                dap_bps[i].bank == regs.k) {
3279
                cJSON_AddItemToArray(hit_ids, cJSON_CreateNumber(dap_bps[i].dap_id));
3280
            }
3281
        }
3282
        if (cJSON_GetArraySize(hit_ids) > 0) {
3283
            cJSON_AddItemToObject(body, "hitBreakpointIds", hit_ids);
3284
        } else {
3285
            cJSON_Delete(hit_ids);
3286
        }
3287
    } else if (!strcmp(reason, "step")) {
3288
        cJSON_AddStringToObject(body, "reason", "step");
3289
    } else if (!strcmp(reason, "user")) {
3290
        cJSON_AddStringToObject(body, "reason", "pause");
3291
    } else {
3292
        cJSON_AddStringToObject(body, "reason", reason);
3293
    }
3294
3295
    cJSON_AddNumberToObject(body, "threadId", 1);
3296
    cJSON_AddBoolToObject(body, "allThreadsStopped", true);
3297
    send_dap_event("stopped", body);
3298
}
3299
3300
bool debug_server_has_client(void) {
3301
    return server_enabled && client_sock != SOCKET_INVALID;
3302
}
3303
3304
bool debug_server_is_enabled(void) {
3305
    return server_enabled;
3306
}
3307
3308
void debug_server_shutdown(void) {
3309
    if (client_sock != SOCKET_INVALID) {
3310
        // Signal the debuggee exited, then that the session terminated.
3311
        cJSON *eb = cJSON_CreateObject();
3312
        cJSON_AddNumberToObject(eb, "exitCode", 0);
3313
        send_dap_event("exited", eb);
3314
        send_dap_event("terminated", NULL);
3315
        CLOSE_SOCKET(client_sock);
3316
        client_sock = SOCKET_INVALID;
3317
    }
3318
    if (listen_sock != SOCKET_INVALID) {
3319
        CLOSE_SOCKET(listen_sock);
3320
        listen_sock = SOCKET_INVALID;
3321
    }
3322
    server_enabled = false;
3323
    dbg_info_free();
3324
    platform_cleanup();
3325
    printf("[dap] Shut down\n");
3326
}