Resource support! Apps now show as icons in the Program Manager.
This commit is contained in:
parent
9b136995b7
commit
fdc72f98a1
23 changed files with 1564 additions and 31 deletions
10
Makefile
10
Makefile
|
|
@ -3,9 +3,9 @@
|
||||||
# Builds the full DVX stack: core library, task switcher,
|
# Builds the full DVX stack: core library, task switcher,
|
||||||
# bootstrap loader, text help library, widgets, shell, and apps.
|
# bootstrap loader, text help library, widgets, shell, and apps.
|
||||||
|
|
||||||
.PHONY: all clean core tasks loader texthelp listhelp widgets shell taskmgr apps
|
.PHONY: all clean core tasks loader texthelp listhelp widgets shell taskmgr apps tools
|
||||||
|
|
||||||
all: core tasks loader texthelp listhelp widgets shell taskmgr apps
|
all: core tasks loader texthelp listhelp widgets shell taskmgr apps tools
|
||||||
|
|
||||||
core:
|
core:
|
||||||
$(MAKE) -C core
|
$(MAKE) -C core
|
||||||
|
|
@ -31,7 +31,10 @@ shell: core tasks
|
||||||
taskmgr: shell
|
taskmgr: shell
|
||||||
$(MAKE) -C taskmgr
|
$(MAKE) -C taskmgr
|
||||||
|
|
||||||
apps: core tasks shell
|
tools:
|
||||||
|
$(MAKE) -C tools
|
||||||
|
|
||||||
|
apps: core tasks shell tools
|
||||||
$(MAKE) -C apps
|
$(MAKE) -C apps
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|
@ -44,6 +47,7 @@ clean:
|
||||||
$(MAKE) -C shell clean
|
$(MAKE) -C shell clean
|
||||||
$(MAKE) -C taskmgr clean
|
$(MAKE) -C taskmgr clean
|
||||||
$(MAKE) -C apps clean
|
$(MAKE) -C apps clean
|
||||||
|
$(MAKE) -C tools clean
|
||||||
-rmdir obj 2>/dev/null
|
-rmdir obj 2>/dev/null
|
||||||
-rm -rf bin/config bin/widgets bin/libs
|
-rm -rf bin/config bin/widgets bin/libs
|
||||||
-rmdir bin/apps/cpanel bin/apps/imgview bin/apps/progman bin/apps/notepad bin/apps/clock bin/apps/dvxdemo bin/apps bin 2>/dev/null
|
-rmdir bin/apps/cpanel bin/apps/imgview bin/apps/progman bin/apps/notepad bin/apps/clock bin/apps/dvxdemo bin/apps bin 2>/dev/null
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/pl
|
||||||
|
|
||||||
OBJDIR = ../obj/apps
|
OBJDIR = ../obj/apps
|
||||||
BINDIR = ../bin/apps
|
BINDIR = ../bin/apps
|
||||||
|
DVXRES = ../bin/dvxres
|
||||||
|
|
||||||
# App definitions: each is a subdir with a single .c file
|
# App definitions: each is a subdir with a single .c file
|
||||||
APPS = progman notepad clock dvxdemo cpanel imgview
|
APPS = progman notepad clock dvxdemo cpanel imgview
|
||||||
|
|
@ -23,25 +24,30 @@ notepad: $(BINDIR)/notepad/notepad.app
|
||||||
clock: $(BINDIR)/clock/clock.app
|
clock: $(BINDIR)/clock/clock.app
|
||||||
dvxdemo: $(BINDIR)/dvxdemo/dvxdemo.app
|
dvxdemo: $(BINDIR)/dvxdemo/dvxdemo.app
|
||||||
|
|
||||||
$(BINDIR)/cpanel/cpanel.app: $(OBJDIR)/cpanel.o | $(BINDIR)/cpanel
|
$(BINDIR)/cpanel/cpanel.app: $(OBJDIR)/cpanel.o cpanel/cpanel.res cpanel/icon32.bmp | $(BINDIR)/cpanel
|
||||||
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
|
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
|
||||||
|
$(DVXRES) build $@ cpanel/cpanel.res
|
||||||
|
|
||||||
$(BINDIR)/imgview/imgview.app: $(OBJDIR)/imgview.o | $(BINDIR)/imgview
|
$(BINDIR)/imgview/imgview.app: $(OBJDIR)/imgview.o imgview/imgview.res imgview/icon32.bmp | $(BINDIR)/imgview
|
||||||
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
|
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
|
||||||
|
$(DVXRES) build $@ imgview/imgview.res
|
||||||
|
|
||||||
$(BINDIR)/progman/progman.app: $(OBJDIR)/progman.o | $(BINDIR)/progman
|
$(BINDIR)/progman/progman.app: $(OBJDIR)/progman.o | $(BINDIR)/progman
|
||||||
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
|
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
|
||||||
|
|
||||||
$(BINDIR)/notepad/notepad.app: $(OBJDIR)/notepad.o | $(BINDIR)/notepad
|
$(BINDIR)/notepad/notepad.app: $(OBJDIR)/notepad.o notepad/notepad.res notepad/icon32.bmp | $(BINDIR)/notepad
|
||||||
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
|
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
|
||||||
|
$(DVXRES) build $@ notepad/notepad.res
|
||||||
|
|
||||||
$(BINDIR)/clock/clock.app: $(OBJDIR)/clock.o | $(BINDIR)/clock
|
$(BINDIR)/clock/clock.app: $(OBJDIR)/clock.o clock/clock.res clock/icon32.bmp | $(BINDIR)/clock
|
||||||
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -E _appShutdown -U $<
|
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -E _appShutdown -U $<
|
||||||
|
$(DVXRES) build $@ clock/clock.res
|
||||||
|
|
||||||
DVXDEMO_BMPS = logo.bmp new.bmp open.bmp sample.bmp save.bmp
|
DVXDEMO_BMPS = logo.bmp new.bmp open.bmp sample.bmp save.bmp
|
||||||
|
|
||||||
$(BINDIR)/dvxdemo/dvxdemo.app: $(OBJDIR)/dvxdemo.o $(addprefix dvxdemo/,$(DVXDEMO_BMPS)) | $(BINDIR)/dvxdemo
|
$(BINDIR)/dvxdemo/dvxdemo.app: $(OBJDIR)/dvxdemo.o $(addprefix dvxdemo/,$(DVXDEMO_BMPS)) dvxdemo/dvxdemo.res dvxdemo/icon32.bmp | $(BINDIR)/dvxdemo
|
||||||
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
|
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
|
||||||
|
$(DVXRES) build $@ dvxdemo/dvxdemo.res
|
||||||
cp $(addprefix dvxdemo/,$(DVXDEMO_BMPS)) $(BINDIR)/dvxdemo/
|
cp $(addprefix dvxdemo/,$(DVXDEMO_BMPS)) $(BINDIR)/dvxdemo/
|
||||||
|
|
||||||
$(OBJDIR)/cpanel.o: cpanel/cpanel.c | $(OBJDIR)
|
$(OBJDIR)/cpanel.o: cpanel/cpanel.c | $(OBJDIR)
|
||||||
|
|
|
||||||
5
apps/clock/clock.res
Normal file
5
apps/clock/clock.res
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# clock.res -- Resource manifest for Clock
|
||||||
|
icon32 icon clock/icon32.bmp
|
||||||
|
name text "Clock"
|
||||||
|
author text "DVX Project"
|
||||||
|
description text "Digital clock with date display"
|
||||||
BIN
apps/clock/icon32.bmp
(Stored with Git LFS)
Normal file
BIN
apps/clock/icon32.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
5
apps/cpanel/cpanel.res
Normal file
5
apps/cpanel/cpanel.res
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# cpanel.res -- Resource manifest for Control Panel
|
||||||
|
icon32 icon cpanel/icon32.bmp
|
||||||
|
name text "Control Panel"
|
||||||
|
author text "DVX Project"
|
||||||
|
description text "System settings and preferences"
|
||||||
BIN
apps/cpanel/icon32.bmp
(Stored with Git LFS)
Normal file
BIN
apps/cpanel/icon32.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
5
apps/dvxdemo/dvxdemo.res
Normal file
5
apps/dvxdemo/dvxdemo.res
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# dvxdemo.res -- Resource manifest for DVX Demo
|
||||||
|
icon32 icon dvxdemo/icon32.bmp
|
||||||
|
name text "DVX Demo"
|
||||||
|
author text "DVX Project"
|
||||||
|
description text "Widget toolkit demonstration"
|
||||||
BIN
apps/dvxdemo/icon32.bmp
(Stored with Git LFS)
Normal file
BIN
apps/dvxdemo/icon32.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
apps/imgview/icon32.bmp
(Stored with Git LFS)
Normal file
BIN
apps/imgview/icon32.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
5
apps/imgview/imgview.res
Normal file
5
apps/imgview/imgview.res
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# imgview.res -- Resource manifest for Image Viewer
|
||||||
|
icon32 icon imgview/icon32.bmp
|
||||||
|
name text "Image Viewer"
|
||||||
|
author text "DVX Project"
|
||||||
|
description text "BMP, PNG, JPEG, and GIF viewer"
|
||||||
BIN
apps/notepad/icon32.bmp
(Stored with Git LFS)
Normal file
BIN
apps/notepad/icon32.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
5
apps/notepad/notepad.res
Normal file
5
apps/notepad/notepad.res
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# notepad.res -- Resource manifest for Notepad
|
||||||
|
icon32 icon notepad/icon32.bmp
|
||||||
|
name text "Notepad"
|
||||||
|
author text "DVX Project"
|
||||||
|
description text "Simple text editor"
|
||||||
|
|
@ -31,6 +31,8 @@
|
||||||
#include "dvxPlatform.h"
|
#include "dvxPlatform.h"
|
||||||
#include "shellApp.h"
|
#include "shellApp.h"
|
||||||
#include "shellInfo.h"
|
#include "shellInfo.h"
|
||||||
|
#include "dvxResource.h"
|
||||||
|
#include "widgetImageButton.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
@ -50,9 +52,12 @@
|
||||||
// DOS 8.3 paths are short, but long names under DJGPP can reach ~260
|
// DOS 8.3 paths are short, but long names under DJGPP can reach ~260
|
||||||
#define MAX_PATH_LEN 260
|
#define MAX_PATH_LEN 260
|
||||||
// Grid layout for app buttons: 4 columns, rows created dynamically
|
// Grid layout for app buttons: 4 columns, rows created dynamically
|
||||||
|
#define PM_TOOLTIP_LEN 128
|
||||||
#define PM_GRID_COLS 4
|
#define PM_GRID_COLS 4
|
||||||
#define PM_BTN_W 100
|
#define PM_BTN_W 100
|
||||||
#define PM_BTN_H 24
|
#define PM_BTN_H 24
|
||||||
|
#define PM_CELL_W 80
|
||||||
|
#define PM_CELL_H 64
|
||||||
#define PM_WIN_W 440
|
#define PM_WIN_W 440
|
||||||
#define PM_WIN_H 340
|
#define PM_WIN_H 340
|
||||||
#define PM_GRID_SPACING 8
|
#define PM_GRID_SPACING 8
|
||||||
|
|
@ -77,8 +82,13 @@
|
||||||
|
|
||||||
// Each discovered .app file in the apps/ directory tree
|
// Each discovered .app file in the apps/ directory tree
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char name[SHELL_APP_NAME_MAX]; // display name (filename without .app)
|
char name[SHELL_APP_NAME_MAX]; // short display name (from "name" resource or filename)
|
||||||
char path[MAX_PATH_LEN]; // full path
|
char tooltip[PM_TOOLTIP_LEN]; // description for tooltip (from "description" resource)
|
||||||
|
char path[MAX_PATH_LEN]; // full path
|
||||||
|
uint8_t *iconData; // 32x32 icon in display format (NULL if none)
|
||||||
|
int32_t iconW;
|
||||||
|
int32_t iconH;
|
||||||
|
int32_t iconPitch;
|
||||||
} AppEntryT;
|
} AppEntryT;
|
||||||
|
|
||||||
// Module-level statics (s prefix). DXE apps use file-scoped statics because
|
// Module-level statics (s prefix). DXE apps use file-scoped statics because
|
||||||
|
|
@ -182,23 +192,53 @@ static void buildPmWindow(void) {
|
||||||
if (sAppCount == 0) {
|
if (sAppCount == 0) {
|
||||||
wgtLabel(appFrame, "(No applications found in apps/ directory)");
|
wgtLabel(appFrame, "(No applications found in apps/ directory)");
|
||||||
} else {
|
} else {
|
||||||
// Build rows of buttons. Each row is an HBox holding PM_GRID_COLS
|
// Build grid of app icons. Each cell is a VBox with an image
|
||||||
// buttons. userData points back to the AppEntryT so the click
|
// button (or text button if no icon) and a label underneath.
|
||||||
// callback knows which app to launch.
|
|
||||||
WidgetT *hbox = NULL;
|
WidgetT *hbox = NULL;
|
||||||
|
|
||||||
for (int32_t i = 0; i < sAppCount; i++) {
|
for (int32_t i = 0; i < sAppCount; i++) {
|
||||||
if (i % PM_GRID_COLS == 0) {
|
if (i % PM_GRID_COLS == 0) {
|
||||||
hbox = wgtHBox(appFrame);
|
hbox = wgtHBox(appFrame);
|
||||||
hbox->spacing = wgtPixels(PM_GRID_SPACING);
|
hbox->spacing = wgtPixels(PM_GRID_SPACING);
|
||||||
|
hbox->align = AlignCenterE;
|
||||||
}
|
}
|
||||||
|
|
||||||
WidgetT *btn = wgtButton(hbox, sAppFiles[i].name);
|
WidgetT *cell = wgtVBox(hbox);
|
||||||
btn->prefW = wgtPixels(PM_BTN_W);
|
cell->align = AlignCenterE;
|
||||||
btn->prefH = wgtPixels(PM_BTN_H);
|
cell->minW = wgtPixels(PM_CELL_W);
|
||||||
btn->userData = &sAppFiles[i];
|
cell->minH = wgtPixels(PM_CELL_H);
|
||||||
|
|
||||||
|
WidgetT *btn = NULL;
|
||||||
|
|
||||||
|
if (sAppFiles[i].iconData) {
|
||||||
|
int32_t dataSize = sAppFiles[i].iconPitch * sAppFiles[i].iconH;
|
||||||
|
uint8_t *iconCopy = (uint8_t *)malloc(dataSize);
|
||||||
|
|
||||||
|
if (iconCopy) {
|
||||||
|
memcpy(iconCopy, sAppFiles[i].iconData, dataSize);
|
||||||
|
btn = wgtImageButton(cell, iconCopy, sAppFiles[i].iconW, sAppFiles[i].iconH, sAppFiles[i].iconPitch);
|
||||||
|
btn->maxW = wgtPixels(sAppFiles[i].iconW + 4);
|
||||||
|
btn->maxH = wgtPixels(sAppFiles[i].iconH + 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!btn) {
|
||||||
|
btn = wgtButton(cell, sAppFiles[i].name);
|
||||||
|
btn->prefW = wgtPixels(PM_BTN_W);
|
||||||
|
btn->prefH = wgtPixels(PM_BTN_H);
|
||||||
|
}
|
||||||
|
|
||||||
|
btn->userData = &sAppFiles[i];
|
||||||
btn->onDblClick = onAppButtonClick;
|
btn->onDblClick = onAppButtonClick;
|
||||||
btn->onClick = onAppButtonClick;
|
btn->onClick = onAppButtonClick;
|
||||||
|
|
||||||
|
if (sAppFiles[i].tooltip[0]) {
|
||||||
|
wgtSetTooltip(btn, sAppFiles[i].tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
|
WidgetT *label = wgtLabel(cell, sAppFiles[i].name);
|
||||||
|
label->maxW = wgtPixels(PM_CELL_W);
|
||||||
|
wgtLabelSetAlign(label, AlignCenterE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -386,7 +426,11 @@ static void scanAppsDirRecurse(const char *dirPath) {
|
||||||
|
|
||||||
AppEntryT *entry = &sAppFiles[sAppCount];
|
AppEntryT *entry = &sAppFiles[sAppCount];
|
||||||
|
|
||||||
// Name = filename without extension
|
snprintf(entry->path, sizeof(entry->path), "%s", fullPath);
|
||||||
|
entry->iconData = NULL;
|
||||||
|
entry->tooltip[0] = '\0';
|
||||||
|
|
||||||
|
// Default name from filename (without .app extension)
|
||||||
int32_t nameLen = len - 4;
|
int32_t nameLen = len - 4;
|
||||||
|
|
||||||
if (nameLen >= SHELL_APP_NAME_MAX) {
|
if (nameLen >= SHELL_APP_NAME_MAX) {
|
||||||
|
|
@ -396,12 +440,43 @@ static void scanAppsDirRecurse(const char *dirPath) {
|
||||||
memcpy(entry->name, ent->d_name, nameLen);
|
memcpy(entry->name, ent->d_name, nameLen);
|
||||||
entry->name[nameLen] = '\0';
|
entry->name[nameLen] = '\0';
|
||||||
|
|
||||||
// Capitalize first letter for display
|
|
||||||
if (entry->name[0] >= 'a' && entry->name[0] <= 'z') {
|
if (entry->name[0] >= 'a' && entry->name[0] <= 'z') {
|
||||||
entry->name[0] -= 32;
|
entry->name[0] -= 32;
|
||||||
}
|
}
|
||||||
|
|
||||||
snprintf(entry->path, sizeof(entry->path), "%s", fullPath);
|
// Override from embedded resources if available
|
||||||
|
DvxResHandleT *res = dvxResOpen(fullPath);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
uint32_t iconSize = 0;
|
||||||
|
void *iconBuf = dvxResRead(res, "icon32", &iconSize);
|
||||||
|
|
||||||
|
if (iconBuf) {
|
||||||
|
entry->iconData = dvxLoadImageFromMemory(sAc, (const uint8_t *)iconBuf, (int32_t)iconSize, &entry->iconW, &entry->iconH, &entry->iconPitch);
|
||||||
|
free(iconBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t nameSize = 0;
|
||||||
|
void *nameBuf = dvxResRead(res, "name", &nameSize);
|
||||||
|
|
||||||
|
if (nameBuf) {
|
||||||
|
strncpy(entry->name, (const char *)nameBuf, SHELL_APP_NAME_MAX - 1);
|
||||||
|
entry->name[SHELL_APP_NAME_MAX - 1] = '\0';
|
||||||
|
free(nameBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t descSize = 0;
|
||||||
|
void *descBuf = dvxResRead(res, "description", &descSize);
|
||||||
|
|
||||||
|
if (descBuf) {
|
||||||
|
strncpy(entry->tooltip, (const char *)descBuf, sizeof(entry->tooltip) - 1);
|
||||||
|
entry->tooltip[sizeof(entry->tooltip) - 1] = '\0';
|
||||||
|
free(descBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
dvxResClose(res);
|
||||||
|
}
|
||||||
|
|
||||||
sAppCount++;
|
sAppCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ LIBSDIR = ../bin/libs
|
||||||
|
|
||||||
# Core sources
|
# Core sources
|
||||||
SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxImage.c dvxImageWrite.c \
|
SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxImage.c dvxImageWrite.c \
|
||||||
dvxApp.c dvxDialog.c dvxPrefs.c \
|
dvxApp.c dvxDialog.c dvxPrefs.c dvxResource.c \
|
||||||
widgetClass.c widgetCore.c widgetScrollbar.c \
|
widgetClass.c widgetCore.c widgetScrollbar.c \
|
||||||
widgetLayout.c widgetEvent.c widgetOps.c
|
widgetLayout.c widgetEvent.c widgetOps.c
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2529,6 +2529,72 @@ uint8_t *dvxLoadImage(const AppContextT *ctx, const char *path, int32_t *outW, i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// dvxLoadImageFromMemory
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// Same as dvxLoadImage but loads from a memory buffer (e.g. a
|
||||||
|
// resource read via dvxResRead). Supports BMP, PNG, JPEG, GIF.
|
||||||
|
|
||||||
|
uint8_t *dvxLoadImageFromMemory(const AppContextT *ctx, const uint8_t *data, int32_t dataLen, int32_t *outW, int32_t *outH, int32_t *outPitch) {
|
||||||
|
if (!ctx || !data || dataLen <= 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DisplayT *d = &ctx->display;
|
||||||
|
|
||||||
|
int imgW;
|
||||||
|
int imgH;
|
||||||
|
int channels;
|
||||||
|
uint8_t *rgb = stbi_load_from_memory(data, dataLen, &imgW, &imgH, &channels, 3);
|
||||||
|
|
||||||
|
if (!rgb) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t bpp = d->format.bytesPerPixel;
|
||||||
|
int32_t pitch = imgW * bpp;
|
||||||
|
uint8_t *buf = (uint8_t *)malloc(pitch * imgH);
|
||||||
|
|
||||||
|
if (!buf) {
|
||||||
|
stbi_image_free(rgb);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t y = 0; y < imgH; y++) {
|
||||||
|
for (int32_t x = 0; x < imgW; x++) {
|
||||||
|
const uint8_t *src = rgb + (y * imgW + x) * 3;
|
||||||
|
uint32_t color = packColor(d, src[0], src[1], src[2]);
|
||||||
|
uint8_t *dst = buf + y * pitch + x * bpp;
|
||||||
|
|
||||||
|
if (bpp == 1) {
|
||||||
|
*dst = (uint8_t)color;
|
||||||
|
} else if (bpp == 2) {
|
||||||
|
*(uint16_t *)dst = (uint16_t)color;
|
||||||
|
} else {
|
||||||
|
*(uint32_t *)dst = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stbi_image_free(rgb);
|
||||||
|
|
||||||
|
if (outW) {
|
||||||
|
*outW = imgW;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outH) {
|
||||||
|
*outH = imgH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outPitch) {
|
||||||
|
*outPitch = pitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// dvxInvalidateRect
|
// dvxInvalidateRect
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,10 @@ void dvxTileWindowsV(AppContextT *ctx);
|
||||||
// image dimensions and row pitch in bytes.
|
// image dimensions and row pitch in bytes.
|
||||||
uint8_t *dvxLoadImage(const AppContextT *ctx, const char *path, int32_t *outW, int32_t *outH, int32_t *outPitch);
|
uint8_t *dvxLoadImage(const AppContextT *ctx, const char *path, int32_t *outW, int32_t *outH, int32_t *outPitch);
|
||||||
|
|
||||||
|
// Load an image from a memory buffer (e.g. resource data). Same output
|
||||||
|
// format as dvxLoadImage. Caller must free the result with dvxFreeImage.
|
||||||
|
uint8_t *dvxLoadImageFromMemory(const AppContextT *ctx, const uint8_t *data, int32_t dataLen, int32_t *outW, int32_t *outH, int32_t *outPitch);
|
||||||
|
|
||||||
// Free a pixel buffer returned by dvxLoadImage.
|
// Free a pixel buffer returned by dvxLoadImage.
|
||||||
void dvxFreeImage(uint8_t *data);
|
void dvxFreeImage(uint8_t *data);
|
||||||
|
|
||||||
|
|
|
||||||
140
core/dvxResource.c
Normal file
140
core/dvxResource.c
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
// dvxResource.c -- DVX resource runtime API
|
||||||
|
//
|
||||||
|
// Reads the resource block appended to DXE3 files. The resource
|
||||||
|
// block is located by reading the footer at the end of the file.
|
||||||
|
|
||||||
|
#include "dvxResource.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
|
DvxResHandleT *dvxResOpen(const char *path) {
|
||||||
|
if (!path) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
|
||||||
|
if (!f) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read footer from end of file
|
||||||
|
if (fseek(f, -(int32_t)sizeof(DvxResFooterT), SEEK_END) != 0) {
|
||||||
|
fclose(f);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
DvxResFooterT footer;
|
||||||
|
|
||||||
|
if (fread(&footer, sizeof(footer), 1, f) != 1) {
|
||||||
|
fclose(f);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (footer.magic != DVX_RES_MAGIC || footer.entryCount == 0) {
|
||||||
|
fclose(f);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read directory
|
||||||
|
if (fseek(f, footer.dirOffset, SEEK_SET) != 0) {
|
||||||
|
fclose(f);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
DvxResDirEntryT *entries = (DvxResDirEntryT *)malloc(footer.entryCount * sizeof(DvxResDirEntryT));
|
||||||
|
|
||||||
|
if (!entries) {
|
||||||
|
fclose(f);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fread(entries, sizeof(DvxResDirEntryT), footer.entryCount, f) != footer.entryCount) {
|
||||||
|
free(entries);
|
||||||
|
fclose(f);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
DvxResHandleT *h = (DvxResHandleT *)malloc(sizeof(DvxResHandleT));
|
||||||
|
|
||||||
|
if (!h) {
|
||||||
|
free(entries);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy(h->path, path, sizeof(h->path) - 1);
|
||||||
|
h->path[sizeof(h->path) - 1] = '\0';
|
||||||
|
h->entries = entries;
|
||||||
|
h->entryCount = footer.entryCount;
|
||||||
|
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const DvxResDirEntryT *dvxResFind(DvxResHandleT *h, const char *name) {
|
||||||
|
if (!h || !name) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < h->entryCount; i++) {
|
||||||
|
if (strcmp(h->entries[i].name, name) == 0) {
|
||||||
|
return &h->entries[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void *dvxResRead(DvxResHandleT *h, const char *name, uint32_t *outSize) {
|
||||||
|
const DvxResDirEntryT *entry = dvxResFind(h, name);
|
||||||
|
|
||||||
|
if (!entry) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *f = fopen(h->path, "rb");
|
||||||
|
|
||||||
|
if (!f) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fseek(f, entry->offset, SEEK_SET) != 0) {
|
||||||
|
fclose(f);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *buf = malloc(entry->size);
|
||||||
|
|
||||||
|
if (!buf) {
|
||||||
|
fclose(f);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fread(buf, 1, entry->size, f) != entry->size) {
|
||||||
|
free(buf);
|
||||||
|
fclose(f);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
if (outSize) {
|
||||||
|
*outSize = entry->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void dvxResClose(DvxResHandleT *h) {
|
||||||
|
if (h) {
|
||||||
|
free(h->entries);
|
||||||
|
free(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
73
core/dvxResource.h
Normal file
73
core/dvxResource.h
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
// dvxResource.h -- DVX resource format and runtime API
|
||||||
|
//
|
||||||
|
// Resources are appended to DXE3 files (.app, .wgt, .lib) after the
|
||||||
|
// normal DXE content. dlopen never reads past the DXE header-specified
|
||||||
|
// sections, so the appended data is invisible to the loader.
|
||||||
|
//
|
||||||
|
// File layout:
|
||||||
|
// [DXE3 content] -- untouched, loaded by dlopen
|
||||||
|
// [resource data entries] -- sequential, variable length
|
||||||
|
// [resource directory] -- fixed-size entries, one per resource
|
||||||
|
// [footer] -- magic + directory offset + count
|
||||||
|
//
|
||||||
|
// Reading starts from the end: seek to EOF - sizeof(footer), verify
|
||||||
|
// the magic, then seek to the directory and read entries.
|
||||||
|
|
||||||
|
#ifndef DVX_RESOURCE_H
|
||||||
|
#define DVX_RESOURCE_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// Resource type IDs
|
||||||
|
#define DVX_RES_ICON 1 // image data (BMP icon: 16x16, 32x32, etc.)
|
||||||
|
#define DVX_RES_TEXT 2 // null-terminated string (author, copyright, etc.)
|
||||||
|
#define DVX_RES_BINARY 3 // arbitrary binary data (app-specific)
|
||||||
|
|
||||||
|
// Footer magic: "DVXR" as a 32-bit little-endian value
|
||||||
|
#define DVX_RES_MAGIC 0x52585644UL
|
||||||
|
|
||||||
|
// Maximum resource name length (including null terminator)
|
||||||
|
#define DVX_RES_NAME_MAX 32
|
||||||
|
|
||||||
|
// Directory entry (48 bytes each)
|
||||||
|
typedef struct {
|
||||||
|
char name[DVX_RES_NAME_MAX]; // resource name (e.g. "icon16", "author")
|
||||||
|
uint32_t type; // DVX_RES_ICON, DVX_RES_TEXT, DVX_RES_BINARY
|
||||||
|
uint32_t offset; // absolute file offset of data
|
||||||
|
uint32_t size; // data size in bytes
|
||||||
|
uint32_t reserved; // padding for future use
|
||||||
|
} DvxResDirEntryT;
|
||||||
|
|
||||||
|
// Footer (16 bytes, at the very end of the file)
|
||||||
|
typedef struct {
|
||||||
|
uint32_t magic; // DVX_RES_MAGIC
|
||||||
|
uint32_t dirOffset; // absolute file offset of directory
|
||||||
|
uint32_t entryCount; // number of resources
|
||||||
|
uint32_t reserved;
|
||||||
|
} DvxResFooterT;
|
||||||
|
|
||||||
|
// Runtime resource handle (opaque to callers)
|
||||||
|
typedef struct {
|
||||||
|
char path[260];
|
||||||
|
DvxResDirEntryT *entries;
|
||||||
|
uint32_t entryCount;
|
||||||
|
} DvxResHandleT;
|
||||||
|
|
||||||
|
// Open a resource handle by reading the footer and directory.
|
||||||
|
// Returns NULL if the file has no resource block.
|
||||||
|
DvxResHandleT *dvxResOpen(const char *path);
|
||||||
|
|
||||||
|
// Find a resource by name and read its data into a malloc'd buffer.
|
||||||
|
// Sets *outSize to the data size. Returns NULL if not found.
|
||||||
|
// Caller must free the returned buffer.
|
||||||
|
void *dvxResRead(DvxResHandleT *h, const char *name, uint32_t *outSize);
|
||||||
|
|
||||||
|
// Find a resource by name and return its directory entry.
|
||||||
|
// Returns NULL if not found. The returned pointer is valid until
|
||||||
|
// dvxResClose is called.
|
||||||
|
const DvxResDirEntryT *dvxResFind(DvxResHandleT *h, const char *name);
|
||||||
|
|
||||||
|
// Close the handle and free all associated memory.
|
||||||
|
void dvxResClose(DvxResHandleT *h);
|
||||||
|
|
||||||
|
#endif // DVX_RESOURCE_H
|
||||||
|
|
@ -290,15 +290,35 @@ void widgetLayoutBox(WidgetT *w, const BitmapFontT *font) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign geometry
|
// Assign geometry
|
||||||
|
int32_t crossSize = availCross;
|
||||||
|
|
||||||
|
// Apply max size on cross axis
|
||||||
|
if (horiz && c->maxH) {
|
||||||
|
int32_t maxPx = wgtResolveSize(c->maxH, innerH, font->charWidth);
|
||||||
|
|
||||||
|
if (crossSize > maxPx) {
|
||||||
|
crossSize = maxPx;
|
||||||
|
}
|
||||||
|
} else if (!horiz && c->maxW) {
|
||||||
|
int32_t maxPx = wgtResolveSize(c->maxW, innerW, font->charWidth);
|
||||||
|
|
||||||
|
if (crossSize > maxPx) {
|
||||||
|
crossSize = maxPx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center on cross axis when child is constrained smaller
|
||||||
|
int32_t crossOff = (crossSize < availCross) ? (availCross - crossSize) / 2 : 0;
|
||||||
|
|
||||||
if (horiz) {
|
if (horiz) {
|
||||||
c->x = pos;
|
c->x = pos;
|
||||||
c->y = innerY;
|
c->y = innerY + crossOff;
|
||||||
c->w = mainSize;
|
c->w = mainSize;
|
||||||
c->h = availCross;
|
c->h = crossSize;
|
||||||
} else {
|
} else {
|
||||||
c->x = innerX;
|
c->x = innerX + crossOff;
|
||||||
c->y = pos;
|
c->y = pos;
|
||||||
c->w = availCross;
|
c->w = crossSize;
|
||||||
c->h = mainSize;
|
c->h = mainSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -276,6 +276,10 @@ int shellMain(int argc, char *argv[]) {
|
||||||
sCtx.onTitleChange = titleChangeHandler;
|
sCtx.onTitleChange = titleChangeHandler;
|
||||||
sCtx.titleChangeCtx = &sCtx;
|
sCtx.titleChangeCtx = &sCtx;
|
||||||
|
|
||||||
|
// Install crash handler before loading apps so faults during app
|
||||||
|
// initialization are caught and recovered from gracefully.
|
||||||
|
platformInstallCrashHandler(&sCrashJmp, &sCrashSignal, dvxLog);
|
||||||
|
|
||||||
// Load the desktop app (configurable via [shell] desktop= in dvx.ini)
|
// Load the desktop app (configurable via [shell] desktop= in dvx.ini)
|
||||||
const char *desktopApp = prefsGetString("shell", "desktop", SHELL_DESKTOP_APP);
|
const char *desktopApp = prefsGetString("shell", "desktop", SHELL_DESKTOP_APP);
|
||||||
int32_t desktopId = shellLoadApp(&sCtx, desktopApp);
|
int32_t desktopId = shellLoadApp(&sCtx, desktopApp);
|
||||||
|
|
@ -287,12 +291,6 @@ int shellMain(int argc, char *argv[]) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install crash handler after everything is initialized -- if
|
|
||||||
// initialization itself crashes, we want the default behavior
|
|
||||||
// (abort with register dump) rather than our recovery path, because
|
|
||||||
// the system isn't in a recoverable state yet.
|
|
||||||
platformInstallCrashHandler(&sCrashJmp, &sCrashSignal, dvxLog);
|
|
||||||
|
|
||||||
dvxLog("DVX Shell ready.");
|
dvxLog("DVX Shell ready.");
|
||||||
|
|
||||||
// Set recovery point for crash handler. setjmp returns 0 on initial
|
// Set recovery point for crash handler. setjmp returns 0 on initial
|
||||||
|
|
|
||||||
22
tools/Makefile
Normal file
22
tools/Makefile
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# DVX Tools Makefile
|
||||||
|
#
|
||||||
|
# Builds native (host) utilities. These run on the development machine
|
||||||
|
# (Linux or DOS), not on the target. No cross-compilation needed.
|
||||||
|
|
||||||
|
CC = gcc
|
||||||
|
CFLAGS = -O2 -Wall -Wextra -I../core
|
||||||
|
|
||||||
|
BINDIR = ../bin
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
|
|
||||||
|
all: $(BINDIR)/dvxres
|
||||||
|
|
||||||
|
$(BINDIR)/dvxres: dvxres.c ../core/dvxResource.c ../core/dvxResource.h | $(BINDIR)
|
||||||
|
$(CC) $(CFLAGS) -o $@ dvxres.c ../core/dvxResource.c
|
||||||
|
|
||||||
|
$(BINDIR):
|
||||||
|
mkdir -p $(BINDIR)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(BINDIR)/dvxres
|
||||||
814
tools/dvxres.c
Normal file
814
tools/dvxres.c
Normal file
|
|
@ -0,0 +1,814 @@
|
||||||
|
// 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 "../core/dvxResource.h"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.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 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);
|
||||||
|
fread(data[i], 1, entries[i].size, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
strncpy(entries[count].name, name, DVX_RES_NAME_MAX - 1);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
fread(buf, 1, dxeSize, f);
|
||||||
|
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;
|
||||||
|
}
|
||||||
271
tools/mkicon.c
Normal file
271
tools/mkicon.c
Normal file
|
|
@ -0,0 +1,271 @@
|
||||||
|
// mkicon.c -- Generate simple 32x32 BMP icons for DVX apps
|
||||||
|
//
|
||||||
|
// Usage: mkicon <output.bmp> <type>
|
||||||
|
// Types: clock, notepad, cpanel, dvxdemo, imgview
|
||||||
|
//
|
||||||
|
// Generates recognizable pixel-art icons at 32x32 24-bit BMP.
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define W 32
|
||||||
|
#define H 32
|
||||||
|
|
||||||
|
static uint8_t pixels[H][W][3]; // BGR
|
||||||
|
|
||||||
|
static void clear(uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
for (int y = 0; y < H; y++) {
|
||||||
|
for (int x = 0; x < W; x++) {
|
||||||
|
pixels[y][x][0] = b;
|
||||||
|
pixels[y][x][1] = g;
|
||||||
|
pixels[y][x][2] = r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
if (x >= 0 && x < W && y >= 0 && y < H) {
|
||||||
|
pixels[y][x][0] = b;
|
||||||
|
pixels[y][x][1] = g;
|
||||||
|
pixels[y][x][2] = r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rect(int x0, int y0, int w, int h, uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
for (int y = y0; y < y0 + h && y < H; y++) {
|
||||||
|
for (int x = x0; x < x0 + w && x < W; x++) {
|
||||||
|
pixel(x, y, r, g, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void circle(int cx, int cy, int radius, uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
for (int y = -radius; y <= radius; y++) {
|
||||||
|
for (int x = -radius; x <= radius; x++) {
|
||||||
|
if (x * x + y * y <= radius * radius) {
|
||||||
|
pixel(cx + x, cy + y, r, g, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hline(int x0, int y, int w, uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
for (int x = x0; x < x0 + w; x++) {
|
||||||
|
pixel(x, y, r, g, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void vline(int x, int y0, int h, uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
for (int y = y0; y < y0 + h; y++) {
|
||||||
|
pixel(x, y, r, g, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clock icon: circle with hands
|
||||||
|
static void iconClock(void) {
|
||||||
|
clear(192, 192, 192);
|
||||||
|
circle(15, 15, 13, 255, 255, 255);
|
||||||
|
circle(15, 15, 12, 240, 240, 255);
|
||||||
|
|
||||||
|
// Rim
|
||||||
|
for (int a = 0; a < 360; a++) {
|
||||||
|
double rad = a * 3.14159265 / 180.0;
|
||||||
|
int x = 15 + (int)(13.0 * __builtin_cos(rad));
|
||||||
|
int y = 15 + (int)(13.0 * __builtin_sin(rad));
|
||||||
|
pixel(x, y, 0, 0, 128);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hour marks
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
double rad = i * 30.0 * 3.14159265 / 180.0;
|
||||||
|
int x = 15 + (int)(11.0 * __builtin_cos(rad));
|
||||||
|
int y = 15 + (int)(11.0 * __builtin_sin(rad));
|
||||||
|
pixel(x, y, 0, 0, 128);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hour hand (pointing ~10 o'clock)
|
||||||
|
vline(15, 7, 8, 0, 0, 128);
|
||||||
|
|
||||||
|
// Minute hand (pointing ~2 o'clock)
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
pixel(15 + i * 2 / 3, 15 - i * 2 / 3, 0, 0, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center dot
|
||||||
|
pixel(15, 15, 200, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notepad icon: page with lines
|
||||||
|
static void iconNotepad(void) {
|
||||||
|
clear(192, 192, 192);
|
||||||
|
rect(6, 3, 20, 26, 255, 255, 240);
|
||||||
|
|
||||||
|
// Border
|
||||||
|
hline(6, 3, 20, 0, 0, 0);
|
||||||
|
hline(6, 28, 20, 0, 0, 0);
|
||||||
|
vline(6, 3, 26, 0, 0, 0);
|
||||||
|
vline(25, 3, 26, 0, 0, 0);
|
||||||
|
|
||||||
|
// Folded corner
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
hline(21 + i, 3 + i, 5 - i, 200, 200, 180);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text lines
|
||||||
|
for (int i = 0; i < 7; i++) {
|
||||||
|
int lineW = (i == 3) ? 12 : (i == 6) ? 8 : 16;
|
||||||
|
hline(9, 8 + i * 3, lineW, 0, 0, 128);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Control Panel icon: gear/wrench
|
||||||
|
static void iconCpanel(void) {
|
||||||
|
clear(192, 192, 192);
|
||||||
|
|
||||||
|
// Gear body
|
||||||
|
circle(15, 15, 8, 128, 128, 128);
|
||||||
|
circle(15, 15, 5, 192, 192, 192);
|
||||||
|
|
||||||
|
// Gear teeth (N/S/E/W and diagonals)
|
||||||
|
rect(13, 2, 5, 4, 128, 128, 128);
|
||||||
|
rect(13, 26, 5, 4, 128, 128, 128);
|
||||||
|
rect(2, 13, 4, 5, 128, 128, 128);
|
||||||
|
rect(26, 13, 4, 5, 128, 128, 128);
|
||||||
|
rect(4, 4, 4, 4, 128, 128, 128);
|
||||||
|
rect(24, 4, 4, 4, 128, 128, 128);
|
||||||
|
rect(4, 24, 4, 4, 128, 128, 128);
|
||||||
|
rect(24, 24, 4, 4, 128, 128, 128);
|
||||||
|
|
||||||
|
// Inner circle (hole)
|
||||||
|
circle(15, 15, 3, 64, 64, 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DVX Demo icon: colorful diamond
|
||||||
|
static void iconDvxdemo(void) {
|
||||||
|
clear(192, 192, 192);
|
||||||
|
|
||||||
|
// Diamond shape with color gradient
|
||||||
|
for (int y = 0; y < 16; y++) {
|
||||||
|
int hw = y + 1;
|
||||||
|
|
||||||
|
for (int x = -hw; x <= hw; x++) {
|
||||||
|
int px = 15 + x;
|
||||||
|
int py = 2 + y;
|
||||||
|
uint8_t r = (uint8_t)(128 + y * 8);
|
||||||
|
uint8_t g = (uint8_t)(64 + (hw - (x < 0 ? -x : x)) * 8);
|
||||||
|
uint8_t b = (uint8_t)(200 - y * 4);
|
||||||
|
pixel(px, py, r, g, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int y = 0; y < 14; y++) {
|
||||||
|
int hw = 14 - y;
|
||||||
|
|
||||||
|
for (int x = -hw; x <= hw; x++) {
|
||||||
|
int px = 15 + x;
|
||||||
|
int py = 18 + y;
|
||||||
|
uint8_t r = (uint8_t)(255 - y * 8);
|
||||||
|
uint8_t g = (uint8_t)(128 - y * 4);
|
||||||
|
uint8_t b = (uint8_t)(100 + y * 8);
|
||||||
|
pixel(px, py, r, g, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image Viewer icon: landscape in a frame
|
||||||
|
static void iconImgview(void) {
|
||||||
|
clear(192, 192, 192);
|
||||||
|
|
||||||
|
// Frame
|
||||||
|
rect(3, 5, 26, 22, 100, 80, 60);
|
||||||
|
rect(5, 7, 22, 18, 135, 200, 235);
|
||||||
|
|
||||||
|
// Sun
|
||||||
|
circle(21, 11, 3, 255, 220, 50);
|
||||||
|
|
||||||
|
// Mountain
|
||||||
|
for (int y = 0; y < 10; y++) {
|
||||||
|
int w = y * 2 + 1;
|
||||||
|
hline(15 - y, 15 + y, w, 80, 140, 80);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ground
|
||||||
|
rect(5, 22, 22, 3, 60, 120, 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void writeBmp(const char *path) {
|
||||||
|
int32_t rowPad = (4 - (W * 3) % 4) % 4;
|
||||||
|
int32_t rowSize = W * 3 + rowPad;
|
||||||
|
int32_t dataSize = rowSize * H;
|
||||||
|
int32_t fileSize = 54 + dataSize;
|
||||||
|
|
||||||
|
uint8_t header[54];
|
||||||
|
memset(header, 0, sizeof(header));
|
||||||
|
|
||||||
|
// BMP header
|
||||||
|
header[0] = 'B';
|
||||||
|
header[1] = 'M';
|
||||||
|
*(int32_t *)&header[2] = fileSize;
|
||||||
|
*(int32_t *)&header[10] = 54;
|
||||||
|
|
||||||
|
// DIB header
|
||||||
|
*(int32_t *)&header[14] = 40;
|
||||||
|
*(int32_t *)&header[18] = W;
|
||||||
|
*(int32_t *)&header[22] = H;
|
||||||
|
*(int16_t *)&header[26] = 1;
|
||||||
|
*(int16_t *)&header[28] = 24;
|
||||||
|
*(int32_t *)&header[34] = dataSize;
|
||||||
|
|
||||||
|
FILE *f = fopen(path, "wb");
|
||||||
|
|
||||||
|
if (!f) {
|
||||||
|
fprintf(stderr, "Cannot write: %s\n", path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite(header, 1, 54, f);
|
||||||
|
|
||||||
|
// BMP rows are bottom-to-top
|
||||||
|
uint8_t pad[3] = {0};
|
||||||
|
|
||||||
|
for (int y = H - 1; y >= 0; y--) {
|
||||||
|
fwrite(pixels[y], 1, W * 3, f);
|
||||||
|
|
||||||
|
if (rowPad > 0) {
|
||||||
|
fwrite(pad, 1, rowPad, f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
if (argc < 3) {
|
||||||
|
fprintf(stderr, "Usage: mkicon <output.bmp> <type>\n");
|
||||||
|
fprintf(stderr, "Types: clock, notepad, cpanel, dvxdemo, imgview\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *path = argv[1];
|
||||||
|
const char *type = argv[2];
|
||||||
|
|
||||||
|
if (strcmp(type, "clock") == 0) {
|
||||||
|
iconClock();
|
||||||
|
} else if (strcmp(type, "notepad") == 0) {
|
||||||
|
iconNotepad();
|
||||||
|
} else if (strcmp(type, "cpanel") == 0) {
|
||||||
|
iconCpanel();
|
||||||
|
} else if (strcmp(type, "dvxdemo") == 0) {
|
||||||
|
iconDvxdemo();
|
||||||
|
} else if (strcmp(type, "imgview") == 0) {
|
||||||
|
iconImgview();
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Unknown icon type: %s\n", type);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeBmp(path);
|
||||||
|
printf("Generated %s (%s)\n", path, type);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue