@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
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import type { JSONSchema } from '@wasm-gaming/engine-specs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Which core boots the ROM. `auto` lets `mCoreFindVF()` sniff the image, which
|
|
5
|
+
* is right for every well-formed dump; the forced modes exist for headerless
|
|
6
|
+
* or mislabelled files.
|
|
7
|
+
*/
|
|
8
|
+
export type MgbaSystem = 'auto' | 'gba' | 'gb';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Game Boy hardware model, for ROMs the GB core boots. `auto` follows the
|
|
12
|
+
* cartridge header (a CGB-aware cart gets CGB, everything else DMG).
|
|
13
|
+
* Maps to mGBA's `gb.model` config value.
|
|
14
|
+
*/
|
|
15
|
+
export type MgbaGbModel = 'auto' | 'dmg' | 'sgb' | 'cgb' | 'agb';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* GBA idle-loop handling (`idleOptimization` in mGBA's config).
|
|
19
|
+
* `remove` skips the busy-waits most games spin in, which is where nearly all
|
|
20
|
+
* of the emulator's speed comes from; `ignore` emulates them cycle for cycle.
|
|
21
|
+
*/
|
|
22
|
+
export type MgbaIdleOptimization = 'ignore' | 'remove' | 'detect';
|
|
23
|
+
|
|
24
|
+
export type MgbaLogLevel = 'off' | 'error' | 'debug';
|
|
25
|
+
|
|
26
|
+
export interface MgbaOptions {
|
|
27
|
+
/** Which core boots the ROM: auto-detected, or forced GBA / Game Boy. */
|
|
28
|
+
system?: MgbaSystem;
|
|
29
|
+
/** Game Boy hardware model. Ignored when the GBA core is running. */
|
|
30
|
+
gbModel?: MgbaGbModel;
|
|
31
|
+
/**
|
|
32
|
+
* Idle-loop optimization for the GBA core. `remove` is mGBA's own default
|
|
33
|
+
* and what makes full speed reachable; `ignore` is the accurate-but-slow
|
|
34
|
+
* setting a handful of games need.
|
|
35
|
+
*/
|
|
36
|
+
idleOptimization?: MgbaIdleOptimization;
|
|
37
|
+
/**
|
|
38
|
+
* Skip the BIOS intro. Without a real BIOS this is forced on — the HLE
|
|
39
|
+
* BIOS has no intro to run.
|
|
40
|
+
*/
|
|
41
|
+
skipBios?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Let the D-pad report left+right (or up+down) at once. Real hardware
|
|
44
|
+
* cannot, and a few games misbehave when it happens.
|
|
45
|
+
*/
|
|
46
|
+
allowOpposingDirections?: boolean;
|
|
47
|
+
/** Canvas scaling filter: `pixelated` for crisp pixels, `smooth` for linear. */
|
|
48
|
+
renderFilter?: 'pixelated' | 'smooth';
|
|
49
|
+
/**
|
|
50
|
+
* Presented aspect ratio. `native` is square pixels — 3:2 on GBA, 10:9 on
|
|
51
|
+
* Game Boy — which is what the LCDs actually had; `4:3` fills a TV-shaped
|
|
52
|
+
* frame instead.
|
|
53
|
+
*/
|
|
54
|
+
aspect?: 'native' | '4:3';
|
|
55
|
+
/**
|
|
56
|
+
* Blend each frame with the previous one, approximating the ghosting of the
|
|
57
|
+
* original unlit LCD. Some games (and most transparency effects done by
|
|
58
|
+
* flickering) were drawn expecting it.
|
|
59
|
+
*/
|
|
60
|
+
interframeBlending?: boolean;
|
|
61
|
+
/** Master audio volume, 0.0–1.0. */
|
|
62
|
+
volume?: number;
|
|
63
|
+
/** Poll connected gamepads (standard mapping) each frame. */
|
|
64
|
+
gamepads?: boolean;
|
|
65
|
+
/** Core messages printed to the console. */
|
|
66
|
+
logLevel?: MgbaLogLevel;
|
|
67
|
+
/** Show the demo shell's in-game settings menu on Escape. Defaults to `true`. */
|
|
68
|
+
escMenu?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const DEFAULT_MGBA_OPTIONS: Required<MgbaOptions> = {
|
|
72
|
+
system: 'auto',
|
|
73
|
+
gbModel: 'auto',
|
|
74
|
+
idleOptimization: 'remove',
|
|
75
|
+
skipBios: true,
|
|
76
|
+
allowOpposingDirections: false,
|
|
77
|
+
renderFilter: 'pixelated',
|
|
78
|
+
aspect: 'native',
|
|
79
|
+
interframeBlending: false,
|
|
80
|
+
volume: 1.0,
|
|
81
|
+
gamepads: true,
|
|
82
|
+
logLevel: 'error',
|
|
83
|
+
escMenu: true,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** Platform ids consumed by `mgbawasm_setup()` (mPLATFORM_* in core/core.h). */
|
|
87
|
+
export const MGBA_PLATFORM_IDS: Record<MgbaSystem, number> = {
|
|
88
|
+
auto: -1,
|
|
89
|
+
gba: 0,
|
|
90
|
+
gb: 1,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/** Values mGBA's `gb.model` config key takes; `auto` means "leave unset". */
|
|
94
|
+
export const MGBA_GB_MODEL_VALUES: Record<MgbaGbModel, string | null> = {
|
|
95
|
+
auto: null,
|
|
96
|
+
dmg: 'DMG',
|
|
97
|
+
sgb: 'SGB',
|
|
98
|
+
cgb: 'CGB',
|
|
99
|
+
agb: 'AGB',
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const MGBA_LOG_LEVEL_IDS: Record<MgbaLogLevel, number> = {
|
|
103
|
+
off: 0,
|
|
104
|
+
error: 1,
|
|
105
|
+
debug: 2,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// --------------------------------------------------------------- catalog
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Settings the SDK can change on a running game, described once here.
|
|
112
|
+
*
|
|
113
|
+
* mGBA keeps its configuration in an `mCoreConfig` the core re-reads when the
|
|
114
|
+
* frontend calls `reloadConfigOption()`, so most of these reach the emulation
|
|
115
|
+
* without a restart; the ones that are consumed while the cartridge is being
|
|
116
|
+
* mapped (the system, the Game Boy model, the BIOS) are tagged `requiresReset`.
|
|
117
|
+
* The rest — canvas filtering, aspect, blending, volume — never reach the core
|
|
118
|
+
* at all and are applied by the SDK.
|
|
119
|
+
*
|
|
120
|
+
* `DEFAULT_MGBA_OPTIONS`, the manifest's options schema and the demo shell's
|
|
121
|
+
* ESC menu are all derived from this catalog, so adding a row here is enough to
|
|
122
|
+
* expose a new setting.
|
|
123
|
+
*/
|
|
124
|
+
export type MgbaOptionKey = Exclude<keyof MgbaOptions, 'escMenu'>;
|
|
125
|
+
|
|
126
|
+
export type MgbaOptionValue = boolean | string | number;
|
|
127
|
+
|
|
128
|
+
interface OptionSpecBase {
|
|
129
|
+
/** Option key, as used in `EngineConfig.options` and the manifest schema. */
|
|
130
|
+
key: MgbaOptionKey;
|
|
131
|
+
label: string;
|
|
132
|
+
description: string;
|
|
133
|
+
/**
|
|
134
|
+
* Takes effect on the next power-on rather than immediately — the ESC menu
|
|
135
|
+
* tags these, and the SDK applies them from `reset()`.
|
|
136
|
+
*/
|
|
137
|
+
requiresReset?: boolean;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** One selectable value, as offered by the menu. */
|
|
141
|
+
export interface MgbaChoice<T> {
|
|
142
|
+
value: T;
|
|
143
|
+
label: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export type MgbaOptionSpec = OptionSpecBase &
|
|
147
|
+
(
|
|
148
|
+
| { type: 'boolean'; default: boolean }
|
|
149
|
+
| { type: 'enum'; default: string; values: MgbaChoice<string>[] }
|
|
150
|
+
| {
|
|
151
|
+
type: 'number';
|
|
152
|
+
default: number;
|
|
153
|
+
/** Values the menu cycles through; the schema may still take a range. */
|
|
154
|
+
values: MgbaChoice<number>[];
|
|
155
|
+
integer?: boolean;
|
|
156
|
+
/**
|
|
157
|
+
* When set, the schema advertises this range instead of the menu's
|
|
158
|
+
* choices, so hosts can pass values the menu does not offer.
|
|
159
|
+
*/
|
|
160
|
+
range?: { minimum: number; maximum: number };
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
export interface MgbaOptionGroup {
|
|
165
|
+
id: string;
|
|
166
|
+
label: string;
|
|
167
|
+
options: MgbaOptionSpec[];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const MGBA_OPTION_GROUPS: MgbaOptionGroup[] = [
|
|
171
|
+
{
|
|
172
|
+
id: 'video',
|
|
173
|
+
label: 'Video',
|
|
174
|
+
options: [
|
|
175
|
+
{
|
|
176
|
+
key: 'renderFilter',
|
|
177
|
+
label: 'Image filtering',
|
|
178
|
+
description:
|
|
179
|
+
'Scaling filter applied when the picture is stretched to the canvas. Pixelated keeps pixel art crisp; smooth softens it.',
|
|
180
|
+
type: 'enum',
|
|
181
|
+
default: DEFAULT_MGBA_OPTIONS.renderFilter,
|
|
182
|
+
values: [
|
|
183
|
+
{ value: 'pixelated', label: 'Pixelated' },
|
|
184
|
+
{ value: 'smooth', label: 'Smooth' },
|
|
185
|
+
],
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
key: 'aspect',
|
|
189
|
+
label: 'Aspect ratio',
|
|
190
|
+
description:
|
|
191
|
+
'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.',
|
|
192
|
+
type: 'enum',
|
|
193
|
+
default: DEFAULT_MGBA_OPTIONS.aspect,
|
|
194
|
+
values: [
|
|
195
|
+
{ value: 'native', label: 'Native' },
|
|
196
|
+
{ value: '4:3', label: '4:3' },
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
key: 'interframeBlending',
|
|
201
|
+
label: 'Interframe blending',
|
|
202
|
+
description:
|
|
203
|
+
'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.',
|
|
204
|
+
type: 'boolean',
|
|
205
|
+
default: DEFAULT_MGBA_OPTIONS.interframeBlending,
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
id: 'audio',
|
|
211
|
+
label: 'Audio',
|
|
212
|
+
options: [
|
|
213
|
+
{
|
|
214
|
+
key: 'volume',
|
|
215
|
+
label: 'Volume',
|
|
216
|
+
description: 'Master audio volume.',
|
|
217
|
+
type: 'number',
|
|
218
|
+
default: DEFAULT_MGBA_OPTIONS.volume,
|
|
219
|
+
values: [
|
|
220
|
+
{ value: 0, label: 'Mute' },
|
|
221
|
+
{ value: 0.25, label: '25%' },
|
|
222
|
+
{ value: 0.5, label: '50%' },
|
|
223
|
+
{ value: 0.75, label: '75%' },
|
|
224
|
+
{ value: 1, label: '100%' },
|
|
225
|
+
],
|
|
226
|
+
range: { minimum: 0, maximum: 1 },
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
id: 'emulation',
|
|
232
|
+
label: 'Emulation',
|
|
233
|
+
options: [
|
|
234
|
+
{
|
|
235
|
+
key: 'system',
|
|
236
|
+
label: 'System',
|
|
237
|
+
description:
|
|
238
|
+
'Which core boots the ROM. Auto sniffs the image, which is right for every well-formed dump; force it for headerless or mislabelled files.',
|
|
239
|
+
type: 'enum',
|
|
240
|
+
default: DEFAULT_MGBA_OPTIONS.system,
|
|
241
|
+
requiresReset: true,
|
|
242
|
+
values: [
|
|
243
|
+
{ value: 'auto', label: 'Auto' },
|
|
244
|
+
{ value: 'gba', label: 'GBA' },
|
|
245
|
+
{ value: 'gb', label: 'Game Boy' },
|
|
246
|
+
],
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
key: 'gbModel',
|
|
250
|
+
label: 'Game Boy model',
|
|
251
|
+
description:
|
|
252
|
+
'Hardware the Game Boy core emulates. Auto follows the cartridge header. Ignored when a GBA ROM is loaded.',
|
|
253
|
+
type: 'enum',
|
|
254
|
+
default: DEFAULT_MGBA_OPTIONS.gbModel,
|
|
255
|
+
requiresReset: true,
|
|
256
|
+
values: [
|
|
257
|
+
{ value: 'auto', label: 'Auto' },
|
|
258
|
+
{ value: 'dmg', label: 'DMG' },
|
|
259
|
+
{ value: 'sgb', label: 'Super GB' },
|
|
260
|
+
{ value: 'cgb', label: 'Color' },
|
|
261
|
+
{ value: 'agb', label: 'GBA' },
|
|
262
|
+
],
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
key: 'idleOptimization',
|
|
266
|
+
label: 'Idle loops',
|
|
267
|
+
description:
|
|
268
|
+
'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.',
|
|
269
|
+
type: 'enum',
|
|
270
|
+
default: DEFAULT_MGBA_OPTIONS.idleOptimization,
|
|
271
|
+
values: [
|
|
272
|
+
{ value: 'remove', label: 'Remove' },
|
|
273
|
+
{ value: 'detect', label: 'Detect' },
|
|
274
|
+
{ value: 'ignore', label: 'Ignore' },
|
|
275
|
+
],
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
key: 'skipBios',
|
|
279
|
+
label: 'Skip BIOS intro',
|
|
280
|
+
description:
|
|
281
|
+
'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.',
|
|
282
|
+
type: 'boolean',
|
|
283
|
+
default: DEFAULT_MGBA_OPTIONS.skipBios,
|
|
284
|
+
requiresReset: true,
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
id: 'controllers',
|
|
290
|
+
label: 'Controllers',
|
|
291
|
+
options: [
|
|
292
|
+
{
|
|
293
|
+
key: 'allowOpposingDirections',
|
|
294
|
+
label: 'Opposing directions',
|
|
295
|
+
description:
|
|
296
|
+
'Let the D-pad report left+right (or up+down) at once. Real hardware cannot, and a few games misbehave when it happens.',
|
|
297
|
+
type: 'boolean',
|
|
298
|
+
default: DEFAULT_MGBA_OPTIONS.allowOpposingDirections,
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
key: 'gamepads',
|
|
302
|
+
label: 'Gamepads',
|
|
303
|
+
description: 'Poll connected gamepads (standard mapping) each frame.',
|
|
304
|
+
type: 'boolean',
|
|
305
|
+
default: DEFAULT_MGBA_OPTIONS.gamepads,
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
id: 'debug',
|
|
311
|
+
label: 'Debug',
|
|
312
|
+
options: [
|
|
313
|
+
{
|
|
314
|
+
key: 'logLevel',
|
|
315
|
+
label: 'Core logging',
|
|
316
|
+
description:
|
|
317
|
+
'Core messages printed to the browser console: errors and warnings, or also its informational ones.',
|
|
318
|
+
type: 'enum',
|
|
319
|
+
default: DEFAULT_MGBA_OPTIONS.logLevel,
|
|
320
|
+
values: [
|
|
321
|
+
{ value: 'off', label: 'Off' },
|
|
322
|
+
{ value: 'error', label: 'Errors' },
|
|
323
|
+
{ value: 'debug', label: 'Debug' },
|
|
324
|
+
],
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
},
|
|
328
|
+
];
|
|
329
|
+
|
|
330
|
+
/** Flat view of every runtime-tweakable option across all groups. */
|
|
331
|
+
export const MGBA_ENGINE_OPTIONS: MgbaOptionSpec[] = MGBA_OPTION_GROUPS.flatMap(
|
|
332
|
+
(group) => group.options,
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
const OPTION_BY_KEY = new Map<string, MgbaOptionSpec>(
|
|
336
|
+
MGBA_ENGINE_OPTIONS.map((option) => [option.key, option]),
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
export function mgbaOption(key: string): MgbaOptionSpec | undefined {
|
|
340
|
+
return OPTION_BY_KEY.get(key);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Coerces a host- or storage-supplied value to what the option accepts,
|
|
345
|
+
* returning `undefined` when it is not a value the option can take. Numbers
|
|
346
|
+
* outside a `range` are clamped rather than rejected; enums are exact.
|
|
347
|
+
*/
|
|
348
|
+
export function coerceOptionValue(
|
|
349
|
+
option: MgbaOptionSpec,
|
|
350
|
+
value: unknown,
|
|
351
|
+
): MgbaOptionValue | undefined {
|
|
352
|
+
if (option.type === 'boolean') {
|
|
353
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (option.type === 'enum') {
|
|
357
|
+
const next = String(value);
|
|
358
|
+
return option.values.some((choice) => choice.value === next) ? next : undefined;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const next = typeof value === 'number' ? value : Number(value);
|
|
362
|
+
if (!Number.isFinite(next)) return undefined;
|
|
363
|
+
if (option.range) {
|
|
364
|
+
return Math.min(option.range.maximum, Math.max(option.range.minimum, next));
|
|
365
|
+
}
|
|
366
|
+
return option.values.some((choice) => choice.value === next) ? next : undefined;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// ------------------------------------------------- engine-specs ESC menu
|
|
370
|
+
|
|
371
|
+
/** One row as the `esc-menu` component of the demo shell renders it. */
|
|
372
|
+
export interface EscMenuOption {
|
|
373
|
+
key: string;
|
|
374
|
+
label: string;
|
|
375
|
+
description: string;
|
|
376
|
+
type: 'boolean' | 'enum';
|
|
377
|
+
value: MgbaOptionValue;
|
|
378
|
+
values?: MgbaChoice<MgbaOptionValue>[];
|
|
379
|
+
requiresReset?: boolean;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export interface EscMenuGroup {
|
|
383
|
+
id: string;
|
|
384
|
+
label: string;
|
|
385
|
+
options: EscMenuOption[];
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Projects the catalog onto the shape `@wasm-gaming/engine-specs` (>=0.2.5)
|
|
390
|
+
* feeds its `esc-menu` component.
|
|
391
|
+
*
|
|
392
|
+
* The menu lives in the demo shell now, not in this package: the shell renders
|
|
393
|
+
* the rows and emits `option-change`, and the host writes the value back
|
|
394
|
+
* through `engine.config`. Since the component only draws chips for `boolean`
|
|
395
|
+
* and `enum`, numeric options are handed over as an enum of their menu choices
|
|
396
|
+
* — the numbers survive, because the chips compare values with `===`.
|
|
397
|
+
*
|
|
398
|
+
* Pass the running engine's `config.values()` so the menu opens on what the
|
|
399
|
+
* emulator is actually set to rather than on this package's defaults.
|
|
400
|
+
*/
|
|
401
|
+
export function toEscMenuGroups(
|
|
402
|
+
values: Record<string, MgbaOptionValue> = {},
|
|
403
|
+
): EscMenuGroup[] {
|
|
404
|
+
return MGBA_OPTION_GROUPS.map((group) => ({
|
|
405
|
+
id: group.id,
|
|
406
|
+
label: group.label,
|
|
407
|
+
options: group.options.map((option): EscMenuOption => ({
|
|
408
|
+
key: option.key,
|
|
409
|
+
label: option.label,
|
|
410
|
+
description: option.description,
|
|
411
|
+
type: option.type === 'boolean' ? 'boolean' : 'enum',
|
|
412
|
+
value: values[option.key] ?? option.default,
|
|
413
|
+
...(option.type === 'boolean' ? {} : { values: option.values }),
|
|
414
|
+
...(option.requiresReset ? { requiresReset: true } : {}),
|
|
415
|
+
})),
|
|
416
|
+
}));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// -------------------------------------------------------------- schema
|
|
420
|
+
|
|
421
|
+
function schemaForOption(option: MgbaOptionSpec): JSONSchema {
|
|
422
|
+
if (option.type === 'boolean') {
|
|
423
|
+
return { type: 'boolean', default: option.default, description: option.description };
|
|
424
|
+
}
|
|
425
|
+
if (option.type === 'enum') {
|
|
426
|
+
return {
|
|
427
|
+
type: 'string',
|
|
428
|
+
enum: option.values.map((choice) => choice.value),
|
|
429
|
+
default: option.default,
|
|
430
|
+
description: option.description,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
type: option.integer ? 'integer' : 'number',
|
|
435
|
+
default: option.default,
|
|
436
|
+
...(option.range
|
|
437
|
+
? { minimum: option.range.minimum, maximum: option.range.maximum }
|
|
438
|
+
: { enum: option.values.map((choice) => choice.value) }),
|
|
439
|
+
description: option.description,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export const MGBA_OPTIONS_SCHEMA: JSONSchema = {
|
|
444
|
+
type: 'object',
|
|
445
|
+
additionalProperties: false,
|
|
446
|
+
properties: {
|
|
447
|
+
...Object.fromEntries(
|
|
448
|
+
MGBA_ENGINE_OPTIONS.map((option) => [option.key, schemaForOption(option)]),
|
|
449
|
+
),
|
|
450
|
+
escMenu: {
|
|
451
|
+
type: 'boolean',
|
|
452
|
+
default: true,
|
|
453
|
+
description: "Show the demo shell's in-game settings menu when the player presses Escape.",
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
};
|