Skip to content

Latest commit

 

History

History
378 lines (294 loc) · 18 KB

File metadata and controls

378 lines (294 loc) · 18 KB

Architecture

ChangeEdit is a single-process, single-window Delphi 7 VCL desktop application.
It is not MDI — the main window uses a TTreeView on the left and a set of stacked TPanels on the right that are shown/hidden based on the selected tree node.


Entry point

ChangEd.dpr creates all 13 forms at startup via Application.CreateForm and then calls Application.Run. Every form is a global singleton; none are created on demand.

Application
└── TMainForm (MainForm)        ← only form that is ever "visible" first
└── TEditTokenForm
└── TMod2DARowForm
└── TNewLabelForm
└── TChange2daForm
└── TAdd2daColForm
└── TInfoForm
└── TInfoCopyForm
└── TMassAddForm
└── TFormNewGFF
└── TFormFilename
└── TNamespaceForm
└── TCopy2daForm

Layer overview

┌──────────────────────────────────────────────────────────────────┐
│  Presentation layer (VCL forms)                                  │
│  UMainForm  +  13 modal dialog forms (U*Form.pas)               │
├──────────────────────────────────────────────────────────────────┤
│  Domain / file-format layer                                      │
│  U2DAEdit   UGFFFile   UTLKFile   UST_IniFile                   │
├──────────────────────────────────────────────────────────────────┤
│  Utilities                                                        │
│  UST_Common   UStrTok                                            │
├──────────────────────────────────────────────────────────────────┤
│  Delphi 7 RTL + VCL (Windows, SysUtils, Classes, Grids, …)     │
└──────────────────────────────────────────────────────────────────┘

There is no MVC separation. UMainForm directly reads from and writes to the loaded TST_IniFile instance (ini : TST_IniFile) and calls the file-format units as needed.


Unit inventory

Presentation

Unit Form class Purpose
UMainForm TMainForm Main window; tree navigation + 8 stacked section panels; holds the live TST_IniFile and orchestrates all load/save/edit operations
UEditTokenForm TEditTokenForm Modal — enter a TLK token: StrRef + token value
UMod2DARowForm TMod2DARowForm Modal — modify a 2DA row: column selector + value grid
UChange2daRowForm TChange2daForm Modal — change a specific 2DA cell by row/column label
UCopy2daRowForm TCopy2daForm Modal — copy a 2DA row
UAddColForm TAdd2daColForm Modal — add a new 2DA column with a label and default value
UMassAddForm TMassAddForm Modal — bulk-import files from a folder
UFormNewGFF TFormNewGFF Modal — add or edit a GFF field (type-specific controls, localised string support)
UFormFilename TFormFilename Modal — generic filename input with file browser
UModLabelForm TNewLabelForm Modal — enter a section/modifier label string
UNamespaceForm TNamespaceForm Modal — manage namespace → INI/RTF file path mappings
UInfoForm TInfoForm Non-modal display — multi-tab help text for each section
UInfoCopyForm TInfoCopyForm Modal — read-only TMemo for copy-able text output

Domain / file-format

Unit Key class(es) Purpose
U2DAEdit T2DAHandler Reads and writes binary 2DA v2.b files. Internal storage: parallel array of string for column labels, row labels, and a 2-D entries array. Exposes array-property accessors (clabels[c], rlabels[r], entry[r,c]). Custom exception: EDead.
UGFFFile TGFFFile, TGFFStruct, TGFFList, TGFFField + 16 typed subclasses Full GFF v3.2 parser/emitter. 18 field types (BYTE through POSITION). Nested struct/list tree. Custom exception: EGFFError. Known gap: no write support for DWORD64 or VOID fields.
UTLKFile TTLKFileHandler, TTLKString Reads and writes the dialog .tlk binary format. Each entry holds flags, sound resref, string offset, and the string itself. Custom exception: EHell.
UST_IniFile TST_IniFile Extends the standard Delphi TIniFile with custom escape sequences (\n → line-feed, \r → carriage-return) needed for multi-line INI values used by TSLPatcher.

