Skip to content

Commit 0eaf201

Browse files
committed
Add built-in game store and menu system
- Store: browse and download carts from wasm4.org directly in the runtime - Menu: pause menu (Enter key) with Continue, Store, Reset options - No-arg launch: running wasm4 without a cart file opens the store - wasm3 fix: skip duplicate memory section for imported memory - Store UX: wrap-around navigation, cursor position remembered - Safe module loading with memory pointer refresh after load
1 parent 5910784 commit 0eaf201

13 files changed

Lines changed: 1173 additions & 34 deletions

File tree

runtimes/native/CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ endif ()
120120
add_subdirectory(vendor/cubeb)
121121
endif ()
122122

123+
find_package(CURL REQUIRED)
124+
123125
file(GLOB COMMON_SOURCES RELATIVE "${CMAKE_SOURCE_DIR}" "src/*.c")
124126

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

173-
target_link_libraries(wasm4 cubeb
175+
target_include_directories(wasm4 PRIVATE ${CURL_INCLUDE_DIRS})
176+
target_link_libraries(wasm4 cubeb CURL::libcurl pthread
174177
$<$<BOOL:${MINIFB}>:minifb>
175178
$<$<BOOL:${GLFW}>:glfw>
176179
$<$<BOOL:${TOYWASM}>:toywasm-core>)

runtimes/native/src/backend/main.c

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include "../apu.h"
88
#include "../runtime.h"
9+
#include "../store.h"
910
#include "../wasm.h"
1011
#include "../window.h"
1112
#include "../util.h"
@@ -126,38 +127,56 @@ int main (int argc, const char* argv[]) {
126127
char* diskPath = NULL;
127128

128129
if (argc < 2) {
130+
// Try bundled cart first
129131
FILE* file = fopen(argv[0], "rb");
130-
if (file == NULL) {
131-
goto usage;
132+
if (file != NULL) {
133+
fseek(file, -sizeof(FileFooter), SEEK_END);
134+
FileFooter footer;
135+
if (fread(&footer, 1, sizeof(FileFooter), file) >= sizeof(FileFooter) && footer.magic == 1414676803) {
136+
footer.title[sizeof(footer.title)-1] = '\0';
137+
title = footer.title;
138+
cartBytes = xmalloc(footer.cartLength);
139+
fseek(file, -sizeof(FileFooter) - footer.cartLength, SEEK_END);
140+
cartLength = fread(cartBytes, 1, footer.cartLength, file);
141+
fclose(file);
142+
diskPath = xmalloc(strlen(argv[0]) + sizeof(DISK_FILE_EXT));
143+
strcpy(diskPath, argv[0]);
144+
#ifdef _WIN32
145+
trimFileExtension(diskPath);
146+
#endif
147+
strcat(diskPath, DISK_FILE_EXT);
148+
loadDiskFile(&disk, diskPath);
149+
goto load_cart;
150+
}
151+
fclose(file);
132152
}
133-
fseek(file, -sizeof(FileFooter), SEEK_END);
134153

135-
FileFooter footer;
136-
if (fread(&footer, 1, sizeof(FileFooter), file) < sizeof(FileFooter) || footer.magic != 1414676803) {
137-
usage:
138-
// No bundled cart found
139-
fprintf(stderr, "Usage: wasm4 <cart>\n");
140-
return 1;
154+
// No bundled cart — launch store with minimal dummy cart
155+
{
156+
// Minimal WASM module: imports env.memory, exports empty start+update
157+
static const uint8_t dummyCart[] = {
158+
0x00,0x61,0x73,0x6d,0x01,0x00,0x00,0x00,0x01,0x04,0x01,0x60,0x00,0x00,0x02,0x0f,
159+
0x01,0x03,0x65,0x6e,0x76,0x06,0x6d,0x65,0x6d,0x6f,0x72,0x79,0x02,0x00,0x01,0x03,
160+
0x03,0x02,0x00,0x00,0x07,0x12,0x02,0x05,0x73,0x74,0x61,0x72,0x74,0x00,0x00,0x06,
161+
0x75,0x70,0x64,0x61,0x74,0x65,0x00,0x01,0x0a,0x07,0x02,0x02,0x00,0x0b,0x02,0x00,
162+
0x0b
163+
};
164+
audioInit();
165+
w4_storeInit();
166+
cartBytes = xmalloc(sizeof(dummyCart));
167+
memcpy(cartBytes, dummyCart, sizeof(dummyCart));
168+
cartLength = sizeof(dummyCart);
169+
170+
uint8_t* memory = w4_wasmInit();
171+
w4_runtimeInit(memory, &disk);
172+
w4_wasmLoadModule(cartBytes, cartLength);
173+
w4_storeOpen();
174+
w4_windowSetStoreMode(true);
175+
w4_windowBoot(title);
176+
audioUninit();
177+
return 0;
141178
}
142179

143-
// Make sure the title is null terminated
144-
footer.title[sizeof(footer.title)-1] = '\0';
145-
title = footer.title;
146-
147-
cartBytes = xmalloc(footer.cartLength);
148-
fseek(file, -sizeof(FileFooter) - footer.cartLength, SEEK_END);
149-
cartLength = fread(cartBytes, 1, footer.cartLength, file);
150-
fclose(file);
151-
152-
// Look for disk file
153-
diskPath = xmalloc(strlen(argv[0]) + sizeof(DISK_FILE_EXT));
154-
strcpy(diskPath, argv[0]);
155-
#ifdef _WIN32
156-
trimFileExtension(diskPath); // Trim .exe on Windows
157-
#endif
158-
strcat(diskPath, DISK_FILE_EXT);
159-
loadDiskFile(&disk, diskPath);
160-
161180
} else if (!strcmp(argv[1], "-") || !strcmp(argv[1], "/dev/stdin")) {
162181
size_t bufsize = 1024;
163182
cartBytes = xmalloc(bufsize);
@@ -205,7 +224,9 @@ int main (int argc, const char* argv[]) {
205224
loadDiskFile(&disk, diskPath);
206225
}
207226

227+
load_cart:
208228
audioInit();
229+
w4_storeInit();
209230

210231
uint8_t* memory = w4_wasmInit();
211232
w4_runtimeInit(memory, &disk);

runtimes/native/src/backend/wasm_wasm3.c

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ static M3Module* module;
1010

1111
static M3Function* start;
1212
static M3Function* update;
13+
static i32 savedStartFunction = -1; // original Section 8 start index for reset
1314

1415
static m3ApiRawFunction (blit) {
1516
m3ApiGetArgMem(const uint8_t*, sprite);
@@ -155,13 +156,35 @@ static m3ApiRawFunction (tracef) {
155156
m3ApiSuccess();
156157
}
157158

159+
static bool storeLoaded = false;
160+
static bool cartCrashed = false;
161+
158162
static void check (M3Result result) {
159163
if (result != m3Err_none) {
160164
M3ErrorInfo info;
161165
m3_GetErrorInfo(runtime, &info);
162-
fprintf(stderr, "WASM error: %s (%s)\n", result, info.message);
163-
exit(1);
166+
if (storeLoaded) {
167+
fprintf(stderr, "WASM warning: %s (%s)\n", result, info.message);
168+
update = NULL;
169+
start = NULL;
170+
cartCrashed = true;
171+
} else {
172+
fprintf(stderr, "WASM error: %s (%s)\n", result, info.message);
173+
exit(1);
174+
}
175+
}
176+
}
177+
178+
bool w4_wasmDidCrash(void) {
179+
if (cartCrashed) {
180+
cartCrashed = false;
181+
return true;
164182
}
183+
return false;
184+
}
185+
186+
void w4_wasmSetStoreLoaded(bool value) {
187+
storeLoaded = value;
165188
}
166189

167190
uint8_t* w4_wasmInit () {
@@ -187,6 +210,15 @@ uint8_t* w4_wasmInit () {
187210
void w4_wasmDestroy () {
188211
m3_FreeRuntime(runtime);
189212
m3_FreeEnvironment(env);
213+
env = NULL;
214+
runtime = NULL;
215+
module = NULL;
216+
start = NULL;
217+
update = NULL;
218+
}
219+
220+
uint8_t* w4_wasmGetMemory () {
221+
return m3_GetMemory(runtime, NULL, 0);
190222
}
191223

192224
void w4_wasmLoadModule (const uint8_t* wasmBuffer, int byteLength) {
@@ -230,7 +262,8 @@ void w4_wasmLoadModule (const uint8_t* wasmBuffer, int byteLength) {
230262
m3_FindFunction(&start, runtime, "start");
231263
m3_FindFunction(&update, runtime, "update");
232264

233-
// First call wasm built-in start
265+
// Save start function index for reset, then run it
266+
savedStartFunction = module->startFunction;
234267
check(m3_RunStart(module));
235268

236269
// Call WASI start functions
@@ -245,6 +278,87 @@ void w4_wasmLoadModule (const uint8_t* wasmBuffer, int byteLength) {
245278
}
246279
}
247280

281+
int w4_wasmLoadModuleSafe (const uint8_t* wasmBuffer, int byteLength) {
282+
M3Result result;
283+
storeLoaded = true;
284+
285+
result = m3_ParseModule(env, &module, wasmBuffer, byteLength);
286+
if (result) {
287+
fprintf(stderr, "WASM parse error: %s\n", result);
288+
return -1;
289+
}
290+
291+
module->memoryImported = true;
292+
293+
result = m3_LoadModule(runtime, module);
294+
if (result) {
295+
fprintf(stderr, "WASM load error: %s\n", result);
296+
return -1;
297+
}
298+
299+
m3_LinkRawFunction(module, "env", "blit", "v(iiiiii)", blit);
300+
m3_LinkRawFunction(module, "env", "blitSub", "v(iiiiiiiii)", blitSub);
301+
m3_LinkRawFunction(module, "env", "line", "v(iiii)", line);
302+
m3_LinkRawFunction(module, "env", "hline", "v(iii)", hline);
303+
m3_LinkRawFunction(module, "env", "vline", "v(iii)", vline);
304+
m3_LinkRawFunction(module, "env", "oval", "v(iiii)", oval);
305+
m3_LinkRawFunction(module, "env", "rect", "v(iiii)", rect);
306+
m3_LinkRawFunction(module, "env", "text", "v(iii)", text);
307+
m3_LinkRawFunction(module, "env", "textUtf8", "v(iiii)", textUtf8);
308+
m3_LinkRawFunction(module, "env", "textUtf16", "v(iiii)", textUtf16);
309+
m3_LinkRawFunction(module, "env", "tone", "v(iiii)", tone);
310+
m3_LinkRawFunction(module, "env", "diskr", "i(ii)", diskr);
311+
m3_LinkRawFunction(module, "env", "diskw", "i(ii)", diskw);
312+
m3_LinkRawFunction(module, "env", "trace", "v(i)", trace);
313+
m3_LinkRawFunction(module, "env", "traceUtf8", "v(ii)", traceUtf8);
314+
m3_LinkRawFunction(module, "env", "traceUtf16", "v(ii)", traceUtf16);
315+
m3_LinkRawFunction(module, "env", "tracef", "v(ii)", tracef);
316+
317+
m3_FindFunction(&start, runtime, "start");
318+
m3_FindFunction(&update, runtime, "update");
319+
320+
result = m3_RunStart(module);
321+
if (result) {
322+
fprintf(stderr, "WASM start error: %s\n", result);
323+
return -1;
324+
}
325+
326+
M3Function* func;
327+
m3_FindFunction(&func, runtime, "_start");
328+
if (func) {
329+
result = m3_CallV(func);
330+
if (result) {
331+
fprintf(stderr, "WASM _start warning: %s (ignored)\n", result);
332+
}
333+
}
334+
m3_FindFunction(&func, runtime, "_initialize");
335+
if (func) {
336+
result = m3_CallV(func);
337+
if (result) {
338+
fprintf(stderr, "WASM _initialize warning: %s (ignored)\n", result);
339+
}
340+
}
341+
342+
return 0;
343+
}
344+
345+
void w4_wasmRerunInit () {
346+
if (!module) return;
347+
// Restore start function index so m3_RunStart will run it again
348+
module->startFunction = savedStartFunction;
349+
check(m3_RunStart(module));
350+
351+
M3Function* func;
352+
m3_FindFunction(&func, runtime, "_start");
353+
if (func) {
354+
check(m3_CallV(func));
355+
}
356+
m3_FindFunction(&func, runtime, "_initialize");
357+
if (func) {
358+
check(m3_CallV(func));
359+
}
360+
}
361+
248362
void w4_wasmCallStart () {
249363
if (start) {
250364
check(m3_CallV(start));

0 commit comments

Comments
 (0)