575 lines
14 KiB
C
575 lines
14 KiB
C
// 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 <file> <name> <type> <data|@file>
|
|
// dvxres build <file> <manifest.res>
|
|
// dvxres list <file>
|
|
// dvxres get <file> <name> [outfile]
|
|
// dvxres strip <file>
|
|
//
|
|
// The resource block is appended after the DXE3 content:
|
|
// [resource data entries]
|
|
// [resource directory]
|
|
// [footer with magic + directory offset + count]
|
|
|
|
#include "dvxResWrite.h"
|
|
|
|
#include <ctype.h>
|
|
#include <stdbool.h>
|
|
|
|
// ============================================================
|
|
// 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 int parseType(const char *typeStr);
|
|
static void usage(void);
|
|
|
|
|
|
// ============================================================
|
|
// 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;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// 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;
|
|
}
|
|
|
|
// Prepare resource 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");
|
|
return 1;
|
|
}
|
|
|
|
memcpy(newData, dataArg, newSize);
|
|
} else {
|
|
// Binary/icon: dataArg is a file path (strip leading @ if present)
|
|
const char *filePath = dataArg;
|
|
|
|
if (filePath[0] == '@') {
|
|
filePath++;
|
|
}
|
|
|
|
FILE *df = fopen(filePath, "rb");
|
|
|
|
if (!df) {
|
|
fprintf(stderr, "Cannot open data file: %s\n", filePath);
|
|
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", filePath);
|
|
return 1;
|
|
}
|
|
|
|
fclose(df);
|
|
}
|
|
|
|
int result = dvxResAppendEntry(dxePath, name, (uint32_t)type, newData, newSize);
|
|
free(newData);
|
|
|
|
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, (unsigned)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 = dvxResDxeContentSize(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, (unsigned)newSize);
|
|
}
|
|
|
|
fclose(mf);
|
|
|
|
if (count == 0) {
|
|
printf("No resources in manifest.\n");
|
|
free(entries);
|
|
free(data);
|
|
return 0;
|
|
}
|
|
|
|
int result = dvxResWriteBlock(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", (unsigned)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, (unsigned)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, (unsigned)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, (unsigned)size, outPath);
|
|
} else {
|
|
// Write to stdout
|
|
fwrite(buf, 1, size, stdout);
|
|
}
|
|
|
|
free(buf);
|
|
return 0;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// cmdStrip
|
|
// ============================================================
|
|
|
|
static int cmdStrip(const char *dxePath) {
|
|
long dxeSize = dvxResDxeContentSize(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 <file> <name> <type> <data|@file>\n");
|
|
fprintf(stderr, " dvxres build <file> <manifest.res>\n");
|
|
fprintf(stderr, " dvxres list <file>\n");
|
|
fprintf(stderr, " dvxres get <file> <name> [outfile]\n");
|
|
fprintf(stderr, " dvxres strip <file>\n\n");
|
|
fprintf(stderr, "Types: icon (or image), text, binary\n\n");
|
|
fprintf(stderr, "For 'add' with type 'text', <data> is the string value.\n");
|
|
fprintf(stderr, "For 'add' with type 'icon' or 'binary', <data> 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 <file> <name> <type> <data>\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 <file> <manifest.res>\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 <file> <name> [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;
|
|
}
|