OXYGENHUB
# 1b40a077e41f retro-remote-debug-controller / core / retro_control.c
24.7 KiB · C · last changed 0.5.0: game controller injection (POST/GET /pad) 3 months ago source blame history raw
1
/* retro_control.c — portable emulator control server. See retro_control.h
2
 * and ../SPEC.md. POSIX (Linux/macOS); on Windows the entry points are no-ops.
3
 *
4
 * One accept thread, one request in flight at a time. State reads are marshalled
5
 * to the emulator thread via retro_control_service() so they observe a
6
 * consistent machine; /step is driven cooperatively by retro_control_on_frame().
7
 */
8
#include "retro_control.h"
9
10
#include <stdio.h>
11
#include <stdlib.h>
12
#include <string.h>
13
14
#if defined(_WIN32)
15
/* No server on Windows for now — stub out so the fork still builds. */
16
int  retro_control_start(int port, const retro_control_backend_t *b) { (void)port; (void)b; return -1; }
17
void retro_control_service(void) {}
18
int  retro_control_running(void) { return 1; }
19
void retro_control_on_frame(void) {}
20
void retro_control_stop(void) {}
21
#else
22
23
#include <pthread.h>
24
#include <unistd.h>
25
#include <errno.h>
26
#include <limits.h>
27
#include <signal.h>
28
#include <sys/socket.h>
29
#include <netinet/in.h>
30
#include <arpa/inet.h>
31
32
#ifndef MSG_NOSIGNAL
33
#define MSG_NOSIGNAL 0
34
#endif
35
36
#define MEM_CAP (1u << 20)   /* /mem hard cap: 1 MiB per request */
37
38
static const retro_control_backend_t *g_be = NULL;
39
static int       g_listen_fd = -1;
40
static pthread_t g_thread;
41
42
static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER;
43
static pthread_cond_t  g_cv   = PTHREAD_COND_INITIALIZER;
44
45
/* run control: -1 free-run, 0 halted, >0 run this many frames then halt */
46
static long g_frames_to_run = -1;
47
static int  g_step_waiting  = 0;
48
49
/* single-slot marshalled request. */
50
typedef enum { REQ_NONE = 0, REQ_MEM, REQ_REGS, REQ_SHOT, REQ_KEY, REQ_RESET,
51
               REQ_WRITE, REQ_AUDIO, REQ_PTR_SET, REQ_PTR_GET,
52
               REQ_PAD_SET, REQ_PAD_GET } req_t;
