wasmcart 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +410 -0
- package/SPEC.md +477 -0
- package/bin/wasmcart-pack.js +257 -0
- package/docs/bind_framebuffer.md +275 -0
- package/docs/fetch.md +105 -0
- package/docs/gl-surface.md +111 -0
- package/docs/input.md +102 -0
- package/docs/networking.md +78 -0
- package/docs/porting.md +88 -0
- package/include/wc_cart.h +144 -0
- package/include/wc_fb.h +275 -0
- package/include/wc_gl.h +224 -0
- package/include/wc_gl_blit.h +129 -0
- package/include/wc_mat4.h +210 -0
- package/include/wc_math.h +116 -0
- package/include/wc_pcm_mixer.h +487 -0
- package/include/wc_vec3.h +80 -0
- package/index.js +3 -0
- package/package.json +55 -0
- package/src/CartHost.js +1713 -0
- package/src/CartHostWeb.js +1381 -0
- package/src/abi.js +94 -0
- package/src/cartWorker.js +201 -0
- package/src/cartWorkerWeb.js +170 -0
- package/src/webgl_imports.js +1483 -0
- package/web.js +3 -0
package/SPEC.md
ADDED
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
# wasmcart Specification
|
|
2
|
+
|
|
3
|
+
> **ABI version: 3.** This is the normative specification for the wasmcart virtual
|
|
4
|
+
> cartridge format - the host↔cart contract that any conforming host (see the
|
|
5
|
+
> reference implementations in [`src/`](src/)) and any cart must follow. The
|
|
6
|
+
> machine-readable form of these constants lives in [`src/abi.js`](src/abi.js); the
|
|
7
|
+
> C-side contract in [`include/wc_cart.h`](include/wc_cart.h).
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
A cart declares its capabilities in a manifest and exports three functions
|
|
13
|
+
(`wc_get_info`, `wc_init`, `wc_render`). Gamepad input is always available;
|
|
14
|
+
networking (WebSocket + data channels) and extended input (pointer + keyboard) are
|
|
15
|
+
opt-in per cart. The host provides everything through a small set of imports and
|
|
16
|
+
shared-memory regions; the cart owns its own memory and reaches nothing else.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Manifest
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"name": "Game Name",
|
|
25
|
+
"version": "1.0.0",
|
|
26
|
+
"abi": 3,
|
|
27
|
+
"entry": "cart.wasm",
|
|
28
|
+
"players": 4,
|
|
29
|
+
"pointer": true,
|
|
30
|
+
"keyboard": true,
|
|
31
|
+
"net": {
|
|
32
|
+
"websocket": ["api.mygame.com", "leaderboard.example.com"],
|
|
33
|
+
"data-channel": true
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Fields
|
|
39
|
+
|
|
40
|
+
**`players`** (integer, optional, default: 1)
|
|
41
|
+
- How many gamepad inputs the game uses (1-4)
|
|
42
|
+
|
|
43
|
+
**`pointer`** (boolean, optional, default: false)
|
|
44
|
+
- If true, host writes pointer state (unified mouse/touch) and delivers pointer event callbacks
|
|
45
|
+
- If false, pointer state is not updated
|
|
46
|
+
|
|
47
|
+
**`keyboard`** (boolean, optional, default: false)
|
|
48
|
+
- If true, host writes raw key state and delivers key event callbacks
|
|
49
|
+
- If false, host does not deliver raw key input to the cart
|
|
50
|
+
|
|
51
|
+
**`net`** (object, optional)
|
|
52
|
+
- Omitted = no networking. Cart receives no network imports.
|
|
53
|
+
- If present, host provides the corresponding network imports to the cart
|
|
54
|
+
- Host MAY refuse to provide networking (e.g., offline device) - cart must handle gracefully
|
|
55
|
+
|
|
56
|
+
**`net.websocket`** (array of strings, optional)
|
|
57
|
+
- Domain allowlist for WebSocket connections
|
|
58
|
+
- Host enforces - connection attempts to unlisted domains fail
|
|
59
|
+
- No wildcards, no raw IPs, no localhost
|
|
60
|
+
|
|
61
|
+
**`net.data-channel`** (boolean, optional)
|
|
62
|
+
- If true, cart gets binary data channel imports
|
|
63
|
+
- Host manages peer connections and signaling (opaque to cart)
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Exports (cart provides)
|
|
68
|
+
|
|
69
|
+
```c
|
|
70
|
+
wc_info_t* wc_get_info(void); // returns cart info struct
|
|
71
|
+
void wc_init(void); // called once at startup
|
|
72
|
+
void wc_render(void); // called every frame
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Imports (host provides)
|
|
78
|
+
|
|
79
|
+
```c
|
|
80
|
+
// Logging
|
|
81
|
+
void wc_log(const char* ptr, uint32_t len);
|
|
82
|
+
|
|
83
|
+
// Assets (v2+)
|
|
84
|
+
int32_t wc_asset_size(const char* path, uint32_t path_len);
|
|
85
|
+
int32_t wc_load_asset(const char* path, uint32_t path_len, void* dest, uint32_t max_size);
|
|
86
|
+
|
|
87
|
+
// GL (~100 functions, optional, imported from "gl" module)
|
|
88
|
+
void glClear(uint32_t mask);
|
|
89
|
+
// ... etc
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## wc_info_t
|
|
95
|
+
|
|
96
|
+
```c
|
|
97
|
+
typedef struct {
|
|
98
|
+
uint32_t version; // 3
|
|
99
|
+
uint32_t width;
|
|
100
|
+
uint32_t height;
|
|
101
|
+
uint32_t fb_ptr;
|
|
102
|
+
uint32_t audio_ptr;
|
|
103
|
+
uint32_t audio_cap;
|
|
104
|
+
uint32_t audio_write_ptr;
|
|
105
|
+
uint32_t input_ptr; // → wc_pad_t[4]
|
|
106
|
+
uint32_t save_ptr;
|
|
107
|
+
uint32_t save_size;
|
|
108
|
+
uint32_t time_ptr;
|
|
109
|
+
uint32_t host_info_ptr;
|
|
110
|
+
uint32_t flags;
|
|
111
|
+
uint32_t audio_sample_rate;
|
|
112
|
+
// v3 additions
|
|
113
|
+
uint32_t pointer_ptr; // → wc_pointer_t[10] (80 bytes), 0 = not used
|
|
114
|
+
uint32_t keys_ptr; // → uint8_t[32] key state bitmask, 0 = not used
|
|
115
|
+
} wc_info_t;
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Flags
|
|
119
|
+
|
|
120
|
+
```c
|
|
121
|
+
#define WC_FLAG_AUDIO_F32 0x01 // audio ring buffer uses float32
|
|
122
|
+
#define WC_FLAG_NET_WS 0x02 // cart wants WebSocket imports
|
|
123
|
+
#define WC_FLAG_NET_DC 0x04 // cart wants data channel imports
|
|
124
|
+
#define WC_FLAG_POINTER 0x08 // cart wants pointer input
|
|
125
|
+
#define WC_FLAG_KEYBOARD 0x10 // cart wants raw keyboard input
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## WebSocket
|
|
131
|
+
|
|
132
|
+
### Cart imports (calls into host)
|
|
133
|
+
|
|
134
|
+
```c
|
|
135
|
+
// Open a WebSocket connection.
|
|
136
|
+
// url must be in the manifest's websocket allowlist.
|
|
137
|
+
// Returns a connection ID (>= 0) or -1 on failure.
|
|
138
|
+
int32_t wc_ws_open(const char* url, uint32_t url_len);
|
|
139
|
+
|
|
140
|
+
// Close a WebSocket connection.
|
|
141
|
+
void wc_ws_close(int32_t conn_id, uint32_t code);
|
|
142
|
+
|
|
143
|
+
// Send binary data. Returns bytes sent, or -1 on error.
|
|
144
|
+
int32_t wc_ws_send(int32_t conn_id, const void* data, uint32_t len);
|
|
145
|
+
|
|
146
|
+
// Send text data. Returns bytes sent, or -1 on error.
|
|
147
|
+
int32_t wc_ws_send_text(int32_t conn_id, const char* str, uint32_t len);
|
|
148
|
+
|
|
149
|
+
// Get readyState: 0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED
|
|
150
|
+
int32_t wc_ws_state(int32_t conn_id);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Cart exports (host calls into cart - all optional)
|
|
154
|
+
|
|
155
|
+
```c
|
|
156
|
+
void wc_ws_on_open(int32_t conn_id);
|
|
157
|
+
void wc_ws_on_message(int32_t conn_id, const void* data, uint32_t len);
|
|
158
|
+
void wc_ws_on_message_text(int32_t conn_id, const char* str, uint32_t len);
|
|
159
|
+
void wc_ws_on_close(int32_t conn_id, uint32_t code);
|
|
160
|
+
void wc_ws_on_error(int32_t conn_id);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Notes
|
|
164
|
+
- Event-driven - mirrors the real WebSocket API
|
|
165
|
+
- Host buffers events and delivers them before each `wc_render()` call
|
|
166
|
+
- Both binary and text frame support
|
|
167
|
+
- Cart exports are optional - if missing, host silently drops events
|
|
168
|
+
- Host validates URL against manifest allowlist before connecting
|
|
169
|
+
- Connection IDs are small integers managed by host (0, 1, 2, ...)
|
|
170
|
+
- `data`/`str` pointers in callbacks are temporary - cart must copy what it needs
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Data Channel
|
|
175
|
+
|
|
176
|
+
For peer-to-peer gameplay. The host manages signaling and peer connections - the cart just sees binary data channels. The underlying transport (WebRTC, relayed, LAN UDP, etc.) is opaque to the cart.
|
|
177
|
+
|
|
178
|
+
### Cart imports (calls into host)
|
|
179
|
+
|
|
180
|
+
```c
|
|
181
|
+
// Get number of currently connected peers.
|
|
182
|
+
int32_t wc_dc_peer_count(void);
|
|
183
|
+
|
|
184
|
+
// Get info about a peer by index (0 to peer_count-1).
|
|
185
|
+
// Writes a null-terminated username/label into dest.
|
|
186
|
+
// Returns the peer's connection ID, or -1 if index out of range.
|
|
187
|
+
int32_t wc_dc_peer_info(uint32_t index, char* dest, uint32_t max_len);
|
|
188
|
+
|
|
189
|
+
// Send binary data to a specific peer. Returns bytes sent, or -1 on error.
|
|
190
|
+
int32_t wc_dc_send(int32_t peer_id, const void* data, uint32_t len);
|
|
191
|
+
|
|
192
|
+
// Send binary data to all connected peers. Returns peer count, or -1 on error.
|
|
193
|
+
int32_t wc_dc_broadcast(const void* data, uint32_t len);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Cart exports (host calls into cart - all optional)
|
|
197
|
+
|
|
198
|
+
```c
|
|
199
|
+
// peer_id is stable for this session.
|
|
200
|
+
void wc_dc_on_connect(int32_t peer_id, const char* label, uint32_t label_len);
|
|
201
|
+
void wc_dc_on_disconnect(int32_t peer_id);
|
|
202
|
+
void wc_dc_on_message(int32_t peer_id, const void* data, uint32_t len);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Notes
|
|
206
|
+
- Cart does NOT manage connections - host handles all signaling
|
|
207
|
+
- peer_id works like a socket descriptor - games can use it to track players
|
|
208
|
+
- Cart exports are optional - if missing, cart can still poll via `wc_dc_peer_count()`/`wc_dc_peer_info()`
|
|
209
|
+
- Binary only - games serialize their own protocols
|
|
210
|
+
- Delivery semantics are host-defined (may be unreliable or reliable depending on transport)
|
|
211
|
+
- Host delivers events before `wc_render()`
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Pointer Input
|
|
216
|
+
|
|
217
|
+
Unified mouse + touch. Opt-in via `"pointer": true` in manifest. Both shared-memory state and event callbacks.
|
|
218
|
+
|
|
219
|
+
### Shared memory (host writes every frame)
|
|
220
|
+
|
|
221
|
+
```c
|
|
222
|
+
typedef struct {
|
|
223
|
+
int16_t x; // cart-space coordinates (0 to width-1)
|
|
224
|
+
int16_t y; // cart-space coordinates (0 to height-1)
|
|
225
|
+
uint8_t buttons; // bitmask: bit0=primary, bit1=secondary, bit2=middle
|
|
226
|
+
uint8_t active; // 1 if this pointer exists
|
|
227
|
+
uint8_t _pad[2];
|
|
228
|
+
} wc_pointer_t; // 8 bytes
|
|
229
|
+
|
|
230
|
+
// 10 pointer slots, 80 bytes total
|
|
231
|
+
// Cart sets wc_info_t.pointer_ptr to a wc_pointer_t[10] buffer
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Cart exports (host calls into cart - all optional)
|
|
235
|
+
|
|
236
|
+
```c
|
|
237
|
+
void wc_ptr_on_down(uint32_t id, int16_t x, int16_t y, uint8_t button);
|
|
238
|
+
void wc_ptr_on_move(uint32_t id, int16_t x, int16_t y);
|
|
239
|
+
void wc_ptr_on_up(uint32_t id, uint8_t button);
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Notes
|
|
243
|
+
- Host normalizes screen coordinates to cart resolution
|
|
244
|
+
- Mouse = pointer 0 (always active when cursor is over window)
|
|
245
|
+
- Touch = each finger gets the next available slot, active only while touching, buttons=0x01
|
|
246
|
+
- If device has both mouse and touch, they coexist - mouse is 0, fingers fill 1+
|
|
247
|
+
- `button` param: 0=primary (left click / touch), 1=secondary (right click), 2=middle
|
|
248
|
+
- State array is always up to date regardless of whether cart exports callbacks
|
|
249
|
+
- Host delivers events before `wc_render()`
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Keyboard Input
|
|
254
|
+
|
|
255
|
+
Opt-in via `"keyboard": true` in manifest. Both shared-memory state and event callbacks.
|
|
256
|
+
|
|
257
|
+
### Shared memory (host writes every frame)
|
|
258
|
+
|
|
259
|
+
```c
|
|
260
|
+
// 256-bit bitmask - one bit per keycode
|
|
261
|
+
// Cart sets wc_info_t.keys_ptr to a uint8_t[32] buffer
|
|
262
|
+
uint8_t wc_keys[32]; // 32 bytes
|
|
263
|
+
|
|
264
|
+
// Test if key is down:
|
|
265
|
+
// wc_keys[keycode >> 3] & (1 << (keycode & 7))
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Cart exports (host calls into cart - all optional)
|
|
269
|
+
|
|
270
|
+
```c
|
|
271
|
+
void wc_kb_on_down(uint8_t keycode, uint8_t modifiers);
|
|
272
|
+
void wc_kb_on_up(uint8_t keycode, uint8_t modifiers);
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Modifier bitmask
|
|
276
|
+
|
|
277
|
+
```c
|
|
278
|
+
#define WC_MOD_SHIFT 0x01
|
|
279
|
+
#define WC_MOD_CTRL 0x02
|
|
280
|
+
#define WC_MOD_ALT 0x04
|
|
281
|
+
#define WC_MOD_META 0x08
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Keycodes (USB HID scancodes)
|
|
285
|
+
|
|
286
|
+
```c
|
|
287
|
+
// Letters (0x04–0x1D)
|
|
288
|
+
#define WC_KEY_A 0x04
|
|
289
|
+
#define WC_KEY_B 0x05
|
|
290
|
+
#define WC_KEY_C 0x06
|
|
291
|
+
#define WC_KEY_D 0x07
|
|
292
|
+
#define WC_KEY_E 0x08
|
|
293
|
+
#define WC_KEY_F 0x09
|
|
294
|
+
#define WC_KEY_G 0x0A
|
|
295
|
+
#define WC_KEY_H 0x0B
|
|
296
|
+
#define WC_KEY_I 0x0C
|
|
297
|
+
#define WC_KEY_J 0x0D
|
|
298
|
+
#define WC_KEY_K 0x0E
|
|
299
|
+
#define WC_KEY_L 0x0F
|
|
300
|
+
#define WC_KEY_M 0x10
|
|
301
|
+
#define WC_KEY_N 0x11
|
|
302
|
+
#define WC_KEY_O 0x12
|
|
303
|
+
#define WC_KEY_P 0x13
|
|
304
|
+
#define WC_KEY_Q 0x14
|
|
305
|
+
#define WC_KEY_R 0x15
|
|
306
|
+
#define WC_KEY_S 0x16
|
|
307
|
+
#define WC_KEY_T 0x17
|
|
308
|
+
#define WC_KEY_U 0x18
|
|
309
|
+
#define WC_KEY_V 0x19
|
|
310
|
+
#define WC_KEY_W 0x1A
|
|
311
|
+
#define WC_KEY_X 0x1B
|
|
312
|
+
#define WC_KEY_Y 0x1C
|
|
313
|
+
#define WC_KEY_Z 0x1D
|
|
314
|
+
|
|
315
|
+
// Numbers (0x1E–0x27)
|
|
316
|
+
#define WC_KEY_1 0x1E
|
|
317
|
+
#define WC_KEY_2 0x1F
|
|
318
|
+
#define WC_KEY_3 0x20
|
|
319
|
+
#define WC_KEY_4 0x21
|
|
320
|
+
#define WC_KEY_5 0x22
|
|
321
|
+
#define WC_KEY_6 0x23
|
|
322
|
+
#define WC_KEY_7 0x24
|
|
323
|
+
#define WC_KEY_8 0x25
|
|
324
|
+
#define WC_KEY_9 0x26
|
|
325
|
+
#define WC_KEY_0 0x27
|
|
326
|
+
|
|
327
|
+
// Common keys
|
|
328
|
+
#define WC_KEY_ENTER 0x28
|
|
329
|
+
#define WC_KEY_ESCAPE 0x29
|
|
330
|
+
#define WC_KEY_BACKSPACE 0x2A
|
|
331
|
+
#define WC_KEY_TAB 0x2B
|
|
332
|
+
#define WC_KEY_SPACE 0x2C
|
|
333
|
+
|
|
334
|
+
// Punctuation
|
|
335
|
+
#define WC_KEY_MINUS 0x2D
|
|
336
|
+
#define WC_KEY_EQUAL 0x2E
|
|
337
|
+
#define WC_KEY_LBRACKET 0x2F
|
|
338
|
+
#define WC_KEY_RBRACKET 0x30
|
|
339
|
+
#define WC_KEY_BACKSLASH 0x31
|
|
340
|
+
#define WC_KEY_SEMICOLON 0x33
|
|
341
|
+
#define WC_KEY_QUOTE 0x34
|
|
342
|
+
#define WC_KEY_GRAVE 0x35
|
|
343
|
+
#define WC_KEY_COMMA 0x36
|
|
344
|
+
#define WC_KEY_PERIOD 0x37
|
|
345
|
+
#define WC_KEY_SLASH 0x38
|
|
346
|
+
|
|
347
|
+
// Function keys (0x3A–0x45)
|
|
348
|
+
#define WC_KEY_F1 0x3A
|
|
349
|
+
#define WC_KEY_F2 0x3B
|
|
350
|
+
#define WC_KEY_F3 0x3C
|
|
351
|
+
#define WC_KEY_F4 0x3D
|
|
352
|
+
#define WC_KEY_F5 0x3E
|
|
353
|
+
#define WC_KEY_F6 0x3F
|
|
354
|
+
#define WC_KEY_F7 0x40
|
|
355
|
+
#define WC_KEY_F8 0x41
|
|
356
|
+
#define WC_KEY_F9 0x42
|
|
357
|
+
#define WC_KEY_F10 0x43
|
|
358
|
+
#define WC_KEY_F11 0x44
|
|
359
|
+
#define WC_KEY_F12 0x45
|
|
360
|
+
|
|
361
|
+
// Navigation
|
|
362
|
+
#define WC_KEY_INSERT 0x49
|
|
363
|
+
#define WC_KEY_HOME 0x4A
|
|
364
|
+
#define WC_KEY_PAGEUP 0x4B
|
|
365
|
+
#define WC_KEY_DELETE 0x4C
|
|
366
|
+
#define WC_KEY_END 0x4D
|
|
367
|
+
#define WC_KEY_PAGEDOWN 0x4E
|
|
368
|
+
|
|
369
|
+
// Arrows
|
|
370
|
+
#define WC_KEY_RIGHT 0x4F
|
|
371
|
+
#define WC_KEY_LEFT 0x50
|
|
372
|
+
#define WC_KEY_DOWN 0x51
|
|
373
|
+
#define WC_KEY_UP 0x52
|
|
374
|
+
|
|
375
|
+
// Numpad
|
|
376
|
+
#define WC_KEY_NUMLOCK 0x53
|
|
377
|
+
#define WC_KEY_KP_DIVIDE 0x54
|
|
378
|
+
#define WC_KEY_KP_MULTIPLY 0x55
|
|
379
|
+
#define WC_KEY_KP_MINUS 0x56
|
|
380
|
+
#define WC_KEY_KP_PLUS 0x57
|
|
381
|
+
#define WC_KEY_KP_ENTER 0x58
|
|
382
|
+
#define WC_KEY_KP_1 0x59
|
|
383
|
+
#define WC_KEY_KP_2 0x5A
|
|
384
|
+
#define WC_KEY_KP_3 0x5B
|
|
385
|
+
#define WC_KEY_KP_4 0x5C
|
|
386
|
+
#define WC_KEY_KP_5 0x5D
|
|
387
|
+
#define WC_KEY_KP_6 0x5E
|
|
388
|
+
#define WC_KEY_KP_7 0x5F
|
|
389
|
+
#define WC_KEY_KP_8 0x60
|
|
390
|
+
#define WC_KEY_KP_9 0x61
|
|
391
|
+
#define WC_KEY_KP_0 0x62
|
|
392
|
+
#define WC_KEY_KP_PERIOD 0x63
|
|
393
|
+
|
|
394
|
+
// Modifiers (0xE0–0xE7)
|
|
395
|
+
#define WC_KEY_LCTRL 0xE0
|
|
396
|
+
#define WC_KEY_LSHIFT 0xE1
|
|
397
|
+
#define WC_KEY_LALT 0xE2
|
|
398
|
+
#define WC_KEY_LMETA 0xE3
|
|
399
|
+
#define WC_KEY_RCTRL 0xE4
|
|
400
|
+
#define WC_KEY_RSHIFT 0xE5
|
|
401
|
+
#define WC_KEY_RALT 0xE6
|
|
402
|
+
#define WC_KEY_RMETA 0xE7
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
All keycodes follow USB HID Usage Tables. SDL provides these natively. Browser `KeyboardEvent.code` requires a static lookup table to convert (well-documented 1:1 mapping).
|
|
406
|
+
|
|
407
|
+
### Notes
|
|
408
|
+
- Host delivers events before `wc_render()`
|
|
409
|
+
- State bitmask is always up to date regardless of whether cart exports callbacks
|
|
410
|
+
- When `"keyboard": true` is in manifest, host does not map keyboard keys to gamepad
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## Security Model
|
|
415
|
+
|
|
416
|
+
A cart is a plain WebAssembly module with **no ambient authority**. It has no
|
|
417
|
+
syscalls, no filesystem, no network, no clock beyond what the host hands it, and no
|
|
418
|
+
way to reach anything outside its own module memory except through the imports the
|
|
419
|
+
host provides. Everything a cart can touch is mediated by the host and validated
|
|
420
|
+
before it acts. This is what makes running an untrusted cart safe.
|
|
421
|
+
|
|
422
|
+
### Filesystem and assets
|
|
423
|
+
|
|
424
|
+
Carts have **no filesystem access.** There is no `open`, `read`, `write`, `stat`,
|
|
425
|
+
directory listing, or any path-based I/O available to a cart - those imports simply
|
|
426
|
+
do not exist.
|
|
427
|
+
|
|
428
|
+
The only files a cart can read are **its own bundled assets**, and only through the
|
|
429
|
+
asset API:
|
|
430
|
+
|
|
431
|
+
- `wc_asset_size(path, len)` and `wc_load_asset(path, len, dest, maxSize)` resolve
|
|
432
|
+
paths **against the cart's own asset bundle only** (the `assets/` entries inside
|
|
433
|
+
its `.wasc`, or the dev-mode directory). A cart cannot name a file outside that
|
|
434
|
+
scope.
|
|
435
|
+
- The host **validates every requested path** and rejects: absolute paths (`/...`,
|
|
436
|
+
`\...`), Windows drive letters (`C:`), parent-directory traversal (`..`), null
|
|
437
|
+
bytes, and backslashes. A rejected or unknown path returns `-1`; there is no way
|
|
438
|
+
for it to resolve to a host file.
|
|
439
|
+
- A cart therefore cannot read the user's files, other carts' assets, or anything
|
|
440
|
+
on the host - its entire readable world is the assets it shipped with.
|
|
441
|
+
|
|
442
|
+
### Saving is host-managed
|
|
443
|
+
|
|
444
|
+
Carts do **not** write files to save progress. Persistence is entirely the host's
|
|
445
|
+
responsibility, through a fixed shared-memory region:
|
|
446
|
+
|
|
447
|
+
- The cart declares a save region via `wc_info_t.save_ptr` / `save_size` (a plain
|
|
448
|
+
byte blob in the cart's own memory).
|
|
449
|
+
- **The host owns storage.** Before `wc_init()`, the host loads any existing save
|
|
450
|
+
bytes into that region so the cart can read them at startup. After a frame (or on
|
|
451
|
+
demand), the host reads the region back and persists it however it sees fit
|
|
452
|
+
(a file, browser storage, a libretro SRAM/save-state, a database - the cart never
|
|
453
|
+
knows or cares).
|
|
454
|
+
- The cart never chooses *where* or *whether* data is stored; it only reads and
|
|
455
|
+
writes its own in-memory save blob. This keeps saving safe (no filesystem write
|
|
456
|
+
authority) and portable (the same cart saves correctly on every host).
|
|
457
|
+
|
|
458
|
+
### Networking
|
|
459
|
+
|
|
460
|
+
1. **No networking by default** - omit `net` from manifest = zero network access
|
|
461
|
+
2. **Domain allowlist** - WebSocket connections only to declared domains
|
|
462
|
+
3. **No raw sockets** - no TCP, no UDP, no localhost, no IP addresses
|
|
463
|
+
4. **Host enforces** - cart can't bypass; imports validate before acting
|
|
464
|
+
5. **Graceful degradation** - offline hosts provide stub imports returning -1
|
|
465
|
+
6. **Data channels are host-managed** - cart can't initiate peer connections
|
|
466
|
+
7. **No DNS resolution** - cart can't enumerate network
|
|
467
|
+
|
|
468
|
+
### Summary
|
|
469
|
+
|
|
470
|
+
| Capability | Cart access |
|
|
471
|
+
|------------|-------------|
|
|
472
|
+
| Host filesystem | none |
|
|
473
|
+
| Own bundled assets | read-only, path-validated, via `wc_load_asset` |
|
|
474
|
+
| Save data | in-memory blob only; host owns persistence |
|
|
475
|
+
| Network | none unless declared in the manifest (allowlisted WebSocket / data channels) |
|
|
476
|
+
| Syscalls / clock / RNG | none except what the host explicitly imports |
|
|
477
|
+
|