OXYGENHUB
◆ main enhanced-x16-emulator / src / control_backend.c
18.3 KiB · C · last changed feat: the RRDC control port (-controlport), contract 0.6: GET/POST /perf from the Performance panel's measurements, built by perf_json.c with DAP's x16/perfStats; a guest marker region named in advance; a framed guest's regions carried across the frame boundary and an interrupt after $F1 counted as work (#1) 25 days ago source blame history raw
2980b7b6 Andy McCall ↶ 1
/* control_backend.c — Commander X16 backend for the Retro Remote Debug
2
 * Controller (retro_control.h, from extern/retro-remote-debug-controller):
3
 * the control port a test harness drives, -controlport N.
4
 *
5
 * Contract 0.6: everything the plain x16emu fork's backend serves (memory,
6
 * registers, the screen, stepping, keys, reset, audio, the pointer, pads),
7
 * and GET/POST /perf, answered from the same measurements as the Performance
8
 * panel and the DAP server's x16/perfStats (perf_json.c). /perf needs cJSON,
9
 * as the DAP server does: without it the backend serves 0.5. */
10
11
#include "retro_control.h"
12
13
#include "glue.h"      /* regs, BRAM, num_ram_banks, machine_reset */
14
#include "memory.h"    /* debug_read6502, write6502, memory_get_ram_bank/rom_bank */
15
#include "video.h"     /* video_get_framebuffer, video_get_frame_count, video_space_read */
16
#include "keyboard.h"  /* handle_keyboard */
17
#include "i2c.h"       /* mouse_move / mouse_button_* / mouse_send_state */
18
#include "audio.h"     /* AUDIO_SAMPLERATE */
19
#include "joystick.h"  /* joystick_set_virtual / joystick_clear_virtual */
20
21
#include <SDL.h>
22
#include <stdbool.h>
23
#include <stdio.h>
24
#include <stdlib.h>
25
#include <string.h>
26
27
extern uint32_t host_sample_rate;   /* audio.c: the real (post-SDL) output rate */
28
29
/* Debug-read into out. bank<0 => current bank. $0000-$9FFF is low RAM
30
 * (bank-independent); $A000-$BFFF is banked RAM; $C000+ is ROM. */
31
static uint32_t
32
x16_read_mem(uint32_t addr, int32_t bank, uint32_t len, uint8_t *out, uint32_t cap)
33
{
34
	int16_t x16bank = (bank >= 0) ? (int16_t)bank : USE_CURRENT_X16_BANK;
35
	uint32_t n = 0;
36
	for (uint32_t i = 0; i < len && n < cap; i++) {
37
		uint16_t a = (uint16_t)(addr + i);
38
		out[n++] = debug_read6502(a, 0, x16bank);
39
	}
40
	return n;
41
}
42
43
/* Write (poke) len bytes at addr (0.3). Banked RAM with an explicit bank= goes
44
 * straight into that bank's backing store, the mirror of x16_read_mem: routed
45
 * through write6502 it would land in whatever bank the CPU had mapped at the
46
 * pause (the KERNAL's IRQ handler maps bank 0). Without bank= the poke hits
47
 * the current bank, as a CPU write would. */
48
static uint32_t
49
x16_write_mem(uint32_t addr, int32_t bank, uint32_t len, const uint8_t *in)
50
{
51
	for (uint32_t i = 0; i < len; i++) {
52
		uint16_t a = (uint16_t)(addr + i);
53
		if (bank >= 0 && a >= 0xa000 && a < 0xc000) {
54
			if ((uint32_t)bank < (uint32_t)num_ram_banks) {
55
				BRAM[((uint32_t)bank << 13) + a - 0xa000] = in[i];
56
			}
57
			continue;
58
		}
59
		write6502(a, 0, in[i]);
60
	}
61
	return len;
62
}
63
64
/* --- Audio drain (0.3): a ring teed from the mixer. Producer (audio_render,
65
 * via x16_control_push_audio) and consumer both run on the emulator thread. */
