// 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 "dvxPlatform.h" #include #include #include #include #include #include #include #include #include // Route stb_ds allocations through the tracking wrappers so that // arrput/arrfree in DXE code is tracked per-app. extern void *dvxRealloc(void *ptr, size_t size); extern void dvxFree(void *ptr); #define STBDS_REALLOC(c, p, s) dvxRealloc((p), (s)) #define STBDS_FREE(c, p) dvxFree(p) #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) static int32_t sSplashActive = 0; static int32_t sSplashTotal = 0; static int32_t sSplashLoaded = 0; // Progress bar geometry #define PBAR_X 10 #define PBAR_Y 188 #define PBAR_W 300 #define PBAR_H 6 static void splashDrawScreen(void) { if (platformSplashLoadRaw("CONFIG/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 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); } // ============================================================ // dvxLog -- append a line to dvx.log // ============================================================ // // Global logging function exported to all DXE modules. // Opens/closes the file per-write so it's never held open. void dvxLog(const char *fmt, ...) { FILE *f = fopen(LOG_PATH, "a"); if (!f) { return; } va_list ap; va_start(ap, fmt); vfprintf(f, fmt, ap); va_end(ap); fprintf(f, "\n"); fclose(f); } // ============================================================ // Module entry for dependency resolution // ============================================================ typedef struct { char path[DVX_MAX_PATH]; char baseName[16]; char **deps; bool loaded; void *handle; } ModuleT; // ============================================================ // Prototypes // ============================================================ static bool allDepsLoaded(const ModuleT *mod, const ModuleT *mods); 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 **loadAllModules(void); static void loadInOrder(ModuleT *mods); static void logAndReadDeps(ModuleT *mods); static void readDeps(ModuleT *mod); static void scanDir(const char *dirPath, const char *ext, ModuleT **mods); // ============================================================ // extractBaseName -- strip directory and extension, lowercase // ============================================================ 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'; } // ============================================================ // readDeps -- parse .dep file for a module // ============================================================ // // 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)) { // Strip \r and \n int32_t len = strlen(line); while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) { line[--len] = '\0'; } // 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); } // ============================================================ // scanDir -- recursively find modules with a given extension // ============================================================ static void scanDir(const char *dirPath, const char *ext, ModuleT **mods) { DIR *dir = opendir(dirPath); if (!dir) { return; } struct dirent *ent; while ((ent = readdir(dir)) != NULL) { const char *name = ent->d_name; // Skip . and .. if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) { continue; } char path[DVX_MAX_PATH]; snprintf(path, sizeof(path), "%s/%s", dirPath, name); // Check for matching extension int32_t nameLen = strlen(name); int32_t extLen = strlen(ext); if (nameLen > extLen && strcasecmp(name + nameLen - extLen, ext) == 0) { 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); continue; } // Recurse into subdirectories struct stat st; if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { scanDir(path, ext, mods); } } closedir(dir); } // ============================================================ // allDepsLoaded -- check if a module's dependencies are satisfied // ============================================================ // // 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; } // ============================================================ // loadInOrder -- topological sort and load // ============================================================ // // 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_GLOBAL); if (!mods[i].handle) { const char *err = dlerror(); dvxLog(" FAILED: %s", err ? err : "(unknown)"); platformSplashShutdown(); sSplashActive = 0; 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, "_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, "_wgtIfaceCount"); IfaceAtFnT atFn = (IfaceAtFnT)dlsym(NULL, "_wgtIfaceAt"); IfaceGetPathFnT getPathFn = (IfaceGetPathFnT)dlsym(NULL, "_wgtIfaceGetPath"); IfaceSetPathFnT setPathFn = (IfaceSetPathFnT)dlsym(NULL, "_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); } } } } // ============================================================ // loadAllModules -- scan libs/ and widgets/, load in dep order // ============================================================ // // Returns a stb_ds dynamic array of dlopen handles. 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++) { pos += snprintf(line + pos, sizeof(line) - pos, " %s", mods[i].deps[d]); } dvxLog("%s", line); } } } 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); } 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); sSplashTotal = (int32_t)arrlen(libs) + (int32_t)arrlen(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; } // ============================================================ // findSymbol -- search loaded handles for a symbol // ============================================================ 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; } // ============================================================ // main // ============================================================ 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 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) { fprintf(stderr, "No modules loaded from %s/ or %s/\n", LIBS_DIR, WIDGET_DIR); arrfree(handles); return 1; } // Find and call shellMain from whichever module exports it typedef int (*ShellMainFnT)(int, char **); ShellMainFnT shellMain = (ShellMainFnT)findSymbol(handles, "_shellMain"); if (!shellMain) { dvxLog("ERROR: No module exports shellMain"); 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; }