// dfm2form.c - Convert Delphi 1.0 binary DFM (TPF0) to .form protocol text // // Usage: dfm2form [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 #include #include #include #include #include #include // --------------------------------------------------------------------------- // 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 [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; }