66
#define X16_AUD_RING 65536              /* int16 samples (~0.67 s stereo @ 48.8 kHz) */
67
static int16_t  g_aud[X16_AUD_RING];
68
static uint32_t g_aud_head, g_aud_count, g_aud_dropped;
69
static int      g_aud_active;           /* capture only once the server is up */
70
71
void
72
x16_control_push_audio(int16_t l, int16_t r)
73
{
74
	if (!g_aud_active) return;
75
	if (g_aud_count + 2 > X16_AUD_RING) { g_aud_dropped += 2; return; }
76
	g_aud[(g_aud_head + g_aud_count) % X16_AUD_RING] = l; g_aud_count++;
77
	g_aud[(g_aud_head + g_aud_count) % X16_AUD_RING] = r; g_aud_count++;
78
}
79
80
static uint32_t
81
x16_capture_audio(int16_t *out, uint32_t cap, int *rate, int *channels, uint32_t *dropped)
82
{
83
	uint32_t n = 0;
84
	if (rate)     *rate     = host_sample_rate ? (int)host_sample_rate : (int)AUDIO_SAMPLERATE;
85
	if (channels) *channels = 2;
86
	if (dropped)  *dropped  = g_aud_dropped;
87
	g_aud_dropped = 0;
88
	while (n < cap && g_aud_count) {
89
		out[n++] = g_aud[g_aud_head];
90
		g_aud_head = (g_aud_head + 1) % X16_AUD_RING;
91
		g_aud_count--;
92
	}
93
	return n;
94
}
95
96
static void
97
x16_get_regs_json(char *buf, size_t cap)
98
{
99
	snprintf(buf, cap,
100
		"{\"a\":%u,\"x\":%u,\"y\":%u,\"sp\":%u,\"pc\":%u,\"status\":%u,"
101
		"\"ram_bank\":%u,\"rom_bank\":%u}",
102
		(unsigned)regs.a, (unsigned)regs.xl, (unsigned)regs.yl,
103
		(unsigned)(regs.sp & 0xff), (unsigned)regs.pc, (unsigned)regs.status,
104
		(unsigned)memory_get_ram_bank(), (unsigned)memory_get_rom_bank());
105
}
106
107
static void
108
x16_get_framebuffer(retro_framebuffer_t *out)
109
{
110
	int w = 0, h = 0;
111
	out->pixels = video_get_framebuffer(&w, &h);
112
	out->width  = w;
113
	out->height = h;
114
	out->fmt    = RETRO_PIX_BGRA8888;   /* x16 framebuffer byte order */
115
}
116
117
static uint64_t
118
x16_get_frame_count(void)
119
{
120
	return video_get_frame_count();
121
}
122
123
/* Keys (0.2). text => the char's key (letters use the lowercase keycode);
124
 * code => a raw SDL scancode. */
125
static void
126
x16_key_event(SDL_Keycode sym, SDL_Scancode sc, int action)
127
{
128
	if (action == RETRO_KEY_DOWN) {
129
		handle_keyboard(true, sym, sc);
130
	} else if (action == RETRO_KEY_UP) {
131
		handle_keyboard(false, sym, sc);
132
	} else {
133
		handle_keyboard(true, sym, sc);
134
		handle_keyboard(false, sym, sc);
135
	}
136
}
137
138
static int
139
x16_inject_key(int is_text, uint32_t value, int action)
140
{
141
	SDL_Keycode sym;
142
	SDL_Scancode sc;
143
	if (is_text) {
144
		int c = (int)value;
145
		if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a';
146
		sym = (SDL_Keycode)c;
147
		sc  = SDL_GetScancodeFromKey(sym);
148
	} else {
149
		sc  = (SDL_Scancode)value;
150
		sym = SDL_GetKeyFromScancode(sc);
151
	}
152
	if (sc == SDL_SCANCODE_UNKNOWN) {
153
		return 0;
154
	}
155
	x16_key_event(sym, sc, action);
156
	return 1;
157
}
158
159
static void
160
x16_reset(void)
161
{
162
	machine_reset();
163
}
164
165
/* Pointer (0.4). The X16 mouse is a relative PS/2 device the KERNAL integrates
166
 * into sprite 0's position, so inject through mouse_move/mouse_send_state and
167
 * read the position back from sprite 0's attributes. */
