WinComm/forms/dfm2form.c
Scott Duensing dd115d3727 Add BitBtn, SpeedButton, TabSet, Notebook, TabbedNotebook, MaskEdit, Outline, Bevel, Header, and ScrollBox control types
Completes the Delphi 1.0 Standard, Additional, and Win31 component
palettes. DFM parser maps Tabs/Lines/Pages/Sections.Strings to Items
and TabIndex/PageIndex to ItemIndex. Kind extended with bk* idents
for BitBtn.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 15:13:59 -06:00

1459 lines
52 KiB
C

// dfm2form.c - Convert Delphi 1.0 binary DFM (TPF0) to .form protocol text
//
// Usage: dfm2form <input.dfm> [output.form]
//
// Reads a binary DFM file, extracts the form and control definitions,
// and outputs protocol commands (FORM.CREATE, CTRL.CREATE, EVENT.BIND,
// FORM.SHOW) suitable for the remote forms system.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <ctype.h>
#include <errno.h>
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
typedef enum {
ctUnknown = 0,
ctLabel,
ctEdit,
ctButton,
ctCheckBox,
ctListBox,
ctComboBox,
ctMemo,
ctImage,
ctGroupBox,
ctRadioButton,
ctPanel,
ctScrollBar,
ctMediaPlayer,
ctMainMenu,
ctPopupMenu,
ctMenuItem,
ctRadioGroup,
ctBitBtn,
ctSpeedButton,
ctTabSet,
ctNotebook,
ctTabbedNotebook,
ctMaskEdit,
ctOutline,
ctBevel,
ctHeader,
ctScrollBox
} CtrlTypeE;
typedef struct {
char name[64];
CtrlTypeE type;
int32_t left;
int32_t top;
int32_t width;
int32_t height;
char caption[256];
char text[4096];
char items[4096];
int32_t checked;
int32_t enabled;
int32_t visible;
int32_t maxLength;
int32_t readOnly;
int32_t scrollBars;
int32_t tabOrder;
int32_t itemIndex;
bool hasCaption;
bool hasText;
bool hasItems;
bool hasChecked;
bool hasEnabled;
bool hasVisible;
bool hasMaxLength;
bool hasReadOnly;
bool hasScrollBars;
bool hasTabOrder;
bool hasItemIndex;
bool hasOnClick;
bool hasOnChange;
bool hasOnDblClick;
bool hasOnEnter;
bool hasOnExit;
bool hasOnKeyDown;
bool hasOnKeyUp;
bool hasOnMouseDown;
bool hasOnMouseUp;
int32_t stretch;
int32_t center;
int32_t transparent;
int32_t bevelOuter;
int32_t bevelInner;
int32_t borderStyle;
int32_t kind;
int32_t min;
int32_t max;
int32_t position;
int32_t largeChange;
int32_t smallChange;
int32_t autoOpen;
char fileName[256];
char deviceType[64];
bool hasStretch;
bool hasCenter;
bool hasTransparent;
bool hasBevelOuter;
bool hasBevelInner;
bool hasBorderStyle;
bool hasKind;
bool hasMin;
bool hasMax;
bool hasPosition;
bool hasLargeChange;
bool hasSmallChange;
bool hasAutoOpen;
bool hasFileName;
bool hasDeviceType;
int32_t parentCtrlIdx;
int32_t columns;
int32_t shortCut;
int32_t layout;
int32_t numGlyphs;
int32_t groupIndex;
int32_t down;
int32_t allowAllUp;
int32_t outlineStyle;
int32_t shape;
int32_t style;
char editMask[256];
bool hasColumns;
bool hasShortCut;
bool hasLayout;
bool hasNumGlyphs;
bool hasGroupIndex;
bool hasDown;
bool hasAllowAllUp;
bool hasOutlineStyle;
bool hasShape;
bool hasStyle;
bool hasEditMask;
} DfmCtrlT;
typedef struct {
char name[64];
int32_t width;
int32_t height;
char caption[256];
DfmCtrlT ctrls[256];
int32_t ctrlCount;
} DfmFormT;
// DFM value type tags
enum {
vaNull = 0x00,
vaList = 0x01,
vaInt8 = 0x02,
vaInt16 = 0x03,
vaInt32 = 0x04,
vaExtended = 0x05,
vaString = 0x06,
vaIdent = 0x07,
vaFalse = 0x08,
vaTrue = 0x09,
vaBinary = 0x0A,
vaSet = 0x0B,
vaLString = 0x0C,
vaNil = 0x0D,
vaCollection = 0x0E
};
// ---------------------------------------------------------------------------
// Prototypes
// ---------------------------------------------------------------------------
static void emitCtrl(FILE *out, int32_t formId, int32_t ctrlId, DfmCtrlT *ctrl);
static void emitForm(FILE *out, int32_t formId, DfmFormT *form);
static void escapeStr(const char *src, char *dst, int32_t dstSize);
static void initCtrl(DfmCtrlT *ctrl);
static void initForm(DfmFormT *form);
static CtrlTypeE mapClassName(const char *className);
static bool parseComponent(const uint8_t *data, int32_t size, int32_t *pos, DfmFormT *form, bool isRoot, int32_t parentIdx);
static void parseProperties(const uint8_t *data, int32_t size, int32_t *pos, DfmFormT *form, DfmCtrlT *ctrl, bool isForm);
static int32_t readByte(const uint8_t *data, int32_t size, int32_t *pos);
static int32_t readInt16LE(const uint8_t *data, int32_t size, int32_t *pos);
static int32_t readInt32LE(const uint8_t *data, int32_t size, int32_t *pos);
static int32_t readIntValue(const uint8_t *data, int32_t size, int32_t *pos, uint8_t tag);
static bool readStr(const uint8_t *data, int32_t size, int32_t *pos, char *buf, int32_t bufSize);
static void skipValue(const uint8_t *data, int32_t size, int32_t *pos, uint8_t tag);
static int stricmp_local(const char *a, const char *b);
static void usage(const char *progName);
// ---------------------------------------------------------------------------
// Low-level DFM readers
// ---------------------------------------------------------------------------
static int32_t readByte(const uint8_t *data, int32_t size, int32_t *pos) {
if (*pos >= size) {
fprintf(stderr, "Error: unexpected end of file at offset %d\n", *pos);
exit(1);
}
return data[(*pos)++];
}
static int32_t readInt16LE(const uint8_t *data, int32_t size, int32_t *pos) {
if (*pos + 2 > size) {
fprintf(stderr, "Error: unexpected end of file at offset %d\n", *pos);
exit(1);
}
int16_t val = (int16_t)(data[*pos] | (data[*pos + 1] << 8));
*pos += 2;
return val;
}
static int32_t readInt32LE(const uint8_t *data, int32_t size, int32_t *pos) {
if (*pos + 4 > size) {
fprintf(stderr, "Error: unexpected end of file at offset %d\n", *pos);
exit(1);
}
int32_t val = (int32_t)(data[*pos] | (data[*pos + 1] << 8) |
(data[*pos + 2] << 16) | (data[*pos + 3] << 24));
*pos += 4;
return val;
}
static bool readStr(const uint8_t *data, int32_t size, int32_t *pos, char *buf, int32_t bufSize) {
int32_t len = readByte(data, size, pos);
if (*pos + len > size) {
fprintf(stderr, "Error: string overflows file at offset %d\n", *pos);
exit(1);
}
int32_t copyLen = (len < bufSize - 1) ? len : bufSize - 1;
memcpy(buf, data + *pos, copyLen);
buf[copyLen] = '\0';
*pos += len;
return true;
}
static int32_t readIntValue(const uint8_t *data, int32_t size, int32_t *pos, uint8_t tag) {
switch (tag) {
case vaInt8:
return (int8_t)readByte(data, size, pos);
case vaInt16:
return readInt16LE(data, size, pos);
case vaInt32:
return readInt32LE(data, size, pos);
default:
return 0;
}
}
// ---------------------------------------------------------------------------
// Skip unknown property values
// ---------------------------------------------------------------------------
static void skipValue(const uint8_t *data, int32_t size, int32_t *pos, uint8_t tag) {
int32_t len;
char buf[256];
switch (tag) {
case vaNull:
break;
case vaList:
// skip items until vaNull
while (*pos < size) {
uint8_t itemTag = readByte(data, size, pos);
if (itemTag == vaNull) {
break;
}
skipValue(data, size, pos, itemTag);
}
break;
case vaInt8:
*pos += 1;
break;
case vaInt16:
*pos += 2;
break;
case vaInt32:
*pos += 4;
break;
case vaExtended:
*pos += 10;
break;
case vaString:
len = readByte(data, size, pos);
*pos += len;
break;
case vaIdent:
len = readByte(data, size, pos);
*pos += len;
break;
case vaFalse:
case vaTrue:
case vaNil:
break;
case vaBinary:
len = readInt32LE(data, size, pos);
*pos += len;
break;
case vaSet:
// repeated strings, empty string terminates
while (*pos < size) {
readStr(data, size, pos, buf, sizeof(buf));
if (buf[0] == '\0') {
break;
}
}
break;
case vaLString:
len = readInt32LE(data, size, pos);
*pos += len;
break;
case vaCollection:
// items bracketed by vaList/vaNull
while (*pos < size) {
uint8_t itemTag = readByte(data, size, pos);
if (itemTag == vaNull) {
break;
}
skipValue(data, size, pos, itemTag);
}
break;
default:
fprintf(stderr, "Warning: unknown value tag 0x%02X at offset %d\n", tag, *pos);
break;
}
}
// ---------------------------------------------------------------------------
// Case-insensitive string compare
// ---------------------------------------------------------------------------
static int stricmp_local(const char *a, const char *b) {
while (*a && *b) {
int ca = tolower((unsigned char)*a);
int cb = tolower((unsigned char)*b);
if (ca != cb) {
return ca - cb;
}
a++;
b++;
}
return (unsigned char)*a - (unsigned char)*b;
}
// ---------------------------------------------------------------------------
// Map Delphi class name to control type
// ---------------------------------------------------------------------------
static CtrlTypeE mapClassName(const char *className) {
if (stricmp_local(className, "TLabel") == 0) {
return ctLabel;
}
if (stricmp_local(className, "TEdit") == 0) {
return ctEdit;
}
if (stricmp_local(className, "TButton") == 0) {
return ctButton;
}
if (stricmp_local(className, "TCheckBox") == 0) {
return ctCheckBox;
}
if (stricmp_local(className, "TListBox") == 0) {
return ctListBox;
}
if (stricmp_local(className, "TComboBox") == 0) {
return ctComboBox;
}
if (stricmp_local(className, "TMemo") == 0) {
return ctMemo;
}
if (stricmp_local(className, "TImage") == 0) {
return ctImage;
}
if (stricmp_local(className, "TGroupBox") == 0) {
return ctGroupBox;
}
if (stricmp_local(className, "TRadioButton") == 0) {
return ctRadioButton;
}
if (stricmp_local(className, "TPanel") == 0) {
return ctPanel;
}
if (stricmp_local(className, "TScrollBar") == 0) {
return ctScrollBar;
}
if (stricmp_local(className, "TMediaPlayer") == 0) {
return ctMediaPlayer;
}
if (stricmp_local(className, "TMainMenu") == 0) {
return ctMainMenu;
}
if (stricmp_local(className, "TPopupMenu") == 0) {
return ctPopupMenu;
}
if (stricmp_local(className, "TMenuItem") == 0) {
return ctMenuItem;
}
if (stricmp_local(className, "TRadioGroup") == 0) {
return ctRadioGroup;
}
if (stricmp_local(className, "TBitBtn") == 0) {
return ctBitBtn;
}
if (stricmp_local(className, "TSpeedButton") == 0) {
return ctSpeedButton;
}
if (stricmp_local(className, "TTabSet") == 0) {
return ctTabSet;
}
if (stricmp_local(className, "TNotebook") == 0) {
return ctNotebook;
}
if (stricmp_local(className, "TTabbedNotebook") == 0) {
return ctTabbedNotebook;
}
if (stricmp_local(className, "TMaskEdit") == 0) {
return ctMaskEdit;
}
if (stricmp_local(className, "TOutline") == 0) {
return ctOutline;
}
if (stricmp_local(className, "TBevel") == 0) {
return ctBevel;
}
if (stricmp_local(className, "THeader") == 0) {
return ctHeader;
}
if (stricmp_local(className, "TScrollBox") == 0) {
return ctScrollBox;
}
return ctUnknown;
}
// ---------------------------------------------------------------------------
// Init helpers
// ---------------------------------------------------------------------------
static void initCtrl(DfmCtrlT *ctrl) {
memset(ctrl, 0, sizeof(DfmCtrlT));
ctrl->enabled = 1;
ctrl->visible = 1;
ctrl->itemIndex = -1;
ctrl->tabOrder = -1;
ctrl->bevelOuter = 1; // bvRaised
ctrl->largeChange = 1;
ctrl->smallChange = 1;
ctrl->parentCtrlIdx = -1;
}
static void initForm(DfmFormT *form) {
memset(form, 0, sizeof(DfmFormT));
}
// ---------------------------------------------------------------------------
// Parse properties from DFM binary
// ---------------------------------------------------------------------------
static void parseProperties(const uint8_t *data, int32_t size, int32_t *pos, DfmFormT *form, DfmCtrlT *ctrl, bool isForm) {
char propName[64];
char strBuf[4096];
while (*pos < size) {
// Property name (empty = end of properties)
int32_t nameLen = readByte(data, size, pos);
if (nameLen == 0) {
break;
}
// Read the property name
(*pos)--; // back up, readStr reads the length byte
readStr(data, size, pos, propName, sizeof(propName));
// Read value tag
uint8_t tag = readByte(data, size, pos);
// Match known properties
if (isForm && stricmp_local(propName, "Caption") == 0) {
if (tag == vaString) {
readStr(data, size, pos, form->caption, sizeof(form->caption));
} else if (tag == vaLString) {
int32_t len = readInt32LE(data, size, pos);
int32_t copyLen = (len < (int32_t)sizeof(form->caption) - 1) ? len : (int32_t)sizeof(form->caption) - 1;
memcpy(form->caption, data + *pos, copyLen);
form->caption[copyLen] = '\0';
*pos += len;
} else {
skipValue(data, size, pos, tag);
}
} else if (isForm && stricmp_local(propName, "ClientWidth") == 0) {
form->width = readIntValue(data, size, pos, tag);
} else if (isForm && stricmp_local(propName, "ClientHeight") == 0) {
form->height = readIntValue(data, size, pos, tag);
} else if (isForm && stricmp_local(propName, "Width") == 0 && form->width == 0) {
form->width = readIntValue(data, size, pos, tag);
} else if (isForm && stricmp_local(propName, "Height") == 0 && form->height == 0) {
form->height = readIntValue(data, size, pos, tag);
} else if (!isForm && stricmp_local(propName, "Left") == 0) {
ctrl->left = readIntValue(data, size, pos, tag);
} else if (!isForm && stricmp_local(propName, "Top") == 0) {
ctrl->top = readIntValue(data, size, pos, tag);
} else if (!isForm && stricmp_local(propName, "Width") == 0) {
ctrl->width = readIntValue(data, size, pos, tag);
} else if (!isForm && stricmp_local(propName, "Height") == 0) {
ctrl->height = readIntValue(data, size, pos, tag);
} else if (!isForm && stricmp_local(propName, "Caption") == 0) {
if (tag == vaString) {
readStr(data, size, pos, ctrl->caption, sizeof(ctrl->caption));
} else if (tag == vaLString) {
int32_t len = readInt32LE(data, size, pos);
int32_t copyLen = (len < (int32_t)sizeof(ctrl->caption) - 1) ? len : (int32_t)sizeof(ctrl->caption) - 1;
memcpy(ctrl->caption, data + *pos, copyLen);
ctrl->caption[copyLen] = '\0';
*pos += len;
} else {
skipValue(data, size, pos, tag);
}
ctrl->hasCaption = true;
} else if (!isForm && stricmp_local(propName, "Text") == 0) {
if (tag == vaString) {
readStr(data, size, pos, ctrl->text, sizeof(ctrl->text));
} else if (tag == vaLString) {
int32_t len = readInt32LE(data, size, pos);
int32_t copyLen = (len < (int32_t)sizeof(ctrl->text) - 1) ? len : (int32_t)sizeof(ctrl->text) - 1;
memcpy(ctrl->text, data + *pos, copyLen);
ctrl->text[copyLen] = '\0';
*pos += len;
} else {
skipValue(data, size, pos, tag);
}
ctrl->hasText = true;
} else if (!isForm && (stricmp_local(propName, "Items.Strings") == 0 || stricmp_local(propName, "Tabs.Strings") == 0 || stricmp_local(propName, "Lines.Strings") == 0 || stricmp_local(propName, "Pages.Strings") == 0 || stricmp_local(propName, "Sections.Strings") == 0) && tag == vaList) {
// List of strings for ListBox/ComboBox
ctrl->items[0] = '\0';
int32_t itemsLen = 0;
while (*pos < size) {
uint8_t itemTag = readByte(data, size, pos);
if (itemTag == vaNull) {
break;
}
if (itemTag == vaString) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else if (itemTag == vaLString) {
int32_t len = readInt32LE(data, size, pos);
int32_t copyLen = (len < (int32_t)sizeof(strBuf) - 1) ? len : (int32_t)sizeof(strBuf) - 1;
memcpy(strBuf, data + *pos, copyLen);
strBuf[copyLen] = '\0';
*pos += len;
} else {
skipValue(data, size, pos, itemTag);
continue;
}
int32_t slen = (int32_t)strlen(strBuf);
if (itemsLen + slen + 2 < (int32_t)sizeof(ctrl->items)) {
if (itemsLen > 0) {
ctrl->items[itemsLen++] = '\n';
}
memcpy(ctrl->items + itemsLen, strBuf, slen);
itemsLen += slen;
ctrl->items[itemsLen] = '\0';
}
}
ctrl->hasItems = true;
} else if (!isForm && stricmp_local(propName, "Checked") == 0) {
if (tag == vaTrue) {
ctrl->checked = 1;
} else if (tag == vaFalse) {
ctrl->checked = 0;
} else {
ctrl->checked = readIntValue(data, size, pos, tag);
}
ctrl->hasChecked = true;
} else if (!isForm && stricmp_local(propName, "State") == 0) {
// TCheckBox.State: cbUnchecked=0, cbChecked=1, cbGrayed=2
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
if (stricmp_local(strBuf, "cbChecked") == 0) {
ctrl->checked = 1;
} else {
ctrl->checked = 0;
}
} else {
ctrl->checked = readIntValue(data, size, pos, tag);
}
ctrl->hasChecked = true;
} else if (!isForm && stricmp_local(propName, "Enabled") == 0) {
if (tag == vaTrue) {
ctrl->enabled = 1;
} else if (tag == vaFalse) {
ctrl->enabled = 0;
} else {
ctrl->enabled = readIntValue(data, size, pos, tag);
}
ctrl->hasEnabled = true;
} else if (!isForm && stricmp_local(propName, "Visible") == 0) {
if (tag == vaTrue) {
ctrl->visible = 1;
} else if (tag == vaFalse) {
ctrl->visible = 0;
} else {
ctrl->visible = readIntValue(data, size, pos, tag);
}
ctrl->hasVisible = true;
} else if (!isForm && stricmp_local(propName, "MaxLength") == 0) {
ctrl->maxLength = readIntValue(data, size, pos, tag);
ctrl->hasMaxLength = true;
} else if (!isForm && stricmp_local(propName, "ReadOnly") == 0) {
if (tag == vaTrue) {
ctrl->readOnly = 1;
} else if (tag == vaFalse) {
ctrl->readOnly = 0;
} else {
ctrl->readOnly = readIntValue(data, size, pos, tag);
}
ctrl->hasReadOnly = true;
} else if (!isForm && stricmp_local(propName, "ScrollBars") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
if (stricmp_local(strBuf, "ssNone") == 0) {
ctrl->scrollBars = 0;
} else if (stricmp_local(strBuf, "ssHorizontal") == 0) {
ctrl->scrollBars = 1;
} else if (stricmp_local(strBuf, "ssVertical") == 0) {
ctrl->scrollBars = 2;
} else if (stricmp_local(strBuf, "ssBoth") == 0) {
ctrl->scrollBars = 3;
} else {
ctrl->scrollBars = 0;
}
} else {
ctrl->scrollBars = readIntValue(data, size, pos, tag);
}
ctrl->hasScrollBars = true;
} else if (!isForm && stricmp_local(propName, "TabOrder") == 0) {
ctrl->tabOrder = readIntValue(data, size, pos, tag);
ctrl->hasTabOrder = true;
} else if (!isForm && (stricmp_local(propName, "ItemIndex") == 0 || stricmp_local(propName, "TabIndex") == 0 || stricmp_local(propName, "PageIndex") == 0)) {
ctrl->itemIndex = readIntValue(data, size, pos, tag);
ctrl->hasItemIndex = true;
} else if (stricmp_local(propName, "OnClick") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else {
skipValue(data, size, pos, tag);
}
if (!isForm) {
ctrl->hasOnClick = true;
}
} else if (stricmp_local(propName, "OnChange") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else {
skipValue(data, size, pos, tag);
}
if (!isForm) {
ctrl->hasOnChange = true;
}
} else if (stricmp_local(propName, "OnDblClick") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else {
skipValue(data, size, pos, tag);
}
if (!isForm) {
ctrl->hasOnDblClick = true;
}
} else if (stricmp_local(propName, "OnEnter") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else {
skipValue(data, size, pos, tag);
}
if (!isForm) {
ctrl->hasOnEnter = true;
}
} else if (stricmp_local(propName, "OnExit") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else {
skipValue(data, size, pos, tag);
}
if (!isForm) {
ctrl->hasOnExit = true;
}
} else if (stricmp_local(propName, "OnKeyDown") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else {
skipValue(data, size, pos, tag);
}
if (!isForm) {
ctrl->hasOnKeyDown = true;
}
} else if (stricmp_local(propName, "OnKeyUp") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else {
skipValue(data, size, pos, tag);
}
if (!isForm) {
ctrl->hasOnKeyUp = true;
}
} else if (stricmp_local(propName, "OnMouseDown") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else {
skipValue(data, size, pos, tag);
}
if (!isForm) {
ctrl->hasOnMouseDown = true;
}
} else if (stricmp_local(propName, "OnMouseUp") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else {
skipValue(data, size, pos, tag);
}
if (!isForm) {
ctrl->hasOnMouseUp = true;
}
} else if (!isForm && stricmp_local(propName, "Stretch") == 0) {
if (tag == vaTrue) {
ctrl->stretch = 1;
} else if (tag == vaFalse) {
ctrl->stretch = 0;
} else {
ctrl->stretch = readIntValue(data, size, pos, tag);
}
ctrl->hasStretch = true;
} else if (!isForm && stricmp_local(propName, "Center") == 0) {
if (tag == vaTrue) {
ctrl->center = 1;
} else if (tag == vaFalse) {
ctrl->center = 0;
} else {
ctrl->center = readIntValue(data, size, pos, tag);
}
ctrl->hasCenter = true;
} else if (!isForm && stricmp_local(propName, "Transparent") == 0) {
if (tag == vaTrue) {
ctrl->transparent = 1;
} else if (tag == vaFalse) {
ctrl->transparent = 0;
} else {
ctrl->transparent = readIntValue(data, size, pos, tag);
}
ctrl->hasTransparent = true;
} else if (!isForm && stricmp_local(propName, "BevelOuter") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
if (stricmp_local(strBuf, "bvNone") == 0) {
ctrl->bevelOuter = 0;
} else if (stricmp_local(strBuf, "bvLowered") == 0) {
ctrl->bevelOuter = 1;
} else if (stricmp_local(strBuf, "bvRaised") == 0) {
ctrl->bevelOuter = 2;
} else {
ctrl->bevelOuter = 0;
}
} else {
ctrl->bevelOuter = readIntValue(data, size, pos, tag);
}
ctrl->hasBevelOuter = true;
} else if (!isForm && stricmp_local(propName, "BevelInner") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
if (stricmp_local(strBuf, "bvNone") == 0) {
ctrl->bevelInner = 0;
} else if (stricmp_local(strBuf, "bvLowered") == 0) {
ctrl->bevelInner = 1;
} else if (stricmp_local(strBuf, "bvRaised") == 0) {
ctrl->bevelInner = 2;
} else {
ctrl->bevelInner = 0;
}
} else {
ctrl->bevelInner = readIntValue(data, size, pos, tag);
}
ctrl->hasBevelInner = true;
} else if (!isForm && stricmp_local(propName, "BorderStyle") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
if (stricmp_local(strBuf, "bsNone") == 0) {
ctrl->borderStyle = 0;
} else if (stricmp_local(strBuf, "bsSingle") == 0) {
ctrl->borderStyle = 1;
} else {
ctrl->borderStyle = 0;
}
} else {
ctrl->borderStyle = readIntValue(data, size, pos, tag);
}
ctrl->hasBorderStyle = true;
} else if (!isForm && stricmp_local(propName, "Kind") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
if (stricmp_local(strBuf, "sbHorizontal") == 0) {
ctrl->kind = 0;
} else if (stricmp_local(strBuf, "sbVertical") == 0) {
ctrl->kind = 1;
} else if (stricmp_local(strBuf, "bkCustom") == 0) {
ctrl->kind = 0;
} else if (stricmp_local(strBuf, "bkOK") == 0) {
ctrl->kind = 1;
} else if (stricmp_local(strBuf, "bkCancel") == 0) {
ctrl->kind = 2;
} else if (stricmp_local(strBuf, "bkHelp") == 0) {
ctrl->kind = 3;
} else if (stricmp_local(strBuf, "bkYes") == 0) {
ctrl->kind = 4;
} else if (stricmp_local(strBuf, "bkNo") == 0) {
ctrl->kind = 5;
} else if (stricmp_local(strBuf, "bkClose") == 0) {
ctrl->kind = 6;
} else if (stricmp_local(strBuf, "bkAbort") == 0) {
ctrl->kind = 7;
} else if (stricmp_local(strBuf, "bkRetry") == 0) {
ctrl->kind = 8;
} else if (stricmp_local(strBuf, "bkIgnore") == 0) {
ctrl->kind = 9;
} else if (stricmp_local(strBuf, "bkAll") == 0) {
ctrl->kind = 10;
} else {
ctrl->kind = 0;
}
} else {
ctrl->kind = readIntValue(data, size, pos, tag);
}
ctrl->hasKind = true;
} else if (!isForm && stricmp_local(propName, "Min") == 0) {
ctrl->min = readIntValue(data, size, pos, tag);
ctrl->hasMin = true;
} else if (!isForm && stricmp_local(propName, "Max") == 0) {
ctrl->max = readIntValue(data, size, pos, tag);
ctrl->hasMax = true;
} else if (!isForm && stricmp_local(propName, "Position") == 0) {
ctrl->position = readIntValue(data, size, pos, tag);
ctrl->hasPosition = true;
} else if (!isForm && stricmp_local(propName, "LargeChange") == 0) {
ctrl->largeChange = readIntValue(data, size, pos, tag);
ctrl->hasLargeChange = true;
} else if (!isForm && stricmp_local(propName, "SmallChange") == 0) {
ctrl->smallChange = readIntValue(data, size, pos, tag);
ctrl->hasSmallChange = true;
} else if (!isForm && stricmp_local(propName, "FileName") == 0) {
if (tag == vaString) {
readStr(data, size, pos, ctrl->fileName, sizeof(ctrl->fileName));
} else if (tag == vaLString) {
int32_t len = readInt32LE(data, size, pos);
int32_t copyLen = (len < (int32_t)sizeof(ctrl->fileName) - 1) ? len : (int32_t)sizeof(ctrl->fileName) - 1;
memcpy(ctrl->fileName, data + *pos, copyLen);
ctrl->fileName[copyLen] = '\0';
*pos += len;
} else {
skipValue(data, size, pos, tag);
}
ctrl->hasFileName = true;
} else if (!isForm && stricmp_local(propName, "DeviceType") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, ctrl->deviceType, sizeof(ctrl->deviceType));
} else if (tag == vaString) {
readStr(data, size, pos, ctrl->deviceType, sizeof(ctrl->deviceType));
} else if (tag == vaLString) {
int32_t len = readInt32LE(data, size, pos);
int32_t copyLen = (len < (int32_t)sizeof(ctrl->deviceType) - 1) ? len : (int32_t)sizeof(ctrl->deviceType) - 1;
memcpy(ctrl->deviceType, data + *pos, copyLen);
ctrl->deviceType[copyLen] = '\0';
*pos += len;
} else {
skipValue(data, size, pos, tag);
}
ctrl->hasDeviceType = true;
} else if (!isForm && stricmp_local(propName, "AutoOpen") == 0) {
if (tag == vaTrue) {
ctrl->autoOpen = 1;
} else if (tag == vaFalse) {
ctrl->autoOpen = 0;
} else {
ctrl->autoOpen = readIntValue(data, size, pos, tag);
}
ctrl->hasAutoOpen = true;
} else if (!isForm && stricmp_local(propName, "Columns") == 0) {
ctrl->columns = readIntValue(data, size, pos, tag);
ctrl->hasColumns = true;
} else if (!isForm && stricmp_local(propName, "ShortCut") == 0) {
ctrl->shortCut = readIntValue(data, size, pos, tag);
ctrl->hasShortCut = true;
} else if (!isForm && stricmp_local(propName, "Layout") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
if (stricmp_local(strBuf, "blGlyphLeft") == 0) {
ctrl->layout = 0;
} else if (stricmp_local(strBuf, "blGlyphRight") == 0) {
ctrl->layout = 1;
} else if (stricmp_local(strBuf, "blGlyphTop") == 0) {
ctrl->layout = 2;
} else if (stricmp_local(strBuf, "blGlyphBottom") == 0) {
ctrl->layout = 3;
} else {
ctrl->layout = 0;
}
} else {
ctrl->layout = readIntValue(data, size, pos, tag);
}
ctrl->hasLayout = true;
} else if (!isForm && stricmp_local(propName, "NumGlyphs") == 0) {
ctrl->numGlyphs = readIntValue(data, size, pos, tag);
ctrl->hasNumGlyphs = true;
} else if (!isForm && stricmp_local(propName, "GroupIndex") == 0) {
ctrl->groupIndex = readIntValue(data, size, pos, tag);
ctrl->hasGroupIndex = true;
} else if (!isForm && stricmp_local(propName, "Down") == 0) {
if (tag == vaTrue) {
ctrl->down = 1;
} else if (tag == vaFalse) {
ctrl->down = 0;
} else {
ctrl->down = readIntValue(data, size, pos, tag);
}
ctrl->hasDown = true;
} else if (!isForm && stricmp_local(propName, "AllowAllUp") == 0) {
if (tag == vaTrue) {
ctrl->allowAllUp = 1;
} else if (tag == vaFalse) {
ctrl->allowAllUp = 0;
} else {
ctrl->allowAllUp = readIntValue(data, size, pos, tag);
}
ctrl->hasAllowAllUp = true;
} else if (!isForm && stricmp_local(propName, "EditMask") == 0) {
if (tag == vaString) {
readStr(data, size, pos, ctrl->editMask, sizeof(ctrl->editMask));
} else if (tag == vaLString) {
int32_t len = readInt32LE(data, size, pos);
int32_t copyLen = (len < (int32_t)sizeof(ctrl->editMask) - 1) ? len : (int32_t)sizeof(ctrl->editMask) - 1;
memcpy(ctrl->editMask, data + *pos, copyLen);
ctrl->editMask[copyLen] = '\0';
*pos += len;
} else {
skipValue(data, size, pos, tag);
}
ctrl->hasEditMask = true;
} else if (!isForm && stricmp_local(propName, "OutlineStyle") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
if (stricmp_local(strBuf, "osText") == 0) {
ctrl->outlineStyle = 0;
} else if (stricmp_local(strBuf, "osPlusMinusText") == 0) {
ctrl->outlineStyle = 1;
} else if (stricmp_local(strBuf, "osPlusMinus") == 0) {
ctrl->outlineStyle = 2;
} else if (stricmp_local(strBuf, "osPictureText") == 0) {
ctrl->outlineStyle = 3;
} else if (stricmp_local(strBuf, "osPicturePlusMinusText") == 0) {
ctrl->outlineStyle = 4;
} else if (stricmp_local(strBuf, "osTreeText") == 0) {
ctrl->outlineStyle = 5;
} else if (stricmp_local(strBuf, "osTreePictureText") == 0) {
ctrl->outlineStyle = 6;
} else {
ctrl->outlineStyle = 0;
}
} else {
ctrl->outlineStyle = readIntValue(data, size, pos, tag);
}
ctrl->hasOutlineStyle = true;
} else if (!isForm && stricmp_local(propName, "Shape") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
if (stricmp_local(strBuf, "bsBox") == 0) {
ctrl->shape = 0;
} else if (stricmp_local(strBuf, "bsFrame") == 0) {
ctrl->shape = 1;
} else if (stricmp_local(strBuf, "bsTopLine") == 0) {
ctrl->shape = 2;
} else if (stricmp_local(strBuf, "bsBottomLine") == 0) {
ctrl->shape = 3;
} else if (stricmp_local(strBuf, "bsLeftLine") == 0) {
ctrl->shape = 4;
} else if (stricmp_local(strBuf, "bsRightLine") == 0) {
ctrl->shape = 5;
} else {
ctrl->shape = 0;
}
} else {
ctrl->shape = readIntValue(data, size, pos, tag);
}
ctrl->hasShape = true;
} else if (!isForm && stricmp_local(propName, "Style") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
if (stricmp_local(strBuf, "bsLowered") == 0) {
ctrl->style = 0;
} else if (stricmp_local(strBuf, "bsRaised") == 0) {
ctrl->style = 1;
} else {
ctrl->style = 0;
}
} else {
ctrl->style = readIntValue(data, size, pos, tag);
}
ctrl->hasStyle = true;
} else {
skipValue(data, size, pos, tag);
}
}
}
// ---------------------------------------------------------------------------
// Parse a component (form or child control) recursively
// ---------------------------------------------------------------------------
static bool parseComponent(const uint8_t *data, int32_t size, int32_t *pos, DfmFormT *form, bool isRoot, int32_t parentIdx) {
// Check for flags byte (Delphi 1.0 rarely uses this but handle it)
uint8_t peek = data[*pos];
if ((peek & 0xF0) == 0xF0) {
uint8_t flags = readByte(data, size, pos);
if (flags & 0x02) {
// ffChildPos: skip tagged integer
uint8_t childTag = readByte(data, size, pos);
readIntValue(data, size, pos, childTag);
}
}
// Class name
char className[64];
readStr(data, size, pos, className, sizeof(className));
// Instance name
char instName[64];
readStr(data, size, pos, instName, sizeof(instName));
if (isRoot) {
// This is the form itself
snprintf(form->name, sizeof(form->name), "%s", instName);
parseProperties(data, size, pos, form, NULL, true);
// Parse child components
while (*pos < size) {
peek = data[*pos];
if (peek == 0x00) {
(*pos)++; // consume terminator
break;
}
parseComponent(data, size, pos, form, false, -1);
}
} else {
// Child control
CtrlTypeE type = mapClassName(className);
if (type == ctUnknown) {
fprintf(stderr, "Warning: unknown control class '%s' (%s), skipping\n", className, instName);
// Still need to parse properties and children to advance pos
DfmCtrlT dummy;
initCtrl(&dummy);
parseProperties(data, size, pos, form, &dummy, false);
// Skip nested children
while (*pos < size) {
peek = data[*pos];
if (peek == 0x00) {
(*pos)++;
break;
}
parseComponent(data, size, pos, form, false, -1);
}
return true;
}
if (form->ctrlCount >= 256) {
fprintf(stderr, "Error: too many controls (max 256)\n");
exit(1);
}
int32_t myIdx = form->ctrlCount;
DfmCtrlT *ctrl = &form->ctrls[myIdx];
initCtrl(ctrl);
ctrl->type = type;
ctrl->parentCtrlIdx = parentIdx;
snprintf(ctrl->name, sizeof(ctrl->name), "%s", instName);
parseProperties(data, size, pos, form, ctrl, false);
form->ctrlCount++;
// Recurse into children (menu items, panels, etc.)
while (*pos < size) {
peek = data[*pos];
if (peek == 0x00) {
(*pos)++;
break;
}
parseComponent(data, size, pos, form, false, myIdx);
}
}
return true;
}
// ---------------------------------------------------------------------------
// Escape a string for protocol output
// ---------------------------------------------------------------------------
static void escapeStr(const char *src, char *dst, int32_t dstSize) {
int32_t di = 0;
for (int32_t si = 0; src[si] != '\0' && di < dstSize - 2; si++) {
char c = src[si];
if (c == '"') {
if (di + 2 >= dstSize - 1) { break; }
dst[di++] = '\\';
dst[di++] = '"';
} else if (c == '\\') {
if (di + 2 >= dstSize - 1) { break; }
dst[di++] = '\\';
dst[di++] = '\\';
} else if (c == '\n') {
if (di + 2 >= dstSize - 1) { break; }
dst[di++] = '\\';
dst[di++] = 'n';
} else if (c == '\r') {
if (di + 2 >= dstSize - 1) { break; }
dst[di++] = '\\';
dst[di++] = 'r';
} else if (c == '\t') {
if (di + 2 >= dstSize - 1) { break; }
dst[di++] = '\\';
dst[di++] = 't';
} else {
dst[di++] = c;
}
}
dst[di] = '\0';
}
// ---------------------------------------------------------------------------
// Emit protocol commands for a single control
// ---------------------------------------------------------------------------
static void emitCtrl(FILE *out, int32_t formId, int32_t ctrlId, DfmCtrlT *ctrl) {
static const char *typeNames[] = {
"Unknown", "Label", "Edit", "Button",
"CheckBox", "ListBox", "ComboBox", "Memo",
"Image", "GroupBox", "RadioButton", "Panel",
"ScrollBar", "MediaPlayer",
"MainMenu", "PopupMenu", "MenuItem", "RadioGroup",
"BitBtn", "SpeedButton", "TabSet", "Notebook",
"TabbedNotebook", "MaskEdit", "Outline", "Bevel",
"Header", "ScrollBox"
};
char escaped[8192];
if (ctrl->type == ctMainMenu || ctrl->type == ctPopupMenu || ctrl->type == ctMenuItem) {
fprintf(out, "CTRL.CREATE %d %d %s 0 0 0 0", formId, ctrlId, typeNames[ctrl->type]);
} else {
fprintf(out, "CTRL.CREATE %d %d %s %d %d %d %d", formId, ctrlId, typeNames[ctrl->type], ctrl->left, ctrl->top, ctrl->width, ctrl->height);
}
// Inline properties
if (ctrl->hasCaption) {
escapeStr(ctrl->caption, escaped, sizeof(escaped));
fprintf(out, " Caption=\"%s\"", escaped);
}
if (ctrl->hasText) {
escapeStr(ctrl->text, escaped, sizeof(escaped));
fprintf(out, " Text=\"%s\"", escaped);
}
if (ctrl->hasItems) {
escapeStr(ctrl->items, escaped, sizeof(escaped));
fprintf(out, " Items=\"%s\"", escaped);
}
if (ctrl->hasChecked) {
fprintf(out, " Checked=%d", ctrl->checked);
}
if (ctrl->hasEnabled && ctrl->enabled == 0) {
fprintf(out, " Enabled=0");
}
if (ctrl->hasVisible && ctrl->visible == 0) {
fprintf(out, " Visible=0");
}
if (ctrl->hasMaxLength && ctrl->maxLength > 0) {
fprintf(out, " MaxLength=%d", ctrl->maxLength);
}
if (ctrl->hasReadOnly && ctrl->readOnly) {
fprintf(out, " ReadOnly=1");
}
if (ctrl->hasScrollBars && ctrl->scrollBars != 0) {
fprintf(out, " ScrollBars=%d", ctrl->scrollBars);
}
if (ctrl->hasTabOrder && ctrl->tabOrder >= 0) {
fprintf(out, " TabOrder=%d", ctrl->tabOrder);
}
if (ctrl->hasItemIndex && ctrl->itemIndex >= 0) {
fprintf(out, " ItemIndex=%d", ctrl->itemIndex);
}
if (ctrl->hasStretch && ctrl->stretch) {
fprintf(out, " Stretch=1");
}
if (ctrl->hasCenter && ctrl->center) {
fprintf(out, " Center=1");
}
if (ctrl->hasTransparent && ctrl->transparent) {
fprintf(out, " Transparent=1");
}
if (ctrl->hasBevelOuter) {
fprintf(out, " BevelOuter=%d", ctrl->bevelOuter);
}
if (ctrl->hasBevelInner && ctrl->bevelInner != 0) {
fprintf(out, " BevelInner=%d", ctrl->bevelInner);
}
if (ctrl->hasBorderStyle && ctrl->borderStyle != 0) {
fprintf(out, " BorderStyle=%d", ctrl->borderStyle);
}
if (ctrl->hasKind && ctrl->kind != 0) {
fprintf(out, " Kind=%d", ctrl->kind);
}
if (ctrl->hasMin) {
fprintf(out, " Min=%d", ctrl->min);
}
if (ctrl->hasMax) {
fprintf(out, " Max=%d", ctrl->max);
}
if (ctrl->hasPosition && ctrl->position != 0) {
fprintf(out, " Position=%d", ctrl->position);
}
if (ctrl->hasLargeChange && ctrl->largeChange != 1) {
fprintf(out, " LargeChange=%d", ctrl->largeChange);
}
if (ctrl->hasSmallChange && ctrl->smallChange != 1) {
fprintf(out, " SmallChange=%d", ctrl->smallChange);
}
if (ctrl->hasFileName) {
escapeStr(ctrl->fileName, escaped, sizeof(escaped));
fprintf(out, " FileName=\"%s\"", escaped);
}
if (ctrl->hasDeviceType) {
escapeStr(ctrl->deviceType, escaped, sizeof(escaped));
fprintf(out, " DeviceType=\"%s\"", escaped);
}
if (ctrl->hasAutoOpen && ctrl->autoOpen) {
fprintf(out, " AutoOpen=1");
}
if (ctrl->type == ctMenuItem && ctrl->parentCtrlIdx >= 0) {
fprintf(out, " Parent=%d", ctrl->parentCtrlIdx + 1);
}
if (ctrl->hasColumns && ctrl->columns != 0) {
fprintf(out, " Columns=%d", ctrl->columns);
}
if (ctrl->hasShortCut && ctrl->shortCut != 0) {
fprintf(out, " ShortCut=%d", ctrl->shortCut);
}
if (ctrl->hasLayout && ctrl->layout != 0) {
fprintf(out, " Layout=%d", ctrl->layout);
}
if (ctrl->hasNumGlyphs && ctrl->numGlyphs > 1) {
fprintf(out, " NumGlyphs=%d", ctrl->numGlyphs);
}
if (ctrl->hasGroupIndex && ctrl->groupIndex != 0) {
fprintf(out, " GroupIndex=%d", ctrl->groupIndex);
}
if (ctrl->hasDown && ctrl->down) {
fprintf(out, " Down=1");
}
if (ctrl->hasAllowAllUp && ctrl->allowAllUp) {
fprintf(out, " AllowAllUp=1");
}
if (ctrl->hasEditMask && ctrl->editMask[0] != '\0') {
escapeStr(ctrl->editMask, escaped, sizeof(escaped));
fprintf(out, " EditMask=\"%s\"", escaped);
}
if (ctrl->hasOutlineStyle && ctrl->outlineStyle != 0) {
fprintf(out, " OutlineStyle=%d", ctrl->outlineStyle);
}
if (ctrl->hasShape) {
fprintf(out, " Shape=%d", ctrl->shape);
}
if (ctrl->hasStyle && ctrl->style != 0) {
fprintf(out, " Style=%d", ctrl->style);
}
fprintf(out, "\n");
// Emit EVENT.BIND for non-auto-wired events
// Auto-wired: Button/CheckBox→Click, Edit→Change, ListBox→Select,
// ComboBox→Select+Change, Memo→Change
bool autoClick = (ctrl->type == ctButton || ctrl->type == ctCheckBox || ctrl->type == ctRadioButton || ctrl->type == ctMenuItem || ctrl->type == ctRadioGroup || ctrl->type == ctBitBtn || ctrl->type == ctSpeedButton);
bool autoChange = (ctrl->type == ctEdit || ctrl->type == ctComboBox || ctrl->type == ctMemo || ctrl->type == ctScrollBar || ctrl->type == ctTabSet || ctrl->type == ctTabbedNotebook || ctrl->type == ctMaskEdit);
bool autoSelect = (ctrl->type == ctListBox || ctrl->type == ctComboBox);
if (ctrl->hasOnClick && !autoClick) {
fprintf(out, "EVENT.BIND %d %d Click\n", formId, ctrlId);
}
if (ctrl->hasOnChange && !autoChange) {
fprintf(out, "EVENT.BIND %d %d Change\n", formId, ctrlId);
}
if (ctrl->hasOnDblClick) {
fprintf(out, "EVENT.BIND %d %d DblClick\n", formId, ctrlId);
}
if (ctrl->hasOnEnter) {
fprintf(out, "EVENT.BIND %d %d Enter\n", formId, ctrlId);
}
if (ctrl->hasOnExit) {
fprintf(out, "EVENT.BIND %d %d Exit\n", formId, ctrlId);
}
if (ctrl->hasOnKeyDown) {
fprintf(out, "EVENT.BIND %d %d KeyDown\n", formId, ctrlId);
}
if (ctrl->hasOnKeyUp) {
fprintf(out, "EVENT.BIND %d %d KeyUp\n", formId, ctrlId);
}
if (ctrl->hasOnMouseDown) {
fprintf(out, "EVENT.BIND %d %d MouseDown\n", formId, ctrlId);
}
if (ctrl->hasOnMouseUp) {
fprintf(out, "EVENT.BIND %d %d MouseUp\n", formId, ctrlId);
}
// Suppress unused variable warning for autoSelect
(void)autoSelect;
}
// ---------------------------------------------------------------------------
// Emit the complete form
// ---------------------------------------------------------------------------
static void emitForm(FILE *out, int32_t formId, DfmFormT *form) {
char escaped[512];
escapeStr(form->caption, escaped, sizeof(escaped));
fprintf(out, "FORM.CREATE %d %d %d \"%s\"\n",
formId, form->width, form->height, escaped);
for (int32_t i = 0; i < form->ctrlCount; i++) {
emitCtrl(out, formId, i + 1, &form->ctrls[i]);
}
fprintf(out, "FORM.SHOW %d\n", formId);
}
// ---------------------------------------------------------------------------
// Usage
// ---------------------------------------------------------------------------
static void usage(const char *progName) {
fprintf(stderr, "Usage: %s <input.dfm> [output.form]\n", progName);
exit(1);
}
// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
int main(int argc, char *argv[]) {
const char *inputPath = NULL;
const char *outputPath = NULL;
// Parse arguments
int32_t i = 1;
while (i < argc) {
if (argv[i][0] == '-') {
usage(argv[0]);
} else if (inputPath == NULL) {
inputPath = argv[i];
} else if (outputPath == NULL) {
outputPath = argv[i];
} else {
usage(argv[0]);
}
i++;
}
if (inputPath == NULL) {
usage(argv[0]);
}
// Read input file
FILE *fin = fopen(inputPath, "rb");
if (fin == NULL) {
fprintf(stderr, "Error: cannot open '%s': %s\n", inputPath, strerror(errno));
exit(1);
}
fseek(fin, 0, SEEK_END);
long fileSize = ftell(fin);
fseek(fin, 0, SEEK_SET);
uint8_t *data = (uint8_t *)malloc(fileSize);
if (data == NULL) {
fprintf(stderr, "Error: out of memory\n");
fclose(fin);
exit(1);
}
if ((long)fread(data, 1, fileSize, fin) != fileSize) {
fprintf(stderr, "Error: failed to read '%s'\n", inputPath);
free(data);
fclose(fin);
exit(1);
}
fclose(fin);
// Verify TPF0 signature
if (fileSize < 4 || memcmp(data, "TPF0", 4) != 0) {
fprintf(stderr, "Error: '%s' is not a Delphi binary DFM (missing TPF0 signature)\n", inputPath);
free(data);
exit(1);
}
// Parse
DfmFormT form;
initForm(&form);
int32_t pos = 4; // skip TPF0
parseComponent(data, (int32_t)fileSize, &pos, &form, true, -1);
// Default caption if empty
if (form.caption[0] == '\0') {
snprintf(form.caption, sizeof(form.caption), "%s", form.name);
}
// Output
FILE *fout;
if (outputPath != NULL) {
fout = fopen(outputPath, "w");
if (fout == NULL) {
fprintf(stderr, "Error: cannot create '%s': %s\n", outputPath, strerror(errno));
free(data);
exit(1);
}
} else {
fout = stdout;
}
emitForm(fout, 0, &form);
if (fout != stdout) {
fclose(fout);
}
free(data);
return 0;
}