Utilities

Unit Key class Purpose
UST_Common Free-standing helper functions: ShowAlertBox, ShowConfirmBox, common string/file utilities, Windows system folder access
UStrTok TStringTokenizer Simple delimiter-based string tokenizer; used by UGFFFile and UFormNewGFF for path splitting

Main window panel layout

The left-side TTreeView (named tree) contains 8 top-level nodes. Clicking a node calls treeChange(), which hides all panels and shows exactly one:

Tree node Visible panel Grid(s) used
Changes (sub-nodes only)
Settings paneSettings none (plain edit controls)
TLK paneTLK gridTlk, gridTokens
2DA pane2da grid2daMod
GFF paneGFF gridGffMod
Install paneInstall gridInstall
Script paneScript gridScript
SSF paneSSF gridSSF

Data model

The live state of the application is a single TST_IniFile instance (ini) that mirrors the on-disk changes.ini. All editing operations read from and write to this object; the physical file is only touched on explicit Save.

TST_IniFile  (ini)
├── [Settings]     key/value pairs (Caption, Confirm, Mode, Backups, …)
├── [TLK]          StrRef=Token entries + StrRefList
├── [filename.2da] per-file subsections
│   ├── AddRow_N   new row entries
│   ├── ModRow_N   cell modifications
│   ├── CopyRow_N  row clones
│   └── AddCol_N   column additions
├── [filename.gff] per-file subsections
│   └── FieldPath=TypedValue entries
├── [SSF]          soundset field assignments
├── [Script_N]     .nss → .ncs compilation instructions
└── [InstallList]  file-copy directives

Dependency graph

ChangEd.dpr
│
├─► UMainForm
│       ├─► U2DAEdit
│       ├─► UGFFFile
│       │       └─► UStrTok
│       │       └─► UST_Common
│       ├─► UTLKFile
│       ├─► UST_IniFile
│       │       └─► UST_Common
│       └─► UST_Common
│
├─► UMod2DARowForm  ──► U2DAEdit, UST_IniFile
├─► UChange2daRowForm ► U2DAEdit, UST_IniFile
├─► UCopy2daRowForm  ─► U2DAEdit, UST_IniFile
├─► UAddColForm      ─► U2DAEdit
├─► UFormNewGFF      ─► UST_IniFile, UST_Common, UStrTok
├─► UNamespaceForm   ─► UST_IniFile
└─► UST_IniFile (direct)

Binary format notes

2DA v2.b (U2DAEdit)

"2DA V2.b" LF                       8-byte header + newline
<TAB-separated column names> NUL     column label block
<32-bit DWORD>                       row count
<TAB-separated row names>            row label block (no NUL)
<16-bit WORD grid [rows × cols]>     offset table into data section
<2 padding bytes>
<NUL-terminated strings>             data section (cell values)

Empty cells are stored as a single NUL byte. The **** sentinel in TSLPatcher means "do not modify this cell" and is preserved as a literal string.

GFF v3.2 (UGFFFile)

Standard BioWare GFF: header → struct array → field array → label array → field data → field indices → list indices. TGFFFile.LoadFile reads all sections into a tree of TGFFStruct/TGFFList/TGFFField objects. SaveFile regenerates all sections from the object tree.

The 18 supported field types map to Delphi classes:

0 BYTE          → TGFF_SByte
1 CHAR          → TGFF_SChar
2 WORD          → TGFF_SWord
3 SHORT         → TGFF_SShort
4 DWORD         → TGFF_SDWORD
5 INT           → TGFF_SInt
6 DWORD64       → TGFF_CDWORD64   (read-only — no write support in D7)
7 INT64         → TGFF_CInt64
8 FLOAT         → TGFF_SFloat
9 DOUBLE        → TGFF_CDouble
10 CExoString   → TGFF_CExoString
11 ResRef       → TGFF_CResRef
12 CExoLocString→ TGFF_CExoLocString (with TGFF_CSubString per language)
13 VOID         → TGFF_CVoid       (read-only — no binary write support)
14 Struct       → TGFFStruct
15 List         → TGFFList
16 Orientation  → TGFF_COrientation
17 Position     → TGFF_CPosition

