979 lines
27 KiB
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;
|
|
}
|