// dvxres.c -- DVX resource tool // // Manages resource blocks appended to DXE3 files (.app, .wgt, .lib). // Builds on both DOS (DJGPP) and Linux (GCC) with no dependencies // beyond standard C. // // Commands: // dvxres add // dvxres build // dvxres list // dvxres get [outfile] // dvxres strip // // The resource block is appended after the DXE3 content: // [resource data entries] // [resource directory] // [footer with magic + directory offset + count] #include "../core/dvxResource.h" #include #include #include #include #include // ============================================================ // Prototypes // ============================================================ static int cmdAdd(const char *dxePath, const char *name, const char *typeStr, const char *dataArg); static int cmdBuild(const char *dxePath, const char *manifestPath); static int cmdGet(const char *dxePath, const char *name, const char *outPath); static int cmdList(const char *dxePath); static int cmdStrip(const char *dxePath); static long dxeContentSize(const char *path); static int parseType(const char *typeStr); static int readExistingResources(const char *path, long dxeSize, DvxResDirEntryT **outEntries, uint32_t *outCount, uint8_t ***outData); static void usage(void); // ============================================================ // dxeContentSize // ============================================================ // // Returns the size of the DXE3 content (before any appended resources). // If the file has a resource footer, returns the start of the resource // data. Otherwise returns the full file size. static long dxeContentSize(const char *path) { FILE *f = fopen(path, "rb"); if (!f) { return -1; } fseek(f, 0, SEEK_END); long fileSize = ftell(f); if (fileSize < (long)sizeof(DvxResFooterT)) { fclose(f); return fileSize; } // Check for resource footer fseek(f, -(long)sizeof(DvxResFooterT), SEEK_END); DvxResFooterT footer; if (fread(&footer, sizeof(footer), 1, f) == 1 && footer.magic == DVX_RES_MAGIC) { // Find the earliest resource data offset fseek(f, footer.dirOffset, SEEK_SET); DvxResDirEntryT entry; long earliest = footer.dirOffset; for (uint32_t i = 0; i < footer.entryCount; i++) { if (fread(&entry, sizeof(entry), 1, f) == 1) { if ((long)entry.offset < earliest) { earliest = (long)entry.offset; } } } fclose(f); return earliest; } fclose(f); return fileSize; } // ============================================================ // parseType // ============================================================ static int parseType(const char *typeStr) { if (strcmp(typeStr, "icon") == 0 || strcmp(typeStr, "image") == 0) { return DVX_RES_ICON; } if (strcmp(typeStr, "text") == 0) { return DVX_RES_TEXT; } if (strcmp(typeStr, "binary") == 0) { return DVX_RES_BINARY; } return -1; } // ============================================================ // readExistingResources // ============================================================ // // Reads any existing resource block from the file. Returns arrays // of directory entries and data blobs. Caller frees everything. static int readExistingResources(const char *path, long dxeSize, DvxResDirEntryT **outEntries, uint32_t *outCount, uint8_t ***outData) { *outEntries = NULL; *outCount = 0; *outData = NULL; FILE *f = fopen(path, "rb"); if (!f) { return -1; } fseek(f, 0, SEEK_END); long fileSize = ftell(f); if (fileSize <= dxeSize) { fclose(f); return 0; } // Check for footer fseek(f, -(long)sizeof(DvxResFooterT), SEEK_END); DvxResFooterT footer; if (fread(&footer, sizeof(footer), 1, f) != 1 || footer.magic != DVX_RES_MAGIC) { fclose(f); return 0; } // Read directory fseek(f, footer.dirOffset, SEEK_SET); DvxResDirEntryT *entries = (DvxResDirEntryT *)malloc(footer.entryCount * sizeof(DvxResDirEntryT)); if (!entries) { fclose(f); return -1; } if (fread(entries, sizeof(DvxResDirEntryT), footer.entryCount, f) != footer.entryCount) { free(entries); fclose(f); return -1; } // Read each resource's data uint8_t **data = (uint8_t **)malloc(footer.entryCount * sizeof(uint8_t *)); if (!data) { free(entries); fclose(f); return -1; } for (uint32_t i = 0; i < footer.entryCount; i++) { data[i] = (uint8_t *)malloc(entries[i].size); if (!data[i]) { for (uint32_t j = 0; j < i; j++) { free(data[j]); } free(data); free(entries); fclose(f); return -1; } fseek(f, entries[i].offset, SEEK_SET); if (fread(data[i], 1, entries[i].size, f) != entries[i].size) { fprintf(stderr, "Short read on entry %d\n", (int)i); for (uint32_t j = 0; j <= i; j++) { free(data[j]); } free(data); free(entries); fclose(f); return -1; } } fclose(f); *outEntries = entries; *outCount = footer.entryCount; *outData = data; return 0; } // ============================================================ // writeResources // ============================================================ // // Truncates the file to dxeSize and writes the resource block. static int writeResources(const char *path, long dxeSize, DvxResDirEntryT *entries, uint32_t count, uint8_t **data) { // Read the DXE content first FILE *f = fopen(path, "rb"); if (!f) { return -1; } uint8_t *dxeBuf = (uint8_t *)malloc(dxeSize); if (!dxeBuf) { fclose(f); return -1; } if (fread(dxeBuf, 1, dxeSize, f) != (size_t)dxeSize) { free(dxeBuf); fclose(f); return -1; } fclose(f); // Rewrite file: DXE content + resources f = fopen(path, "wb"); if (!f) { free(dxeBuf); return -1; } fwrite(dxeBuf, 1, dxeSize, f); free(dxeBuf); // Write resource data entries and update offsets for (uint32_t i = 0; i < count; i++) { entries[i].offset = (uint32_t)ftell(f); fwrite(data[i], 1, entries[i].size, f); } // Write directory uint32_t dirOffset = (uint32_t)ftell(f); fwrite(entries, sizeof(DvxResDirEntryT), count, f); // Write footer DvxResFooterT footer; footer.magic = DVX_RES_MAGIC; footer.dirOffset = dirOffset; footer.entryCount = count; footer.reserved = 0; fwrite(&footer, sizeof(footer), 1, f); fclose(f); return 0; } // ============================================================ // cmdAdd // ============================================================ static int cmdAdd(const char *dxePath, const char *name, const char *typeStr, const char *dataArg) { int type = parseType(typeStr); if (type < 0) { fprintf(stderr, "Unknown resource type: %s (use icon, text, or binary)\n", typeStr); return 1; } if (strlen(name) >= DVX_RES_NAME_MAX) { fprintf(stderr, "Resource name too long (max %d chars)\n", DVX_RES_NAME_MAX - 1); return 1; } long dxeSize = dxeContentSize(dxePath); if (dxeSize < 0) { fprintf(stderr, "Cannot open: %s\n", dxePath); return 1; } // Read existing resources DvxResDirEntryT *entries = NULL; uint32_t count = 0; uint8_t **data = NULL; readExistingResources(dxePath, dxeSize, &entries, &count, &data); // Remove existing entry with same name (replace) for (uint32_t i = 0; i < count; i++) { if (strcmp(entries[i].name, name) == 0) { free(data[i]); for (uint32_t j = i; j < count - 1; j++) { entries[j] = entries[j + 1]; data[j] = data[j + 1]; } count--; break; } } // Prepare new resource data uint8_t *newData = NULL; uint32_t newSize = 0; if (type == DVX_RES_TEXT) { // Text data from command line argument newSize = (uint32_t)strlen(dataArg) + 1; newData = (uint8_t *)malloc(newSize); if (!newData) { fprintf(stderr, "Out of memory\n"); return 1; } memcpy(newData, dataArg, newSize); } else { // Binary/icon data from file FILE *df = fopen(dataArg, "rb"); if (!df) { fprintf(stderr, "Cannot open data file: %s\n", dataArg); return 1; } fseek(df, 0, SEEK_END); newSize = (uint32_t)ftell(df); fseek(df, 0, SEEK_SET); newData = (uint8_t *)malloc(newSize); if (!newData) { fclose(df); fprintf(stderr, "Out of memory\n"); return 1; } if (fread(newData, 1, newSize, df) != newSize) { fclose(df); free(newData); fprintf(stderr, "Failed to read: %s\n", dataArg); return 1; } fclose(df); } // Append to arrays entries = (DvxResDirEntryT *)realloc(entries, (count + 1) * sizeof(DvxResDirEntryT)); data = (uint8_t **)realloc(data, (count + 1) * sizeof(uint8_t *)); memset(&entries[count], 0, sizeof(DvxResDirEntryT)); strncpy(entries[count].name, name, DVX_RES_NAME_MAX - 1); entries[count].type = (uint32_t)type; entries[count].size = newSize; data[count] = newData; count++; // Write int result = writeResources(dxePath, dxeSize, entries, count, data); // Cleanup for (uint32_t i = 0; i < count; i++) { free(data[i]); } free(data); free(entries); if (result != 0) { fprintf(stderr, "Failed to write resources to: %s\n", dxePath); return 1; } printf("Added '%s' (%s, %u bytes) to %s\n", name, typeStr, newSize, dxePath); return 0; } // ============================================================ // cmdBuild // ============================================================ static int cmdBuild(const char *dxePath, const char *manifestPath) { FILE *mf = fopen(manifestPath, "r"); if (!mf) { fprintf(stderr, "Cannot open manifest: %s\n", manifestPath); return 1; } long dxeSize = dxeContentSize(dxePath); if (dxeSize < 0) { fclose(mf); fprintf(stderr, "Cannot open: %s\n", dxePath); return 1; } // Strip any existing resources first -- start fresh DvxResDirEntryT *entries = NULL; uint32_t count = 0; uint8_t **data = NULL; char line[1024]; int lineNum = 0; while (fgets(line, sizeof(line), mf)) { lineNum++; // Strip trailing newline/CR size_t len = strlen(line); while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) { line[--len] = '\0'; } // Skip empty lines and comments char *p = line; while (*p && isspace((unsigned char)*p)) { p++; } if (*p == '\0' || *p == '#') { continue; } // Parse: name type data // name and type are whitespace-delimited // data is either a quoted string or a filename (rest of line) char name[DVX_RES_NAME_MAX]; char typeStr[16]; char dataArg[512]; if (sscanf(p, "%31s %15s", name, typeStr) != 2) { fprintf(stderr, "%s:%d: expected: name type data\n", manifestPath, lineNum); goto fail; } // Advance past name and type to get data char *dp = p; // Skip name while (*dp && !isspace((unsigned char)*dp)) { dp++; } while (*dp && isspace((unsigned char)*dp)) { dp++; } // Skip type while (*dp && !isspace((unsigned char)*dp)) { dp++; } while (*dp && isspace((unsigned char)*dp)) { dp++; } if (*dp == '\0') { fprintf(stderr, "%s:%d: missing data argument\n", manifestPath, lineNum); goto fail; } // Handle quoted strings: strip quotes if (*dp == '"') { dp++; char *end = strrchr(dp, '"'); if (end) { *end = '\0'; } } strncpy(dataArg, dp, sizeof(dataArg) - 1); dataArg[sizeof(dataArg) - 1] = '\0'; int type = parseType(typeStr); if (type < 0) { fprintf(stderr, "%s:%d: unknown type: %s\n", manifestPath, lineNum, typeStr); goto fail; } // Prepare data uint8_t *newData = NULL; uint32_t newSize = 0; if (type == DVX_RES_TEXT) { newSize = (uint32_t)strlen(dataArg) + 1; newData = (uint8_t *)malloc(newSize); if (!newData) { fprintf(stderr, "Out of memory\n"); goto fail; } memcpy(newData, dataArg, newSize); } else { FILE *df = fopen(dataArg, "rb"); if (!df) { fprintf(stderr, "%s:%d: cannot open: %s\n", manifestPath, lineNum, dataArg); goto fail; } fseek(df, 0, SEEK_END); newSize = (uint32_t)ftell(df); fseek(df, 0, SEEK_SET); newData = (uint8_t *)malloc(newSize); if (!newData) { fclose(df); fprintf(stderr, "Out of memory\n"); goto fail; } if (fread(newData, 1, newSize, df) != newSize) { fclose(df); free(newData); fprintf(stderr, "%s:%d: failed to read: %s\n", manifestPath, lineNum, dataArg); goto fail; } fclose(df); } // Append entries = (DvxResDirEntryT *)realloc(entries, (count + 1) * sizeof(DvxResDirEntryT)); data = (uint8_t **)realloc(data, (count + 1) * sizeof(uint8_t *)); memset(&entries[count], 0, sizeof(DvxResDirEntryT)); snprintf(entries[count].name, DVX_RES_NAME_MAX, "%s", name); entries[count].type = (uint32_t)type; entries[count].size = newSize; data[count] = newData; count++; printf(" %s (%s, %u bytes)\n", name, typeStr, newSize); } fclose(mf); if (count == 0) { printf("No resources in manifest.\n"); free(entries); free(data); return 0; } int result = writeResources(dxePath, dxeSize, entries, count, data); for (uint32_t i = 0; i < count; i++) { free(data[i]); } free(data); free(entries); if (result != 0) { fprintf(stderr, "Failed to write resources to: %s\n", dxePath); return 1; } printf("Built %u resource(s) into %s\n", count, dxePath); return 0; fail: fclose(mf); for (uint32_t i = 0; i < count; i++) { free(data[i]); } free(data); free(entries); return 1; } // ============================================================ // cmdList // ============================================================ static int cmdList(const char *dxePath) { FILE *f = fopen(dxePath, "rb"); if (!f) { fprintf(stderr, "Cannot open: %s\n", dxePath); return 1; } fseek(f, -(long)sizeof(DvxResFooterT), SEEK_END); DvxResFooterT footer; if (fread(&footer, sizeof(footer), 1, f) != 1 || footer.magic != DVX_RES_MAGIC) { fclose(f); printf("No resources in %s\n", dxePath); return 0; } fseek(f, footer.dirOffset, SEEK_SET); printf("Resources in %s (%u entries):\n", dxePath, footer.entryCount); printf(" %-31s %-8s %s\n", "Name", "Type", "Size"); printf(" %-31s %-8s %s\n", "-------------------------------", "--------", "--------"); for (uint32_t i = 0; i < footer.entryCount; i++) { DvxResDirEntryT entry; if (fread(&entry, sizeof(entry), 1, f) != 1) { break; } const char *typeStr = "unknown"; if (entry.type == DVX_RES_ICON) { typeStr = "icon"; } else if (entry.type == DVX_RES_TEXT) { typeStr = "text"; } else if (entry.type == DVX_RES_BINARY) { typeStr = "binary"; } printf(" %-31s %-8s %u\n", entry.name, typeStr, entry.size); } fclose(f); return 0; } // ============================================================ // cmdGet // ============================================================ static int cmdGet(const char *dxePath, const char *name, const char *outPath) { DvxResHandleT *h = dvxResOpen(dxePath); if (!h) { fprintf(stderr, "No resources in %s\n", dxePath); return 1; } uint32_t size = 0; void *buf = dvxResRead(h, name, &size); dvxResClose(h); if (!buf) { fprintf(stderr, "Resource not found: %s\n", name); return 1; } if (outPath) { FILE *f = fopen(outPath, "wb"); if (!f) { free(buf); fprintf(stderr, "Cannot write: %s\n", outPath); return 1; } fwrite(buf, 1, size, f); fclose(f); printf("Extracted '%s' (%u bytes) to %s\n", name, size, outPath); } else { // Write to stdout fwrite(buf, 1, size, stdout); } free(buf); return 0; } // ============================================================ // cmdStrip // ============================================================ static int cmdStrip(const char *dxePath) { long dxeSize = dxeContentSize(dxePath); if (dxeSize < 0) { fprintf(stderr, "Cannot open: %s\n", dxePath); return 1; } // Read file size FILE *f = fopen(dxePath, "rb"); if (!f) { return 1; } fseek(f, 0, SEEK_END); long fileSize = ftell(f); fclose(f); if (fileSize == dxeSize) { printf("No resources to strip.\n"); return 0; } // Read DXE content f = fopen(dxePath, "rb"); uint8_t *buf = (uint8_t *)malloc(dxeSize); if (!buf) { fclose(f); fprintf(stderr, "Out of memory\n"); return 1; } if (fread(buf, 1, dxeSize, f) != (size_t)dxeSize) { fprintf(stderr, "Short read on DXE file\n"); free(buf); fclose(f); return 1; } fclose(f); // Rewrite truncated f = fopen(dxePath, "wb"); if (!f) { free(buf); return 1; } fwrite(buf, 1, dxeSize, f); fclose(f); free(buf); printf("Stripped %ld bytes of resources from %s\n", fileSize - dxeSize, dxePath); return 0; } // ============================================================ // usage // ============================================================ static void usage(void) { fprintf(stderr, "DVX Resource Tool\n\n"); fprintf(stderr, "Usage:\n"); fprintf(stderr, " dvxres add \n"); fprintf(stderr, " dvxres build \n"); fprintf(stderr, " dvxres list \n"); fprintf(stderr, " dvxres get [outfile]\n"); fprintf(stderr, " dvxres strip \n\n"); fprintf(stderr, "Types: icon (or image), text, binary\n\n"); fprintf(stderr, "For 'add' with type 'text', is the string value.\n"); fprintf(stderr, "For 'add' with type 'icon' or 'binary', is a file path.\n\n"); fprintf(stderr, "Manifest format (one per line):\n"); fprintf(stderr, " name type data\n"); fprintf(stderr, " # lines starting with # are comments\n"); } // ============================================================ // main // ============================================================ int main(int argc, char **argv) { if (argc < 3) { usage(); return 1; } const char *cmd = argv[1]; const char *file = argv[2]; if (strcmp(cmd, "add") == 0) { if (argc < 6) { fprintf(stderr, "Usage: dvxres add \n"); return 1; } return cmdAdd(file, argv[3], argv[4], argv[5]); } if (strcmp(cmd, "build") == 0) { if (argc < 4) { fprintf(stderr, "Usage: dvxres build \n"); return 1; } return cmdBuild(file, argv[3]); } if (strcmp(cmd, "list") == 0) { return cmdList(file); } if (strcmp(cmd, "get") == 0) { if (argc < 4) { fprintf(stderr, "Usage: dvxres get [outfile]\n"); return 1; } return cmdGet(file, argv[3], argc >= 5 ? argv[4] : NULL); } if (strcmp(cmd, "strip") == 0) { return cmdStrip(file); } fprintf(stderr, "Unknown command: %s\n", cmd); usage(); return 1; }