168
#define VERA_SPRITE0_ATTR 0x1FC00U
169
static int g_ptr_buttons;               /* what this backend last pressed */
170
171
static int32_t
172
x16_pointer_x(void)
173
{
174
	return (int32_t)video_space_read(VERA_SPRITE0_ATTR + 2)
175
	     | ((int32_t)(video_space_read(VERA_SPRITE0_ATTR + 3) & 3) << 8);
176
}
177
static int32_t
178
x16_pointer_y(void)
179
{
180
	return (int32_t)video_space_read(VERA_SPRITE0_ATTR + 4)
181
	     | ((int32_t)(video_space_read(VERA_SPRITE0_ATTR + 5) & 3) << 8);
182
}
183
184
static int
185
x16_set_pointer(int absolute, int32_t x, int32_t y, int buttons)
186
{
187
	if (absolute) {
188
		mouse_move((int)(x - x16_pointer_x()), (int)(y - x16_pointer_y()));
189
	} else {
190
		mouse_move((int)x, (int)y);
191
	}
192
	if (buttons >= 0) {
193
		if (buttons & 1) mouse_button_down(0); else mouse_button_up(0);
194
		if (buttons & 2) mouse_button_down(1); else mouse_button_up(1);
195
		g_ptr_buttons = buttons & 3;
196
	}
197
	mouse_send_state();
198
	return 1;
199
}
200
201
static int
202
x16_get_pointer(int32_t *x, int32_t *y, int *buttons)
203
{
204
	*x = x16_pointer_x();
205
	*y = x16_pointer_y();
206
	*buttons = g_ptr_buttons;
207
	return 1;
208
}
209
210
/* --- Pads (0.5) ---------------------------------------------------------
211
 * RRDC's canonical layout (bit 0 LEFT, 1 RIGHT, 2 UP, 3 DOWN, 4 A, 5 B, 6 X,
212
 * 7 Y, 8 START, 9 SELECT, 10 L, 11 R) to the X16's SNES serial order, active
213
 * low: bit 0 B, 1 Y, 2 SELECT, 3 START, 4 UP, 5 DOWN, 6 LEFT, 7 RIGHT, 8 A,
214
 * 9 X, 10 L, 11 R. joystick.h's JOY_BIT_* names those bits after SDL's
215
 * buttons -- its JOY_BIT_A is SDL's bottom button, SNES B -- so read the
216
 * table below against the SNES names, not those.
217
 *
218
 * Driven through the emulator's own virtual joysticks (joystick_set_virtual,
219
 * as the DAP server's x16/joystick is), which the latch reads exactly as it
220
 * reads a real pad. */
