Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion runtimes/native/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ endif ()
add_subdirectory(vendor/cubeb)
endif ()

find_package(CURL REQUIRED)

file(GLOB COMMON_SOURCES RELATIVE "${CMAKE_SOURCE_DIR}" "src/*.c")

# Include a strnlen polyfill for some platforms where it's missing (OSX PPC, maybe others)
Expand Down Expand Up @@ -170,7 +172,8 @@ target_link_directories(wasm4 PRIVATE
$<$<BOOL:${TOYWASM}>:${toywasm_tmp_install}/lib>)
endif ()

target_link_libraries(wasm4 cubeb
target_include_directories(wasm4 PRIVATE ${CURL_INCLUDE_DIRS})
target_link_libraries(wasm4 cubeb CURL::libcurl pthread
$<$<BOOL:${MINIFB}>:minifb>
$<$<BOOL:${GLFW}>:glfw>
$<$<BOOL:${TOYWASM}>:toywasm-core>)
Expand Down
75 changes: 48 additions & 27 deletions runtimes/native/src/backend/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "../apu.h"
#include "../runtime.h"
#include "../store.h"
#include "../wasm.h"
#include "../window.h"
#include "../util.h"
Expand Down Expand Up @@ -126,38 +127,56 @@ int main (int argc, const char* argv[]) {
char* diskPath = NULL;

if (argc < 2) {
// Try bundled cart first
FILE* file = fopen(argv[0], "rb");
if (file == NULL) {
goto usage;
if (file != NULL) {
fseek(file, -sizeof(FileFooter), SEEK_END);
FileFooter footer;
if (fread(&footer, 1, sizeof(FileFooter), file) >= sizeof(FileFooter) && footer.magic == 1414676803) {
footer.title[sizeof(footer.title)-1] = '\0';
title = footer.title;
cartBytes = xmalloc(footer.cartLength);
fseek(file, -sizeof(FileFooter) - footer.cartLength, SEEK_END);
cartLength = fread(cartBytes, 1, footer.cartLength, file);
fclose(file);
diskPath = xmalloc(strlen(argv[0]) + sizeof(DISK_FILE_EXT));
strcpy(diskPath, argv[0]);
#ifdef _WIN32
trimFileExtension(diskPath);
#endif
strcat(diskPath, DISK_FILE_EXT);
loadDiskFile(&disk, diskPath);
goto load_cart;
}
fclose(file);
}
fseek(file, -sizeof(FileFooter), SEEK_END);

FileFooter footer;
if (fread(&footer, 1, sizeof(FileFooter), file) < sizeof(FileFooter) || footer.magic != 1414676803) {
usage:
// No bundled cart found
fprintf(stderr, "Usage: wasm4 <cart>\n");
return 1;
// No bundled cart — launch store with minimal dummy cart
{
// Minimal WASM module: imports env.memory, exports empty start+update
static const uint8_t dummyCart[] = {
0x00,0x61,0x73,0x6d,0x01,0x00,0x00,0x00,0x01,0x04,0x01,0x60,0x00,0x00,0x02,0x0f,
0x01,0x03,0x65,0x6e,0x76,0x06,0x6d,0x65,0x6d,0x6f,0x72,0x79,0x02,0x00,0x01,0x03,
0x03,0x02,0x00,0x00,0x07,0x12,0x02,0x05,0x73,0x74,0x61,0x72,0x74,0x00,0x00,0x06,
0x75,0x70,0x64,0x61,0x74,0x65,0x00,0x01,0x0a,0x07,0x02,0x02,0x00,0x0b,0x02,0x00,
0x0b
};
audioInit();
w4_storeInit();
cartBytes = xmalloc(sizeof(dummyCart));
memcpy(cartBytes, dummyCart, sizeof(dummyCart));
cartLength = sizeof(dummyCart);

uint8_t* memory = w4_wasmInit();
w4_runtimeInit(memory, &disk);
w4_wasmLoadModule(cartBytes, cartLength);
w4_storeOpen();
w4_windowSetStoreMode(true);
w4_windowBoot(title);
audioUninit();
return 0;
}

// Make sure the title is null terminated
footer.title[sizeof(footer.title)-1] = '\0';
title = footer.title;

cartBytes = xmalloc(footer.cartLength);
fseek(file, -sizeof(FileFooter) - footer.cartLength, SEEK_END);
cartLength = fread(cartBytes, 1, footer.cartLength, file);
fclose(file);

// Look for disk file
diskPath = xmalloc(strlen(argv[0]) + sizeof(DISK_FILE_EXT));
strcpy(diskPath, argv[0]);
#ifdef _WIN32
trimFileExtension(diskPath); // Trim .exe on Windows
#endif
strcat(diskPath, DISK_FILE_EXT);
loadDiskFile(&disk, diskPath);

} else if (!strcmp(argv[1], "-") || !strcmp(argv[1], "/dev/stdin")) {
size_t bufsize = 1024;
cartBytes = xmalloc(bufsize);
Expand Down Expand Up @@ -205,7 +224,9 @@ int main (int argc, const char* argv[]) {
loadDiskFile(&disk, diskPath);
}

load_cart:
audioInit();
w4_storeInit();

uint8_t* memory = w4_wasmInit();
w4_runtimeInit(memory, &disk);
Expand Down
99 changes: 97 additions & 2 deletions runtimes/native/src/backend/wasm_wasm3.c
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,35 @@ static m3ApiRawFunction (tracef) {
m3ApiSuccess();
}

static bool storeLoaded = false;
static bool cartCrashed = false;

static void check (M3Result result) {
if (result != m3Err_none) {
M3ErrorInfo info;
m3_GetErrorInfo(runtime, &info);
fprintf(stderr, "WASM error: %s (%s)\n", result, info.message);
exit(1);
if (storeLoaded) {
fprintf(stderr, "WASM warning: %s (%s)\n", result, info.message);
update = NULL;
start = NULL;
cartCrashed = true;
} else {
fprintf(stderr, "WASM error: %s (%s)\n", result, info.message);
exit(1);
}
}
}

bool w4_wasmDidCrash(void) {
if (cartCrashed) {
cartCrashed = false;
return true;
}
return false;
}

void w4_wasmSetStoreLoaded(bool value) {
storeLoaded = value;
}

uint8_t* w4_wasmInit () {
Expand All @@ -187,6 +209,15 @@ uint8_t* w4_wasmInit () {
void w4_wasmDestroy () {
m3_FreeRuntime(runtime);
m3_FreeEnvironment(env);
env = NULL;
runtime = NULL;
module = NULL;
start = NULL;
update = NULL;
}

uint8_t* w4_wasmGetMemory () {
return m3_GetMemory(runtime, NULL, 0);
}

void w4_wasmLoadModule (const uint8_t* wasmBuffer, int byteLength) {
Expand Down Expand Up @@ -245,6 +276,70 @@ void w4_wasmLoadModule (const uint8_t* wasmBuffer, int byteLength) {
}
}

int w4_wasmLoadModuleSafe (const uint8_t* wasmBuffer, int byteLength) {
M3Result result;
storeLoaded = true;

result = m3_ParseModule(env, &module, wasmBuffer, byteLength);
if (result) {
fprintf(stderr, "WASM parse error: %s\n", result);
return -1;
}

module->memoryImported = true;

result = m3_LoadModule(runtime, module);
if (result) {
fprintf(stderr, "WASM load error: %s\n", result);
return -1;
}

m3_LinkRawFunction(module, "env", "blit", "v(iiiiii)", blit);
m3_LinkRawFunction(module, "env", "blitSub", "v(iiiiiiiii)", blitSub);
m3_LinkRawFunction(module, "env", "line", "v(iiii)", line);
m3_LinkRawFunction(module, "env", "hline", "v(iii)", hline);
m3_LinkRawFunction(module, "env", "vline", "v(iii)", vline);
m3_LinkRawFunction(module, "env", "oval", "v(iiii)", oval);
m3_LinkRawFunction(module, "env", "rect", "v(iiii)", rect);
m3_LinkRawFunction(module, "env", "text", "v(iii)", text);
m3_LinkRawFunction(module, "env", "textUtf8", "v(iiii)", textUtf8);
m3_LinkRawFunction(module, "env", "textUtf16", "v(iiii)", textUtf16);
m3_LinkRawFunction(module, "env", "tone", "v(iiii)", tone);
m3_LinkRawFunction(module, "env", "diskr", "i(ii)", diskr);
m3_LinkRawFunction(module, "env", "diskw", "i(ii)", diskw);
m3_LinkRawFunction(module, "env", "trace", "v(i)", trace);
m3_LinkRawFunction(module, "env", "traceUtf8", "v(ii)", traceUtf8);
m3_LinkRawFunction(module, "env", "traceUtf16", "v(ii)", traceUtf16);
m3_LinkRawFunction(module, "env", "tracef", "v(ii)", tracef);

m3_FindFunction(&start, runtime, "start");
m3_FindFunction(&update, runtime, "update");

result = m3_RunStart(module);
if (result) {
fprintf(stderr, "WASM start error: %s\n", result);
return -1;
}

M3Function* func;
m3_FindFunction(&func, runtime, "_start");
if (func) {
result = m3_CallV(func);
if (result) {
fprintf(stderr, "WASM _start warning: %s (ignored)\n", result);
}
}
m3_FindFunction(&func, runtime, "_initialize");
if (func) {
result = m3_CallV(func);
if (result) {
fprintf(stderr, "WASM _initialize warning: %s (ignored)\n", result);
}
}

return 0;
}

void w4_wasmCallStart () {
if (start) {
check(m3_CallV(start));
Expand Down
94 changes: 92 additions & 2 deletions runtimes/native/src/backend/window_glfw.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

#include "../window.h"
#include "../runtime.h"
#include "../wasm.h"
#include "../menu.h"
#include "../store.h"

static uint32_t table[256];
static GLuint paletteLocation;
Expand All @@ -24,6 +27,11 @@ static int viewportY;
static int viewportSize;

static bool should_close = false;
static bool storeMode = false; // true when launched without a cart

void w4_windowSetStoreMode (bool enabled) {
storeMode = enabled;
}

static void initLookupTable () {
// Create a lookup table for each byte mapping to 4 bytes:
Expand Down Expand Up @@ -183,8 +191,32 @@ static void update (GLFWwindow* window) {
w4_runtimeSetGamepad(0, gamepad);

if (glfwGetKey(window, GLFW_KEY_ESCAPE)) {
should_close = true;
if (w4_storeIsOpen()) {
if (storeMode) {
should_close = true;
} else {
w4_storeClose();
}
} else if (w4_menuIsOpen()) {
w4_menuClose();
} else {
should_close = true;
}
}

// Enter toggles pause menu
static int enterWasPressed = 0;
int enterPressed = glfwGetKey(window, GLFW_KEY_ENTER);
if (enterPressed && !enterWasPressed) {
if (w4_storeIsOpen()) {
// ignore Enter in store
} else if (w4_menuIsOpen()) {
w4_menuClose();
} else {
w4_menuOpen();
}
}
enterWasPressed = enterPressed;

// Mouse handling
double mouseX, mouseY;
Expand All @@ -201,7 +233,52 @@ static void update (GLFWwindow* window) {
}
w4_runtimeSetMouse(160*(mouseX-contentX)/contentSizeX, 160*(mouseY-contentY)/contentSizeY, mouseButtons);

w4_runtimeUpdate();
if (w4_storeIsOpen()) {
w4_storeInput(gamepad);

// Store was closed via Z button without selecting a cart
if (!w4_storeIsOpen() && storeMode) {
should_close = true;
return;
}

// Check if a cart was downloaded
int cartLen = 0;
uint8_t* cartData = w4_storeGetSelectedCart(&cartLen);
if (cartData) {
w4_storeJoinThread();
fprintf(stderr, "[store] Loading cart (%d bytes)\n", cartLen);
w4_wasmDestroy();
uint8_t* mem = w4_wasmInit();
static w4_Disk storeDisk = {0};
storeDisk.size = 0;
w4_runtimeInit(mem, &storeDisk);
w4_wasmSetStoreLoaded(true);
w4_wasmLoadModule(cartData, cartLen);
// Note: cartData must NOT be freed — wasm3 holds pointers into it
// Refresh memory pointer — wasm3 may realloc during module load
w4_runtimeSetMemory(w4_wasmGetMemory());
storeMode = false;
}
} else if (w4_menuIsOpen()) {
w4_menuInput(gamepad);
int action = w4_menuGetAction();
switch (action) {
case MENU_ACTION_CONTINUE:
w4_menuClose();
break;
case MENU_ACTION_STORE:
w4_menuClose();
w4_storeOpen();
break;
}
} else {
w4_runtimeUpdate();
if (w4_wasmDidCrash()) {
fprintf(stderr, "[store] Cart crashed, opening store\n");
w4_storeOpen();
}
}
}

void w4_windowBoot (const char* title) {
Expand Down Expand Up @@ -242,6 +319,19 @@ void w4_windowBoot (const char* title) {
}

update(window);

if (w4_storeIsOpen()) {
static uint32_t storePalette[4];
static uint8_t storeFb[160*160/4];
w4_storeRender(storePalette, storeFb);
w4_windowComposite(storePalette, storeFb);
} else if (w4_menuIsOpen()) {
static uint32_t menuPalette[4];
static uint8_t menuFb[160*160/4];
w4_menuRender(menuPalette, menuFb);
w4_windowComposite(menuPalette, menuFb);
}

glfwSwapBuffers(window);
glfwPollEvents();

Expand Down
Loading