romdevtools 0.56.1 → 0.71.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +338 -0
  2. package/README.md +9 -7
  3. package/examples/dreamcast/hello/main.c +24 -0
  4. package/examples/dreamcast/platformer/main.c +31 -0
  5. package/examples/dreamcast/puzzle/main.c +44 -0
  6. package/examples/dreamcast/racing/main.c +39 -0
  7. package/examples/dreamcast/shmup/main.c +50 -0
  8. package/examples/dreamcast/sports/main.c +39 -0
  9. package/package.json +5 -3
  10. package/src/analysis/analyze.js +60 -5
  11. package/src/analysis/decompile.js +3 -0
  12. package/src/analysis/rizin.js +3 -1
  13. package/src/cores/capabilities.js +43 -7
  14. package/src/cores/registry.js +13 -8
  15. package/src/host/LibretroGL.js +26 -23
  16. package/src/host/LibretroHost.js +302 -24
  17. package/src/host/callbacks.js +72 -1
  18. package/src/host/coreLoader.js +17 -4
  19. package/src/host/cpu-state.js +32 -0
  20. package/src/host/dc-aica-state.js +67 -0
  21. package/src/mcp/tools/audio.js +1 -1
  22. package/src/mcp/tools/disasm.js +1 -1
  23. package/src/mcp/tools/index.js +1 -1
  24. package/src/mcp/tools/platform-docs.js +1 -1
  25. package/src/mcp/tools/platform-tools.js +9 -1
  26. package/src/mcp/tools/project.js +16 -0
  27. package/src/mcp/tools/toolchain.js +115 -10
  28. package/src/platforms/dreamcast/MENTAL_MODEL.md +87 -0
  29. package/src/platforms/dreamcast/TROUBLESHOOTING.md +55 -0
  30. package/src/platforms/dreamcast/UPSTREAM_SOURCES.md +57 -0
  31. package/src/platforms/n64/MENTAL_MODEL.md +84 -0
  32. package/src/platforms/n64/TROUBLESHOOTING.md +60 -0
  33. package/src/platforms/n64/UPSTREAM_SOURCES.md +52 -0
  34. package/src/platforms/n64/lib/c/n64.c +181 -80
  35. package/src/platforms/ps1/MENTAL_MODEL.md +85 -0
  36. package/src/platforms/ps1/TROUBLESHOOTING.md +55 -0
  37. package/src/platforms/ps1/UPSTREAM_SOURCES.md +54 -0
  38. package/src/platforms/snes/TROUBLESHOOTING.md +10 -0
  39. package/src/toolchains/asar/asar.js +84 -14
  40. package/src/toolchains/index.js +30 -0
  41. package/src/toolchains/mips-c/mips-c.js +35 -1
  42. package/src/toolchains/sh-c/lib/dc-crt0.s +23 -0
  43. package/src/toolchains/sh-c/lib/dc.h +152 -0
  44. package/src/toolchains/sh-c/lib/dc.ld +11 -0
  45. package/src/toolchains/sh-c/lib/libc.a +0 -0
  46. package/src/toolchains/sh-c/lib/libgcc.a +0 -0
  47. package/src/toolchains/sh-c/lib/libm.a +0 -0
  48. package/src/toolchains/sh-c/sh-c.js +104 -0
  49. package/src/toolchains/sh-elf-gcc/gcc.js +122 -0
@@ -135,13 +135,14 @@ export function sniffPlatform(p) {
135
135
  if (/\.pce$/i.test(p)) return "pce";
136
136
  if (/\.(z64|n64|v64)$/i.test(p)) return "n64";
137
137
  if (/\.(psexe|psx)$/i.test(p)) return "ps1"; // .exe/.bin ambiguous — pass platform explicitly
138
+ if (/\.(cdi|gdi)$/i.test(p)) return "dreamcast"; // DC disc images; .elf is cross-platform — pass platform explicitly
138
139
  if (/\.(gen|md|bin)$/i.test(p)) return "genesis";
139
140
  return null;
140
141
  }
141
142
 
142
143
  /** rizin asm.bits per arch (analysis defaults; rizin's loader usually sets
143
144
  * these for recognized formats, but raw blobs need a hint). */
144
- const BITS = { arm: 32, m68k: 32, snes: 16, mips: 32 };
145
+ const BITS = { arm: 32, m68k: 32, snes: 16, mips: 32, sh: 32 };
145
146
 
