Extends the remote forms system from 7 to 13 control types across dfm2form converter, formcli client engine, and documentation. Image and MediaPlayer support file paths resolved via BasePath. MediaPlayer adds a Command pseudo-property for method calls. RadioButton auto-wires Click; ScrollBar auto-wires Change. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1191 lines
41 KiB
C
1191 lines
41 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
|
|
} 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;
|
|
} 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);
|
|
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;
|
|
}
|
|
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;
|
|
}
|
|
|
|
|
|
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 && 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) {
|
|
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 {
|
|
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 {
|
|
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) {
|
|
// 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);
|
|
}
|
|
} 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);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (form->ctrlCount >= 256) {
|
|
fprintf(stderr, "Error: too many controls (max 256)\n");
|
|
exit(1);
|
|
}
|
|
|
|
DfmCtrlT *ctrl = &form->ctrls[form->ctrlCount];
|
|
initCtrl(ctrl);
|
|
ctrl->type = type;
|
|
snprintf(ctrl->name, sizeof(ctrl->name), "%s", instName);
|
|
|
|
parseProperties(data, size, pos, form, ctrl, false);
|
|
form->ctrlCount++;
|
|
|
|
// Skip nested children (controls within controls, e.g., panels)
|
|
while (*pos < size) {
|
|
peek = data[*pos];
|
|
if (peek == 0x00) {
|
|
(*pos)++;
|
|
break;
|
|
}
|
|
parseComponent(data, size, pos, form, false);
|
|
}
|
|
}
|
|
|
|
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"
|
|
};
|
|
char escaped[8192];
|
|
|
|
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");
|
|
}
|
|
|
|
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);
|
|
bool autoChange = (ctrl->type == ctEdit || ctrl->type == ctComboBox || ctrl->type == ctMemo || ctrl->type == ctScrollBar);
|
|
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);
|
|
|
|
// 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;
|
|
}
|