// The MIT License (MIT) // // Copyright (C) 2026 Scott Duensing // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // loaderMain.c -- DVX bootstrap loader entry point // // Loads all DXE modules from two directories: // libs/ *.lib -- core libraries (libtasks, libdvx, dvxshell) // widgets/ *.wgt -- widget type plugins (box, button, listview, etc.) // // Each module may have a .dep file (same base name, .dep extension) // listing base names of modules that must be loaded before it. // The loader resolves the dependency graph and loads in topological // order. After loading, any module that exports wgtRegister() has // it called. Finally, the loader finds and calls shellMain(). #include "stddclmr.h" #include "dvxPlat.h" #include "dvxPrefs.h" #include "../tools/hlpcCompile.h" #include #include #include #include #include #include #include #include // The loader is not a DXE -- use plain realloc/free for stb_ds so that // all translation units (loaderMain.o, dvxPrefs.o) share the same heap. #define STB_DS_IMPLEMENTATION #include "stb_ds_wrap.h" // ============================================================ // Constants // ============================================================ #define LIBS_DIR "LIBS" #define WIDGET_DIR "WIDGETS" #define LOG_PATH "dvx.log" // ============================================================ // Splash screen (delegates to platformSplash* in dvxPlatformDos.c) // ============================================================ // Palette indices for progress bar (indices into SPLASH.RAW palette) #define SPLASH_BAR_BG 50 // dark gray (RGB 68,68,68) #define SPLASH_BAR_FG 135 // light gray (RGB 168,168,168) #define SPLASH_BAR_OUT 45 // darker gray (RGB 60,60,60) // Progress bar geometry #define PBAR_X 10 #define PBAR_Y 188 #define PBAR_W 300 #define PBAR_H 6 // ============================================================ // Module entry for dependency resolution // ============================================================ typedef struct { char path[DVX_MAX_PATH]; char baseName[16]; char **deps; bool loaded; void *handle; } ModuleT; // Scratch state for processHcf callback -- collects output=, imagedir=, and // the accumulated source= glob expansions in one pass. typedef struct { const char *hcfDir; char outputFile[DVX_MAX_PATH]; char imgDir[DVX_MAX_PATH]; char **inputFiles; } ProcessHcfCtxT; // ============================================================ // Module state // ============================================================ static int32_t sSplashActive = 0; static int32_t sSplashTotal = 0; static int32_t sSplashLoaded = 0; static int32_t sLibCount = 0; static int32_t sWidgetCount = 0; // ============================================================ // Prototypes // ============================================================ static bool allDepsLoaded(const ModuleT *mod, const ModuleT *mods); static void collectGlobFiles(char ***outFiles, const char *pattern, const char *excludePattern); static void countHcfCallback(const char *key, const char *val, const char *exclude, void *user); static int32_t countHcfInputFiles(const char *hcfPath); static int32_t countTotalHelpSteps(const char *dirPath); void dvxLog(const char *fmt, ...); static void extractBaseName(const char *path, const char *ext, char *out, int32_t outSize); static void *findSymbol(void **handles, const char *symbol); static void freeMods(ModuleT *mods); static void hcfForEachEntry(const char *hcfPath, void (*cb)(const char *key, const char *val, const char *exclude, void *user), void *user); static void helpRecompileIfNeeded(void); static void hlpcProgressCallback(void *ctx, int32_t current, int32_t total); static void **loadAllModules(void); static void loadInOrder(ModuleT *mods); static void logAndReadDeps(ModuleT *mods); bool platformGlobMatch(const char *pattern, const char *name); static void processHcf(const char *hcfPath, const char *hcfDir); static void processHcfCallback(const char *key, const char *val, const char *exclude, void *user); static void processHcfDir(const char *dirPath); static void readDeps(ModuleT *mod); static void scanDir(const char *dirPath, const char *ext, ModuleT **mods); static void splashDrawScreen(void); static void splashShutdownIfActive(void); static void splashUpdateProgress(void); static void validateDeps(const ModuleT *libs, const ModuleT *widgets); int main(int argc, char *argv[]); // A dep is satisfied if either: // 1. No module with that base name exists (external, assumed OK) // 2. The module with that base name is already loaded static bool allDepsLoaded(const ModuleT *mod, const ModuleT *mods) { for (int32_t d = 0; d < arrlen(mod->deps); d++) { for (int32_t j = 0; j < arrlen(mods); j++) { if (strcasecmp(mods[j].baseName, mod->deps[d]) == 0) { if (!mods[j].loaded) { return false; } break; } } } return true; } // Collect matching filenames into an stb_ds array of strdup'd paths. static void collectGlobFiles(char ***outFiles, const char *pattern, const char *excludePattern) { char dirPart[DVX_MAX_PATH]; const char *globPart = NULL; snprintf(dirPart, sizeof(dirPart), "%s", pattern); char *lastSep = platformPathDirEnd(dirPart); if (lastSep) { *lastSep = '\0'; globPart = lastSep + 1; } else { globPart = pattern; dirPart[0] = '.'; dirPart[1] = '\0'; } char **names = dvxReadDir(dirPart); if (!names) { return; } int32_t nEntries = (int32_t)arrlen(names); for (int32_t i = 0; i < nEntries; i++) { // Skip hidden files (dvxReadDir already strips "." and "..") if (names[i][0] == '.') { continue; } char fullPath[DVX_MAX_PATH]; snprintf(fullPath, sizeof(fullPath), "%s" DVX_PATH_SEP "%s", dirPart, names[i]); struct stat st; if (stat(fullPath, &st) != 0) { continue; } if (S_ISDIR(st.st_mode)) { char subPattern[DVX_MAX_PATH]; snprintf(subPattern, sizeof(subPattern), "%s" DVX_PATH_SEP "%s", fullPath, globPart); collectGlobFiles(outFiles, subPattern, excludePattern); } else if (platformGlobMatch(globPart, names[i])) { if (!excludePattern || !platformGlobMatch(excludePattern, names[i])) { arrput(*outFiles, strdup(fullPath)); } } } dvxReadDirFree(names); } // Callback for countHcfInputFiles: expand source= globs into the user file list. static void countHcfCallback(const char *key, const char *val, const char *exclude, void *user) { if (strcasecmp(key, "source") == 0) { collectGlobFiles((char ***)user, val, exclude); } } // Count input files for a single .hcf config (for progress total calculation). static int32_t countHcfInputFiles(const char *hcfPath) { char **files = NULL; hcfForEachEntry(hcfPath, countHcfCallback, &files); int32_t count = (int32_t)arrlen(files); for (int32_t i = 0; i < count; i++) { free(files[i]); } arrfree(files); return count; } // Count total progress steps across all .hcf files under a directory. static int32_t countTotalHelpSteps(const char *dirPath) { char **names = dvxReadDir(dirPath); if (!names) { return 0; } int32_t total = 0; int32_t nEntries = (int32_t)arrlen(names); for (int32_t i = 0; i < nEntries; i++) { if (names[i][0] == '.') { continue; } char fullPath[DVX_MAX_PATH]; snprintf(fullPath, sizeof(fullPath), "%s" DVX_PATH_SEP "%s", dirPath, names[i]); struct stat st; if (stat(fullPath, &st) != 0) { continue; } if (S_ISDIR(st.st_mode)) { total += countTotalHelpSteps(fullPath); } else if (dvxHasExt(names[i], ".hcf")) { int32_t fileCount = countHcfInputFiles(fullPath); total += hlpcProgressTotal(fileCount); } } dvxReadDirFree(names); return total; } static void extractBaseName(const char *path, const char *ext, char *out, int32_t outSize) { // Find last directory separator const char *start = path; const char *p = path; while (*p) { if (*p == '/' || *p == '\\') { start = p + 1; } p++; } // Copy up to the extension int32_t extLen = strlen(ext); int32_t len = strlen(start); if (len > extLen && strcasecmp(start + len - extLen, ext) == 0) { len -= extLen; } if (len >= outSize) { len = outSize - 1; } for (int32_t i = 0; i < len; i++) { out[i] = tolower((unsigned char)start[i]); } out[len] = '\0'; } static void *findSymbol(void **handles, const char *symbol) { for (int32_t i = 0; i < arrlen(handles); i++) { void *sym = dlsym(handles[i], symbol); if (sym) { return sym; } } return NULL; } static void freeMods(ModuleT *mods) { for (int32_t i = 0; i < arrlen(mods); i++) { for (int32_t d = 0; d < arrlen(mods[i].deps); d++) { free(mods[i].deps[d]); } arrfree(mods[i].deps); } arrfree(mods); } // Parse a .hcf INI-like file and invoke cb for each key=value entry. // Lines starting with # and blank lines are skipped. For source= entries, // the value may include a "!excludePattern" suffix which is split out and // passed separately. For other keys, exclude is "". static void hcfForEachEntry(const char *hcfPath, void (*cb)(const char *key, const char *val, const char *exclude, void *user), void *user) { FILE *f = fopen(hcfPath, "r"); if (!f) { return; } char line[512]; while (fgets(line, (int)sizeof(line), f)) { dvxTrimRight(line); if (line[0] == '#' || line[0] == '\0') { continue; } // Split "key = value" - key is the run of chars up to space or '=' const char *p = line; int32_t klen = 0; while (p[klen] && p[klen] != ' ' && p[klen] != '=') { klen++; } char key[32] = {0}; if (klen >= (int32_t)sizeof(key)) { klen = (int32_t)sizeof(key) - 1; } memcpy(key, p, klen); key[klen] = '\0'; p += klen; while (*p == ' ' || *p == '=') { p++; } // For source=, split out !exclude suffix if (strcasecmp(key, "source") == 0) { char pattern[DVX_MAX_PATH] = {0}; char exclude[DVX_MAX_PATH] = {0}; const char *bang = strchr(p, '!'); if (bang) { int32_t patLen = (int32_t)(bang - p); while (patLen > 0 && p[patLen - 1] == ' ') { patLen--; } memcpy(pattern, p, patLen); pattern[patLen] = '\0'; bang++; while (*bang == ' ') { bang++; } snprintf(exclude, sizeof(exclude), "%s", bang); } else { snprintf(pattern, sizeof(pattern), "%s", p); } cb(key, pattern, exclude[0] ? exclude : NULL, user); } else { cb(key, p, NULL, user); } } fclose(f); } static void helpRecompileIfNeeded(void) { PrefsHandleT *ini = prefsLoad(DVX_INI_PATH); if (!ini) { dvxLog("helpRecompile: cannot load INI"); return; } int32_t storedLibs = prefsGetInt(ini, "help", "libCount", -1); int32_t storedWidgets = prefsGetInt(ini, "help", "widgetCount", -1); // First startup: no [help] section. Save counts, skip recompile. if (storedLibs == -1 && storedWidgets == -1) { dvxLog("helpRecompile: first startup, saving initial counts (libs=%d widgets=%d)", (int)sLibCount, (int)sWidgetCount); prefsSetInt(ini, "help", "libCount", sLibCount); prefsSetInt(ini, "help", "widgetCount", sWidgetCount); prefsSave(ini); prefsClose(ini); return; } // Counts unchanged: no recompile needed if (storedLibs == sLibCount && storedWidgets == sWidgetCount) { prefsClose(ini); return; } dvxLog("helpRecompile: counts changed (libs: %d->%d, widgets: %d->%d)", (int)storedLibs, (int)sLibCount, (int)storedWidgets, (int)sWidgetCount); // Count total progress steps across all .hcf compilations int32_t totalSteps = countTotalHelpSteps("APPS"); if (totalSteps == 0) { dvxLog("helpRecompile: no help sources found"); prefsSetInt(ini, "help", "libCount", sLibCount); prefsSetInt(ini, "help", "widgetCount", sWidgetCount); prefsSave(ini); prefsClose(ini); return; } // Reset progress bar for recompile phase sSplashLoaded = 0; sSplashTotal = totalSteps; platformSplashFillRect(PBAR_X, PBAR_Y, PBAR_W, PBAR_H, SPLASH_BAR_BG); // Recursively scan APPS/ for .hcf files and process each processHcfDir("APPS"); // Save new counts prefsSetInt(ini, "help", "libCount", sLibCount); prefsSetInt(ini, "help", "widgetCount", sWidgetCount); prefsSave(ini); prefsClose(ini); dvxLog("helpRecompile: done"); } // Progress callback for hlpcCompile -- updates the splash progress bar. static void hlpcProgressCallback(void *ctx, int32_t current, int32_t total) { (void)ctx; (void)total; (void)current; sSplashLoaded++; splashUpdateProgress(); } static void **loadAllModules(void) { void **handles = NULL; // Scan both directories first to get total module count for progress bar ModuleT *libs = NULL; scanDir(LIBS_DIR, ".lib", &libs); logAndReadDeps(libs); ModuleT *widgets = NULL; scanDir(WIDGET_DIR, ".wgt", &widgets); logAndReadDeps(widgets); sLibCount = (int32_t)arrlen(libs); sWidgetCount = (int32_t)arrlen(widgets); sSplashTotal = sLibCount + sWidgetCount; // Abort early on typos or missing prerequisite .lib/.wgt files. validateDeps(libs, widgets); // Phase 1: load libraries in dependency order loadInOrder(libs); for (int32_t i = 0; i < arrlen(libs); i++) { if (libs[i].handle) { arrput(handles, libs[i].handle); } } freeMods(libs); // Phase 2: load widgets in dependency order (all libs already loaded) loadInOrder(widgets); for (int32_t i = 0; i < arrlen(widgets); i++) { if (widgets[i].handle) { arrput(handles, widgets[i].handle); } } freeMods(widgets); return handles; } // Repeatedly scans the module list for entries whose dependencies // are all satisfied, loads them, and marks them done. Stops when // all modules are loaded or no progress can be made (circular dep). static void loadInOrder(ModuleT *mods) { typedef void (*RegFnT)(void); int32_t total = arrlen(mods); int32_t loaded = 0; bool progress; do { progress = false; for (int32_t i = 0; i < total; i++) { if (mods[i].loaded) { continue; } if (!allDepsLoaded(&mods[i], mods)) { continue; } dvxLog("Loading: %s", mods[i].path); mods[i].handle = dlopen(mods[i].path, RTLD_NOW | RTLD_GLOBAL); if (!mods[i].handle) { const char *err = dlerror(); dvxLog(" FAILED: %s", err ? err : "(unknown)"); splashShutdownIfActive(); fprintf(stderr, "FATAL: Failed to load %s\n %s\n", mods[i].path, err ? err : "(unknown error)"); exit(1); } RegFnT regFn = (RegFnT)dlsym(mods[i].handle, DVX_SYM("wgtRegister")); if (regFn) { regFn(); // Record the .wgt path for any newly registered ifaces typedef int32_t (*IfaceCountFnT)(void); typedef const void *(*IfaceAtFnT)(int32_t, const char **); typedef const char *(*IfaceGetPathFnT)(const char *); typedef void (*IfaceSetPathFnT)(const char *, const char *); IfaceCountFnT countFn = (IfaceCountFnT)dlsym(NULL, DVX_SYM("wgtIfaceCount")); IfaceAtFnT atFn = (IfaceAtFnT)dlsym(NULL, DVX_SYM("wgtIfaceAt")); IfaceGetPathFnT getPathFn = (IfaceGetPathFnT)dlsym(NULL, DVX_SYM("wgtIfaceGetPath")); IfaceSetPathFnT setPathFn = (IfaceSetPathFnT)dlsym(NULL, DVX_SYM("wgtIfaceSetPath")); if (countFn && atFn && getPathFn && setPathFn) { int32_t ic = countFn(); for (int32_t k = 0; k < ic; k++) { const char *ifaceName = NULL; atFn(k, &ifaceName); if (ifaceName && !getPathFn(ifaceName)) { setPathFn(ifaceName, mods[i].path); } } } } else if (strstr(mods[i].path, ".wgt") || strstr(mods[i].path, ".WGT")) { dvxLog(" No _wgtRegister in %s", mods[i].baseName); } mods[i].loaded = true; loaded++; progress = true; sSplashLoaded++; splashUpdateProgress(); } } while (progress && loaded < total); if (loaded < total) { fprintf(stderr, "Module loader: %d of %d modules could not be loaded (circular deps or missing deps)\n", (int)(total - loaded), (int)total); for (int32_t i = 0; i < total; i++) { if (!mods[i].loaded) { fprintf(stderr, " %s\n", mods[i].path); } } } } static void logAndReadDeps(ModuleT *mods) { dvxLog("Discovered %d modules:", arrlen(mods)); for (int32_t i = 0; i < arrlen(mods); i++) { dvxLog(" [%d] %s (base: %s)", i, mods[i].path, mods[i].baseName); } for (int32_t i = 0; i < arrlen(mods); i++) { readDeps(&mods[i]); if (arrlen(mods[i].deps) > 0) { char line[512]; int32_t pos = snprintf(line, sizeof(line), " %s deps:", mods[i].baseName); for (int32_t d = 0; d < arrlen(mods[i].deps); d++) { // dvxStrAppendf bounds the accumulate and returns -1 on // truncation (or a prior failure), so a long dep list can // never overrun the fixed line buffer. pos = dvxStrAppendf(line, sizeof(line), pos, " %s", mods[i].deps[d]); if (pos < 0) { break; } } dvxLog("%s", line); } } } static void processHcf(const char *hcfPath, const char *hcfDir) { ProcessHcfCtxT ctx; memset(&ctx, 0, sizeof(ctx)); ctx.hcfDir = hcfDir; hcfForEachEntry(hcfPath, processHcfCallback, &ctx); int32_t inputCount = (int32_t)arrlen(ctx.inputFiles); if (!ctx.outputFile[0] || inputCount == 0) { for (int32_t i = 0; i < inputCount; i++) { free(ctx.inputFiles[i]); } arrfree(ctx.inputFiles); return; } dvxLog("helpRecompile: %s -> %s (%d files)", hcfPath, ctx.outputFile, (int)inputCount); int32_t rc = hlpcCompile((const char **)ctx.inputFiles, inputCount, ctx.outputFile, ctx.imgDir[0] ? ctx.imgDir : NULL, NULL, HLPC_QUIET, hlpcProgressCallback, NULL); if (rc != 0) { dvxLog("helpRecompile: FAILED (rc=%d)", (int)rc); } for (int32_t i = 0; i < inputCount; i++) { free(ctx.inputFiles[i]); } arrfree(ctx.inputFiles); } // Callback for processHcf: accumulate output=, imagedir=, and expanded source= entries. static void processHcfCallback(const char *key, const char *val, const char *exclude, void *user) { ProcessHcfCtxT *ctx = (ProcessHcfCtxT *)user; if (strcasecmp(key, "output") == 0) { snprintf(ctx->outputFile, sizeof(ctx->outputFile), "%s" DVX_PATH_SEP "%s", ctx->hcfDir, val); } else if (strcasecmp(key, "imagedir") == 0) { snprintf(ctx->imgDir, sizeof(ctx->imgDir), "%s", val); } else if (strcasecmp(key, "source") == 0) { collectGlobFiles(&ctx->inputFiles, val, exclude); } } // Recursively scan a directory for .hcf files and process each one. static void processHcfDir(const char *dirPath) { char **names = dvxReadDir(dirPath); if (!names) { return; } int32_t nEntries = (int32_t)arrlen(names); for (int32_t i = 0; i < nEntries; i++) { if (names[i][0] == '.') { continue; } char fullPath[DVX_MAX_PATH]; snprintf(fullPath, sizeof(fullPath), "%s" DVX_PATH_SEP "%s", dirPath, names[i]); struct stat st; if (stat(fullPath, &st) != 0) { continue; } if (S_ISDIR(st.st_mode)) { processHcfDir(fullPath); } else if (dvxHasExt(names[i], ".hcf")) { processHcf(fullPath, dirPath); } } dvxReadDirFree(names); } // The .dep file has the same path as the module but with a .dep // extension. Each line is a dependency base name. Empty lines // and lines starting with # are ignored. static void readDeps(ModuleT *mod) { // Build dep file path: replace extension with .dep char depPath[DVX_MAX_PATH]; strncpy(depPath, mod->path, sizeof(depPath) - 1); depPath[sizeof(depPath) - 1] = '\0'; char *dot = strrchr(depPath, '.'); if (!dot) { return; } strcpy(dot, ".dep"); FILE *f = fopen(depPath, "r"); if (!f) { return; } char line[64]; while (fgets(line, sizeof(line), f)) { // A physical line longer than the buffer is split across fgets // calls. Drain the remainder so an over-long comment's tail (which // does not start with '#') is not mistaken for a separate dep name. // fgetc returns EOF immediately at end of file, so this is safe even // for an unterminated final line. if (strchr(line, '\n') == NULL) { int32_t ch; while ((ch = fgetc(f)) != '\n' && ch != EOF) { // Discard the rest of the over-long physical line. } } // Strip trailing whitespace (CR/LF/space/tab) int32_t len = dvxTrimRight(line); // Skip empty lines and comments if (len == 0 || line[0] == '#') { continue; } // Lowercase the dep name for case-insensitive matching for (int32_t i = 0; i < len; i++) { line[i] = tolower((unsigned char)line[i]); } arrput(mod->deps, strdup(line)); } fclose(f); } static void scanDir(const char *dirPath, const char *ext, ModuleT **mods) { // Collect all entries first, close the handle, then process. // DOS has limited file handles; keeping a DIR open during // recursion or stat() causes intermittent failures. char **names = dvxReadDir(dirPath); if (!names) { return; } int32_t count = (int32_t)arrlen(names); for (int32_t i = 0; i < count; i++) { char path[DVX_MAX_PATH]; snprintf(path, sizeof(path), "%s" DVX_PATH_SEP "%s", dirPath, names[i]); if (dvxHasExt(names[i], ext)) { ModuleT mod; memset(&mod, 0, sizeof(mod)); snprintf(mod.path, sizeof(mod.path), "%s", path); extractBaseName(path, ext, mod.baseName, sizeof(mod.baseName)); arrput(*mods, mod); } else { struct stat st; if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { scanDir(path, ext, mods); } } } dvxReadDirFree(names); } static void splashDrawScreen(void) { if (platformSplashLoadRaw("SYSTEM/SPLASH.RAW")) { platformSplashFillRect(PBAR_X - 1, PBAR_Y - 1, PBAR_W + 2, PBAR_H + 2, SPLASH_BAR_OUT); platformSplashFillRect(PBAR_X, PBAR_Y, PBAR_W, PBAR_H, SPLASH_BAR_BG); } } static void splashShutdownIfActive(void) { if (sSplashActive) { platformSplashShutdown(); sSplashActive = 0; } } static void splashUpdateProgress(void) { if (!sSplashActive || sSplashTotal <= 0) { return; } int32_t fillW = (PBAR_W * sSplashLoaded) / sSplashTotal; if (fillW > PBAR_W) { fillW = PBAR_W; } platformSplashFillRect(PBAR_X, PBAR_Y, fillW, PBAR_H, SPLASH_BAR_FG); } // allDepsLoaded silently treats an unknown dep name as "external, assumed // OK" so that libc / platform symbols don't trip the topological sort. // That's fine for intentional externals but masks typos and missing // .lib/.wgt files. This pre-flight check ensures every name in any .dep // resolves to a module we actually discovered; anything else is fatal. static void validateDeps(const ModuleT *libs, const ModuleT *widgets) { const ModuleT *pools[2] = { libs, widgets }; const char *kinds[2] = { "lib", "widget" }; int32_t errors = 0; for (int32_t pi = 0; pi < 2; pi++) { const ModuleT *mods = pools[pi]; for (int32_t i = 0; i < arrlen(mods); i++) { for (int32_t d = 0; d < arrlen(mods[i].deps); d++) { const char *depName = mods[i].deps[d]; bool found = false; for (int32_t qi = 0; qi < 2 && !found; qi++) { const ModuleT *pool = pools[qi]; for (int32_t j = 0; j < arrlen(pool); j++) { if (strcasecmp(pool[j].baseName, depName) == 0) { found = true; break; } } } if (!found) { dvxLog("FATAL: %s %s requires missing dep: %s", kinds[pi], mods[i].baseName, depName); // Restore text mode before the first print or the // message prints invisibly under the graphics-mode // splash. splashShutdownIfActive(); fprintf(stderr, "FATAL: %s %s requires missing dep: %s\n", kinds[pi], mods[i].baseName, depName); errors++; } } } } if (errors > 0) { // The splash was already shut down when the first error printed. fprintf(stderr, "%d unresolved dependency reference(s); aborting.\n", (int)errors); exit(1); } } int main(int argc, char *argv[]) { // Change to the directory containing the executable so relative // paths (LIBS/, WIDGETS/, APPS/, CONFIG/) resolve correctly. char exeDir[DVX_MAX_PATH]; strncpy(exeDir, argv[0], sizeof(exeDir) - 1); exeDir[sizeof(exeDir) - 1] = '\0'; char *sep = platformPathDirEnd(exeDir); if (sep) { *sep = '\0'; platformChdir(exeDir); } // Truncate log, then use append-per-write FILE *logInit = fopen(LOG_PATH, "w"); if (logInit) { fclose(logInit); } // Suppress Ctrl+C before anything else platformSetLogPath(LOG_PATH); platformInit(); // Switch to VGA mode 13h and show graphical splash platformSplashInit(); sSplashActive = 1; splashDrawScreen(); dvxLog("DVX Loader starting..."); // Register platform + libc/libm/runtime symbols for DXE resolution platformRegisterDxeExports(); dvxLog("Platform exports registered."); // Load all modules from libs/ and widgets/ in dependency order. // Each module may have a .dep file specifying load-before deps. // Widget modules that export wgtRegister() get it called. void **handles = loadAllModules(); if (!handles || arrlen(handles) == 0) { splashShutdownIfActive(); fprintf(stderr, "No modules loaded from %s/ or %s/\n", LIBS_DIR, WIDGET_DIR); arrfree(handles); return 1; } // Check if help files need recompilation (module count changed) helpRecompileIfNeeded(); // Find and call shellMain from whichever module exports it typedef int (*ShellMainFnT)(int, char **); ShellMainFnT shellMain = (ShellMainFnT)findSymbol(handles, DVX_SYM("shellMain")); if (!shellMain) { dvxLog("ERROR: No module exports shellMain"); splashShutdownIfActive(); for (int32_t i = arrlen(handles) - 1; i >= 0; i--) { dlclose(handles[i]); } arrfree(handles); return 1; } int result = shellMain(argc, argv); // Clean up in reverse load order for (int32_t i = arrlen(handles) - 1; i >= 0; i--) { dlclose(handles[i]); } arrfree(handles); return result; }