146
147
  /**
147
148
  * The rizin analysis-SEED command for a context. For the 8/16-bit platforms
@@ -154,7 +155,10 @@ const BITS = { arm: 32, m68k: 32, snes: 16, mips: 32 };
154
155
  * @returns {string} the seed command (no trailing semicolon)
155
156
  */
156
157
  function analysisSeed({ arch, codeStart }) {
157
- if (arch === "mips") {
158
+ // MIPS (N64/PS1) and SH-4 (Dreamcast) are raw code images with no rizin-recognized
159
+ // entry, so `aaa` finds nothing — seed a function at the code start + recursively
160
+ // analyze its call graph (`af` + `aac`). The 8/16-bit bin formats self-seed via aaa.
161
+ if (arch === "mips" || arch === "sh") {
158
162
  const at = "0x" + (codeStart || 0).toString(16);
159
163
  return `af @ ${at}; aac @ ${at}`;
160
164
  }
@@ -167,8 +171,12 @@ function analysisSeed({ arch, codeStart }) {
167
171
  * `rebase` so callers get real VAs that round-trip through the decompile
168
172
  * VA→fileOffset math. N64 (.z64) keeps its absolute VAs baked into the code and is
169
173
  * analyzed flat from codeStart, so no rebase. */
170
- function mipsAnalysisBase({ arch, platform, loadBase, codeStart }) {
171
- const rebase = (arch === "mips" && platform === "ps1" && loadBase) ? (loadBase & 0xfff00000) >>> 0 : 0;
174
+ function mipsAnalysisBase({ platform, loadBase, codeStart }) {
175
+ // PS1 (left-padded to the VA's low 20 bits) and Dreamcast (same, loadBase
176
+ // 0x8c010000) need the VA's HIGH bits added back so addresses round-trip. N64 runs
177
+ // flat with VAs already baked in (no rebase).
178
+ const padded = (platform === "ps1" || platform === "dreamcast") && loadBase;
179
+ const rebase = padded ? (loadBase & 0xfff00000) >>> 0 : 0;
172
180
  return { baddr: undefined, seedAt: codeStart, rebase };
173
181
  }
174
182
 
@@ -276,6 +284,39 @@ async function loadContext(romPath, platformOverride) {
276
284
  romBytes = text;
277
285
  }
278
286
  }
287
+ // Dreamcast SH-4: KOS produces an ELF (load base 0x8c010000) or a stripped flat
288
+ // binary. For an ELF, strip to the first PT_LOAD segment's bytes + take its vaddr
289
+ // as loadBase. Like PS1, SH-4 uses PC-relative + absolute addressing, so left-pad
290
+ // so flat offset == the VA's low bits and add the high bits back as rebase.
291
+ if (platform === "dreamcast") {
292
+ let text = romBytes, loadVa = 0x8c010000;
293
+ if (romBytes.length >= 0x34 && romBytes[0] === 0x7f && romBytes[1] === 0x45 &&
294
+ romBytes[2] === 0x4c && romBytes[3] === 0x46) { // "\x7fELF"
295
+ // ELF32 LE: e_phoff @0x1c, e_phentsize @0x2a, e_phnum @0x2c. Find first PT_LOAD.
296
+ const u32 = (o) => (romBytes[o] | (romBytes[o + 1] << 8) | (romBytes[o + 2] << 16) | (romBytes[o + 3] << 24)) >>> 0;
297
+ const u16 = (o) => romBytes[o] | (romBytes[o + 1] << 8);
298
+ const phoff = u32(0x1c), phentsize = u16(0x2a), phnum = u16(0x2c);
299
+ for (let i = 0; i < phnum; i++) {
300
+ const ph = phoff + i * phentsize;
301
+ if (u32(ph) === 1) { // PT_LOAD
302
+ const p_offset = u32(ph + 4), p_vaddr = u32(ph + 8), p_filesz = u32(ph + 16);
303
+ loadVa = p_vaddr >>> 0;
304
+ text = romBytes.subarray(p_offset, p_offset + p_filesz);
305
+ break;
306
+ }
307
+ }
308
+ }
309
+ loadBase = loadVa >>> 0;
310
+ const lowPad = loadBase & 0x000fffff;
311
+ if (lowPad > 0 && lowPad <= 0x200000) {
312
+ const padded = new Uint8Array(lowPad + text.length);
313
+ padded.set(text, lowPad);
314
+ romBytes = padded;
315
+ codeStart = lowPad;
316
+ } else {
317
+ romBytes = text;
318
+ }
319
+ }
279
320
 
280
321
  // A6: container/format sniff. Some dumps are interleaved/headered such that a
281
322
  // FLAT read scrambles every byte → fake "bad instruction" noise everywhere.
@@ -623,12 +664,26 @@ export async function analyzeDecompile(romPath, address, platformOverride) {
623
664
  // offset and decompile via the MIPS SLEIGH spec (MIPS:BE:32 for N64, MIPS:LE:32
624
665
  // for PS1). N64: normalize byte order, fileOff = vaddr - entryVaddr + 0x1000 (post
625
666
  // IPL3). PS1: strip PS-EXE, fileOff = vaddr - loadAddr.
626
- if (platform === "n64" || platform === "ps1") {
667
+ if (platform === "n64" || platform === "ps1" || platform === "dreamcast") {
627
668
  let fileOff;
628
669
  if (platform === "n64") {
629
670
  romBytes = normalizeN64ByteOrder(romBytes).bytes;
630
671
  const entry = ((romBytes[0x08] << 24) | (romBytes[0x09] << 16) | (romBytes[0x0a] << 8) | romBytes[0x0b]) >>> 0;
631
672
  fileOff = ((address >>> 0) - entry + 0x1000) >>> 0;
673
+ } else if (platform === "dreamcast") {
674
+ // SH-4: strip the ELF to its first PT_LOAD segment (vaddr = loadBase), or treat
675
+ // a raw image as flat at 0x8c010000. fileOff = address - segment vaddr.
676
+ let loadVa = 0x8c010000;
677
+ if (romBytes.length >= 0x34 && romBytes[0] === 0x7f && romBytes[1] === 0x45 && romBytes[2] === 0x4c && romBytes[3] === 0x46) {
678
+ const u32 = (o) => (romBytes[o] | (romBytes[o + 1] << 8) | (romBytes[o + 2] << 16) | (romBytes[o + 3] << 24)) >>> 0;
679
+ const u16 = (o) => romBytes[o] | (romBytes[o + 1] << 8);
680
+ const phoff = u32(0x1c), phentsize = u16(0x2a), phnum = u16(0x2c);
681
+ for (let i = 0; i < phnum; i++) {
682
+ const ph = phoff + i * phentsize;
683
+ if (u32(ph) === 1) { loadVa = u32(ph + 8) >>> 0; romBytes = romBytes.subarray(u32(ph + 4), u32(ph + 4) + u32(ph + 16)); break; }
684
+ }
685
+ }
686
+ fileOff = ((address >>> 0) - loadVa) >>> 0;
632
687
  } else {
633
688
  let loadAddr = 0;
634
689
  if (romBytes.length >= 0x800 && romBytes[0] === 0x50 && romBytes[1] === 0x53 && romBytes[2] === 0x2d && romBytes[3] === 0x58) {
@@ -51,6 +51,9 @@ export const SLEIGH_LANGID = {
51
51
  // 32-bit MIPS III code). Ghidra ships both MIPS variants in its stock SLEIGH.
52
52
  ps1: "MIPS:LE:32:default",
53
53
  n64: "MIPS:BE:32:default",
54
+ // Dreamcast = SH-4 (SuperH), little-endian. Ghidra ships SuperH4 in its stock
55
+ // SLEIGH; we compile SuperH4_le.sla (see scripts/build-decompiler.sh).
56
+ dreamcast: "SuperH4:LE:32:default",
54
57
  };
55
58
 
56
59
  /**
@@ -59,14 +59,16 @@ export const RIZIN_ARCH = {
59
59
  // below — same arch, different byte order.
60
60
  ps1: "mips",
61
61
  n64: "mips",
62
+ dreamcast: "sh", // SH-4 (SuperH) — rizin's `sh` plugin covers it
62
63
  };
63
64
 
64
65
  /** Byte order per platform, for shared-arch families that ship both (MIPS). Only
65
66
  * platforms NOT matching their arch default need an entry; absent → use the
66
- * loader/arch default. PS1 is little, N64 is big. */
67
+ * loader/arch default. PS1 is little, N64 is big. Dreamcast SH-4 is little. */
67
68
  export const RIZIN_ENDIAN = {
68
69
  ps1: "little",
69
70
  n64: "big",
71
+ dreamcast: "little",
70
72
  };
71
73
 
72
74
  /**
@@ -219,12 +219,17 @@ export const CAPABILITIES = {
219
219
  cpus: { main: "r3000", secondary: ["gte"] },
220
220
  audioChips: ["spu"],
221
221
  memoryRegions: [...GENERIC_REGIONS],
222
- renderingKind: "framebuffer", introspection: "shallow",
222
+ renderingKind: "3d", introspection: "shallow",
223
223
  ops: {
224
- // pcsx_rearmed: software render + HLE BIOS (no firmware, no GL). run/screenshot
225
- // + cpuState (R3000 regsnap) + cheats live; disasm + decompile work (MIPS
226
- // Capstone + the MIPS:LE:32 SLEIGH spec). build needs a PS1 toolchain
227
- // (PSn00bSDK, not yet). The framebuffer renderer has no tile/sprite inspectors.
224
+ // beetle_psx_hw: the GPU renders on the REAL GPU via the GLES3/WebGL2 hardware
225
+ // renderer through native-gles (like glide64-N64 + Flycast-DC), with OpenBIOS
226
+ // EMBEDDED (PCSX-Redux, MIT, region-free) no Sony firmware to ship, no BIOS file.
227
+ // run/screenshot + cheats + cpuState + audioDebug live; disasm + decompile work
228
+ // (MIPS Capstone + the MIPS:LE:32 SLEIGH spec). cpuState (R3000A GPR_full/BACKED_PC)
229
+ // + audioDebug (SPU $1F801C00 register block) come from beetle-side
230
+ // romdev_mips_regs_get/romdev_spu_get exports patched into cpu.c (see
231
+ // scripts/patches/romdev-snippets/beetle-psx-regsnap.c). build needs a PS1
232
+ // toolchain (PSn00bSDK, not yet).
228
233
  build: true, run: true, screenshot: true,
229
234
  inspectSprites: false, inspectPalette: false, inspectBackground: false,
230
235
  renderingContext: false, cpuState: true, audioDebug: true,
@@ -248,6 +253,29 @@ export const CAPABILITIES = {
248
253
  cart: false, disasm: true, decompile: true,
249
254
  },
250
255
  },
256
+
257
+ dreamcast: {
258
+ cpuFamily: "sh", decompileQuality: "good", tier: "sh",
259
+ cpus: { main: "sh4", secondary: ["arm7"] }, // SH-4 main + ARM7 AICA sound CPU
260
+ audioChips: ["aica"],
261
+ memoryRegions: [...GENERIC_REGIONS],
262
+ renderingKind: "3d", introspection: "shallow",
263
+ ops: {
264
+ // Flycast WASM boots + RUNS homebrew .elf (reios HLE): the SH-4 executes guest
265
+ // code (run + memory introspection), and the PowerVR2 present-path works — flycast
266
+ // renders to the GL FBO and the host reads it back (verified: a framebuffer-writing
267
+ // program shows ~727k captured pixels). `build` lands with the sh-elf-gcc WASM
268
+ // toolchain. cpuState (SH-4 Sh4cntx regs) + audioDebug (AICA 64-channel register
269
+ // window) come from romdev_sh4_regs_get/romdev_aica_get patched into the flycast
270
+ // libretro entry (see scripts/patches/romdev-snippets/flycast-debug.c). The 3D
271
+ // renderer has no tile/sprite inspectors (N/A by hw). disasm/decompile = SH-4
272
+ // analysis slice (rizin `sh` + Ghidra SuperH4 SLEIGH).
273
+ build: true, run: true, screenshot: true,
274
+ inspectSprites: false, inspectPalette: false, inspectBackground: false,
275
+ renderingContext: false, cpuState: true, audioDebug: true,
276
+ cart: false, disasm: true, decompile: true,
277
+ },
278
+ },
251
279
  };
252
280
 
253
281
  /** The 32-bit MIPS tier (PS1/N64) — marked `tier:"mips"`. A PARTIAL tier: they
@@ -259,14 +287,22 @@ export const MIPS_TIER_PLATFORMS = Object.entries(CAPABILITIES)
259
287
  .filter(([, c]) => c.tier === "mips")
260
288
  .map(([p]) => p);
261
289
 
290
+ /** The next-gen tiers (32-bit CPU families added after the canonical 14): MIPS
291
+ * (PS1/N64) + SH (Dreamcast) + future. Any platform carrying a CPU-family `tier`
292
+ * is held to its OWN conformance, not the "all 14" cross-checks; a new platform
293
+ * starts here (analysis-first) and graduates as run/build/etc. land. */
294
+ export const NEXTGEN_TIER_PLATFORMS = Object.entries(CAPABILITIES)
295
+ .filter(([, c]) => c.tier === "mips" || c.tier === "sh")
296
+ .map(([p]) => p);
297
+
262
298
  /** Back-compat: the analysis-only set is now empty (PS1/N64 gained run/screenshot
263
299
  * in the run-side wiring). Kept so the name still resolves for older imports. */
264
300
  export const ANALYSIS_ONLY_PLATFORMS = [];
265
301
 
266
302
  /** The canonical tier-1 platforms (the 14): full op surface, universal build/run/
267
- * screenshot. Excludes the partial MIPS tier above. */
303
+ * screenshot. Excludes the partial next-gen tiers above. */
268
304
  export const CONTRACT_PLATFORMS = Object.keys(CAPABILITIES)
269
- .filter((p) => !MIPS_TIER_PLATFORMS.includes(p));
305
+ .filter((p) => !NEXTGEN_TIER_PLATFORMS.includes(p));
270
306
 
271
307
  /** Does `platform` support `op`? Unknown platform/op → false. */
272
308
  export function supports(platform, op) {
@@ -57,14 +57,19 @@ export const CORES = {
57
57
  // webgl-node bridge only when one of these boots (hwRender:true). The other 14 are
58
58
  // software-rendered and never touch GL, so a headless user without the GPU module
59
59
  // is unaffected.
60
- // parallel_n64 built headless-angrylion (software RDP + VI scanout, no GL) so raw
61
- // CPU-rendered homebrew framebuffers display the clean no-GL-dependency N64 path,
62
- // like pcsx_rearmed for PS1. (The GL/glide64 build only presents RDP display-lists.)
63
- n64: { platform: "n64", coreName: "parallel_n64", pkg: "romdev-core-parallel-n64", displayName: "Nintendo 64 (ParaLLEl N64, software)", hwRender: false },
64
- // pcsx_rearmed = SOFTWARE renderer + built-in HLE BIOS (no firmware to ship, no
65
- // GL dependency) the clean no-dependency PS1 path, like the other 14. The GL
66
- // beetle_psx_hw (+ real BIOS) stays available as a higher-fidelity alternative.
67
- ps1: { platform: "ps1", coreName: "pcsx_rearmed", pkg: "romdev-core-pcsx-rearmed", displayName: "Sony PlayStation (PCSX-ReARMed, HLE)", aka: "psx,playstation" },
60
+ // parallel_n64 renders the RDP on the REAL GPU through glide64 (GL HLE) native-gles
61
+ // (the host's WebGL2 bridge), same path as Flycast. The host forces the glide64 plugin
62
+ // via a core option. NOT software RDP (angrylion) that was the old headless build.
63
+ n64: { platform: "n64", coreName: "parallel_n64", pkg: "romdev-core-parallel-n64", displayName: "Nintendo 64 (ParaLLEl N64, glide64 GL)", hwRender: true },
64
+ // beetle_psx_hw = mednafen PSX with the GLES3/WebGL2 HARDWARE renderer rendered on
65
+ // the real GPU through native-gles (like glide64-N64 + Flycast-DC). Ships with OpenBIOS
66
+ // EMBEDDED (PCSX-Redux, MIT-licensed, region-free) so there's no copyrighted Sony
67
+ // firmware to ship and no BIOS file to supply the GPU PS1 path with an open BIOS.
68
+ ps1: { platform: "ps1", coreName: "beetle_psx_hw", pkg: "romdev-core-beetle-psx-hw", displayName: "Sony PlayStation (Beetle PSX HW, OpenBIOS)", aka: "psx,playstation", hwRender: true },
69
+ // Flycast = full Dreamcast emulator, GLES3/WebGL2 HW-render (PowerVR2 is GPU-first,
70
+ // no software framebuffer path) → driven through the native-gles/webgl-node bridge
71
+ // like the GL N64 build. HLE BIOS (reios) on by default — no firmware to ship.
72
+ dreamcast: { platform: "dreamcast", coreName: "flycast", pkg: "romdev-core-flycast", displayName: "Sega Dreamcast (Flycast)", aka: "dc", hwRender: true },
68
73
  };
69
74
 
70
75
  /** Try to get {jsPath,wasmPath} for a core from its binary package. */
@@ -174,13 +174,20 @@ export class LibretroGL {
174
174
 
175
175
  /**
176
176
  * Read back the rendered frame as RGBA pixels.
177
+ * @param {number} [cropW] active width the core reported (e.g. DC 640); when smaller
178
+ * than the FBO, read back only that bottom-left region instead of the whole viewport
179
+ * (the GL bottom-left origin puts the active framebuffer in the lower-left corner).
180
+ * @param {number} [cropH] active height the core reported (e.g. DC 480).
177
181
  * Returns { pixels, width, height } or null if not active.
178
182
  */
179
- readbackFrame() {
183
+ readbackFrame(cropW, cropH) {
180
184
  if (!this.active) return null;
181
185
 
182
186
  gl.glFinish();
183
- const w = this.fboW, h = this.fboH;
187
+ // Crop to the core's active resolution when it's a sub-region of the FBO; this
188
+ // strips the dead border a fixed-size GL FBO leaves around a smaller native frame.
189
+ const w = (cropW > 0 && cropW <= this.fboW) ? cropW : this.fboW;
190
+ const h = (cropH > 0 && cropH <= this.fboH) ? cropH : this.fboH;
184
191
  const GL_RGBA = 0x1908, GL_UNSIGNED_BYTE = 0x1401;
185
192
 
186
193
  if (!this._glPixels || this._glPixels.length !== w * h * 4) {
@@ -188,6 +195,7 @@ export class LibretroGL {
188
195
  }
189
196
 
190
197
  gl.glBindFramebuffer(0x8D40, 0); // GL_FRAMEBUFFER — read from default FBO
198
+ // x=0,y=0 is the bottom-left of the FBO, which is where the native frame is drawn.
191
199
  gl.glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, this._glPixels);
192
200
 
193
201
 
@@ -225,32 +233,27 @@ export class LibretroGL {
225
233
  return this._procAddressCache.get(name);
226
234
  }
227
235
 
228
- // Look up the function on native-gles
236
+ // PREFERRED: cores built with emscripten's own WebGL layer (GL_ENABLE_GET_PROC_
237
+ // ADDRESS=1, e.g. Flycast) resolve GL through emscripten_GetProcAddress, which
238
+ // returns a REAL function-table pointer into the WebGL JS implementation —
239
+ // correct signature, no stub. Use it when the core exports it. (The old
240
+ // native-gles `gl[name]` + addFunction(...,'i') stub path traps the moment the
241
+ // core calls a multi-arg GL fn through a 0-arg pointer — that's the
242
+ // context_reset "null function or function signature mismatch".)
243
+ if (typeof mod._emscripten_GetProcAddress === "function") {
244
+ const ptr = mod._emscripten_GetProcAddress(symPtr) >>> 0;
245
+ this._procAddressCache.set(name, ptr);
246
+ return ptr;
247
+ }
248
+
249
+ // Fallback (native-gles bridge cores, e.g. the glide64 N64 build): the GL fns
250
+ // are linked directly into the core, so a non-zero "available" marker suffices —
251
+ // the core calls its own linked function, not this pointer.
229
252
  const fn = gl[name];
230
253
  if (!fn) {
231
- // Not found — some cores probe for extension functions
232
254
  this._procAddressCache.set(name, 0);
233
255
  return 0;
234
256
  }
235
-
236
- // We need to create an Emscripten addFunction wrapper that bridges
237
- // the WASM call to the native GL call. The challenge is that each
238
- // GL function has a different signature.
239
- //
240
- // For libretro cores compiled with Emscripten, they typically call
241
- // GL functions directly (linked at compile time) rather than through
242
- // get_proc_address. get_proc_address is mainly used for extension
243
- // functions. We return 0 for now if they're probing — the core's
244
- // Emscripten build should have the GL functions linked directly.
245
- //
246
- // If a core truly needs runtime-resolved GL, we'd need a signature
247
- // table mapping GL function names to their parameter types.
248
-
249
- // For common GL functions, return a stub that indicates "available"
250
- // The actual calls go through the Emscripten-linked GL functions.
251
- // Returning non-zero tells the core the function exists.
252
- // We create a minimal no-op wrapper since the core will call the
253
- // directly-linked version, not this pointer.
254
257
  const stub = mod.addFunction(() => { return 0; }, 'i');
255
258
  this._procAddressCache.set(name, stub);
256
259
  return stub;