Known architectural issues

The UMainForm comment acknowledges: "textbook example of an unplanned, unstructured hack."
Specific issues to be aware of when editing:

  • All 13 forms are created at startup — avoid adding heavyweight initialisation to FormCreate handlers.
  • UMainForm mixes file I/O, validation, and UI update in the same event handlers.
  • Dialogs share state via public properties and field-level variables (box_2daname, box_ini).
  • l_ prefix variables in TMainForm are conceptually private but may be read across units.

Public APIs

T2DAHandler (U2DAEdit.pas)

Member Kind Description
Load2daFile(sFilename) procedure Open and parse a binary 2DA v2.b file into internal arrays
Save2daFile(sFilename) procedure Write the current state to disk as a binary 2DA v2.b file
AddLine() function → integer Append a new blank row; return its 0-based index
AddColumn() function → integer Append a new blank column; return its 0-based index
CloneLine(iIndex, sNewLabel) function → integer Duplicate row iIndex with an optional new label; return the new row index
GetColByLabel(sLabel) function → integer Find a column index by its label string; −1 if not found
GetRowByLabel(sLabel) function → integer Find a row index by its label string; −1 if not found
rowcount property → integer Number of data rows
colcount property → integer Number of columns
clabels[c] property string (r/w) Column label at index c
rlabels[r] property string (r/w) Row label at index r
entry[r, c] property string (r/w) Cell value at row r, column c

Exception on any parse error: EDead.


TGFFFile (UGFFFile.pas)

Member Kind Description
LoadFile(sFilename) procedure Read a binary GFF v3.2 file into an object tree
SaveFile(sFilename) procedure Write the current object tree back to disk (optional filename; defaults to original)
NewFile(sType, sFilename) procedure Create an empty GFF file with the given 4-char type tag
GetFieldByLabel(sFieldPath) function → TGFFField Return the field at a dot-separated path (e.g. "Struct.FieldName")
GetFirstRootField() function → TGFFField Iterator: return the first field of the root struct
GetNextRootField() function → TGFFField Iterator: return the next field of the root struct
AddField(oField, sPath) procedure Insert a field at the given path
DeleteField(sFieldPath) procedure Remove a field by path
ChangeFieldValue(sPath, sValue) function → boolean Update a field's value by path; returns false if path not found

Exceptions: EGFFError. See FORMATS.md for field type constants and write limitations.


TTLKFileHandler (UTLKFile.pas)

Member Kind Description
Create() constructor Empty handler
Create(sFilename) constructor Open and parse a TLK file immediately
LoadTlkFile(sFilename) procedure Parse a binary TLK v3.0 file
SaveTlkFile(sFilename) procedure Write current entries to disk (strips read-only first)
NewTlkFile() procedure Reset to an empty TLK
AddEntry(oEntry) procedure Append a TTLKString entry
ReplaceEntry(oEntry) procedure Replace an existing entry by StrRef
Reset() procedure Clear all loaded data
strings property → TStringDataList Doubly-linked list of all TTLKString entries
count property → DWORD Number of string entries
fileid property → T4Char 4-char file type tag ("TLK ")
version property → T4Char 4-char version tag ("V3.0")
fileexists property → boolean True if a file has been loaded
language property DWORD (r/w) Language ID of the file

TTLKString properties: strflags, strsound, sndvolume, sndpitch, stroffset, strsize, sndlength, strtext, strref, iscustom.

TStringDataList iteration: call first() then next() in a loop; check eol() to stop. Use Insert(oEntry) and Delete(bFreeObject) to modify.

Exception on parse error: EHell.


