From 8d37628ae7696ec22a002cc2d4077815300c3e2c Mon Sep 17 00:00:00 2001 From: shorekeeper Date: Wed, 1 Apr 2026 19:18:00 +0300 Subject: [PATCH] Fix Windows build and several cross-platform bugs Cross-platform bugs: - ncomplib.c: fseek was called as fseek(f, whence, offset) instead of fseek(f, offset, whence). Seeks only worked correctly when offset happened to numerically equal a valid whence constant. - neonucleus.c: nn_realloc passed memory pointer instead of ctx to nn_alloc in the NULL and sentinel paths. Any call with memory=NULL dereferences NULL as a context struct and crashes. Confirmed by test_realloc.c which crashes before fix and passes after. - neonucleus.h: NN_TiB was defined as (1024 * NN_TiB), referencing itself. Fixed to ((size_t)1024 * NN_GiB). - luaarch.c: unicode functions were registered on the component Lua table instead of the unicode table. Wrong stack index variable was used in all five lua_setfield calls. - neonucleus.c: NN_ATOMIC_NONE versions of nn_incRef/nn_decRef took 1 argument but were called with 2 everywhere. Added the missing size_t n parameter. Windows build: - neonucleus.c: NN_LOCK_CREATE in NN_THREAD_WINDOWS was missing a return statement, falling through into NN_LOCK_DESTROY and immediately closing the freshly created mutex handle. - ncomplib.c: Added Windows implementations for opendir, readdir, closedir, stat, mkdir and directory removal using FindFirstFileA, FindNextFileA, _stat, _mkdir and _rmdir. - main.c: Fall back to USERNAME env var when USER is not set. - neonucleus.h: Added NN_VLA macro. Expands to a plain VLA on GCC/Clang, uses _alloca on MSVC. Applied in luaarch.c and neonucleus.c where VLAs were used. - neonucleus.h: Added NN_INIT macro. Expands to a compound literal cast on GCC/Clang, expands to nothing on MSVC which does not support compound literals as constant initializers at file scope. Applied to all global struct initializers in neonucleus.c. - neonucleus.c: Auto-define NN_ATOMIC_NONE on MSVC in C mode since MSVC does not provide stdatomic.h outside of C++ mode. --- build.zig | 5 +- foreign/lua52 | 1 - src/luaarch.c | 7 +- src/main.c | 214 ++++++++++++++++++++++++++++++++++++++++++++- src/ncomplib.c | 118 ++++++++++++++++++++++++- src/neonucleus.c | 73 +++++++++------- src/neonucleus.h | 46 ++++++++-- src/neonucleus.obj | Bin 0 -> 109442 bytes 8 files changed, 418 insertions(+), 46 deletions(-) delete mode 160000 foreign/lua52 create mode 100644 src/neonucleus.obj diff --git a/build.zig b/build.zig index ac92f50..493f32f 100644 --- a/build.zig +++ b/build.zig @@ -63,7 +63,10 @@ fn compileRaylib(b: *std.Build, os: std.Target.Os.Tag, c: *std.Build.Step.Compil c.linkSystemLibrary("raylib"); if (os == .windows) { c.linkSystemLibrary("WinMM"); - c.linkSystemLibrary("GDI32"); + c.linkSystemLibrary("GDI32"); // <--- + c.linkSystemLibrary("User32"); // ^ Windows can't just rely on GDI + c.linkSystemLibrary("Shell32"); + c.linkSystemLibrary("OpenGL32"); } } diff --git a/foreign/lua52 b/foreign/lua52 deleted file mode 160000 index 4324904..0000000 --- a/foreign/lua52 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4324904b60db5243ede68d0922c1bf3c0dd05986 diff --git a/src/luaarch.c b/src/luaarch.c index 0649a02..5bddfe7 100644 --- a/src/luaarch.c +++ b/src/luaarch.c @@ -285,7 +285,7 @@ static int luaArch_component_list(lua_State *L) { lua_createtable(L, 0, 0); return 1; } - const char *comps[len]; + NN_VLA(const char *, comps, len); nn_getComponents(arch->computer, comps); for(size_t i = 0; i < len; i++) { nn_Component *c = nn_getComponent(arch->computer, comps[i]); @@ -385,7 +385,7 @@ static int luaArch_component_methods(lua_State *L) { lua_createtable(L, 0, 0); return 1; } - const char *methods[methodLen]; + NN_VLA(const char *, methods, methodLen); nn_getComponentMethods(c, methods, &methodLen); lua_createtable(L, 0, methodLen); for(size_t i = 0; i < methodLen; i++) { @@ -414,7 +414,8 @@ static int luaArch_component_fields(lua_State *L) { lua_createtable(L, 0, 0); return 1; } - const char *methods[methodLen]; + // const char *methods[methodLen]; + NN_VLA(const char *, methods, methodLen); nn_getComponentMethods(c, methods, &methodLen); lua_createtable(L, 0, methodLen); for(size_t i = 0; i < methodLen; i++) { diff --git a/src/main.c b/src/main.c index 22e5478..94d7482 100644 --- a/src/main.c +++ b/src/main.c @@ -12,6 +12,11 @@ #include #include +#ifdef NN_WINDOWS +#include +#include +#endif + nn_Architecture getLuaArch(); static const char minBIOS[] = { @@ -323,8 +328,213 @@ double ne_energy_accumulator(void *state, nn_Computer *c, double n) { return nn_getTotalEnergy(c); } + + +#ifdef NN_WINDOWS +// Quick self-tests for Windows-specific fixes +// These run before anything else so failures are caught early + +static jmp_buf nn_test_jmpbuf; +static volatile int nn_test_caught; + +static void nn_test_crash_handler(int sig) { + nn_test_caught = 1; + longjmp(nn_test_jmpbuf, 1); +} + +static int nn_test_try(void (*func)(void *), void *arg) { + nn_test_caught = 0; + signal(SIGSEGV, nn_test_crash_handler); + signal(SIGABRT, nn_test_crash_handler); + if(setjmp(nn_test_jmpbuf) == 0) { + func(arg); + } + signal(SIGSEGV, SIG_DFL); + signal(SIGABRT, SIG_DFL); + return nn_test_caught; +} + +static int nn_test_failed = 0; +static int nn_test_passed = 0; + +static void nn_test_report(const char *name, int crashed, int expected_crash) { + if(crashed && !expected_crash) { + printf("[CRASH] %s\n", name); + nn_test_failed++; + } else if(!crashed && expected_crash) { + printf("[FAIL] %s (expected crash)\n", name); + nn_test_failed++; + } else { + printf("[OK] %s\n", name); + nn_test_passed++; + } + fflush(stdout); +} + +// --- realloc tests --- + +static void nn_test_realloc_null(void *arg) { + nn_Context *ctx = arg; + void *p = nn_realloc(ctx, NULL, 0, 64); + if(p == NULL) { nn_test_failed++; return; } + nn_free(ctx, p, 64); +} + +static void nn_test_realloc_grow(void *arg) { + nn_Context *ctx = arg; + void *a = nn_alloc(ctx, 64); + if(a == NULL) return; + void *b = nn_realloc(ctx, a, 64, 128); + if(b != NULL) nn_free(ctx, b, 128); + else nn_free(ctx, a, 64); +} + +static void nn_test_realloc_free(void *arg) { + nn_Context *ctx = arg; + void *c = nn_alloc(ctx, 64); + if(c == NULL) return; + nn_realloc(ctx, c, 64, 0); +} + +// --- lock tests --- + +static void nn_test_lock_create_destroy(void *arg) { + nn_Context *ctx = arg; + nn_Lock *lock = nn_createLock(ctx); + if(lock == NULL) { nn_test_failed++; return; } + nn_destroyLock(ctx, lock); +} + +static void nn_test_lock_cycle(void *arg) { + nn_Context *ctx = arg; + nn_Lock *lock = nn_createLock(ctx); + if(lock == NULL) { nn_test_failed++; return; } + // lock and unlock 100 times to stress it + for(int i = 0; i < 100; i++) { + nn_lock(ctx, lock); + nn_unlock(ctx, lock); + } + nn_destroyLock(ctx, lock); +} + +static void nn_test_lock_two(void *arg) { + nn_Context *ctx = arg; + // two locks at the same time, make sure they dont interfere + nn_Lock *a = nn_createLock(ctx); + nn_Lock *b = nn_createLock(ctx); + if(a == NULL || b == NULL) { nn_test_failed++; return; } + nn_lock(ctx, a); + nn_lock(ctx, b); + nn_unlock(ctx, b); + nn_unlock(ctx, a); + nn_destroyLock(ctx, a); + nn_destroyLock(ctx, b); +} + +// --- VFS tests --- + +static void nn_test_vfs_stat(void *arg) { + (void)arg; + // stat current directory, should always work + ncl_Stat s; + bool ok = ncl_stat(ncl_defaultFS, ".", &s); + if(!ok) nn_test_failed++; + if(!s.isDirectory) nn_test_failed++; +} + +static void nn_test_vfs_dir(void *arg) { + (void)arg; + void *dir = ncl_opendir(ncl_defaultFS, "."); + if(dir == NULL) { nn_test_failed++; return; } + char name[NN_MAX_PATH]; + // just read one entry, dont care what it is + ncl_readdir(ncl_defaultFS, dir, name); + ncl_closedir(ncl_defaultFS, dir); +} + +static void nn_test_vfs_mkdir_remove(void *arg) { + (void)arg; + const char *testdir = "nn_test_tmpdir"; + ncl_mkdir(ncl_defaultFS, testdir); + ncl_Stat s; + bool ok = ncl_stat(ncl_defaultFS, testdir, &s); + if(!ok || !s.isDirectory) nn_test_failed++; + ncl_remove(ncl_defaultFS, testdir); + // should be gone now + ok = ncl_stat(ncl_defaultFS, testdir, &s); + if(ok) nn_test_failed++; +} + +static void nn_test_vfs_seek(void *arg) { + (void)arg; + // write a small file, seek around, read back + const char *path = "nn_test_seekfile"; + const char *data = "abcdefghij"; + void *f = ncl_openfile(ncl_defaultFS, path, "w"); + if(f == NULL) { nn_test_failed++; return; } + ncl_writefile(ncl_defaultFS, f, data, 10); + ncl_closefile(ncl_defaultFS, f); + + f = ncl_openfile(ncl_defaultFS, path, "r"); + if(f == NULL) { nn_test_failed++; ncl_remove(ncl_defaultFS, path); return; } + // seek to offset 5 from start + int off = 5; + bool ok = ncl_seekfile(ncl_defaultFS, f, NN_SEEK_SET, &off); + if(!ok || off != 5) nn_test_failed++; + // read from there + char buf[5]; + size_t len = 5; + ok = ncl_readfile(ncl_defaultFS, f, buf, &len); + if(!ok || len != 5) nn_test_failed++; + // should be "fghij" + if(buf[0] != 'f' || buf[4] != 'j') nn_test_failed++; + ncl_closefile(ncl_defaultFS, f); + ncl_remove(ncl_defaultFS, path); +} + +static void nn_run_selftests(nn_Context *ctx) { + printf("--- nn self tests ---\n"); + fflush(stdout); + + nn_test_report("realloc(NULL)", + nn_test_try(nn_test_realloc_null, ctx), 0); + nn_test_report("realloc(ptr, grow)", + nn_test_try(nn_test_realloc_grow, ctx), 0); + nn_test_report("realloc(ptr, free)", + nn_test_try(nn_test_realloc_free, ctx), 0); + + nn_test_report("lock create/destroy", + nn_test_try(nn_test_lock_create_destroy, ctx), 0); + nn_test_report("lock 100 cycles", + nn_test_try(nn_test_lock_cycle, ctx), 0); + nn_test_report("two locks interleaved", + nn_test_try(nn_test_lock_two, ctx), 0); + + nn_test_report("vfs stat cwd", + nn_test_try(nn_test_vfs_stat, NULL), 0); + nn_test_report("vfs readdir cwd", + nn_test_try(nn_test_vfs_dir, NULL), 0); + nn_test_report("vfs mkdir/remove", + nn_test_try(nn_test_vfs_mkdir_remove, NULL), 0); + nn_test_report("vfs seek", + nn_test_try(nn_test_vfs_seek, NULL), 0); + + printf("--- %d passed, %d failed ---\n\n", nn_test_passed, nn_test_failed); + fflush(stdout); + + if(nn_test_failed > 0) { + printf("self tests failed, aborting\n"); + fflush(stdout); + exit(1); + } +} +#endif + int main(int argc, char **argv) { const char *player = getenv("USER"); +#ifdef NN_WINDOWS + if(player == NULL) player = getenv("USERNAME"); +#endif if(player == NULL) player = "me"; bool sandboxMem = getenv("NN_MEMSAND") != NULL; @@ -336,7 +546,9 @@ int main(int argc, char **argv) { nn_Context ctx; nn_initContext(&ctx); nn_initPalettes(); - +#ifdef NN_WINDOWS + nn_run_selftests(&ctx); +#endif ne_memSand sand; sand.buf = NULL; diff --git a/src/ncomplib.c b/src/ncomplib.c index 3f71a88..8f0ce2b 100644 --- a/src/ncomplib.c +++ b/src/ncomplib.c @@ -17,8 +17,19 @@ bool ncl_defaultHandler(ncl_VFSRequest *request) { #include #include +// Read me all my rights #elif defined(NN_WINDOWS) -#error "Windows is not supported yet" + +#include +#include +#include + +typedef struct ncl_WinDir { + HANDLE handle; + WIN32_FIND_DATAA findData; + bool isFirst; +} ncl_WinDir; + #endif bool ncl_defaultHandler(ncl_VFSRequest *request) { @@ -54,11 +65,47 @@ bool ncl_defaultHandler(ncl_VFSRequest *request) { if(wanted == NN_SEEK_SET) whence = SEEK_SET; if(wanted == NN_SEEK_CUR) whence = SEEK_CUR; if(wanted == NN_SEEK_END) whence = SEEK_END; - if(fseek(f, whence, request->seek.off) < 0) return false; + // fseek takes (file, offset, whence), not (file, whence, offset). + // The original code had these two arguments swapped, which caused + // every seek to go to the wrong position or fail entirely. + /* + * We want to: seek 100 bytes from the beginning (SEEK_SET = 0) + * Call: fseek(f, 0, 100) + * offset=0, whence=100 + * Result: whence=100 is invalid, fseek returns -1, the function returns false + + * We want to: seek 0 from the current position (SEEK_CUR = 1) + * Call: fseek(f, 1, 0) + * offset=1, whence=SEEK_SET(0) + * Result: jumps to position 1 from the beginning of the file instead of staying at the same position + + * We want to: seek 0 from the beginning (SEEK_SET = 0) + * Call: fseek(f, 0, 0) + * offset=0, whence=SEEK_SET(0) + * Result: works correctly by chance + + * We want to: seek 2 from the beginning (SEEK_SET = 0) + * Call: fseek(f, 0, 2) + * offset=0, whence=SEEK_END(2) + * Result: Goes to the end of the file instead of position 2 + */ + // Original fseek signature: int fseek(FILE *stream, long offset, int whence) remains, + // yet the : `if(fseek(f, whence, request->seek.off) < 0) return false;` + // ...variant was wrong + if(fseek(f, request->seek.off, whence) < 0) return false; + request->seek.off = ftell(f); return true; } if(request->action == NCL_VFS_REMOVE) { + // On Windows, remove() cannot delete directories. + // We need to check if the path is a directory and use _rmdir instead. +#ifdef NN_WINDOWS + DWORD attrs = GetFileAttributesA(request->remove); + if(attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY)) { + return _rmdir(request->remove) == 0; + } +#endif return remove(request->remove) == 0; } #ifdef NN_POSIX @@ -102,8 +149,73 @@ bool ncl_defaultHandler(ncl_VFSRequest *request) { if(request->action == NCL_VFS_MKDIR) { return mkdir(request->mkdir, 0777) == 0; } +#elif defined(NN_WINDOWS) + if(request->action == NCL_VFS_OPENDIR) { + // FindFirstFileA needs a glob pattern, so we append "\\*" to the path. + // We allocate the wrapper struct on the heap because FindFirstFileA + // fills in the first result immediately and we need to remember that. + ncl_WinDir *dir = malloc(sizeof(ncl_WinDir)); + if(dir == NULL) return false; + char searchPath[NN_MAX_PATH + 4]; + snprintf(searchPath, sizeof(searchPath), "%s\\*", request->opendir.path); + dir->handle = FindFirstFileA(searchPath, &dir->findData); + if(dir->handle == INVALID_HANDLE_VALUE) { + free(dir); + return false; + } + dir->isFirst = true; + request->opendir.dir = dir; + return true; + } + if(request->action == NCL_VFS_CLOSEDIR) { + ncl_WinDir *dir = request->closedir; + FindClose(dir->handle); + free(dir); + return true; + } + if(request->action == NCL_VFS_READDIR) { + // Mirrors the POSIX readdir loop: skip "." and "..", + // copy name into the caller buffer, signal end with NULL. + ncl_WinDir *dir = request->readdir.dir; + while(1) { + if(!dir->isFirst) { + if(!FindNextFileA(dir->handle, &dir->findData)) { + request->readdir.name = NULL; + return true; + } + } + dir->isFirst = false; + if(strcmp(dir->findData.cFileName, ".") == 0 || + strcmp(dir->findData.cFileName, "..") == 0) { + continue; + } + strncpy(request->readdir.name, dir->findData.cFileName, NN_MAX_PATH - 1); + request->readdir.name[NN_MAX_PATH - 1] = '\0'; + return true; + } + } + if(request->action == NCL_VFS_STAT) { + // Windows does not have st_blocks, so we approximate disk size + // as the file size itself. This is less accurate than the POSIX + // version but avoids needing the full Win32 file information API. + struct _stat s; + if(_stat(request->stat.path, &s) != 0) { + request->stat.path = NULL; + return false; + } + ncl_Stat *st = request->stat.stat; + st->isDirectory = (s.st_mode & _S_IFDIR) != 0; + st->diskSize = s.st_size; + st->size = st->isDirectory ? 0 : s.st_size; + st->lastModified = s.st_mtime; + return true; + } + if(request->action == NCL_VFS_MKDIR) { + // _mkdir on Windows does not take a permissions argument. + return _mkdir(request->mkdir) == 0; + } #endif - return false; // not supported + return false; } #endif diff --git a/src/neonucleus.c b/src/neonucleus.c index 64633dc..e4ddcc0 100644 --- a/src/neonucleus.c +++ b/src/neonucleus.c @@ -15,15 +15,16 @@ // to use the numerical accuracy better #define NN_COMPONENT_CALLBUDGET 10000 +// NN_ATOMIC_NONE accepts 1 args, others 2 #ifdef NN_ATOMIC_NONE typedef size_t nn_refc_t; -void nn_incRef(nn_refc_t *refc) { - (*refc)++; +void nn_incRef(nn_refc_t *refc, size_t n) { + (*refc) += n; } -bool nn_decRef(nn_refc_t *refc) { - (*refc)--; +bool nn_decRef(nn_refc_t *refc, size_t n) { + (*refc) -= n; return (*refc) == 0; } #else @@ -87,8 +88,13 @@ void nn_free(nn_Context *ctx, void *memory, size_t size) { } void *nn_realloc(nn_Context *ctx, void *memory, size_t oldSize, size_t newSize) { - if(memory == NULL) return nn_alloc(memory, newSize); - if(memory == ctx->alloc) return nn_alloc(memory, newSize); + // nn_realloc passed memory (which is NULL here) as first argument + // to nn_alloc instead of ctx. nn_alloc dereferences it as a context + // struct to call ctx->alloc(), so this is a NULL pointer dereference. + // Confirmed by test_realloc crashing on nn_realloc(&ctx, NULL, 0, 64). + // Original: if(memory == NULL) return nn_alloc(memory, newSize); if(memory == ctx->alloc) return nn_alloc(memory, newSize); + if(memory == NULL) return nn_alloc(ctx, newSize); + if(memory == ctx->alloc) return nn_alloc(ctx, newSize); if(newSize == 0) { nn_free(ctx, memory, oldSize); return ctx->alloc; @@ -570,7 +576,8 @@ static void nn_defaultLock(void *state, nn_LockRequest *req) { #elif defined(NN_THREAD_WINDOWS) switch(req->action) { case NN_LOCK_CREATE:; - req->lock = CreateMutex(NULL, false, NULL); + req->lock = CreateMutex(NULL, FALSE, NULL); + return; // don't fall into destroy case NN_LOCK_DESTROY:; CloseHandle(req->lock); return; @@ -2226,8 +2233,8 @@ nn_Exit nn_popSignal(nn_Computer *computer, size_t *valueCount) { // todo: everything const nn_EEPROM nn_defaultEEPROMs[4] = { - (nn_EEPROM) { - .size = 4 * NN_KiB, + NN_INIT(nn_EEPROM) { + .size = 4 * NN_KiB, .dataSize = 256, .readEnergyCost = 1, .writeEnergyCost = 100, @@ -2236,7 +2243,7 @@ const nn_EEPROM nn_defaultEEPROMs[4] = { .writeDelay = 2, .writeDataDelay = 1, }, - (nn_EEPROM) { + NN_INIT(nn_EEPROM) { .size = 8 * NN_KiB, .dataSize = 1 * NN_KiB, .readEnergyCost = 2, @@ -2246,7 +2253,7 @@ const nn_EEPROM nn_defaultEEPROMs[4] = { .writeDelay = 2, .writeDataDelay = 1, }, - (nn_EEPROM) { + NN_INIT(nn_EEPROM) { .size = 16 * NN_KiB, .dataSize = 2 * NN_KiB, .readEnergyCost = 4, @@ -2256,7 +2263,7 @@ const nn_EEPROM nn_defaultEEPROMs[4] = { .writeDelay = 1, .writeDataDelay = 0.5, }, - (nn_EEPROM) { + NN_INIT(nn_EEPROM) { .size = 32 * NN_KiB, .dataSize = 4 * NN_KiB, .readEnergyCost = 8, @@ -2269,25 +2276,25 @@ const nn_EEPROM nn_defaultEEPROMs[4] = { }; const nn_Filesystem nn_defaultFilesystems[4] = { - (nn_Filesystem) { + NN_INIT(nn_Filesystem) { .spaceTotal = 1 * NN_MiB, .readsPerTick = 4, .writesPerTick = 2, .dataEnergyCost = 256.0 / NN_MiB, }, - (nn_Filesystem) { + NN_INIT(nn_Filesystem) { .spaceTotal = 2 * NN_MiB, .readsPerTick = 4, .writesPerTick = 2, .dataEnergyCost = 512.0 / NN_MiB, }, - (nn_Filesystem) { + NN_INIT(nn_Filesystem) { .spaceTotal = 4 * NN_MiB, .readsPerTick = 7, .writesPerTick = 3, .dataEnergyCost = 1024.0 / NN_MiB, }, - (nn_Filesystem) { + NN_INIT(nn_Filesystem) { .spaceTotal = 8 * NN_MiB, .readsPerTick = 13, .writesPerTick = 5, @@ -2296,14 +2303,14 @@ const nn_Filesystem nn_defaultFilesystems[4] = { }; -const nn_Filesystem nn_defaultFloppy = (nn_Filesystem) { +const nn_Filesystem nn_defaultFloppy = NN_INIT(nn_Filesystem) { .spaceTotal = 512 * NN_KiB, .readsPerTick = 1, .writesPerTick = 1, .dataEnergyCost = 8.0 / NN_MiB, }; -const nn_Filesystem nn_defaultTmpFS = (nn_Filesystem) { +const nn_Filesystem nn_defaultTmpFS = NN_INIT(nn_Filesystem) { .spaceTotal = 64 * NN_KiB, .readsPerTick = 1024, .writesPerTick = 512, @@ -2311,7 +2318,7 @@ const nn_Filesystem nn_defaultTmpFS = (nn_Filesystem) { }; const nn_Drive nn_defaultDrives[4] = { - (nn_Drive) { + NN_INIT(nn_Drive) { .capacity = 1 * NN_MiB, .sectorSize = 512, .platterCount = 2, @@ -2321,7 +2328,7 @@ const nn_Drive nn_defaultDrives[4] = { .onlySpinForwards = false, .dataEnergyCost = 256.0 / NN_MiB, }, - (nn_Drive) { + NN_INIT(nn_Drive) { .capacity = 2 * NN_MiB, .sectorSize = 512, .platterCount = 4, @@ -2331,7 +2338,7 @@ const nn_Drive nn_defaultDrives[4] = { .onlySpinForwards = false, .dataEnergyCost = 512.0 / NN_MiB, }, - (nn_Drive) { + NN_INIT(nn_Drive) { .capacity = 4 * NN_MiB, .sectorSize = 512, .platterCount = 8, @@ -2341,7 +2348,7 @@ const nn_Drive nn_defaultDrives[4] = { .onlySpinForwards = false, .dataEnergyCost = 1024.0 / NN_MiB, }, - (nn_Drive) { + NN_INIT(nn_Drive) { .capacity = 8 * NN_MiB, .sectorSize = 512, .platterCount = 16, @@ -2366,7 +2373,7 @@ const nn_Drive nn_floppyDrive = { const nn_ScreenConfig nn_defaultScreens[4] = { - (nn_ScreenConfig) { + NN_INIT(nn_ScreenConfig) { .maxWidth = 50, .maxHeight = 16, .maxDepth = 1, @@ -2375,7 +2382,7 @@ const nn_ScreenConfig nn_defaultScreens[4] = { .editableColors = 0, .features = NN_SCRF_NONE, }, - (nn_ScreenConfig) { + NN_INIT(nn_ScreenConfig) { .maxWidth = 80, .maxHeight = 25, .maxDepth = 4, @@ -2384,7 +2391,7 @@ const nn_ScreenConfig nn_defaultScreens[4] = { .editableColors = 0, .features = NN_SCRF_MOUSE | NN_SCRF_TOUCHINVERTED, }, - (nn_ScreenConfig) { + NN_INIT(nn_ScreenConfig) { .maxWidth = 160, .maxHeight = 50, .maxDepth = 8, @@ -2393,7 +2400,7 @@ const nn_ScreenConfig nn_defaultScreens[4] = { .editableColors = 16, .features = NN_SCRF_MOUSE | NN_SCRF_TOUCHINVERTED | NN_SCRF_PRECISE | NN_SCRF_EDITABLECOLORS, }, - (nn_ScreenConfig) { + NN_INIT(nn_ScreenConfig) { .maxWidth = 240, .maxHeight = 80, .maxDepth = 16, @@ -2405,7 +2412,7 @@ const nn_ScreenConfig nn_defaultScreens[4] = { }; const nn_GPU nn_defaultGPUs[4] = { - (nn_GPU) { + NN_INIT(nn_GPU) { .maxWidth = 50, .maxHeight = 16, .maxDepth = 1, @@ -2418,7 +2425,7 @@ const nn_GPU nn_defaultGPUs[4] = { .energyPerWrite = 0.0002, .energyPerClear = 0.0001, }, - (nn_GPU) { + NN_INIT(nn_GPU) { .maxWidth = 80, .maxHeight = 25, .maxDepth = 4, @@ -2431,7 +2438,7 @@ const nn_GPU nn_defaultGPUs[4] = { .energyPerWrite = 0.001, .energyPerClear = 0.0005, }, - (nn_GPU) { + NN_INIT(nn_GPU) { .maxWidth = 160, .maxHeight = 50, .maxDepth = 8, @@ -2444,7 +2451,7 @@ const nn_GPU nn_defaultGPUs[4] = { .energyPerWrite = 0.002, .energyPerClear = 0.001, }, - (nn_GPU) { + NN_INIT(nn_GPU) { .maxWidth = 240, .maxHeight = 80, .maxDepth = 16, @@ -3049,10 +3056,12 @@ nn_Exit nn_pushDrop(nn_Computer *computer, const char *screenAddress, double x, return nn_pushSignal(computer, 6); } +// the value is not returned for all execution paths - not a windows bug probably, need tests on *nix nn_Exit nn_pushScroll(nn_Computer *computer, const char *screenAddress, double x, double y, double direction, const char *player) { if(!nn_hasUser(computer, player)) return NN_OK; } +// the value is not returned for all execution paths - not a windows bug probably, need tests on *nix nn_Exit nn_pushWalk(nn_Computer *computer, const char *screenAddress, double x, double y, const char *player) { if(!nn_hasUser(computer, player)) return NN_OK; } @@ -3149,7 +3158,7 @@ static nn_Exit nn_eepromHandler(nn_ComponentRequest *req) { } if(method == NN_EENUM_GET) { ereq.action = NN_EEPROM_GET; - char buf[eeprom.size]; + NN_VLA(char, buf, eeprom.size); ereq.buf = buf; ereq.buflen = eeprom.size; e = state->handler(&ereq); @@ -3159,7 +3168,7 @@ static nn_Exit nn_eepromHandler(nn_ComponentRequest *req) { } if(method == NN_EENUM_GETDATA) { ereq.action = NN_EEPROM_GETDATA; - char buf[eeprom.size]; + NN_VLA(char, buf, eeprom.size); ereq.buf = buf; ereq.buflen = eeprom.size; e = state->handler(&ereq); diff --git a/src/neonucleus.h b/src/neonucleus.h index 400b14a..2b6562f 100644 --- a/src/neonucleus.h +++ b/src/neonucleus.h @@ -9,21 +9,43 @@ extern "C" { // Used internally as well. // Based off https://stackoverflow.com/questions/5919996/how-to-detect-reliably-mac-os-x-ios-linux-windows-in-c-preprocessor #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - //define something for Windows (32-bit and 64-bit, this part is common) + #ifndef NN_WINDOWS #define NN_WINDOWS + #endif #elif __APPLE__ + #ifndef NN_MACOS #define NN_MACOS + #endif #elif __linux__ + #ifndef NN_LINUX #define NN_LINUX + #endif #endif -#if __unix__ // all unices not caught above - // Unix +#if __unix__ + #ifndef NN_UNIX #define NN_UNIX + #endif + #ifndef NN_POSIX #define NN_POSIX + #endif #elif defined(_POSIX_VERSION) - // POSIX + #ifndef NN_POSIX #define NN_POSIX + #endif +#endif + + +#if defined(_MSC_VER) && !defined(__cplusplus) +#ifndef NN_ATOMIC_NONE +#define NN_ATOMIC_NONE +#endif +#endif + +#ifdef _MSC_VER +#define NN_INIT(type) +#else +#define NN_INIT(type) (type) #endif // every C standard header we depend on, conveniently put here @@ -31,6 +53,19 @@ extern "C" { #include // for intptr_t #include // for true, false and bool +/* MSVC can't use VLA; + * What we see : NN_VLA(const char *, comps, len); + * What compiler see after preproccessor : const char * comps[len]; + * What actaully was : const char *comps[len]; +*/ +// Test: gcc -E -DNN_VLA\(type,name,count\)="type name[count]" -x c - <<< 'NN_VLA(const char *, comps, len);' +#ifdef _MSC_VER +#include +#define NN_VLA(type, name, count) type *name = (type *)_alloca(sizeof(type) * (count)) +#else +#define NN_VLA(type, name, count) type name[count] +#endif + // Internally we need stdatomic.h and, if NN_BAREMETAL is not defined, stdlib.h and time.h // The entire NeoNucleus API, in one header file @@ -40,7 +75,8 @@ extern "C" { #define NN_KiB (1024) #define NN_MiB (1024 * NN_KiB) #define NN_GiB (1024 * NN_MiB) -#define NN_TiB (1024 * NN_TiB) +// probably recursive: #define NN_TiB (1024 * NN_TiB) +#define NN_TiB ((size_t)1024 * NN_GiB) // the alignment an allocation should have #define NN_ALLOC_ALIGN 16 diff --git a/src/neonucleus.obj b/src/neonucleus.obj new file mode 100644 index 0000000000000000000000000000000000000000..1eeb55c96db6dc9c4a27bea43b21ef676f9a4247 GIT binary patch literal 109442 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 VnMa?NE?wEx)hNp1?BR0ke*lKkuOt8f literal 0 HcmV?d00001