DVX_GUI/loader/loaderMain.c

979 lines
27 KiB
C

// 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 "dvxPlat.h"
#include "dvxPrefs.h"
#include <ctype.h>
#include <dirent.h>
#include <dlfcn.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/stat.h>
// 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"
#define INI_PATH "CONFIG/DVX.INI"
#define HLPC_PATH "SYSTEM/DVXHLPC.EXE"
// ============================================================
// 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("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 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 int32_t countHcfFilesRecurse(const char *dirPath);
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 processHcfDir(const char *dirPath);
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) {
// Copy d_name — readdir may use a shared buffer across recursion
char name[DVX_MAX_PATH];
snprintf(name, sizeof(name), "%s", 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%c%s", dirPath, DVX_PATH_SEP, 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);
}
// ============================================================
// Help recompilation
// ============================================================
static int32_t sLibCount = 0;
static int32_t sWidgetCount = 0;
// ============================================================
// Glob matching
// ============================================================
//
// Simple glob pattern matching for filenames. Supports:
// * matches zero or more characters
// ? matches exactly one character
// !pat at the start means "exclude files matching pat"
// Case-insensitive. Exported for use by DXE modules.
bool platformGlobMatch(const char *pattern, const char *name) {
while (*pattern && *name) {
if (*pattern == '*') {
pattern++;
if (!*pattern) {
return true;
}
while (*name) {
if (platformGlobMatch(pattern, name)) {
return true;
}
name++;
}
return false;
} else if (*pattern == '?' || tolower((unsigned char)*pattern) == tolower((unsigned char)*name)) {
pattern++;
name++;
} else {
return false;
}
}
// Skip trailing *
while (*pattern == '*') {
pattern++;
}
return *pattern == '\0' && *name == '\0';
}
// ============================================================
// HCF-driven help recompilation
// ============================================================
//
// Scans APPS/*/ for .hcf (help config) files. Each .hcf defines
// an output .hlp file and source patterns:
//
// output = DVXHELP.HLP
// source = LIBS/*.DHS
// source = WIDGETS/*.DHS
//
// Source lines specify a directory/glob pattern. An optional !pattern
// excludes matching files. The loader expands these by scanning
// directories and matching filenames.
// Recursively count .hcf files under the given directory
static int32_t countHcfFilesRecurse(const char *dirPath) {
int32_t count = 0;
DIR *dir = opendir(dirPath);
if (!dir) {
return 0;
}
struct dirent *ent;
while ((ent = readdir(dir)) != NULL) {
if (ent->d_name[0] == '.') {
continue;
}
// Copy d_name before any recursion — readdir may use a shared buffer
char name[DVX_MAX_PATH];
snprintf(name, sizeof(name), "%s", ent->d_name);
char fullPath[DVX_MAX_PATH];
snprintf(fullPath, sizeof(fullPath), "%s%c%s", dirPath, DVX_PATH_SEP, name);
struct stat st;
if (stat(fullPath, &st) != 0) {
continue;
}
if (S_ISDIR(st.st_mode)) {
count += countHcfFilesRecurse(fullPath);
} else {
int32_t nameLen = (int32_t)strlen(name);
if (nameLen > 4 && strcasecmp(name + nameLen - 4, ".hcf") == 0) {
count++;
}
}
}
closedir(dir);
return count;
}
// Count .hcf files under APPS/
static int32_t countHcfFiles(void) {
return countHcfFilesRecurse("APPS");
}
// Process a single .hcf file: parse it and invoke the compiler.
#define HELP_RESP_FILE "SYSTEM/HLPFILES.TMP"
// Write matching filenames to the response file (one per line).
static void writeGlobToResp(FILE *resp, const char *pattern, const char *excludePattern) {
// Split pattern into directory and filename glob
char dirPart[DVX_MAX_PATH];
const char *globPart = NULL;
snprintf(dirPart, sizeof(dirPart), "%s", pattern);
char *lastSep = strrchr(dirPart, '/');
if (!lastSep) {
lastSep = strrchr(dirPart, '\\');
}
if (lastSep) {
*lastSep = '\0';
globPart = lastSep + 1;
} else {
globPart = pattern;
dirPart[0] = '.';
dirPart[1] = '\0';
}
DIR *d = opendir(dirPart);
if (!d) {
return;
}
struct dirent *ent;
while ((ent = readdir(d)) != NULL) {
if (ent->d_name[0] == '.') {
continue;
}
char name[DVX_MAX_PATH];
snprintf(name, sizeof(name), "%s", ent->d_name);
char fullPath[DVX_MAX_PATH];
snprintf(fullPath, sizeof(fullPath), "%s%c%s", dirPart, DVX_PATH_SEP, name);
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%c%s", fullPath, DVX_PATH_SEP, globPart);
writeGlobToResp(resp, subPattern, excludePattern);
} else if (platformGlobMatch(globPart, name)) {
if (excludePattern && platformGlobMatch(excludePattern, name)) {
continue;
}
fprintf(resp, "%s\n", fullPath);
}
}
closedir(d);
}
static void processHcf(const char *hcfPath, const char *hcfDir) {
FILE *f = fopen(hcfPath, "r");
if (!f) {
return;
}
char outputFile[DVX_MAX_PATH] = {0};
// Write matching source files to a response file
FILE *resp = fopen(HELP_RESP_FILE, "w");
if (!resp) {
fclose(f);
dvxLog("helpRecompile: cannot create %s", HELP_RESP_FILE);
return;
}
bool hasFiles = false;
char line[512];
while (fgets(line, (int)sizeof(line), f)) {
int32_t len = (int32_t)strlen(line);
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r' || line[len - 1] == ' ')) {
line[--len] = '\0';
}
if (line[0] == '#' || line[0] == '\0') {
continue;
}
if (strncasecmp(line, "output", 6) == 0) {
const char *p = line + 6;
while (*p == ' ' || *p == '=') {
p++;
}
snprintf(outputFile, sizeof(outputFile), "%s%c%s", hcfDir, DVX_PATH_SEP, p);
} else if (strncasecmp(line, "source", 6) == 0) {
const char *p = line + 6;
while (*p == ' ' || *p == '=') {
p++;
}
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);
}
long before = ftell(resp);
writeGlobToResp(resp, pattern, exclude[0] ? exclude : NULL);
if (ftell(resp) > before) {
hasFiles = true;
}
}
}
fclose(f);
fclose(resp);
if (!outputFile[0] || !hasFiles) {
remove(HELP_RESP_FILE);
return;
}
// Short command: compiler + output + response file
char cmd[DVX_MAX_PATH * 2];
snprintf(cmd, sizeof(cmd), "%s --quiet -o %s @%s", HLPC_PATH, outputFile, HELP_RESP_FILE);
dvxLog("helpRecompile: %s -> %s", hcfPath, outputFile);
int rc = system(cmd);
if (rc != 0) {
dvxLog("helpRecompile: FAILED (rc=%d)", rc);
}
remove(HELP_RESP_FILE);
}
// Recursively scan a directory for .hcf files and process each one.
static void processHcfDir(const char *dirPath) {
DIR *dir = opendir(dirPath);
if (!dir) {
return;
}
struct dirent *ent;
while ((ent = readdir(dir)) != NULL) {
if (ent->d_name[0] == '.') {
continue;
}
// Copy d_name before any recursion — readdir may use a shared buffer
char name[DVX_MAX_PATH];
snprintf(name, sizeof(name), "%s", ent->d_name);
char fullPath[DVX_MAX_PATH];
snprintf(fullPath, sizeof(fullPath), "%s%c%s", dirPath, DVX_PATH_SEP, name);
struct stat st;
if (stat(fullPath, &st) != 0) {
continue;
}
if (S_ISDIR(st.st_mode)) {
processHcfDir(fullPath);
} else {
int32_t nameLen = (int32_t)strlen(name);
if (nameLen > 4 && strcasecmp(name + nameLen - 4, ".hcf") == 0) {
processHcf(fullPath, dirPath);
sSplashLoaded++;
splashUpdateProgress();
}
}
}
closedir(dir);
}
static void helpRecompileIfNeeded(void) {
PrefsHandleT *ini = prefsLoad(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);
// Check if compiler exists
FILE *test = fopen(HLPC_PATH, "rb");
if (!test) {
dvxLog("helpRecompile: %s not found, skipping", HLPC_PATH);
prefsSetInt(ini, "help", "libCount", sLibCount);
prefsSetInt(ini, "help", "widgetCount", sWidgetCount);
prefsSave(ini);
prefsClose(ini);
return;
}
fclose(test);
// Count .hcf files for progress bar
int32_t hcfCount = countHcfFiles();
if (hcfCount == 0) {
dvxLog("helpRecompile: no .hcf files 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 = hcfCount;
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");
}
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;
// 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;
}
// 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, "_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;
}