@wasm-gaming/mgba-wasm 0.1.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/README.md +203 -0
- package/dist/manifest.json +146 -0
- package/dist/mgba/mgba.config.d.ts +41 -0
- package/dist/mgba/mgba.config.d.ts.map +1 -0
- package/dist/mgba/mgba.config.js +93 -0
- package/dist/mgba/mgba.config.js.map +1 -0
- package/dist/mgba/mgba.js +2 -0
- package/dist/mgba/mgba.manifest.d.ts +4 -0
- package/dist/mgba/mgba.manifest.d.ts.map +1 -0
- package/dist/mgba/mgba.manifest.js +38 -0
- package/dist/mgba/mgba.manifest.js.map +1 -0
- package/dist/mgba/mgba.options.d.ts +171 -0
- package/dist/mgba/mgba.options.d.ts.map +1 -0
- package/dist/mgba/mgba.options.js +273 -0
- package/dist/mgba/mgba.options.js.map +1 -0
- package/dist/mgba/mgba.sdk.d.ts +24 -0
- package/dist/mgba/mgba.sdk.d.ts.map +1 -0
- package/dist/mgba/mgba.sdk.js +846 -0
- package/dist/mgba/mgba.sdk.js.map +1 -0
- package/dist/mgba/mgba.wasm +0 -0
- package/dist/mgba/mgba.zip.d.ts +36 -0
- package/dist/mgba/mgba.zip.d.ts.map +1 -0
- package/dist/mgba/mgba.zip.js +105 -0
- package/dist/mgba/mgba.zip.js.map +1 -0
- package/package.json +53 -0
- package/src/mgba.config.ts +145 -0
- package/src/mgba.manifest.ts +43 -0
- package/src/mgba.options.ts +456 -0
- package/src/mgba.sdk.ts +967 -0
- package/src/mgba.zip.ts +140 -0
package/README.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# @wasm-gaming/mgba-wasm
|
|
2
|
+
|
|
3
|
+
[mGBA](https://github.com/mgba-emu/mgba) — the accuracy-focused **Game Boy
|
|
4
|
+
Advance** emulator, which also runs **Game Boy** and **Game Boy Color** —
|
|
5
|
+
compiled to WebAssembly via Emscripten and packaged as a wasm-gaming engine SDK.
|
|
6
|
+
|
|
7
|
+
This subproject follows the same engine-package approach used by snes9x-wasm,
|
|
8
|
+
geolith-wasm, fbneo-wasm, jgenesis-wasm, blastem-wasm, and rsdkv*:
|
|
9
|
+
|
|
10
|
+
- typed `manifest`
|
|
11
|
+
- typed `options`
|
|
12
|
+
- `load(config)` engine SDK surface
|
|
13
|
+
- Makefile-driven build (`build-sdk`, `build-wasm`, `preview`)
|
|
14
|
+
|
|
15
|
+
It conforms to the [`@wasm-gaming/engine-specs`](https://github.com/wasm-gaming/engine-specs)
|
|
16
|
+
contract (`EngineSDK` = `{ manifest, load }`), targeting **0.2.6** — 0.2.5 was the first
|
|
17
|
+
version in which the in-game ESC menu belongs to the host's demo shell rather
|
|
18
|
+
than to each engine, so this package ships the option rows and applies the
|
|
19
|
+
writes instead of drawing a menu itself.
|
|
20
|
+
|
|
21
|
+
mGBA ships several frontends (`qt/`, `sdl/`, `libretro/`); none of them suit a
|
|
22
|
+
browser, so this package supplies its own
|
|
23
|
+
([scripts/shim/mgba_shim.c](scripts/shim/mgba_shim.c)) instead of an Emscripten
|
|
24
|
+
SDL layer: **no ASYNCIFY, no SDL, no emulated GL**. Because mGBA already
|
|
25
|
+
publishes `struct mCore` — a frontend-facing vtable that is identical for both
|
|
26
|
+
its cores — the shim is a thin flat-C ABI over it rather than a reimplemented
|
|
27
|
+
port. The JS SDK drives one `core->runFrame()` per frame, blits the framebuffer
|
|
28
|
+
to a 2D canvas, and streams audio through an `AudioWorklet` ring buffer.
|
|
29
|
+
Emulation is **audio-clocked**: frames are produced to keep ~90 ms of audio
|
|
30
|
+
queued.
|
|
31
|
+
|
|
32
|
+
## ROM format
|
|
33
|
+
|
|
34
|
+
Pass the cartridge image as the `rom` asset: `.gba`, `.gb`, `.gbc` or `.sgb`.
|
|
35
|
+
**Zipped dumps work too** — the archive is opened in JS
|
|
36
|
+
([src/mgba.zip.ts](src/mgba.zip.ts)) with `DecompressionStream`, since this
|
|
37
|
+
build carries no libzip.
|
|
38
|
+
|
|
39
|
+
The right core is chosen from the image itself, so a Game Boy ROM and a GBA ROM
|
|
40
|
+
both just work; force it with `options.system` for headerless or mislabelled
|
|
41
|
+
files.
|
|
42
|
+
|
|
43
|
+
### BIOS
|
|
44
|
+
|
|
45
|
+
**Optional.** mGBA has a high-level BIOS replacement that runs virtually every
|
|
46
|
+
commercial title, so the `bios` asset is only needed for the boot animation and
|
|
47
|
+
the handful of games that read the BIOS directly. Supply a 16 KiB GBA BIOS dump
|
|
48
|
+
as `assets.bios` if you want it; without one, `skipBios` is forced on and drops
|
|
49
|
+
out of the settings menu, because there is no intro to skip.
|
|
50
|
+
|
|
51
|
+
## Contract surface
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
import { manifest, load } from '@wasm-gaming/mgba-wasm';
|
|
55
|
+
|
|
56
|
+
const engine = await load({
|
|
57
|
+
canvasEl: canvas, // or attachTo: containerEl
|
|
58
|
+
assets: {
|
|
59
|
+
rom: gbaFileBytes, // .gba/.gb/.gbc image, or a zip of one
|
|
60
|
+
// bios: gbaBiosBytes, // optional
|
|
61
|
+
},
|
|
62
|
+
options: { system: 'auto', aspect: 'native' },
|
|
63
|
+
persist: 'opfs',
|
|
64
|
+
storageNamespace: 'minish-cap',
|
|
65
|
+
onEvent: (e) => console.log(e),
|
|
66
|
+
});
|
|
67
|
+
engine.start();
|
|
68
|
+
|
|
69
|
+
engine.system; // 'gba' | 'gb' — which core actually booted
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Options
|
|
73
|
+
|
|
74
|
+
| Option | Default | Description |
|
|
75
|
+
|--------|---------|-------------|
|
|
76
|
+
| `system` | `auto` | Which core boots the ROM: `auto` (sniffed from the image), `gba`, or `gb`. **Needs reset.** |
|
|
77
|
+
| `gbModel` | `auto` | Game Boy hardware: `auto` (from the header), `dmg`, `sgb`, `cgb`, `agb`. Ignored on GBA. **Needs reset.** |
|
|
78
|
+
| `idleOptimization` | `remove` | How the GBA core treats busy-wait loops. `remove` is where nearly all the speed comes from; `ignore` emulates them cycle for cycle. |
|
|
79
|
+
| `skipBios` | `true` | Skip the boot animation. Forced on, and hidden, when no BIOS image was supplied. **Needs reset.** |
|
|
80
|
+
| `allowOpposingDirections` | `false` | Let the D-pad report left+right at once. Real hardware cannot. |
|
|
81
|
+
| `renderFilter` | `pixelated` | Canvas scaling filter (`pixelated` or `smooth`). |
|
|
82
|
+
| `aspect` | `native` | `native` is square pixels — 3:2 on GBA, 10:9 on Game Boy; `4:3` fills a TV-shaped frame. |
|
|
83
|
+
| `interframeBlending` | `false` | Blend consecutive frames, approximating the ghosting of the original unlit LCD. Restores transparency effects games drew by flickering sprites. |
|
|
84
|
+
| `volume` | `1.0` | Master audio volume (0–1). |
|
|
85
|
+
| `gamepads` | `true` | Poll connected gamepads (standard mapping) each frame. |
|
|
86
|
+
| `logLevel` | `error` | Core messages printed to the console (`off`, `error`, `debug`). |
|
|
87
|
+
| `escMenu` | `true` | Let the demo shell show its in-game settings menu on Escape. |
|
|
88
|
+
|
|
89
|
+
## In-game settings menu
|
|
90
|
+
|
|
91
|
+
As of engine-specs 0.2.5 the ESC menu is a **demo-shell component**, not
|
|
92
|
+
something each engine draws. This package supplies the rows and applies the
|
|
93
|
+
writes:
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
demo.init({
|
|
97
|
+
escMenu: {
|
|
98
|
+
optionGroups: toEscMenuGroups(), // from ./options
|
|
99
|
+
onOptionChange: (key, value) => engine.config.write(key, value),
|
|
100
|
+
onRestoreDefaults: () => engine.config.restoreDefaults(),
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Most of these apply to the running game: mGBA re-reads its `mCoreConfig` when
|
|
106
|
+
the frontend calls `reloadConfigOption()`, which is how its own Qt frontend
|
|
107
|
+
applies a settings change mid-play. The ones the core consumes while mapping
|
|
108
|
+
the cartridge — `system`, `gbModel`, `skipBios` — are tagged **needs reset**,
|
|
109
|
+
and the menu's *Reset game* re-maps it (battery RAM is carried across).
|
|
110
|
+
|
|
111
|
+
- Changes persist per `storageNamespace` in `localStorage` and are re-applied
|
|
112
|
+
on the next `load()`. Explicit `options` passed by the host still win.
|
|
113
|
+
- Settings the loaded `mgba.wasm` cannot apply — a build predating one of the
|
|
114
|
+
shim's setters — are omitted rather than erroring, so the menu degrades
|
|
115
|
+
cleanly against an older artifact.
|
|
116
|
+
|
|
117
|
+
Every knob is declared once in [src/mgba.options.ts](src/mgba.options.ts); the
|
|
118
|
+
manifest's options schema, the defaults and the menu rows are all derived from
|
|
119
|
+
that catalog, so a new row there is enough to expose a new setting.
|
|
120
|
+
|
|
121
|
+
Drive it yourself with `options: { escMenu: false }`:
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
engine.config.write('interframeBlending', true);
|
|
125
|
+
engine.config.read('idleOptimization'); // 'remove'
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Sizing the canvas
|
|
129
|
+
|
|
130
|
+
The presentation ratio is not a constant a host can hardcode: 3:2 on GBA,
|
|
131
|
+
10:9 on Game Boy, 8:7 once a Super Game Boy border appears, and 4:3 when the
|
|
132
|
+
player picks `aspect: '4:3'`. So `load()` publishes it on the canvas two ways —
|
|
133
|
+
`aspect-ratio`, which shapes the element, and `--mgba-aspect`, the same number
|
|
134
|
+
in a form arithmetic can use:
|
|
135
|
+
|
|
136
|
+
```css
|
|
137
|
+
.runtime canvas {
|
|
138
|
+
width: min(100%, calc(100% * var(--mgba-aspect)));
|
|
139
|
+
height: auto;
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Both are updated whenever the geometry or the option changes, so a game
|
|
144
|
+
switching resolution re-fits without the host doing anything.
|
|
145
|
+
|
|
146
|
+
### Capabilities
|
|
147
|
+
|
|
148
|
+
- **Save states**: `saveState()` / `loadState()` via mGBA's in-memory snapshot
|
|
149
|
+
API — 388 KiB on GBA, 70 KiB on Game Boy.
|
|
150
|
+
- **SRAM persistence**: battery-backed savedata is persisted to OPFS
|
|
151
|
+
(`mgba/<storageNamespace>/sram.bin`) on pause/destroy and every 15 s;
|
|
152
|
+
`purgeStorage()` removes the active namespace, along with the menu settings
|
|
153
|
+
saved for it.
|
|
154
|
+
- **Screenshots**: `screenshot()` returns a PNG blob.
|
|
155
|
+
- **`coreSelectable`**: one package, two cores. `options.system` is where a
|
|
156
|
+
host's core choice lands.
|
|
157
|
+
|
|
158
|
+
### Default controls
|
|
159
|
+
|
|
160
|
+
| Control | Key |
|
|
161
|
+
|---------|-----|
|
|
162
|
+
| D-pad | Arrow keys |
|
|
163
|
+
| A / B | X / Z |
|
|
164
|
+
| L / R | A / S |
|
|
165
|
+
| Start | Enter |
|
|
166
|
+
| Select | Right Shift |
|
|
167
|
+
|
|
168
|
+
Gamepads (standard mapping) are polled automatically. Rebind via
|
|
169
|
+
`engine.setInput({ 'a': 'KeyJ', ... })` (KeyboardEvent codes); a `p1.` prefix is
|
|
170
|
+
accepted but not required, since the handheld has one controller.
|
|
171
|
+
|
|
172
|
+
## Build
|
|
173
|
+
|
|
174
|
+
```sh
|
|
175
|
+
make build # Full build: WASM (Docker/Emscripten) + TypeScript SDK
|
|
176
|
+
make build-sdk # TypeScript only (SDK + manifest + demo shell)
|
|
177
|
+
make build-wasm # mGBA WASM only (via Docker)
|
|
178
|
+
make preview # Serve dist/ at :8030 with COOP/COEP headers
|
|
179
|
+
make smoke # Headless boot test (needs a ROM in roms/)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The WASM build clones a pinned mGBA revision and builds it with its own CMake —
|
|
183
|
+
`DISABLE_FRONTENDS` plus `DISABLE_DEPS` is exactly the "just the emulator"
|
|
184
|
+
configuration this needs, and it generates `version.c` and `flags.h` along the
|
|
185
|
+
way — then links the shim against the resulting `libmgba.a`. No upstream
|
|
186
|
+
patching.
|
|
187
|
+
|
|
188
|
+
## WASM artifacts
|
|
189
|
+
|
|
190
|
+
| File | Description |
|
|
191
|
+
|------|-------------|
|
|
192
|
+
| `mgba.js` | Emscripten module loader (`createMgbaModule`). |
|
|
193
|
+
| `mgba.wasm` | Compiled mGBA core + shim (~790 KB). |
|
|
194
|
+
|
|
195
|
+
No SharedArrayBuffer or COOP/COEP headers are required at runtime (the preview
|
|
196
|
+
server sets them anyway for parity with sibling engines).
|
|
197
|
+
|
|
198
|
+
See [CORE.md](CORE.md) for the mapping between upstream mGBA capabilities and
|
|
199
|
+
what this wrapper exposes, and for the Emscripten-specific build traps.
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
|
|
203
|
+
The SDK and shim are MPL-2.0, matching mGBA.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "mgba",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"name": "mGBA (WebAssembly)",
|
|
5
|
+
"description": "mGBA — the accuracy-focused Game Boy Advance emulator, which also runs Game Boy and Game Boy Color — compiled to WebAssembly. Loads .gba/.gb/.gbc cartridge images; the GBA BIOS is optional, since mGBA ships a high-level replacement.",
|
|
6
|
+
"artifacts": {
|
|
7
|
+
"wasm": "mgba/mgba.wasm",
|
|
8
|
+
"js": "mgba/mgba.js"
|
|
9
|
+
},
|
|
10
|
+
"assets": [
|
|
11
|
+
{
|
|
12
|
+
"key": "rom",
|
|
13
|
+
"mountPath": "/rom.gba",
|
|
14
|
+
"required": true,
|
|
15
|
+
"accept": [
|
|
16
|
+
".gba",
|
|
17
|
+
".gb",
|
|
18
|
+
".gbc",
|
|
19
|
+
".sgb"
|
|
20
|
+
],
|
|
21
|
+
"description": "Game Boy Advance, Game Boy or Game Boy Color cartridge image. The right core is picked from the image itself, so the extension only has to be one mGBA recognizes."
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"key": "bios",
|
|
25
|
+
"mountPath": "/bios.bin",
|
|
26
|
+
"required": false,
|
|
27
|
+
"accept": [
|
|
28
|
+
".bin",
|
|
29
|
+
".rom"
|
|
30
|
+
],
|
|
31
|
+
"description": "Optional GBA BIOS dump (16 KiB). mGBA has a high-level replacement that runs virtually everything, so this is only needed for the boot animation and the handful of titles that read the BIOS directly.",
|
|
32
|
+
"validate": {
|
|
33
|
+
"bytes": 16384
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"input": "mgba",
|
|
38
|
+
"video": {
|
|
39
|
+
"baseWidth": 240,
|
|
40
|
+
"baseHeight": 160,
|
|
41
|
+
"aspect": "3:2"
|
|
42
|
+
},
|
|
43
|
+
"options": {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"additionalProperties": false,
|
|
46
|
+
"properties": {
|
|
47
|
+
"renderFilter": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"enum": [
|
|
50
|
+
"pixelated",
|
|
51
|
+
"smooth"
|
|
52
|
+
],
|
|
53
|
+
"default": "pixelated",
|
|
54
|
+
"description": "Scaling filter applied when the picture is stretched to the canvas. Pixelated keeps pixel art crisp; smooth softens it."
|
|
55
|
+
},
|
|
56
|
+
"aspect": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"enum": [
|
|
59
|
+
"native",
|
|
60
|
+
"4:3"
|
|
61
|
+
],
|
|
62
|
+
"default": "native",
|
|
63
|
+
"description": "Native is square pixels, as the handheld LCDs had them: 3:2 on GBA, 10:9 on Game Boy. 4:3 stretches the picture into a TV-shaped frame."
|
|
64
|
+
},
|
|
65
|
+
"interframeBlending": {
|
|
66
|
+
"type": "boolean",
|
|
67
|
+
"default": false,
|
|
68
|
+
"description": "Blend each frame with the previous one, approximating the ghosting of the original unlit LCD. Restores transparency effects that games drew by flickering sprites every other frame."
|
|
69
|
+
},
|
|
70
|
+
"volume": {
|
|
71
|
+
"type": "number",
|
|
72
|
+
"default": 1,
|
|
73
|
+
"minimum": 0,
|
|
74
|
+
"maximum": 1,
|
|
75
|
+
"description": "Master audio volume."
|
|
76
|
+
},
|
|
77
|
+
"system": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"enum": [
|
|
80
|
+
"auto",
|
|
81
|
+
"gba",
|
|
82
|
+
"gb"
|
|
83
|
+
],
|
|
84
|
+
"default": "auto",
|
|
85
|
+
"description": "Which core boots the ROM. Auto sniffs the image, which is right for every well-formed dump; force it for headerless or mislabelled files."
|
|
86
|
+
},
|
|
87
|
+
"gbModel": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"enum": [
|
|
90
|
+
"auto",
|
|
91
|
+
"dmg",
|
|
92
|
+
"sgb",
|
|
93
|
+
"cgb",
|
|
94
|
+
"agb"
|
|
95
|
+
],
|
|
96
|
+
"default": "auto",
|
|
97
|
+
"description": "Hardware the Game Boy core emulates. Auto follows the cartridge header. Ignored when a GBA ROM is loaded."
|
|
98
|
+
},
|
|
99
|
+
"idleOptimization": {
|
|
100
|
+
"type": "string",
|
|
101
|
+
"enum": [
|
|
102
|
+
"remove",
|
|
103
|
+
"detect",
|
|
104
|
+
"ignore"
|
|
105
|
+
],
|
|
106
|
+
"default": "remove",
|
|
107
|
+
"description": "How the GBA core treats the busy-wait loops games spin in. Remove skips them and is where nearly all of the speed comes from; ignore emulates them cycle for cycle."
|
|
108
|
+
},
|
|
109
|
+
"skipBios": {
|
|
110
|
+
"type": "boolean",
|
|
111
|
+
"default": true,
|
|
112
|
+
"description": "Jump straight into the game instead of playing the boot animation. Forced on when no BIOS image was supplied, since the built-in HLE BIOS has no intro."
|
|
113
|
+
},
|
|
114
|
+
"allowOpposingDirections": {
|
|
115
|
+
"type": "boolean",
|
|
116
|
+
"default": false,
|
|
117
|
+
"description": "Let the D-pad report left+right (or up+down) at once. Real hardware cannot, and a few games misbehave when it happens."
|
|
118
|
+
},
|
|
119
|
+
"gamepads": {
|
|
120
|
+
"type": "boolean",
|
|
121
|
+
"default": true,
|
|
122
|
+
"description": "Poll connected gamepads (standard mapping) each frame."
|
|
123
|
+
},
|
|
124
|
+
"logLevel": {
|
|
125
|
+
"type": "string",
|
|
126
|
+
"enum": [
|
|
127
|
+
"off",
|
|
128
|
+
"error",
|
|
129
|
+
"debug"
|
|
130
|
+
],
|
|
131
|
+
"default": "error",
|
|
132
|
+
"description": "Core messages printed to the browser console: errors and warnings, or also its informational ones."
|
|
133
|
+
},
|
|
134
|
+
"escMenu": {
|
|
135
|
+
"type": "boolean",
|
|
136
|
+
"default": true,
|
|
137
|
+
"description": "Show the demo shell's in-game settings menu when the player presses Escape."
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
"capabilities": {
|
|
142
|
+
"saveStates": true,
|
|
143
|
+
"sram": true,
|
|
144
|
+
"coreSelectable": true
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type MgbaOptionKey, type MgbaOptionValue } from './mgba.options.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pushes a value into the running emulator: an `mCoreConfig` write followed by
|
|
4
|
+
* `reloadConfigOption()`, a canvas style change, a gain node — whatever that
|
|
5
|
+
* option means. Called after the value has been validated and recorded.
|
|
6
|
+
*/
|
|
7
|
+
export type MgbaApplier = (value: MgbaOptionValue) => void;
|
|
8
|
+
/**
|
|
9
|
+
* The appliers the SDK installs for the build it actually loaded. An option
|
|
10
|
+
* with no applier is reported as unsupported and disappears from the menu,
|
|
11
|
+
* which is what keeps this package usable against an `mgba.wasm` built before
|
|
12
|
+
* a given shim setter existed.
|
|
13
|
+
*/
|
|
14
|
+
export type MgbaAppliers = Partial<Record<MgbaOptionKey, MgbaApplier>>;
|
|
15
|
+
/**
|
|
16
|
+
* Typed façade over the emulator's live settings.
|
|
17
|
+
*
|
|
18
|
+
* mGBA does own a configuration object — the `mCoreConfig` embedded in every
|
|
19
|
+
* `mCore` — but it is C-side and the SDK keeps the rest (canvas, audio graph,
|
|
20
|
+
* frame blending) in JS, so `state` is the record both this façade and the
|
|
21
|
+
* running SDK read from, and every write goes through the matching applier.
|
|
22
|
+
*/
|
|
23
|
+
export interface MgbaConfig {
|
|
24
|
+
supports(key: string): boolean;
|
|
25
|
+
read(key: string): MgbaOptionValue | undefined;
|
|
26
|
+
/** Returns `false` when the key is unknown, unsupported, or the value invalid. */
|
|
27
|
+
write(key: string, value: MgbaOptionValue): boolean;
|
|
28
|
+
/** Current value of every supported option. */
|
|
29
|
+
values(): Record<string, MgbaOptionValue>;
|
|
30
|
+
/** Restores this package's declared defaults. */
|
|
31
|
+
restoreDefaults(): void;
|
|
32
|
+
}
|
|
33
|
+
export declare function bindConfig(state: Record<string, MgbaOptionValue>, appliers: MgbaAppliers): MgbaConfig;
|
|
34
|
+
/** Per-namespace persistence for menu tweaks, so they survive a page reload. */
|
|
35
|
+
export interface MgbaSettingsStore {
|
|
36
|
+
load(): Record<string, MgbaOptionValue>;
|
|
37
|
+
save(values: Record<string, MgbaOptionValue>): void;
|
|
38
|
+
clear(): void;
|
|
39
|
+
}
|
|
40
|
+
export declare function createSettingsStore(namespace: string): MgbaSettingsStore;
|
|
41
|
+
//# sourceMappingURL=mgba.config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mgba.config.d.ts","sourceRoot":"","sources":["../../src/mgba.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,eAAe,EACrB,MAAM,mBAAmB,CAAC;AAE3B;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;AAE3D;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC,CAAC;AAEvE;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAAC;IAC/C,kFAAkF;IAClF,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC;IACpD,+CAA+C;IAC/C,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC1C,iDAAiD;IACjD,eAAe,IAAI,IAAI,CAAC;CACzB;AAED,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EACtC,QAAQ,EAAE,YAAY,GACrB,UAAU,CAwCZ;AAED,gFAAgF;AAChF,MAAM,WAAW,iBAAiB;IAChC,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,IAAI,CAAC;IACpD,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,iBAAiB,CAkDxE"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { coerceOptionValue, mgbaOption, MGBA_ENGINE_OPTIONS, } from './mgba.options.js';
|
|
2
|
+
export function bindConfig(state, appliers) {
|
|
3
|
+
const applierFor = (key) => appliers[key];
|
|
4
|
+
const supports = (key) => Boolean(mgbaOption(key)) && applierFor(key) !== undefined;
|
|
5
|
+
const read = (key) => mgbaOption(key) ? state[key] : undefined;
|
|
6
|
+
const write = (key, value) => {
|
|
7
|
+
const option = mgbaOption(key);
|
|
8
|
+
const apply = applierFor(key);
|
|
9
|
+
if (!option || !apply)
|
|
10
|
+
return false;
|
|
11
|
+
const next = coerceOptionValue(option, value);
|
|
12
|
+
if (next === undefined)
|
|
13
|
+
return false;
|
|
14
|
+
state[key] = next;
|
|
15
|
+
apply(next);
|
|
16
|
+
return true;
|
|
17
|
+
};
|
|
18
|
+
return {
|
|
19
|
+
supports,
|
|
20
|
+
read,
|
|
21
|
+
write,
|
|
22
|
+
values() {
|
|
23
|
+
const snapshot = {};
|
|
24
|
+
for (const option of MGBA_ENGINE_OPTIONS) {
|
|
25
|
+
if (supports(option.key))
|
|
26
|
+
snapshot[option.key] = state[option.key];
|
|
27
|
+
}
|
|
28
|
+
return snapshot;
|
|
29
|
+
},
|
|
30
|
+
restoreDefaults() {
|
|
31
|
+
for (const option of MGBA_ENGINE_OPTIONS) {
|
|
32
|
+
write(option.key, option.default);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function createSettingsStore(namespace) {
|
|
38
|
+
const storageKey = `mgba:options:${namespace}`;
|
|
39
|
+
// Storage access throws outright in some privacy modes, so every call is
|
|
40
|
+
// guarded — losing persistence must never take the emulator down with it.
|
|
41
|
+
const storage = () => {
|
|
42
|
+
try {
|
|
43
|
+
return typeof localStorage === 'undefined' ? null : localStorage;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
load() {
|
|
51
|
+
try {
|
|
52
|
+
const raw = storage()?.getItem(storageKey);
|
|
53
|
+
if (!raw)
|
|
54
|
+
return {};
|
|
55
|
+
const parsed = JSON.parse(raw);
|
|
56
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
57
|
+
return {};
|
|
58
|
+
// Anything the catalog no longer recognizes is dropped rather than fed
|
|
59
|
+
// back into the core: this is user-writable storage.
|
|
60
|
+
const values = {};
|
|
61
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
62
|
+
const option = mgbaOption(key);
|
|
63
|
+
if (!option)
|
|
64
|
+
continue;
|
|
65
|
+
const coerced = coerceOptionValue(option, value);
|
|
66
|
+
if (coerced !== undefined)
|
|
67
|
+
values[key] = coerced;
|
|
68
|
+
}
|
|
69
|
+
return values;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return {};
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
save(values) {
|
|
76
|
+
try {
|
|
77
|
+
storage()?.setItem(storageKey, JSON.stringify(values));
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// persistence is best-effort
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
clear() {
|
|
84
|
+
try {
|
|
85
|
+
storage()?.removeItem(storageKey);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// persistence is best-effort
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=mgba.config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mgba.config.js","sourceRoot":"","sources":["../../src/mgba.config.ts"],"names":[],"mappings":"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"}
|