221
static const uint8_t pad_canon_to_x16[12] = {
222
	6,  /* LEFT  */ 7,  /* RIGHT  */ 4,  /* UP */ 5,  /* DOWN */
223
	8,  /* A     */ 0,  /* B      */ 9,  /* X  */ 1,  /* Y    */
224
	3,  /* START */ 2,  /* SELECT */ 10, /* L  */ 11, /* R    */
225
};
226
227
static uint16_t
228
pad_canon_to_mask(int buttons)
229
{
230
	uint16_t mask = 0xffff;
231
	for (int b = 0; b < 12; ++b) {
232
		if (buttons & (1 << b)) {
233
			mask &= (uint16_t)~(1u << pad_canon_to_x16[b]);
234
		}
235
	}
236
	return mask;
237
}
238
239
static int
240
pad_mask_to_canon(uint16_t mask)
241
{
242
	int buttons = 0;
243
	for (int b = 0; b < 12; ++b) {
244
		if ((mask & (1u << pad_canon_to_x16[b])) == 0) {
245
			buttons |= (1 << b);
246
		}
247
	}
248
	return buttons;
249
}
250
251
static int
252
x16_set_pad(int index, int buttons, int connected)
253
{
254
	if (index < 0 || index >= NUM_JOYSTICKS) {
255
		return 0;
256
	}
257
	if (connected == 0) {
258
		joystick_clear_virtual(index);
259
		return 1;
260
	}
261
	if (buttons >= 0) {
262
		joystick_set_virtual(index, pad_canon_to_mask(buttons));
263
	} else if (connected == 1 && !joystick_virtual_active(index)) {
264
		joystick_set_virtual(index, 0xffff);   /* present, nothing held */
265
	}
266
	return 1;
267
}
268
269
static int
270
x16_get_pad(int index, int *buttons, int *connected)
271
{
272
	if (index < 0 || index >= NUM_JOYSTICKS) {
273
		return 0;
274
	}
275
	joystick_debug_state_t st;
276
	joystick_debug_get_state(&st);
277
	const joystick_slot_debug_t *s = &st.slots[index];
278
	*connected = (s->virtual_active || s->controller_bound) ? 1 : 0;
279
	*buttons   = pad_mask_to_canon(*connected ? s->button_mask : 0xffff);
280
	return 1;
281
}
282
283
/* --- Performance metrics (0.6) ------------------------------------------- */
284
#ifdef HAS_DAP
285
#include "perf_json.h"
286
#include "perf_budget.h"
287
#include "vera_bandwidth.h"
288
#include "sprite_trace.h"
289
290
/* A query string's value for `key`, or NULL. */
291
static const char *
292
q_find(const char *q, const char *key, size_t *len)
293
{
294
	const size_t kl = strlen(key);
295
	for (const char *p = q; p && *p; ) {
296
		if (!strncmp(p, key, kl) && p[kl] == '=') {
297
			const char *v = p + kl + 1;
298
			const char *e = strchr(v, '&');
299
			*len = e ? (size_t)(e - v) : strlen(v);
300
			return v;
301
		}
302
		p = strchr(p, '&');
303
		if (p) p++;
304
	}
305
	return NULL;
306
}
307
308
static long
309
q_long(const char *q, const char *key, long def)
310
{
311
	size_t n;
312
	const char *v = q_find(q, key, &n);
313
	return v ? strtol(v, NULL, 0) : def;
314
}
315
316
/* Measuring is the remote owner's to switch: a client asking for numbers is a
317
 * client that wants them collected (the SPEC lets a backend start on the
318
 * first request), and the panel or a DAP client closing does not stop it. */