53
static volatile req_t g_req = REQ_NONE;
54
static uint32_t g_req_addr, g_req_len;
55
static int32_t  g_req_bank;
56
static const uint8_t *g_write_data;   /* POST /mem body (HTTP-thread owned) */
57
static int      g_key_is_text;   /* 1 => g_key_value is a char code point */
58
static uint32_t g_key_value;
59
static int      g_key_action;    /* retro_key_action_t */
60
static int      g_ptr_absolute;  /* 1 => (x,y) absolute; 0 => relative deltas */
61
static int32_t  g_ptr_x, g_ptr_y;
62
static int      g_ptr_buttons;   /* button bitmask, or -1 = leave unchanged */
63
static int      g_pad_index;     /* which pad (0-based) */
64
static int      g_pad_buttons;   /* held mask, or -1 = leave unchanged */
65
static int      g_pad_connected; /* 1 plug, 0 unplug, -1 = unchanged */
66
static char     g_resp_extra[64];     /* extra response header line, or "" */
67
68
static uint8_t    *g_resp_body  = NULL;
69
static size_t      g_resp_len   = 0;
70
static int         g_resp_status = 200;
71
static const char *g_resp_ctype = "application/octet-stream";
72
static int         g_resp_ready = 0;
73
74
/* ---- emulator-thread side ------------------------------------------------ */
75
76
static void build_ppm(void)
77
{
78
    retro_framebuffer_t fb;
79
    memset(&fb, 0, sizeof fb);
80
    g_be->get_framebuffer(&fb);
81
    int w = fb.width, h = fb.height;
82
    int bpp = (fb.fmt == RETRO_PIX_RGB888) ? 3 : 4;
83
84
    char hdr[64];
85
    int hl = snprintf(hdr, sizeof hdr, "P6\n%d %d\n255\n", w, h);
86
    size_t total = (size_t)hl + (size_t)3 * w * h;
87
    uint8_t *out = (uint8_t *)malloc(total ? total : 1);
88
    if (!out) { g_resp_body = NULL; g_resp_len = 0; g_resp_status = 500; return; }
89
90
    memcpy(out, hdr, hl);
91
    uint8_t *p = out + hl;
92
    const uint8_t *s = fb.pixels;
93
    for (int i = 0; i < w * h; i++) {
94
        const uint8_t *px = s + (size_t)i * bpp;
95
        if (fb.fmt == RETRO_PIX_BGRA8888) {
96
            p[0] = px[2]; p[1] = px[1]; p[2] = px[0];
97
        } else {                       /* RGB888 / RGBA8888 */
98
            p[0] = px[0]; p[1] = px[1]; p[2] = px[2];
99
        }
100
        p += 3;
101
    }
102
    g_resp_body = out;
103
    g_resp_len = total;
104
    g_resp_status = 200;
105
    g_resp_ctype = "image/x-portable-pixmap";
106
}
107
108
#define AUDIO_MAX_SAMPLES (1u << 18)   /* drain cap: 256K int16 (~2.7 s stereo @48.8k) */
109
110
static void le16(uint8_t *p, uint16_t v) { p[0] = v; p[1] = v >> 8; }
111
static void le32(uint8_t *p, uint32_t v) { p[0] = v; p[1] = v >> 8; p[2] = v >> 16; p[3] = v >> 24; }
112
113
/* Drain the backend's audio ring and wrap it as a canonical PCM WAV. */
114
static void build_wav(void)
115
{
116
    static int16_t samp[AUDIO_MAX_SAMPLES];
117
    int rate = 48000, channels = 1;
118
    uint32_t dropped = 0;
119
    uint32_t n = g_be->capture_audio(samp, AUDIO_MAX_SAMPLES, &rate, &channels, &dropped);
120
    if (channels < 1) channels = 1;
121
    if (rate < 1) rate = 1;
122
123
    uint32_t data_bytes = n * 2;               /* int16 samples */
124
    uint32_t total = 44 + data_bytes;
125
    uint8_t *out = (uint8_t *)malloc(total);
126
    if (!out) { g_resp_body = NULL; g_resp_len = 0; g_resp_status = 500; return; }
127
128
    uint32_t byte_rate = (uint32_t)rate * channels * 2;
129
    memcpy(out, "RIFF", 4);       le32(out + 4, 36 + data_bytes);
130
    memcpy(out + 8, "WAVE", 4);
131
    memcpy(out + 12, "fmt ", 4);  le32(out + 16, 16);
132
    le16(out + 20, 1);            le16(out + 22, (uint16_t)channels);
133
    le32(out + 24, (uint32_t)rate);
134
    le32(out + 28, byte_rate);    le16(out + 32, (uint16_t)(channels * 2));
135
    le16(out + 34, 16);
136
    memcpy(out + 36, "data", 4);  le32(out + 40, data_bytes);
137
    memcpy(out + 44, samp, data_bytes);
138
139
    g_resp_body = out; g_resp_len = total; g_resp_status = 200;
140
    g_resp_ctype = "audio/wav";
141
    snprintf(g_resp_extra, sizeof g_resp_extra,
142
             "X-Rrdc-Audio-Dropped: %u\r\n", (unsigned)dropped);
143
}
144
145
void retro_control_service(void)
146
{
147
    if (!g_be || g_req == REQ_NONE) return;   /* cheap unlocked fast-path */
148
149
    pthread_mutex_lock(&g_lock);
150
    g_resp_extra[0] = 0;
151
    switch (g_req) {
152
    case REQ_MEM: {
153
        uint32_t len = g_req_len > MEM_CAP ? MEM_CAP : g_req_len;
154
        uint8_t *buf = (uint8_t *)malloc(len ? len : 1);
155
        uint32_t got = 0;
156
        if (buf && g_be->read_mem) got = g_be->read_mem(g_req_addr, g_req_bank, len, buf, len);
157
        g_resp_body = buf; g_resp_len = got;
158
        g_resp_status = g_be->read_mem ? 200 : 501;
159
        g_resp_ctype = "application/octet-stream";
160
        break;
161
    }
162
    case REQ_REGS: {
163
        char *buf = (char *)malloc(1024);
164
        if (buf && g_be->get_regs_json) { g_be->get_regs_json(buf, 1024); }
165
        else if (buf) { buf[0] = 0; }
166
        g_resp_body = (uint8_t *)buf; g_resp_len = buf ? strlen(buf) : 0;
167
        g_resp_status = g_be->get_regs_json ? 200 : 501;
168
        g_resp_ctype = "application/json";
169
        break;
170
    }
171
    case REQ_SHOT:
172
        if (g_be->get_framebuffer) build_ppm();
173
        else { g_resp_body = NULL; g_resp_len = 0; g_resp_status = 501; }
174
        break;
175
    case REQ_KEY: {
176
        const char *json;
177
        if (!g_be->inject_key) {
178
            json = "{\"error\":\"not implemented\"}"; g_resp_status = 501;
179
        } else if (g_be->inject_key(g_key_is_text, g_key_value, g_key_action)) {
180
            json = "{\"injected\":true}"; g_resp_status = 200;
181
        } else {
182
            json = "{\"error\":\"unmapped key\"}"; g_resp_status = 400;
183
        }
184
        size_t l = strlen(json);
185
        uint8_t *b = (uint8_t *)malloc(l + 1);
186
        if (b) memcpy(b, json, l + 1);
187
        g_resp_body = b; g_resp_len = b ? l : 0;
188
        g_resp_ctype = "application/json";
189
        break;
190
    }
191
    case REQ_RESET: {
192
        const char *json;
193
        if (g_be->reset) { g_be->reset(); json = "{\"reset\":true}"; g_resp_status = 200; }
194
        else { json = "{\"error\":\"not implemented\"}"; g_resp_status = 501; }
195
        size_t l = strlen(json);
196
        uint8_t *b = (uint8_t *)malloc(l + 1);
197
        if (b) memcpy(b, json, l + 1);
198
        g_resp_body = b; g_resp_len = b ? l : 0;
199
        g_resp_ctype = "application/json";
200
        break;
201
    }
202
    case REQ_WRITE: {
203
        const char *json; char *b;
204
        if (!g_be->write_mem) {
205
            json = "{\"error\":\"not implemented\"}"; g_resp_status = 501;
206
            size_t l = strlen(json); b = (char *)malloc(l + 1);
207
            if (b) memcpy(b, json, l + 1);
208
            g_resp_len = b ? l : 0;
209
        } else {
210
            uint32_t wrote = g_be->write_mem(g_req_addr, g_req_bank, g_req_len, g_write_data);
211
            b = (char *)malloc(48);
212
            g_resp_len = b ? (size_t)snprintf(b, 48, "{\"written\":%u}", (unsigned)wrote) : 0;
213
            g_resp_status = 200;
214
        }
215
        g_resp_body = (uint8_t *)b;
216
        g_resp_ctype = "application/json";
217
        break;
218
    }
219
    case REQ_AUDIO:
220
        if (g_be->capture_audio) build_wav();
221
        else {
222
            const char *json = "{\"error\":\"not implemented\"}";
223
            size_t l = strlen(json); uint8_t *b = (uint8_t *)malloc(l + 1);
224
            if (b) memcpy(b, json, l + 1);
225
            g_resp_body = b; g_resp_len = b ? l : 0; g_resp_status = 501;
226
            g_resp_ctype = "application/json";
227
        }
228
        break;
229
    case REQ_PTR_SET: {
230
        const char *json;
231
        if (!g_be->set_pointer) {
232
            json = "{\"error\":\"not implemented\"}"; g_resp_status = 501;
233
        } else if (g_be->set_pointer(g_ptr_absolute, g_ptr_x, g_ptr_y, g_ptr_buttons)) {
234
            json = "{\"injected\":true}"; g_resp_status = 200;
235
        } else {
236
            json = "{\"error\":\"no pointer\"}"; g_resp_status = 400;
237
        }
238
        size_t l = strlen(json);
239
        uint8_t *b = (uint8_t *)malloc(l + 1);
240
        if (b) memcpy(b, json, l + 1);
241
        g_resp_body = b; g_resp_len = b ? l : 0;
242
        g_resp_ctype = "application/json";
243
        break;
244
    }
245
    case REQ_PTR_GET: {
246
        char *b;
247
        if (!g_be->get_pointer) {
248
            const char *json = "{\"error\":\"not implemented\"}";
249
            size_t l = strlen(json); b = (char *)malloc(l + 1);
250
            if (b) memcpy(b, json, l + 1);
251
            g_resp_len = b ? l : 0; g_resp_status = 501;
252
        } else {
253
            int32_t x = 0, y = 0; int buttons = 0;
254
            int ok = g_be->get_pointer(&x, &y, &buttons);
255
            b = (char *)malloc(96);
256
            if (!b) { g_resp_len = 0; g_resp_status = 500; }
257
            else if (ok) {
258
                g_resp_len = (size_t)snprintf(b, 96,
259
                    "{\"x\":%ld,\"y\":%ld,\"buttons\":%d}",
260
                    (long)x, (long)y, buttons);
261
                g_resp_status = 200;
262
            } else {
263
                g_resp_len = (size_t)snprintf(b, 96, "{\"error\":\"no pointer\"}");
264
                g_resp_status = 400;
265
            }
266
        }
267
        g_resp_body = (uint8_t *)b;
268
        g_resp_ctype = "application/json";
269
        break;
270
    }
271
    case REQ_PAD_SET: {
272
        const char *json;
273
        if (!g_be->set_pad) {
274
            json = "{\"error\":\"not implemented\"}"; g_resp_status = 501;
275
        } else if (g_be->set_pad(g_pad_index, g_pad_buttons, g_pad_connected)) {
276
            json = "{\"injected\":true}"; g_resp_status = 200;
277
        } else {
278
            json = "{\"error\":\"no controller\"}"; g_resp_status = 400;
279
        }
280
        size_t l = strlen(json);
281
        uint8_t *b = (uint8_t *)malloc(l + 1);
282
        if (b) memcpy(b, json, l + 1);
283
        g_resp_body = b; g_resp_len = b ? l : 0;
284
        g_resp_ctype = "application/json";
285
        break;
286
    }
287
    case REQ_PAD_GET: {
288
        char *b;
289
        if (!g_be->get_pad) {
290
            const char *json = "{\"error\":\"not implemented\"}";
291
            size_t l = strlen(json); b = (char *)malloc(l + 1);
292
            if (b) memcpy(b, json, l + 1);
293
            g_resp_len = b ? l : 0; g_resp_status = 501;
294
        } else {
295
            int buttons = 0, connected = 0;
296
            int ok = g_be->get_pad(g_pad_index, &buttons, &connected);
297
            b = (char *)malloc(96);
298
            if (!b) { g_resp_len = 0; g_resp_status = 500; }
299
            else if (ok) {
300
                g_resp_len = (size_t)snprintf(b, 96,
301
                    "{\"index\":%d,\"connected\":%s,\"buttons\":%d}",
302
                    g_pad_index, connected ? "true" : "false", buttons);
303
                g_resp_status = 200;
304
            } else {
305
                g_resp_len = (size_t)snprintf(b, 96,
306
                    "{\"error\":\"no controller\"}");
307
                g_resp_status = 400;
308
            }
309
        }
310
        g_resp_body = (uint8_t *)b;
311
        g_resp_ctype = "application/json";
312
        break;
313
    }
314
    default: break;
315
    }
316
    g_req = REQ_NONE;
317
    g_resp_ready = 1;
318
    pthread_cond_broadcast(&g_cv);
319
    pthread_mutex_unlock(&g_lock);
320
}
321
322
int retro_control_running(void)
323
{
324
    if (!g_be) return 1;
325
    pthread_mutex_lock(&g_lock);
326
    long f = g_frames_to_run;
327
    pthread_mutex_unlock(&g_lock);
328
    return f != 0;
329
}
330
331
void retro_control_on_frame(void)
332
{
333
    if (!g_be) return;
334
    pthread_mutex_lock(&g_lock);
335
    if (g_frames_to_run > 0) {
336
        g_frames_to_run--;
337
        if (g_frames_to_run == 0 && g_step_waiting) {
338
            g_step_waiting = 0;
339
            g_resp_ready = 1;
340
            pthread_cond_broadcast(&g_cv);
341
        }
342
    }
343
    pthread_mutex_unlock(&g_lock);
344
}
345
346
/* ---- server-thread side -------------------------------------------------- */
347
348
static void write_all(int fd, const void *buf, size_t len)
349
{
350
    const char *p = (const char *)buf;
351
    while (len) {
352
        ssize_t w = send(fd, p, len, MSG_NOSIGNAL);
353
        if (w <= 0) return;
354
        p += w; len -= (size_t)w;
355
    }
356
}
357
358
/* `extra`, if non-NULL, is a complete header line ("Name: value\r\n"). */
359
static void send_http(int fd, int status, const char *ctype,
360
                      const uint8_t *body, size_t len, const char *extra)
361
{
362
    const char *reason =
363
        status == 200 ? "OK" :
364
        status == 400 ? "Bad Request" :
365
        status == 404 ? "Not Found" :
366
        status == 405 ? "Method Not Allowed" :
367
        status == 501 ? "Not Implemented" : "Error";
368
    char hdr[320];
369
    int hl = snprintf(hdr, sizeof hdr,
370
        "HTTP/1.1 %d %s\r\nContent-Type: %s\r\nContent-Length: %zu\r\n"
371
        "Connection: close\r\nAccess-Control-Allow-Origin: *\r\n%s\r\n",
372
        status, reason, ctype, len, extra ? extra : "");
373
    write_all(fd, hdr, hl);
374
    if (body && len) write_all(fd, body, len);
375
}
376
377
static void send_json(int fd, int status, const char *json)
378
{
379
    send_http(fd, status, "application/json", (const uint8_t *)json, strlen(json), NULL);
380
}
381
382
/* value of query key, decimal or 0x-hex (strtol base 0); def if absent. */
383
static long query_long(const char *q, const char *key, long def)
384
{
385
    size_t kl = strlen(key);
386
    const char *p = q;
387
    while (p && *p) {
388
        if (!strncmp(p, key, kl) && p[kl] == '=')
389
            return strtol(p + kl + 1, NULL, 0);
390
        p = strchr(p, '&');
391
        if (p) p++;
392
    }
393
    return def;
394
}
395
396
/* copy the value of query key into out (NUL-terminated, truncated to cap).
397
 * Returns 1 if the key was present, 0 otherwise. */
