diff --git a/package-lock.json b/package-lock.json index fa72512b..69236aaf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,7 @@ "typescript-formatter": "^7.2.2" }, "optionalDependencies": { + "@wasmer/wasi": "^1.2.2", "chromedriver": "*", "heapdump": "^0.3.15", "jsfuzz": "^1.0.14", @@ -1046,6 +1047,12 @@ "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, + "node_modules/@wasmer/wasi": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@wasmer/wasi/-/wasi-1.2.2.tgz", + "integrity": "sha512-39ZB3gefOVhBmkhf7Ta79RRSV/emIV8LhdvcWhP/MOZEjMmtzoZWMzt7phdKj8CUXOze+AwbvGK60lKaKldn1w==", + "optional": true + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -8829,6 +8836,12 @@ "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, + "@wasmer/wasi": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@wasmer/wasi/-/wasi-1.2.2.tgz", + "integrity": "sha512-39ZB3gefOVhBmkhf7Ta79RRSV/emIV8LhdvcWhP/MOZEjMmtzoZWMzt7phdKj8CUXOze+AwbvGK60lKaKldn1w==", + "optional": true + }, "abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", diff --git a/package.json b/package.json index f9a87a73..6344dac7 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "typescript-formatter": "^7.2.2" }, "optionalDependencies": { + "@wasmer/wasi": "^1.2.2", "chromedriver": "*", "heapdump": "^0.3.15", "jsfuzz": "^1.0.14", diff --git a/res/quicknes_libretro.wasm b/res/quicknes_libretro.wasm new file mode 100755 index 00000000..fdc1475b Binary files /dev/null and b/res/quicknes_libretro.wasm differ diff --git a/res/stella2014_libretro.wasm b/res/stella2014_libretro.wasm new file mode 100755 index 00000000..b2001a5e Binary files /dev/null and b/res/stella2014_libretro.wasm differ diff --git a/src/common/libretroplatform.ts b/src/common/libretroplatform.ts new file mode 100644 index 00000000..67f2b082 --- /dev/null +++ b/src/common/libretroplatform.ts @@ -0,0 +1,51 @@ +import { init, WASI } from '@wasmer/wasi'; +import { AnimationTimer } from './emu'; +import { Platform } from './baseplatform'; + +export class BaseLibretroPlatform { // implements Platform { + + mainElement: HTMLElement; + timer: AnimationTimer; + wasi: WASI; + + constructor(mainElement) { + this.mainElement = mainElement; + this.timer = new AnimationTimer(60, this.update.bind(this)); + } + + async start() { + // This is needed to load the WASI library first (since is a Wasm module) + await init(); + + let wasi = new WASI({ + env: { + // 'ENVVAR1': '1', + // 'ENVVAR2': '2' + }, + args: [ + // 'command', 'arg1', 'arg2' + ], + }); + + const moduleBytes = fetch("res/quicknes_libretro.wasm"); + const module = await WebAssembly.compileStreaming(moduleBytes); + // Instantiate the WASI module + const instance = await wasi.instantiate(module, {}); + + // Run the start function + let exitCode = wasi.start(); + let stdout = wasi.getStdoutString(); + + // This should print "hello world (exit code: 0)" + console.log(`${stdout}(exit code: ${exitCode})`); + + this.wasi = wasi; + } + + update() { + // TODO + } + + reset() { + } +} diff --git a/src/platform/nes.ts b/src/platform/nes.ts index 39f471b5..dfedfea1 100644 --- a/src/platform/nes.ts +++ b/src/platform/nes.ts @@ -9,6 +9,7 @@ import { NullProbe, Probeable, ProbeAll } from "../common/devices"; import Mousetrap = require('mousetrap'); import jsnes = require('../../jsnes'); import { BaseMAME6502Platform } from "../common/mameplatform"; +import { BaseLibretroPlatform } from "../common/libretroplatform"; const JSNES_PRESETS = [ {id:'hello.c', name:'Hello World'}, @@ -567,4 +568,3 @@ class NESMAMEPlatform extends BaseMAME6502Platform implements Platform { PLATFORMS['nes'] = JSNESPlatform; PLATFORMS['nes-asm'] = JSNESPlatform; PLATFORMS['nes.mame'] = NESMAMEPlatform; - diff --git a/src/test/testlibretro.ts b/src/test/testlibretro.ts new file mode 100644 index 00000000..97fd4e0a --- /dev/null +++ b/src/test/testlibretro.ts @@ -0,0 +1,126 @@ +import * as fs from 'fs'; +import { init, WASI } from '@wasmer/wasi'; + +describe('libretro WASI', function () { + it('Should run core', async function () { + await init(); + + let wasi = new WASI({ + env: { + // 'ENVVAR1': '1', + // 'ENVVAR2': '2' + }, + args: [ + // 'command', 'arg1', 'arg2' + ], + }); + + // import sock_accept + let imports = { + wasi_snapshot_preview1: { + sock_accept: function (fd, addr, addrlen) { + console.log('sock_accept', fd, addr, addrlen); + } + }, + env: { + // A placeholder implementation of __cxa_allocate_exception + __cxa_allocate_exception: (size) => { + console.error('__cxa_allocate_exception called with size:', size); + // You should allocate 'size' bytes and return a pointer to this memory + // The implementation here depends on how your WASM module handles memory + // This is just a dummy implementation and likely won't work correctly + return 0; // Returning 0 as a dummy pointer + }, + __cxa_throw: (ptr, type, destructor) => { + console.error('__cxa_throw called, ptr:', ptr, 'type:', type, 'destructor:', destructor); + // This function should not return + // This is just a dummy implementation and likely won't work correctly + throw new Error('cxa_throw called'); + }, + retro_environment_callback: function (cmd, data) { + console.log('retro_environment_callback', cmd, data); + return 0; + }, + retro_video_refresh_callback: function (data, width, height, pitch) { + console.log('retro_video_refresh_callback', data, width, height, pitch); + }, + retro_audio_sample_callback: function (left, right) { + console.log('retro_audio_sample_callback', left, right); + }, + retro_audio_sample_batch_callback: function (data, frames) { + console.log('retro_audio_sample_batch_callback', data, frames); + return frames; + }, + retro_input_poll_callback: function () { + console.log('retro_input_poll_callback'); + }, + retro_input_state_callback: function (port, device, index, id) { + console.log('retro_input_state_callback', port, device, index, id); + return 1; + }, + } + }; + + //const moduleBytes = fs.readFileSync("res/quicknes_libretro.wasm"); + const moduleBytes = fs.readFileSync("res/stella2014_libretro.wasm"); + const module = await WebAssembly.compile(moduleBytes); + // Instantiate the WASI module + const instance = await wasi.instantiate(module, imports); + const exports: any = instance.exports; + + // Run the initialize function + exports._initialize(); + + console.log(exports); + console.log('API version', exports.retro_api_version()); + console.log('callbacks') + exports.retro_init_callbacks(); + console.log('init'); + exports.retro_init(); + console.log('loading'); + const rompath = 'test/roms/nes/shoot2.c.rom'; + // load rom bytes + const romdata = fs.readFileSync(rompath); + /* + const RetroGameInfo = new Struct({ + path: 'string', + data: ['char',0], + size: 'size_t', + meta: ['char',0] + }); + const gameinfo = new RetroGameInfo({ + path: rompath, + data: romdata, + size: 40976, + meta: [] + }); + const library = new Wrapper({ + manipulate: [RetroGameInfo], + }, {memory: instance.exports.memory}); + library.manipulate(gameinfo); + console.log(gameinfo, gameinfo.ref()); + exports.retro_load_game(gameinfo); + */ + // TODO + const rombytes = new Int32Array(romdata.buffer, 0, romdata.length / 4); + //exports.retro_load_rom(rompath, rombytes, rombytes.length, ""); + + let biosBinary = romdata.buffer; + let biosptr = exports.malloc(biosBinary.byteLength); + console.log(biosptr, romdata.length); + //let biosarr = new Uint8Array(exports.memory.buffer, biosptr, biosBinary.byteLength); + exports.retro_load_rom(rompath, biosptr, romdata.length, rompath); + + //console.log('callbacks again') + //exports.retro_init_callbacks(); + console.log('set inputs'); + exports.retro_set_controller_port_device(0,1); + exports.retro_set_controller_port_device(1,1); + // run + exports.retro_reset(); + console.log('running'); + exports.retro_run(); + console.log('ran'); + }); +}); +