TST_IniFile (UST_IniFile.pas)

Extends Delphi's TIniFile. All inherited methods work normally; the three overrides add TSLPatcher escape-sequence handling.

Member Kind Description
ReadString(Section, Ident, Default) function → string Read a value; converts <#LF#> → Chr(10), <#CR#> → Chr(13)
WriteString(Section, Ident, Value) procedure Write a value; converts Chr(10) → <#LF#>, Chr(13) → <#CR#>
ReadSectionValues(Section, Strings) procedure Read all key=value pairs in a section; applies same escape conversion to every value

All other TIniFile methods (ReadInteger, WriteInteger, ReadBool, SectionExists, ReadSection, etc.) are inherited unchanged.


TStringTokenizer (UStrTok.pas)

Member Kind Description
Create(sString, sToken) constructor Parse sString using sToken as the single-char delimiter; store tokens internally
first() function → string Reset iterator; return the first token
next() function → string Advance iterator; return the next token, or '' at end
strings[i] property string (default) Indexed token access
count property → integer Number of tokens
current property → integer Current iterator position
delimiter property → Char The delimiter character
text property → string The original full string

Notes: strings shorter than 3 chars are stored as-is without splitting. Empty tokens (consecutive delimiters) are filtered out.


UST_Common free functions (UST_Common.pas)

Version 2.0 (last changed 2006-05-17).

Message dialogs

Function Return Description
ShowAlertBox(sMessage) word Warning dialog (MB_OK). Use instead of ShowMessage.
ShowInfoBox(sMessage) word Information dialog (MB_OK)
ShowConfirmBox(sMessage) word Confirmation dialog (MB_YESNO); returns mrYes or mrNo

Number validation

Function Return Description
GetIsNumber(sStr) boolean True if sStr is a valid unsigned integer
GetIsNumberSigned(sStr) boolean True if sStr is a valid signed integer
GetIsFloat(sStr) boolean True if sStr is a valid decimal number (accepts , or .)

Safe type conversion

Function Return Description
SafeStrToDouble(sStr) Double Locale-safe — normalises ,/. to the system decimal separator before conversion
SafeStrToFloat(sStr) Single Same as above, returns Single
SafeStrToInt(S) Integer Handles the DWORD maximum 4294967295 by converting via hex (Delphi 7 limitation workaround)

File operations

Function Return Description
MakeFileWritable(sFilename) Remove the read-only attribute from a file
GetFileIsWriteProtected(sFilename) boolean True if the file has the read-only attribute
BackupFile(sFilename, sNewfile) Copy sFilename to sNewfile (despite the name, this is a copy, not a backup-rotate)
OpenFolderDialog(sTitle, iFlags) string SHBrowseForFolder-based folder picker; returns selected path or '' if cancelled

System paths

Function Return
GetWindowsDir() Windows directory path
GetTempDir() Temp directory path
GetSystemDir() System32 directory path

Folder operations

Function Return Description
DeleteFolder(sFolder, bRecursive) boolean Delete a folder, optionally recursively
GetFilesInFolder(sFolder, bNameOnly) TStringList List filenames (or full paths) in a folder; caller must free the list

String utilities

Function Return Description
ReplaceInString(sSource, sFind, sReplace) string Brute-force substring replacement; recursive implementation
StringToResRef(sText) string Truncate to 16 chars, lowercase, strip non-alphanumeric/underscore

Shell / system

Function Return Description
RunShellGetOutput(sExe, sParams, sWork) string Run a console executable with Win32 pipes; capture stdout; wait for completion. Read buffer: 2400 bytes
GetFileTypeSmallIcon(sFilename) TIcon Shell small icon for a file's extension
GetFileTypeLargeIcon(sFilename) TIcon Shell large icon for a file's extension
GetFileSizeString(iBytes) string Human-readable size: "N bytes" or "N kB"
GetRegistryString(sKeyName, sValue) string Read a string value from HKEY_LOCAL_MACHINE