emscripten friendly sdl loop

This commit is contained in:
lizzie 2026-06-10 00:03:43 +00:00
parent 6247795f12
commit 9da56ebaba
6 changed files with 155 additions and 146 deletions

View file

@ -258,6 +258,8 @@ If running under Firefox and you hit "out of memory" on dev console, close the e
To run the binary (after building) you should be fine with `node ./eden-cli.js`. For obvious reasons no Qt frontend is available on WASM, support for Vulkan is done charily via [llvmpipe2wasm](https://github.com/Devsh-Graphics-Programming/llvmpipe2wasm). To run the binary (after building) you should be fine with `node ./eden-cli.js`. For obvious reasons no Qt frontend is available on WASM, support for Vulkan is done charily via [llvmpipe2wasm](https://github.com/Devsh-Graphics-Programming/llvmpipe2wasm).
If you run into the error "acorn.js can't be found" check [this associated issue](https://github.com/emscripten-core/emscripten/issues/13368), the fix in short is `npm --global install acorn`. On FreeBSD you could run `npm` under root, or you could do the sane thing and do `sudo chown -R $USER /usr/local/lib/node_modules/ /usr/local/bin/` (remember to restore permissions afterwards!) unless you wish to run `npm` under root which is generally a bad idea.
2026-06-09: As of writing, no Dynarmic-based JIT is possible on this target, full interpreted emulation is the only reasonable option. While there is some efforts on making a JIT like [here](https://github.com/wingo/wasm-jit) or [here](https://wingolog.org/archives/2022/08/18/just-in-time-code-generation-within-webassembly), the result is so latency expensive we're better off using an interpreter instead. 2026-06-09: As of writing, no Dynarmic-based JIT is possible on this target, full interpreted emulation is the only reasonable option. While there is some efforts on making a JIT like [here](https://github.com/wingo/wasm-jit) or [here](https://wingolog.org/archives/2022/08/18/just-in-time-code-generation-within-webassembly), the result is so latency expensive we're better off using an interpreter instead.
## Windows ## Windows

View file

@ -77,12 +77,13 @@ if (NOT MSVC)
endif() endif()
if (PLATFORM_EMSCRIPTEN) if (PLATFORM_EMSCRIPTEN)
# 10GB is required... yikes! # 10GB is required at max... yikes!
target_link_options(yuzu-cmd PRIVATE target_link_options(yuzu-cmd PRIVATE
-sALLOW_MEMORY_GROWTH=1 -sALLOW_MEMORY_GROWTH=1
-sINITIAL_MEMORY=33554432
-sMAXIMUM_MEMORY=10737418240 -sMAXIMUM_MEMORY=10737418240
-sGLOBAL_BASE=16777216 -sGLOBAL_BASE=16777216
-sEXPORTED_RUNTIME_METHODS="['FS']" -sEXPORTED_RUNTIME_METHODS=['FS']
-sPTHREAD_POOL_SIZE_STRICT=0 -sPTHREAD_POOL_SIZE_STRICT=0
-sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency) -sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency)
endif() endif()

View file

@ -32,9 +32,6 @@ EmuWindow_SDL3::EmuWindow_SDL3(InputCommon::InputSubsystem* input_subsystem_, Co
} }
EmuWindow_SDL3::~EmuWindow_SDL3() { EmuWindow_SDL3::~EmuWindow_SDL3() {
#ifdef __EMSCRIPTEN__
emscripten_cancel_main_loop();
#endif
system.HIDCore().UnloadInputDevices(); system.HIDCore().UnloadInputDevices();
input_subsystem->Shutdown(); input_subsystem->Shutdown();
SDL_Quit(); SDL_Quit();
@ -169,23 +166,8 @@ void EmuWindow_SDL3::Fullscreen() {
} }
} }
void EmuWindow_SDL3::WaitEvent() { void EmuWindow_SDL3::OnEvent(SDL_Event& event) {
// Called on main thread // Called on main thread
SDL_Event event;
if (!SDL_WaitEvent(&event)) {
const char* error = SDL_GetError();
if (!error || strcmp(error, "") == 0) {
// https://github.com/libsdl-org/SDL/issues/5780
// Sometimes SDL will return without actually having hit an error condition;
// just ignore it in this case.
return;
}
LOG_CRITICAL(Frontend, "SDL_WaitEvent failed: {}", error);
exit(1);
}
switch (event.type) { switch (event.type) {
case SDL_EVENT_WINDOW_RESIZED: case SDL_EVENT_WINDOW_RESIZED:
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
@ -255,6 +237,9 @@ void EmuWindow_SDL3::WaitEvent() {
// Credits to Samantas5855 and others for this function. // Credits to Samantas5855 and others for this function.
void EmuWindow_SDL3::SetWindowIcon() { void EmuWindow_SDL3::SetWindowIcon() {
#if defined(__EMSCRIPTEN__) || defined(__wasi__)
// Icons do not work yet
#else
SDL_IOStream* const yuzu_icon_stream = SDL_IOFromConstMem((void*)yuzu_icon, yuzu_icon_size); SDL_IOStream* const yuzu_icon_stream = SDL_IOFromConstMem((void*)yuzu_icon, yuzu_icon_size);
if (yuzu_icon_stream == nullptr) { if (yuzu_icon_stream == nullptr) {
LOG_WARNING(Frontend, "Failed to create Eden icon stream."); LOG_WARNING(Frontend, "Failed to create Eden icon stream.");
@ -268,6 +253,7 @@ void EmuWindow_SDL3::SetWindowIcon() {
// The icon is attached to the window pointer // The icon is attached to the window pointer
SDL_SetWindowIcon(render_window, window_icon); SDL_SetWindowIcon(render_window, window_icon);
SDL_DestroySurface(window_icon); SDL_DestroySurface(window_icon);
#endif
} }
void EmuWindow_SDL3::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) { void EmuWindow_SDL3::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {

View file

@ -13,6 +13,7 @@
#include "core/frontend/graphics_context.h" #include "core/frontend/graphics_context.h"
struct SDL_Window; struct SDL_Window;
union SDL_Event;
namespace Core { namespace Core {
class System; class System;
@ -35,7 +36,7 @@ public:
bool IsShown() const override; bool IsShown() const override;
/// Wait for the next event on the main thread. /// Wait for the next event on the main thread.
void WaitEvent(); void OnEvent(SDL_Event& event);
// Sets the window icon from yuzu.bmp // Sets the window icon from yuzu.bmp
void SetWindowIcon(); void SetWindowIcon();

View file

@ -11,6 +11,8 @@
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__
#include <emscripten.h> #include <emscripten.h>
#endif #endif
#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL_main.h>
#include <fmt/ostream.h> #include <fmt/ostream.h>
@ -43,9 +45,7 @@
#ifdef _WIN32 #ifdef _WIN32
// windows.h needs to be included before shellapi.h // windows.h needs to be included before shellapi.h
#include <windows.h> #include <windows.h>
#include <shellapi.h> #include <shellapi.h>
#include "common/windows/timer_resolution.h" #include "common/windows/timer_resolution.h"
#endif #endif
@ -178,8 +178,15 @@ static void OnStatusMessageReceived(const Network::StatusMessageEntry& msg) {
std::cout << std::endl << "* " << message << std::endl << std::endl; std::cout << std::endl << "* " << message << std::endl << std::endl;
} }
/// Application entry point struct SdlState {
int main(int argc, char** argv) { Common::DetachedTasks detached_tasks{};
Core::System system{};
std::unique_ptr<EmuWindow_SDL3> emu_window;
};
extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
SdlState* state = new SdlState();
#ifdef _WIN32 #ifdef _WIN32
if (AttachConsole(ATTACH_PARENT_PROCESS)) { if (AttachConsole(ATTACH_PARENT_PROCESS)) {
freopen("CONOUT$", "wb", stdout); freopen("CONOUT$", "wb", stdout);
@ -190,16 +197,14 @@ int main(int argc, char** argv) {
Common::Log::Initialize(); Common::Log::Initialize();
Common::Log::SetColorConsoleBackendEnabled(true); Common::Log::SetColorConsoleBackendEnabled(true);
Common::Log::Start(); Common::Log::Start();
Common::DetachedTasks detached_tasks;
int option_index = 0; int option_index = 0;
#ifdef _WIN32 #ifdef _WIN32
int argc_w; int argc_w;
auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w); auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w);
if (argv_w == nullptr) { if (argv_w == nullptr) {
LOG_CRITICAL(Frontend, "Failed to get command line arguments"); LOG_CRITICAL(Frontend, "Failed to get command line arguments");
return -1; return SDL_APP_FAILURE;
} }
#endif #endif
std::string filepath; std::string filepath;
@ -247,7 +252,7 @@ int main(int argc, char** argv) {
break; break;
case 'h': case 'h':
PrintHelp(argv[0]); PrintHelp(argv[0]);
return 0; return SDL_APP_FAILURE;
case 'g': case 'g':
filepath = std::string(optarg); filepath = std::string(optarg);
break; break;
@ -264,7 +269,7 @@ int main(int argc, char** argv) {
if (!std::regex_match(str_arg, re)) { if (!std::regex_match(str_arg, re)) {
std::cout << "Wrong format for option --multiplayer\n"; std::cout << "Wrong format for option --multiplayer\n";
PrintHelp(argv[0]); PrintHelp(argv[0]);
return 0; return SDL_APP_FAILURE;
} }
std::smatch match; std::smatch match;
@ -274,17 +279,16 @@ int main(int argc, char** argv) {
password = match[2]; password = match[2];
address = match[3]; address = match[3];
if (!match[4].str().empty()) { if (!match[4].str().empty()) {
port = static_cast<u16>(std::strtoul(match[4].str().c_str(), nullptr, 0)); port = u16(std::strtoul(match[4].str().c_str(), nullptr, 0));
} }
std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$"); std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
if (!std::regex_match(nickname, nickname_re)) { if (!std::regex_match(nickname, nickname_re)) {
std::cout LOG_ERROR(Frontend, "Nickname is not valid. Must be 4 to 20 alphanumeric characters");
<< "Nickname is not valid. Must be 4 to 20 alphanumeric characters.\n"; return SDL_APP_FAILURE;
return 0;
} }
if (address.empty()) { if (address.empty()) {
std::cout << "Address to room must not be empty.\n"; LOG_ERROR(Frontend, "Address to room must not be empty");
return 0; return SDL_APP_FAILURE;
} }
break; break;
} }
@ -297,7 +301,7 @@ int main(int argc, char** argv) {
break; break;
case 'v': case 'v':
PrintVersion(); PrintVersion();
return 0; return SDL_APP_FAILURE;
} }
} else { } else {
#ifdef _WIN32 #ifdef _WIN32
@ -341,79 +345,73 @@ int main(int argc, char** argv) {
if (filepath.empty()) { if (filepath.empty()) {
LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified"); LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
return -1; return SDL_APP_FAILURE;
} }
Core::System system{}; state->system.Initialize();
system.Initialize();
InputCommon::InputSubsystem input_subsystem{}; InputCommon::InputSubsystem input_subsystem{};
// Apply the command line arguments // Apply the command line arguments
system.ApplySettings(); state->system.ApplySettings();
std::unique_ptr<EmuWindow_SDL3> emu_window;
switch (Settings::values.renderer_backend.GetValue()) { switch (Settings::values.renderer_backend.GetValue()) {
#ifdef HAS_OPENGL #ifdef HAS_OPENGL
case Settings::RendererBackend::OpenGL_GLSL: case Settings::RendererBackend::OpenGL_GLSL:
case Settings::RendererBackend::OpenGL_GLASM: case Settings::RendererBackend::OpenGL_GLASM:
case Settings::RendererBackend::OpenGL_SPIRV: case Settings::RendererBackend::OpenGL_SPIRV:
emu_window = std::make_unique<EmuWindow_SDL3_GL>(&input_subsystem, system, fullscreen); state->emu_window = std::make_unique<EmuWindow_SDL3_GL>(&input_subsystem, state->system, fullscreen);
break; break;
#endif #endif
case Settings::RendererBackend::Vulkan: case Settings::RendererBackend::Vulkan:
emu_window = std::make_unique<EmuWindow_SDL3_VK>(&input_subsystem, system, fullscreen); state->emu_window = std::make_unique<EmuWindow_SDL3_VK>(&input_subsystem, state->system, fullscreen);
break; break;
case Settings::RendererBackend::Null: case Settings::RendererBackend::Null:
emu_window = std::make_unique<EmuWindow_SDL3_Null>(&input_subsystem, system, fullscreen); state->emu_window = std::make_unique<EmuWindow_SDL3_Null>(&input_subsystem, state->system, fullscreen);
break; break;
default: default:
LOG_CRITICAL(Frontend, "Invalid renderer backend"); LOG_CRITICAL(Frontend, "Invalid renderer backend");
return -1; return SDL_APP_FAILURE;
} }
#ifdef _WIN32 #ifdef _WIN32
Common::Windows::SetCurrentTimerResolutionToMaximum(); Common::Windows::SetCurrentTimerResolutionToMaximum();
system.CoreTiming().SetTimerResolutionNs(Common::Windows::GetCurrentTimerResolution()); state->system.CoreTiming().SetTimerResolutionNs(Common::Windows::GetCurrentTimerResolution());
#endif #endif
system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); state->system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); state->system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); state->system.GetFileSystemController().CreateFactories(*state->system.GetFilesystem());
system.GetUserChannel().clear(); state->system.GetUserChannel().clear();
Service::AM::FrontendAppletParameters load_parameters{ Service::AM::FrontendAppletParameters load_parameters{
.applet_id = Service::AM::AppletId::Application, .applet_id = Service::AM::AppletId::Application,
}; };
const Core::SystemResultStatus load_result{system.Load(*emu_window, filepath, load_parameters)}; const Core::SystemResultStatus load_result = state->system.Load(*state->emu_window, filepath, load_parameters);
switch (load_result) { switch (load_result) {
case Core::SystemResultStatus::ErrorGetLoader:
LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filepath);
return -1;
case Core::SystemResultStatus::ErrorLoader:
LOG_CRITICAL(Frontend, "Failed to load ROM!");
return -1;
case Core::SystemResultStatus::ErrorNotInitialized:
LOG_CRITICAL(Frontend, "CPUCore not initialized");
return -1;
case Core::SystemResultStatus::ErrorVideoCore:
LOG_CRITICAL(Frontend, "Failed to initialize VideoCore!");
return -1;
case Core::SystemResultStatus::Success: case Core::SystemResultStatus::Success:
break; // Expected case break; // Expected case
case Core::SystemResultStatus::ErrorGetLoader:
LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filepath);
return SDL_APP_FAILURE;
case Core::SystemResultStatus::ErrorLoader:
LOG_CRITICAL(Frontend, "Failed to load ROM!");
return SDL_APP_FAILURE;
case Core::SystemResultStatus::ErrorNotInitialized:
LOG_CRITICAL(Frontend, "CPUCore not initialized");
return SDL_APP_FAILURE;
case Core::SystemResultStatus::ErrorVideoCore:
LOG_CRITICAL(Frontend, "Failed to initialize VideoCore!");
return SDL_APP_FAILURE;
default: default:
if (static_cast<u32>(load_result) > const u16 loader_id = u16(Core::SystemResultStatus::ErrorLoader);
static_cast<u32>(Core::SystemResultStatus::ErrorLoader)) { const u16 error_id = u16(load_result) - loader_id;
const u16 loader_id = static_cast<u16>(Core::SystemResultStatus::ErrorLoader); LOG_CRITICAL(Frontend,
const u16 error_id = static_cast<u16>(load_result) - loader_id; "While attempting to load the ROM requested, an error occurred. Please "
LOG_CRITICAL(Frontend, "refer to the Eden wiki for more information or the Eden discord for "
"While attempting to load the ROM requested, an error occurred. Please " "additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}",
"refer to the Eden wiki for more information or the Eden discord for " loader_id, error_id, Loader::ResultStatus(error_id));
"additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}", return SDL_APP_FAILURE;
loader_id, error_id, static_cast<Loader::ResultStatus>(error_id));
}
break;
} }
if (use_multiplayer) { if (use_multiplayer) {
@ -422,49 +420,47 @@ int main(int argc, char** argv) {
member->BindOnStatusMessageReceived(OnStatusMessageReceived); member->BindOnStatusMessageReceived(OnStatusMessageReceived);
member->BindOnStateChanged(OnStateChanged); member->BindOnStateChanged(OnStateChanged);
member->BindOnError(OnNetworkError); member->BindOnError(OnNetworkError);
LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port, LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port, nickname);
nickname);
member->Join(nickname, address.c_str(), port, 0, Network::NoPreferredIP, password); member->Join(nickname, address.c_str(), port, 0, Network::NoPreferredIP, password);
} else { } else {
LOG_ERROR(Network, "Could not access RoomMember"); LOG_ERROR(Network, "Could not access RoomMember");
return 0; return SDL_APP_FAILURE;
} }
} }
// Core is loaded, start the GPU (makes the GPU contexts current to this thread) // Core is loaded, start the GPU (makes the GPU contexts current to this thread)
system.GPU().Start(); state->system.GPU().Start();
system.GetCpuManager().OnGpuReady(); state->system.GetCpuManager().OnGpuReady();
if (Settings::values.use_disk_shader_cache.GetValue()) { if (Settings::values.use_disk_shader_cache.GetValue()) {
system.Renderer().ReadRasterizer()->LoadDiskResources( state->system.Renderer().ReadRasterizer()->LoadDiskResources(
system.GetApplicationProcessProgramID(), std::stop_token{}, state->system.GetApplicationProcessProgramID(), std::stop_token{},
[](VideoCore::LoadCallbackStage, size_t value, size_t total) {}); [](VideoCore::LoadCallbackStage, size_t value, size_t total) {});
} }
system.RegisterExitCallback([&] { // don't do anything, SDL3 already exists for us :D
// Just exit right away. state->system.RegisterExitCallback([] {});
exit(0); void(state->system.Run());
}); if (state->system.DebuggerEnabled())
void(system.Run()); state->system.InitializeDebugger();
if (system.DebuggerEnabled()) { return SDL_APP_SUCCESS;
system.InitializeDebugger(); }
} extern "C" SDL_AppResult SDL_AppIterate(void *appstate) {
SdlState *state = (SdlState *)appstate;
#ifdef __EMSCRIPTEN__ return state->emu_window->IsOpen() ? SDL_APP_CONTINUE : SDL_APP_SUCCESS;
// Required so lambda fits snuggly into our "main loop" }
static EmuWindow_SDL3* static_ems_emu_window = emu_window.get(); extern "C" SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {
emscripten_set_main_loop([]() { SdlState *state = (SdlState *)appstate;
static_ems_emu_window->WaitEvent(); state->emu_window->OnEvent(*event);
}, 0, 1); return SDL_APP_SUCCESS;
#else }
while (emu_window->IsOpen()) extern "C" void SDL_AppQuit(void *appstate, SDL_AppResult result) {
emu_window->WaitEvent(); SdlState *state = (SdlState *)appstate;
#endif state->system.DetachDebugger();
system.DetachDebugger(); void(state->system.Pause());
void(system.Pause()); state->system.ShutdownMainProcess();
system.ShutdownMainProcess(); state->detached_tasks.WaitForAllTasks();
detached_tasks.WaitForAllTasks(); delete state;
return 0;
} }
#define VMA_IMPLEMENTATION #define VMA_IMPLEMENTATION

99
tools/miniserver.js Normal file → Executable file
View file

@ -1,4 +1,4 @@
#!/usr/local/env node #!/usr/bin/env node
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
@ -21,51 +21,61 @@ const server = createServer((req, res) => {
<html> <html>
<head> <head>
<title>eden-cli</title> <title>eden-cli</title>
</head>
<body style="margin:0;padding:0;background-color:black;font-family:Monospace,Tahoma,Arial;">
<script> <script>
var Module = {}; // MUST be global otherwise wasm does wawawawa var Module = { //do not prepend var
function finished_body() { mainScriptUrlOrBlob: 'eden-cli.js',
Module = { //do not prepend var print: (e) => {
print: (e) => { e = e.replace('[1;31m', '<span style="color:red;font-weight:bold;">');
e = e.replace('[1;31m', '<span style="color:red;font-weight:bold;">'); e = e.replace('[0;37m', '<span style="color:white;font-weight:bold;">');
e = e.replace('[0;37m', '<span style="color:white;font-weight:bold;">'); e = e.replace('[1;35m', '<span style="color:pink;font-weight:bold;">');
e = e.replace('[1;35m', '<span style="color:pink;font-weight:bold;">'); e = e.replace('[1;33m', '<span style="color:yellow;font-weight:bold;">');
e = e.replace('[1;33m', '<span style="color:yellow;font-weight:bold;">'); e = e.replace('[0m', '</span>');
e = e.replace('[0m', '</span>'); tty_stdout.innerHTML += \`\${e}</br>\`;
tty_stdout.innerHTML += e + '</br>'; },
}, printErr: (e) => {
printErr: (e) => { tty_stdout.innerHTML += \`<span style="color:red">\${e}</span></br>\`;
tty_stdout.innerHTML += '<span style="color:red">' + e + '</span></br>'; },
}, // not a wasm func but idc
// not a wasm func but idc printInternal: (e) => {
printInternal: (e) => { tty_stdout.innerHTML += \`<span style="color:pink">Internal WASM: \${e}</span></br>\`;
tty_stdout.innerHTML += '<span style="color:pink">Internal WASM: ' + e + '</span></br>'; },
}, onRuntimeInitialized: () => { Module.printInternal("runtime ok"); },
onRuntimeInitialized: () => { Module.printInternal("runtime ok"); }, setStatus: (e) => { Module.printInternal(e); },
setStatus: (e) => { Module.printInternal(e); }, monitorRunDependencies: (e) => { Module.printInternal("monitor deps: " + e); },
monitorRunDependencies: (e) => { Module.printInternal("monitor deps: " + e); }, __wasm_call_ctors: () => { Module.printInternal("ctors beep"); },
__wasm_call_ctors: () => { Module.printInternal("ctors beep"); }, };
}; var tty_stdout = document.createElement('div');
document.body.appendChild(tty_stdout);
Module.arguments = ['game.nro'];
var tty_stdout = document.createElement('div'); var gameNroFileBuffer = {};
document.body.appendChild(tty_stdout);
Module.printInternal("loading from ${build_dir}/eden-cli.js");
Module.arguments = ['game.nro']; Module.printInternal("Atomics: " + window.Atomics);
Module.printInternal("SharedArrayBuffer: " + window.SharedArrayBuffer);
Module.printInternal("trying to load script (if it hangs here check console)");
fetch('game.nro').then((resp) => {
if (!resp.ok)
throw Error(\`\${resp.status}\`);
return resp.arrayBuffer();
}).then((buffer) => {
gameNroFileBuffer = buffer;
Module.printInternal("buffer: " + gameNroFileBuffer);
// load the thingy AFTER loading the nro
Module.printInternal(\`loading from ${build_dir}\${Module.mainScriptUrlOrBlob}\`);
var script = document.createElement('script'); var script = document.createElement('script');
script.src = '/eden-cli.js'; script.src = '/eden-cli.js';
script.onload = () => { script.onload = () => {
Module.printInternal("Atomics: " + window.Atomics); // copy just most relevant :)
Module.printInternal("SharedArrayBuffer: " + window.SharedArrayBuffer); console.log(Module);
Module.printInternal("trying to load script (if it hangs here check console)"); Module.FS.writeFile('/game.nro', new DataView(gameNroFileBuffer));
Module.FS.writeFile('/game.nro', [1,2,3]);
}; };
document.head.appendChild(script); document.head.appendChild(script);
} }).catch(Module.printErr);
</script> </script>
</head>
<body style="margin:0;padding:0;background-color:black;font-family:Monospace,Tahoma,Arial;" onload="finished_body();">
</body> </body>
</html>`); </html>`);
} else if (req.url === '/eden-cli.js') { } else if (req.url === '/eden-cli.js') {
@ -86,12 +96,25 @@ function finished_body() {
}); });
res.end(content, 'utf-8'); res.end(content, 'utf-8');
}); });
} else if (req.url === '/game.nro') {
readFile(nro_file, (err, content) => {
res.writeHead(200, {
'Content-Type': 'application/wasm',
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp'
});
res.end(content, 'utf-8');
});
} else {
res.writeHead(404, {});
res.end('', 'utf-8');
} }
}); });
const build_dir = process.argv[2]; const build_dir = process.argv[2];
if (build_dir === undefined) { const nro_file = process.argv[3];
console.log(`Usage: ${process.argv[0]} ${process.argv[1]} [build directory]`); if (typeof build_dir == "undefined" || typeof nro_file == "undefined") {
console.log(`Usage: ${process.argv[0]} ${process.argv[1]} [build directory] [NRO file]`);
} else { } else {
server.listen(2210, () => { server.listen(2210, () => {
console.log(`${process.argv[0]} ${process.argv[1]} http://localhost:2210`); console.log(`${process.argv[0]} ${process.argv[1]} http://localhost:2210`);