398
static int query_str(const char *q, const char *key, char *out, size_t cap)
399
{
400
    size_t kl = strlen(key);
401
    const char *p = q;
402
    while (p && *p) {
403
        if (!strncmp(p, key, kl) && p[kl] == '=') {
404
            const char *v = p + kl + 1;
405
            const char *e = strchr(v, '&');
406
            size_t n = e ? (size_t)(e - v) : strlen(v);
407
            if (n >= cap) n = cap - 1;
408
            memcpy(out, v, n);
409
            out[n] = 0;
410
            return 1;
411
        }
412
        p = strchr(p, '&');
413
        if (p) p++;
414
    }
415
    if (cap) out[0] = 0;
416
    return 0;
417
}
418
419
/* marshal a read to the emulator thread, wait, send the response. */
420
static void do_marshalled(int fd, req_t r)
421
{
422
    pthread_mutex_lock(&g_lock);
423
    g_resp_ready = 0; g_resp_body = NULL; g_resp_len = 0;
424
    g_req = r;
425
    while (!g_resp_ready) pthread_cond_wait(&g_cv, &g_lock);
426
    uint8_t *body = g_resp_body; size_t len = g_resp_len;
427
    int st = g_resp_status; const char *ct = g_resp_ctype;
428
    char extra[64]; memcpy(extra, g_resp_extra, sizeof extra);
429
    g_resp_body = NULL;
430
    pthread_mutex_unlock(&g_lock);
431
432
    send_http(fd, st, ct, body, len, extra[0] ? extra : NULL);
433
    free(body);
434
}
435
436
static void do_status(int fd)
437
{
438
    pthread_mutex_lock(&g_lock);
439
    long f = g_frames_to_run;
440
    pthread_mutex_unlock(&g_lock);
441
    unsigned long long fc = g_be->get_frame_count ? g_be->get_frame_count() : 0;
442
    /* Advertise the highest level whose defining callbacks are all present. */
443
    const char *contract =
444
        (g_be->inject_key && g_be->write_mem && g_be->capture_audio &&
445
         g_be->set_pointer && g_be->get_pointer &&
446
         g_be->set_pad && g_be->get_pad) ? "0.5.0" :
447
        (g_be->inject_key && g_be->write_mem && g_be->capture_audio &&
448
         g_be->set_pointer && g_be->get_pointer) ? "0.4.0" :
449
        (g_be->inject_key && g_be->write_mem && g_be->capture_audio) ? "0.3.0" :
450
        g_be->inject_key ? "0.2.0" : "0.1.0";
451
    char buf[320];
452
    snprintf(buf, sizeof buf,
453
        "{\"contract\":\"%s\",\"emulator\":\"%s\",\"platform\":\"%s\","
454
        "\"frame\":%llu,\"paused\":%s,\"running\":true}",
455
        contract,
456
        g_be->emulator ? g_be->emulator : "",
457
        g_be->platform ? g_be->platform : "",
458
        fc, (f == 0) ? "true" : "false");
459
    send_json(fd, 200, buf);
460
}
461
462
static void do_step(int fd, const char *query)
463
{
464
    long n = query_long(query, "frames", 1);
465
    if (n < 1) n = 1;
466
    pthread_mutex_lock(&g_lock);
467
    g_frames_to_run = n;
468
    g_step_waiting = 1;
469
    g_resp_ready = 0;
470
    while (!g_resp_ready) pthread_cond_wait(&g_cv, &g_lock);
471
    pthread_mutex_unlock(&g_lock);
472
473
    unsigned long long fc = g_be->get_frame_count ? g_be->get_frame_count() : 0;
474
    char buf[64];
475
    snprintf(buf, sizeof buf, "{\"frame\":%llu}", fc);
476
    send_json(fd, 200, buf);
477
}
478
479
static void do_setrun(int fd, long v, int paused)
480
{
481
    pthread_mutex_lock(&g_lock);
482
    g_frames_to_run = v;
483
    pthread_mutex_unlock(&g_lock);
484
    send_json(fd, 200, paused ? "{\"paused\":true}" : "{\"paused\":false}");
485
}
486
487
/* Read a request body (POST /mem). `req`/`n` = what handle_conn already recv'd
488
 * (headers + maybe some body); reads the rest per Content-Length. Returns a
489
 * malloc'd buffer of *out_len bytes (caller frees), capped at MEM_CAP. */
490
static uint8_t *read_body(int fd, char *req, size_t n, size_t *out_len)
491
{
492
    *out_len = 0;
493
    char *hdr_end = strstr(req, "\r\n\r\n");
494
    if (!hdr_end) return NULL;
495
    char *body_start = hdr_end + 4;
496
497
    long clen = 0;
498
    char *cl = strstr(req, "Content-Length:");
499
    if (!cl) cl = strstr(req, "content-length:");
500
    if (cl) clen = strtol(cl + 15, NULL, 10);
501
    if (clen < 0) clen = 0;
502
    if (clen > (long)MEM_CAP) clen = (long)MEM_CAP;
503
504
    uint8_t *body = (uint8_t *)malloc((size_t)clen ? (size_t)clen : 1);
505
    if (!body) return NULL;
506
507
    size_t have = (size_t)(req + n - body_start);
508
    if (have > (size_t)clen) have = (size_t)clen;
509
    memcpy(body, body_start, have);
510
    size_t got = have;
511
    while (got < (size_t)clen) {
512
        ssize_t r = recv(fd, body + got, (size_t)clen - got, 0);
513
        if (r <= 0) break;
514
        got += (size_t)r;
515
    }
516
    *out_len = got;
517
    return body;
518
}
519
520
static void handle_conn(int fd)
521
{
522
    char req[2048];
523
    size_t n = 0;
524
    while (n < sizeof(req) - 1) {
525
        ssize_t r = recv(fd, req + n, sizeof(req) - 1 - n, 0);
526
        if (r <= 0) break;
527
        n += (size_t)r;
528
        if (strstr(req, "\r\n\r\n")) break;
529
    }
530
    req[n] = 0;
531
532
    char method[8] = {0}, target[1024] = {0};
533
    if (sscanf(req, "%7s %1023s", method, target) != 2) {
534
        send_json(fd, 400, "{\"error\":\"bad request\"}");
535
        return;
536
    }
537
    char *q = strchr(target, '?');
538
    const char *query = "";
539
    if (q) { *q = 0; query = q + 1; }
540
    int is_post = !strcmp(method, "POST");
541
542
    if (!strcmp(target, "/status"))          { do_status(fd); return; }
543
    if (!strcmp(target, "/regs"))            { do_marshalled(fd, REQ_REGS); return; }
544
    if (!strcmp(target, "/screenshot"))      { do_marshalled(fd, REQ_SHOT); return; }
545
    if (!strcmp(target, "/audio"))           { do_marshalled(fd, REQ_AUDIO); return; }
546
    if (!strcmp(target, "/mem")) {
547
        g_req_addr = (uint32_t)query_long(query, "addr", 0);
548
        g_req_bank = (int32_t)query_long(query, "bank", -1);
549
        if (is_post) {                        /* 0.3: write / poke */
550
            size_t blen = 0;
551
            uint8_t *body = read_body(fd, req, n, &blen);
552
            g_req_len = (uint32_t)blen;
553
            g_write_data = body;
554
            do_marshalled(fd, REQ_WRITE);
555
            free(body);
556
        } else {                              /* read */
557
            g_req_len = (uint32_t)query_long(query, "len", 0);
558
            do_marshalled(fd, REQ_MEM);
559
        }
560
        return;
561
    }
562
    if (!strcmp(target, "/step")) {
563
        if (!is_post) { send_json(fd, 405, "{\"error\":\"POST only\"}"); return; }
564
        do_step(fd, query); return;
565
    }
566
    if (!strcmp(target, "/pause")) {
567
        if (!is_post) { send_json(fd, 405, "{\"error\":\"POST only\"}"); return; }
568
        do_setrun(fd, 0, 1); return;
569
    }
570
    if (!strcmp(target, "/resume")) {
571
        if (!is_post) { send_json(fd, 405, "{\"error\":\"POST only\"}"); return; }
572
        do_setrun(fd, -1, 0); return;
573
    }
574
    if (!strcmp(target, "/key")) {
575
        if (!is_post) { send_json(fd, 405, "{\"error\":\"POST only\"}"); return; }
576
        char text[8];
577
        long code = query_long(query, "code", -1);
578
        long down = query_long(query, "down", -1);
579
        if (query_str(query, "text", text, sizeof text) && text[0]) {
580
            g_key_is_text = 1; g_key_value = (uint32_t)(unsigned char)text[0];
581
        } else if (code >= 0) {
582
            g_key_is_text = 0; g_key_value = (uint32_t)code;
583
        } else {
584
            send_json(fd, 400, "{\"error\":\"key needs text= or code=\"}"); return;
585
        }
586
        g_key_action = (down < 0) ? RETRO_KEY_TAP
587
                     : (down != 0) ? RETRO_KEY_DOWN : RETRO_KEY_UP;
588
        do_marshalled(fd, REQ_KEY);
589
        return;
590
    }
591
    if (!strcmp(target, "/reset")) {
592
        if (!is_post) { send_json(fd, 405, "{\"error\":\"POST only\"}"); return; }
593
        do_marshalled(fd, REQ_RESET);
594
        return;
595
    }
596
    if (!strcmp(target, "/pointer")) {
597
        if (is_post) {                        /* 0.4: move/click the pointer */
598
            long x  = query_long(query, "x",  LONG_MIN);
599
            long y  = query_long(query, "y",  LONG_MIN);
600
            long dx = query_long(query, "dx", LONG_MIN);
601
            long dy = query_long(query, "dy", LONG_MIN);
602
            if (x != LONG_MIN && y != LONG_MIN) {
603
                g_ptr_absolute = 1; g_ptr_x = (int32_t)x; g_ptr_y = (int32_t)y;
604
            } else if (dx != LONG_MIN || dy != LONG_MIN) {
605
                g_ptr_absolute = 0;
606
                g_ptr_x = (int32_t)(dx == LONG_MIN ? 0 : dx);
607
                g_ptr_y = (int32_t)(dy == LONG_MIN ? 0 : dy);
608
            } else {
609
                send_json(fd, 400, "{\"error\":\"pointer needs x&y or dx&dy\"}"); return;
610
            }
611
            g_ptr_buttons = (int)query_long(query, "buttons", -1);   /* -1 = unchanged */
612
            do_marshalled(fd, REQ_PTR_SET);
613
        } else {                              /* 0.4: read back the pointer */
614
            do_marshalled(fd, REQ_PTR_GET);
615
        }
616
        return;
617
    }
618
    if (!strcmp(target, "/pad")) {
619
        g_pad_index = (int)query_long(query, "index", 0);
620
        if (is_post) {                        /* 0.5: present / set a pad */
621
            g_pad_buttons = (int)query_long(query, "buttons", -1);
622
            /* `connected` defaults to plugging it in whenever buttons were
623
             * given -- injecting a press on an absent pad is never what the
624
             * caller meant -- and to leaving it alone otherwise. */
625
            g_pad_connected = (int)query_long(query, "connected",
626
                                              g_pad_buttons >= 0 ? 1 : -1);
627
            do_marshalled(fd, REQ_PAD_SET);
628
        } else {                              /* 0.5: read a pad back */
629
            do_marshalled(fd, REQ_PAD_GET);
630
        }
631
        return;
632
    }
633
    send_json(fd, 404, "{\"error\":\"not found\"}");
634
}
635
636
static void *server_main(void *arg)
637
{
638
    (void)arg;
639
    for (;;) {
640
        int fd = accept(g_listen_fd, NULL, NULL);
641
        if (fd < 0) {
642
            if (errno == EINTR) continue;
643
            break;
644
        }
645
        handle_conn(fd);
646
        close(fd);
647
    }
648
    return NULL;
649
}
650
651
int retro_control_start(int port, const retro_control_backend_t *backend)
652
{
653
    g_be = backend;
654
    signal(SIGPIPE, SIG_IGN);
655
656
    int fd = socket(AF_INET, SOCK_STREAM, 0);
657
    if (fd < 0) return -1;
658
    int one = 1;
659
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
660
661
    struct sockaddr_in addr;
662
    memset(&addr, 0, sizeof addr);
663
    addr.sin_family = AF_INET;
664
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);   /* 127.0.0.1 only */
665
    addr.sin_port = htons((uint16_t)port);
666
    if (bind(fd, (struct sockaddr *)&addr, sizeof addr) < 0) {
667
        fprintf(stderr, "[retro_control] bind 127.0.0.1:%d failed: %s\n",
668
                port, strerror(errno));
669
        close(fd);
670
        return -1;
671
    }
672
    if (listen(fd, 4) < 0) { close(fd); return -1; }
673
674
    g_listen_fd = fd;
675
    if (pthread_create(&g_thread, NULL, server_main, NULL) != 0) {
676
        close(fd); g_listen_fd = -1; return -1;
677
    }
678
    pthread_detach(g_thread);
679
    fprintf(stderr, "[retro_control] listening on 127.0.0.1:%d (contract %s)\n",
680
            port, RETRO_CONTROL_CONTRACT);
681
    return 0;
682
}
683
684
void retro_control_stop(void)
685
{
686
    if (g_listen_fd >= 0) { close(g_listen_fd); g_listen_fd = -1; }
687
}
688
689
#endif /* !_WIN32 */