319
static void
320
perf_arm_remote(void)
321
{
322
	if (!perf_budget_is_enabled()) {
323
		perf_budget_arm(PERF_OWNER_REMOTE, true);
324
	}
325
	if (!vera_bandwidth_is_enabled()) {
326
		vera_bandwidth_arm(VERA_BW_OWNER_REMOTE, true);
327
	}
328
}
329
330
static cJSON *
331
perf_frame_record(const perf_frame_t *f)
332
{
333
	cJSON *o = perf_json_frame(f);
334
	cJSON *z = cJSON_CreateArray();
335
	const int nz = perf_budget_zone_count();
336
	for (int i = 0; i < nz && i < PERF_RING_ZONES; i++) {
337
		cJSON_AddItemToArray(z, cJSON_CreateNumber(f->zone[i]));
338
	}
339
	cJSON_AddItemToObject(o, "zones", z);
340
	return o;
341
}
342
343
static cJSON *
344
perf_zones_json(const float *secs, int nsecs)
345
{
346
	cJSON *zones = cJSON_CreateArray();
347
	perf_frame_t last;
348
	const bool have_last = perf_budget_last_frame(&last);
349
	const int nz = perf_budget_zone_count();
350
	for (int i = 0; i < nz; i++) {
351
		perf_zone_t z;
352
		if (!perf_budget_zone_get(i, &z)) continue;
353
		cJSON *o = cJSON_CreateObject();
354
		cJSON_AddNumberToObject(o, "id", i);
355
		cJSON_AddStringToObject(o, "name", z.name);
356
		cJSON_AddStringToObject(o, "kind", z.is_marker ? "marker" : "address");
357
		if (z.is_marker) {
358
			cJSON_AddNumberToObject(o, "markerId", z.marker_id);
359
		} else {
360
			cJSON_AddNumberToObject(o, "start", z.start);
361
			cJSON_AddNumberToObject(o, "end", z.end);
362
			cJSON_AddNumberToObject(o, "bank", z.bank);
363
		}
364
		cJSON_AddBoolToObject(o, "idle", z.is_idle);
365
		cJSON_AddNumberToObject(o, "current", (have_last && i < PERF_RING_ZONES) ? last.zone[i] : 0);
366
		cJSON *w = cJSON_CreateObject();
367
		for (int k = 0; i < PERF_RING_ZONES && k < nsecs; k++) {   /* history: the first 8 only */
368
			perf_stat_t st;
369
			if (!perf_budget_zone_window(i, secs[k], &st)) continue;
370
			char name[32];
371
			perf_json_window_name(secs[k], name, sizeof name);
372
			cJSON_AddItemToObject(w, name, perf_json_stat(&st));
373
		}
374
		cJSON_AddItemToObject(o, "windows", w);
375
		cJSON_AddItemToArray(zones, o);
376
	}
377
	return zones;
378
}
379
380
/* The GET /perf body. settings_only: POST /perf's answer, the settings as they
381
 * stand (no windows, zones or frames). */
382
static char *
383
perf_body(const char *query, bool settings_only)
384
{
385
	cJSON *body = cJSON_CreateObject();
386
	cJSON_AddBoolToObject(body, "enabled", perf_budget_is_enabled());
387
	cJSON_AddItemToObject(body, "budget", perf_json_budget());
388
389
	perf_frame_t last;
390
	if (!perf_budget_last_frame(&last)) {
391
		memset(&last, 0, sizeof last);
392
	}
393
	cJSON_AddItemToObject(body, "current", perf_json_frame(&last));
394
395
	const bool lines = q_long(query, "lines", 0) != 0;
396
	if (!settings_only) {
397
		float secs[16];
398
		int nsecs = 0;
399
		size_t n;
400
		const char *v = q_find(query, "windows", &n);
401
		if (v) {
402
			char buf[256];
403
			if (n >= sizeof buf) n = sizeof buf - 1;
404
			memcpy(buf, v, n);
405
			buf[n] = 0;
406
			for (char *tok = strtok(buf, ","); tok && nsecs < 16; tok = strtok(NULL, ",")) {
407
				cJSON *item = cJSON_CreateString(tok);
408
				secs[nsecs++] = perf_json_window_seconds(item);
409
				cJSON_Delete(item);
410
			}
411
		} else {
412
			static const float defaults[] = {1.0f, 10.0f, 30.0f, 60.0f, 0.0f};
413
			for (; nsecs < 5; nsecs++) secs[nsecs] = defaults[nsecs];
414
		}
415
		cJSON *windows = cJSON_CreateArray();
416
		for (int i = 0; i < nsecs; i++) {
417
			cJSON *w = perf_json_window(secs[i]);
418
			if (w) cJSON_AddItemToArray(windows, w);
419
		}
420
		cJSON_AddItemToObject(body, "windows", windows);
421
422
		if (q_long(query, "zones", 1) != 0) {
423
			cJSON_AddItemToObject(body, "zones", perf_zones_json(secs, nsecs));
424
		}
425
426
		/* frames=n: the last n; since=f: every frame after f still held (the
427
		 * newest `frames` of them when both are given). Oldest first. */
428
		const long want  = q_long(query, "frames", 0);
429
		const long since = q_long(query, "since", -1);
430
		if (want > 0 || since >= 0) {
431
			const int have = perf_budget_frame_count();
432
			int take = 0;
433
			for (; take < have; take++) {
434
				perf_frame_t f;
435
				if (!perf_budget_frame_at(take, &f)) break;
436
				if (since >= 0 && (long)f.frame <= since) break;
437
				if (want > 0 && take >= want) break;
438
			}
439
			cJSON *frames = cJSON_CreateArray();
440
			for (int i = take - 1; i >= 0; i--) {
441
				perf_frame_t f;
442
				if (perf_budget_frame_at(i, &f)) {
443
					cJSON_AddItemToArray(frames, perf_frame_record(&f));
444
				}
445
			}
446
			cJSON_AddItemToObject(body, "frames", frames);
447
		}
448
	}
449
450
	cJSON *platform = cJSON_CreateObject();
451
	cJSON *bw = perf_json_bandwidth();
452
	if (lines) {
453
		cJSON_AddItemToObject(bw, "lines", perf_json_bandwidth_lines());
454
	}
455
	cJSON_AddItemToObject(platform, "bandwidth", bw);
456
	cJSON_AddItemToObject(platform, "sprites", perf_json_sprites(lines));
457
	cJSON_AddItemToObject(body, "platform", platform);
458
459
	char *out = cJSON_PrintUnformatted(body);
460
	cJSON_Delete(body);
461
	return out;   /* cJSON's allocator is malloc: the server free()s it */
462
}
463
464
static char *
465
x16_get_perf(const char *query)
466
{
467
	perf_arm_remote();
468
	return perf_body(query ? query : "", false);
469
}
470
471
static char *
472
perf_error(int *status, const char *msg)
473
{
474
	*status = 400;
475
	char *b = (char *)malloc(strlen(msg) + 16);
476
	if (b) sprintf(b, "{\"error\":\"%s\"}", msg);
477
	return b;
478
}
479
480
static char *
481
x16_set_perf(const char *text, int *status)
482
{
483
	cJSON *args = cJSON_Parse(text);
484
	if (!cJSON_IsObject(args)) {
485
		cJSON_Delete(args);
486
		return perf_error(status, "body is not a JSON object");
487
	}
488
	/* Check every field's type before applying any, so a bad body changes
489
	 * nothing. */
490
	cJSON *en = cJSON_GetObjectItemCaseSensitive(args, "enabled");
491
	cJSON *fps = cJSON_GetObjectItemCaseSensitive(args, "targetFps");
492
	cJSON *idle = cJSON_GetObjectItemCaseSensitive(args, "idleMode");
493
	cJSON *hist = cJSON_GetObjectItemCaseSensitive(args, "historyFrames");
494
	cJSON *zones = cJSON_GetObjectItemCaseSensitive(args, "zones");
495
	cJSON *rst = cJSON_GetObjectItemCaseSensitive(args, "reset");
496
	cJSON *spr = cJSON_GetObjectItemCaseSensitive(args, "sprites");
497
	const char *bad =
498
		(en && !cJSON_IsBool(en)) ? "enabled is not a boolean" :
499
		(fps && !(cJSON_IsNumber(fps) && fps->valuedouble > 0)) ? "targetFps is not a positive number" :
500
		(idle && !(cJSON_IsString(idle) && (!strcmp(idle->valuestring, "auto") ||
501
		           !strcmp(idle->valuestring, "markers") || !strcmp(idle->valuestring, "none"))))
502
			? "idleMode is not auto, markers or none" :
503
		(hist && !(cJSON_IsNumber(hist) && hist->valuedouble >= 2)) ? "historyFrames is not a number of 2 or more" :
504
		(zones && !cJSON_IsArray(zones)) ? "zones is not an array" :
505
		(rst && !cJSON_IsBool(rst)) ? "reset is not a boolean" :
506
		(spr && !cJSON_IsBool(spr)) ? "sprites is not a boolean" : NULL;
507
	if (bad) {
508
		cJSON_Delete(args);
509
		return perf_error(status, bad);
510
	}
511
512
	if (en) {
513
		const bool want = cJSON_IsTrue(en);
514
		perf_budget_arm(PERF_OWNER_REMOTE, want);
515
		vera_bandwidth_arm(VERA_BW_OWNER_REMOTE, want);
516
	}
517
	if (fps)  perf_budget_set_target_fps((float)fps->valuedouble);
518
	if (idle) {
519
		perf_budget_set_idle_mode(!strcmp(idle->valuestring, "markers") ? PERF_IDLE_MARKERS :
520
		                          !strcmp(idle->valuestring, "none")    ? PERF_IDLE_NONE
521
		                                                                : PERF_IDLE_AUTO);
522
	}
523
	if (hist) perf_budget_set_capacity((int)hist->valuedouble);
524
	if (zones) {
525
		/* Replaces the list: address zones and marker names alike. */
526
		perf_budget_zone_clear();
527
		cJSON *z;
528
		cJSON_ArrayForEach(z, zones) {
529
			if (!cJSON_IsObject(z)) continue;
530
			cJSON *jn = cJSON_GetObjectItemCaseSensitive(z, "name");
531
			const char *name = cJSON_IsString(jn) ? jn->valuestring : "zone";
532
			cJSON *jm = cJSON_GetObjectItemCaseSensitive(z, "marker");
533
			if (cJSON_IsNumber(jm)) {
534
				perf_budget_zone_add_marker(name, (uint8_t)jm->valuedouble);
535
				continue;
536
			}
537
			cJSON *js = cJSON_GetObjectItemCaseSensitive(z, "start");
538
			cJSON *je = cJSON_GetObjectItemCaseSensitive(z, "end");
539
			cJSON *jb = cJSON_GetObjectItemCaseSensitive(z, "bank");
540
			cJSON *ji = cJSON_GetObjectItemCaseSensitive(z, "idle");
541
			if (!cJSON_IsNumber(js) || !cJSON_IsNumber(je)) continue;
542
			perf_budget_zone_add(name, (uint16_t)js->valuedouble, (uint16_t)je->valuedouble,
543
			                     cJSON_IsNumber(jb) ? (int16_t)jb->valuedouble : (int16_t)-1,
544
			                     cJSON_IsTrue(ji));
545
		}
546
	}
547
	if (spr) sprite_trace_set_enabled(cJSON_IsTrue(spr));
548
	if (rst && cJSON_IsTrue(rst)) {
549
		perf_budget_reset();
550
		vera_bandwidth_reset();
551
		if (sprite_trace_active) sprite_trace_reset();
552
	}
553
	cJSON_Delete(args);
554
	*status = 200;
555
	return perf_body("", true);
556
}
557
#endif /* HAS_DAP */
558
559
static const retro_control_backend_t x16_backend = {
560
	.platform        = "x16",
561
	.emulator        = "x16emu-enhanced",
562
	.read_mem        = x16_read_mem,
563
	.get_regs_json   = x16_get_regs_json,
564
	.get_framebuffer = x16_get_framebuffer,
565
	.get_frame_count = x16_get_frame_count,
566
	.inject_key      = x16_inject_key,
567
	.reset           = x16_reset,
568
	.write_mem       = x16_write_mem,
569
	.capture_audio   = x16_capture_audio,
570
	.set_pointer     = x16_set_pointer,
571
	.get_pointer     = x16_get_pointer,
572
	.set_pad         = x16_set_pad,
573
	.get_pad         = x16_get_pad,
574
#ifdef HAS_DAP
575
	.get_perf        = x16_get_perf,
576
	.set_perf        = x16_set_perf,
577
#endif
578
};
579
580
bool control_port_active = false;
581
582
int
583
x16_control_start(int port)
584
{
585
	g_aud_active = 1;
586
	const int rc = retro_control_start(port, &x16_backend);
587
	control_port_active = (rc == 0);
588
	return rc;
589
}