From f745fce9d1c8e898b24a32022f1a2b7e667694b8 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Wed, 3 Jun 2026 17:11:55 +0200 Subject: [PATCH] Update the project file format documentation. --- docs/Features/Packages/Import-export tool.md | 8 +- .../Features/Packages/TWINPACK file format.md | 133 ++++++++++-------- indexer/sample.twinpack | Bin 0 -> 154560 bytes scripts/impexp.mjs | 102 ++++++++++---- scripts/impexp.py | 121 +++++++++++----- 5 files changed, 238 insertions(+), 126 deletions(-) create mode 100644 indexer/sample.twinpack diff --git a/docs/Features/Packages/Import-export tool.md b/docs/Features/Packages/Import-export tool.md index 5cfdcce0..44489d51 100644 --- a/docs/Features/Packages/Import-export tool.md +++ b/docs/Features/Packages/Import-export tool.md @@ -55,7 +55,7 @@ python impexp.py import MyProject.twinproj unpacked/ Scans a directory tree and writes a `.twinproj` or `.twinpack` binary. The directory name becomes the root entry name in the output file. Well-known directory and file names (`Sources`, `Resources`, `Settings`, etc.) are -tagged with the correct `mark2` category values automatically. +tagged with the correct `category` values automatically. ``` node impexp.mjs export unpacked/ MyProject.twinproj @@ -81,9 +81,9 @@ Importing and re-exporting a binary file preserves all file contents byte-for-byte. The following metadata fields are reset to defaults on a disk round-trip (they are not stored on the filesystem): -- **mark1** (revision counter) -- directories get `0x0000`; files get - `0x0002`. -- **Revision entries** -- always written as zero. +- **revision counter** -- directories get `0x0000`; files get `0x0002`. +- **flags** -- always written as zero (no flags set). +- **Revision trailer entries** -- always written as zero. - **Entry order** -- directories first, then files, sorted alphabetically within each group. diff --git a/docs/Features/Packages/TWINPACK file format.md b/docs/Features/Packages/TWINPACK file format.md index 66bb40df..76c9427f 100644 --- a/docs/Features/Packages/TWINPACK file format.md +++ b/docs/Features/Packages/TWINPACK file format.md @@ -38,33 +38,26 @@ A length-prefixed byte string. Encoding is UTF-8 for filenames and text content ## Entry structure -Every node in the tree — the root, directories, and files — shares a common header: +Every node in the tree — the root, directories, and files — shares the same common header. The first 2 bytes carry one of two meanings depending on position: at the root entry they hold the file format version; everywhere else they hold the entry kind. -| Offset | Size | Type | Field | Description | -|--------|------|-----------|---------|-------------| -| +0 | 2 | int16 | `kind` | Entry type (see below). | -| +2 | var | LenString | `name` | Entry name (filename or folder name). | -| +2+var | 2 | uint16 | `mark1` | Revision counter (see [mark1](#mark1-revision-counter)). | -| ... | 10 | byte[] | `pad` | Reserved. Always observed as all zeros. | -| ... | 1 | uint8 | `mark2` | Category tag (see [mark2](#mark2-category-tag)). | +| Offset | Size | Type | Field | Description | +|--------|------|-----------|------------|-------------| +| +0 | 2 | int16 | `kind` | At the root: file format version (currently `1`). Everywhere else: entry kind (`1` = file, `2` = directory). | +| +2 | var | LenString | `name` | Entry name (filename or folder name). | +| +2+var | 8 | uint64 | `revision` | Revision counter (see [revision](#revision-counter)). | +| ... | 4 | uint32 | `flags` | File-system flags bitmask (see [flags](#flags)). | +| ... | 1 | uint8 | `category` | Category tag (see [category](#category-tag)). | After this common header, the entry body depends on whether the entry is a **file** or a **directory**. ### Determining entry type -The root entry is always the first entry parsed. It is always a directory (even though it has `kind=1`), because the format uses positional logic: +The root entry is always the first entry parsed. It is always a directory — its 2-byte field is the file format version, not a kind tag, and its value (currently `1`) coincides with the file-kind value but should not be read as one. Every entry after the root is determined by its `kind`: -- **Directory** — the root entry, or any entry with `kind != 1`. - Body: child count followed by child entries. -- **File** — any non-root entry with `kind == 1`. +- **File** — `kind == 1`. Body: content blob followed by a revision trailer. - -Observed `kind` values: - -| kind | Meaning | -|------|---------| -| 1 | File (or root directory — always the first entry) | -| 2 | Directory | +- **Directory** — `kind == 2`. + Body: child count followed by child entries. ### Directory body @@ -89,9 +82,9 @@ The `revisionCount` field is 0 for the vast majority of files, making the file b ## Field details -### mark1 (revision counter) +### revision counter -For files, `mark1` is a revision counter that starts at a low value and increments with each edit inside the IDE. For the root entry and directories it is always 0. +For files, `revision` is a 64-bit counter that starts at a low value and increments with each edit inside the IDE. For the root entry and directories it is always 0. | Context | Typical values | |---------|----------------| @@ -99,19 +92,38 @@ For files, `mark1` is a revision counter that starts at a low value and incremen | Heavily edited file | `0x17D5`, `0x1AA0` | | Root and directories | `0x0000` | -### mark2 (category tag) +Only the low 16 bits have been observed to vary in real-world files; the upper 48 bits are always zero in practice. + +### flags + +A 32-bit bitmask describing file-system-level properties of the entry. Every entry observed so far has `flags == 0`, but the IDE recognises the following bits: + +| Bit value | Name | Meaning | +|--------------|---------------|---------| +| `0x00000000` | `None` | Default — no flags set. | +| `0x00000001` | `Hidden` | Hidden from the user, but accessible via the VFS. | +| `0x00000002` | `SuperHidden` | Not accessible via the VFS — internal only. | +| `0x00000004` | `Virtual` | Virtual items are skipped during serialization. | + +Other bits are reserved. + +### category tag Encodes the semantic role of the entry within the project: -| mark2 | Entry name | Meaning | -|-------|-------------------------|---------| -| 0x00 | *(various)* | Default. Used for the root, most files, and resource subdirectories (`BITMAP`, `ICON`, `MANIFEST`). | -| 0x02 | `Resources` | Resource directory. | -| 0x03 | `Sources` | Source code directory. | -| 0x04 | `Settings` | Project settings file (JSON). | -| 0x05 | `ImportedTypeLibraries` | Imported type library directory. | -| 0x06 | `Miscellaneous` | Miscellaneous files directory (screenshots, etc.). | -| 0x07 | `Packages` | Package references directory. | +| category | Entry name | Meaning | +|----------|-------------------------|---------| +| 0x00 | *(various)* | Default. Used for the root, most files, and resource subdirectories (`BITMAP`, `ICON`, `MANIFEST`). | +| 0x01 | `References` | References directory. Always virtual — see note below. | +| 0x02 | `Resources` | Resource directory. | +| 0x03 | `Sources` | Source code directory. | +| 0x04 | `Settings` | Project settings file (JSON). | +| 0x05 | `ImportedTypeLibraries` | Imported type library directory. | +| 0x06 | `Miscellaneous` | Miscellaneous files directory (screenshots, etc.). | +| 0x07 | `Packages` | Package references directory. | + +> [!NOTE] +> The `References` directory (category `0x01`) is a virtual folder — it carries the [`Virtual`](#flags) flag and is skipped during serialization, so it never appears in saved `.twinproj` or `.twinpack` files. The IDE materialises it at runtime from the project's references list. ## Differences between .twinproj and .twinpack @@ -120,7 +132,6 @@ Both formats use the identical binary structure. The differences are in which e | Entry | .twinproj | .twinpack | |------------------------|-----------|-----------| | `.meta` file | Yes | No | -| `References` directory | Sometimes | No | | `CHANGELOG.md` | Sometimes | Sometimes | | `LICENCE.md` | Sometimes | Sometimes | | `Settings` file | Yes | Yes | @@ -128,51 +139,53 @@ Both formats use the identical binary structure. The differences are in which e | `Resources` directory | Yes | Yes | | `Packages` directory | Yes | Yes | +The `References` directory (category `0x01`) is virtual and is omitted from both formats during serialization — see [category tag](#category-tag). + ### .meta file Present only in `.twinproj` files. Contains JSON storing the user's IDE layout preferences — expanded folders, open editors, watch list, and outline-panel options. This file is stripped when the IDE generates a `.twinpack` for distribution. ### Settings file -Always present (`mark2 = 0x04`). Contains JSON with project configuration including the build type, references, version numbers, and other project settings. +Always present (`category = 0x04`). Contains JSON with project configuration including the build type, references, version numbers, and other project settings. ## Typical tree structures ### .twinproj (Standard EXE project) ``` -ROOT "NewProject" (kind=1, mark2=0x00) - DIR "Miscellaneous" (kind=2, mark2=0x06) - DIR "Packages" (kind=2, mark2=0x07) - DIR "ImportedTypeLibraries" (kind=2, mark2=0x05) - DIR "Resources" (kind=2, mark2=0x02) - DIR "ICON" (kind=2, mark2=0x00) - FILE "twinBASIC.ico" (kind=1, mark2=0x00) - DIR "Sources" (kind=2, mark2=0x03) - FILE "Form1.tbform" (kind=1, mark2=0x00) - FILE "Form1.twin" (kind=1, mark2=0x00) - FILE "Settings" (kind=1, mark2=0x04) - FILE ".meta" (kind=1, mark2=0x00) +ROOT "NewProject" (version=1, category=0x00) + DIR "Miscellaneous" (kind=2, category=0x06) + DIR "Packages" (kind=2, category=0x07) + DIR "ImportedTypeLibraries" (kind=2, category=0x05) + DIR "Resources" (kind=2, category=0x02) + DIR "ICON" (kind=2, category=0x00) + FILE "twinBASIC.ico" (kind=1, category=0x00) + DIR "Sources" (kind=2, category=0x03) + FILE "Form1.tbform" (kind=1, category=0x00) + FILE "Form1.twin" (kind=1, category=0x00) + FILE "Settings" (kind=1, category=0x04) + FILE ".meta" (kind=1, category=0x00) ``` ### .twinpack (distributed package) ``` -ROOT "CustomControlsPackage" (kind=1, mark2=0x00) - FILE "CHANGELOG.md" (kind=1, mark2=0x00) - FILE "LICENCE.md" (kind=1, mark2=0x00) - DIR "Miscellaneous" (kind=2, mark2=0x06) - FILE "frmTextbox.png" (kind=1, mark2=0x00) - DIR "ImportedTypeLibraries" (kind=2, mark2=0x05) - FILE "Settings" (kind=1, mark2=0x04) - DIR "Sources" (kind=2, mark2=0x03) - FILE "WaynesGrid.twin" (kind=1, mark2=0x00) - DIR "Resources" (kind=2, mark2=0x02) - DIR "MANIFEST" (kind=2, mark2=0x00) - FILE "#1.xml" (kind=1, mark2=0x00) - DIR "BITMAP" (kind=2, mark2=0x00) - FILE "twinBASIC.bmp" (kind=1, mark2=0x00) - DIR "Packages" (kind=2, mark2=0x07) +ROOT "CustomControlsPackage" (version=1, category=0x00) + FILE "CHANGELOG.md" (kind=1, category=0x00) + FILE "LICENCE.md" (kind=1, category=0x00) + DIR "Miscellaneous" (kind=2, category=0x06) + FILE "frmTextbox.png" (kind=1, category=0x00) + DIR "ImportedTypeLibraries" (kind=2, category=0x05) + FILE "Settings" (kind=1, category=0x04) + DIR "Sources" (kind=2, category=0x03) + FILE "WaynesGrid.twin" (kind=1, category=0x00) + DIR "Resources" (kind=2, category=0x02) + DIR "MANIFEST" (kind=2, category=0x00) + FILE "#1.xml" (kind=1, category=0x00) + DIR "BITMAP" (kind=2, category=0x00) + FILE "twinBASIC.bmp" (kind=1, category=0x00) + DIR "Packages" (kind=2, category=0x07) ``` ## Notes diff --git a/indexer/sample.twinpack b/indexer/sample.twinpack new file mode 100644 index 0000000000000000000000000000000000000000..841b6e283e5523be1ad41969c726391f77bdd707 GIT binary patch literal 154560 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 z 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 tree) -------------------------- @@ -32,8 +59,8 @@ function parse(buffer) { let pos = 0; let entryCount = 0; + function readU64() { const v = view.getBigUint64(pos, true); pos += 8; return Number(v); } function readU32() { const v = view.getUint32(pos, true); pos += 4; return v; } - function readU16() { const v = view.getUint16(pos, true); pos += 2; return v; } function readI16() { const v = view.getInt16(pos, true); pos += 2; return v; } function readU8() { const v = view.getUint8(pos); pos += 1; return v; } function readStr() { @@ -57,25 +84,32 @@ function parse(buffer) { `expected 0x${MAGIC.toString(16).padStart(8, '0').toUpperCase()}`); function readEntry() { + // At the root this 2-byte field is the file format version; everywhere + // else it is the entry kind (1 = file, 2 = directory). const kind = readI16(); - const name = readStr(); - const mark1 = readU16(); - pos += 10; // reserved padding -- always zeros - const mark2 = readU8(); entryCount++; + const isRoot = (entryCount === 1); + if (isRoot && kind !== FORMAT_VERSION) + throw new Error( + `Unsupported file format version: ${kind}, expected ${FORMAT_VERSION}`); + + const name = readStr(); + const revision = readU64(); + const flags = readU32(); + const category = readU8(); - if (kind === 1 && entryCount > 1) { + if (kind === 1 && !isRoot) { const content = readBlob(); const revisionCount = readU32(); const revisions = []; for (let i = 0; i < revisionCount; i++) revisions.push(readU32()); - return { kind: 'file', name, mark1, mark2, content, revisions }; + return { kind: 'file', name, revision, flags, category, content, revisions }; } const count = readU32(); const children = []; for (let i = 0; i < count; i++) children.push(readEntry()); - return { kind: 'directory', name, mark1, mark2, children }; + return { kind: 'directory', name, revision, flags, category, children }; } return readEntry(); @@ -87,9 +121,9 @@ function serialize(root) { const chunks = []; const encoder = new TextEncoder(); + function writeU64(v) { const b = Buffer.alloc(8); b.writeBigUInt64LE(BigInt(v)); chunks.push(b); } function writeU32(v) { const b = Buffer.alloc(4); b.writeUInt32LE(v); chunks.push(b); } function writeI16(v) { const b = Buffer.alloc(2); b.writeInt16LE(v); chunks.push(b); } - function writeU16(v) { const b = Buffer.alloc(2); b.writeUInt16LE(v); chunks.push(b); } function writeU8(v) { chunks.push(Buffer.from([v])); } function writeStr(s) { const e = encoder.encode(s); @@ -111,19 +145,20 @@ function serialize(root) { if (entry.kind === 'file' && !isRoot) { writeI16(1); writeStr(entry.name); - writeU16(entry.mark1 ?? 0x0002); - chunks.push(Buffer.alloc(10)); - writeU8(entry.mark2 ?? 0x00); + writeU64(entry.revision ?? 0x0002); + writeU32(entry.flags ?? FLAGS.None); + writeU8(entry.category ?? 0x00); writeBlob(entry.content); const revs = entry.revisions ?? []; writeU32(revs.length); for (const r of revs) writeU32(r); } else { - writeI16(isRoot ? 1 : 2); + // Root entry writes the format version; non-root directory writes kind=2. + writeI16(isRoot ? FORMAT_VERSION : 2); writeStr(entry.name); - writeU16(entry.mark1 ?? 0x0000); - chunks.push(Buffer.alloc(10)); - writeU8(entry.mark2 ?? 0x00); + writeU64(entry.revision ?? 0x0000); + writeU32(entry.flags ?? FLAGS.None); + writeU8(entry.category ?? 0x00); const children = entry.children ?? []; writeU32(children.length); for (const child of children) writeEntry(child); @@ -162,9 +197,8 @@ function doImport(inputPath, outputDir, { quiet = false } = {}) { // -------------------------- Export (disk -> binary) -------------------------- -function mark2For(name, isDir) { - if (isDir) return DIR_MARK2[name] ?? 0x00; - return name === 'Settings' ? 0x04 : 0x00; +function categoryFor(name) { + return CATEGORY_BY_NAME[name] ?? CATEGORY.Default; } function buildTree(dirPath) { @@ -180,14 +214,14 @@ function buildTree(dirPath) { for (const f of files) { children.push({ kind: 'file', name: f.name, - mark1: 0x0002, mark2: mark2For(f.name, false), + revision: 0x0002, flags: FLAGS.None, category: categoryFor(f.name), content: fs.readFileSync(path.join(dirPath, f.name)), revisions: [], }); } return { kind: 'directory', name, - mark1: 0x0000, mark2: mark2For(name, true), + revision: 0x0000, flags: FLAGS.None, category: categoryFor(name), children, }; } @@ -293,7 +327,7 @@ function selfTest() { }); test('Empty project round-trip', () => { - const tree = { kind: 'directory', name: 'Empty', mark1: 0, mark2: 0, children: [] }; + const tree = { kind: 'directory', name: 'Empty', revision: 0, flags: 0, category: 0, children: [] }; const rt = parse(serialize(tree)); eq(rt.name, 'Empty', 'name'); eq(rt.children.length, 0, 'children'); @@ -302,8 +336,8 @@ function selfTest() { test('Single-file project round-trip', () => { const content = Buffer.from('Hello twinBASIC'); const tree = { - kind: 'directory', name: 'Mini', mark1: 0, mark2: 0, - children: [{ kind: 'file', name: 'test.twin', mark1: 2, mark2: 0, content, revisions: [] }], + kind: 'directory', name: 'Mini', revision: 0, flags: 0, category: 0, + children: [{ kind: 'file', name: 'test.twin', revision: 2, flags: 0, category: 0, content, revisions: [] }], }; const rt = parse(serialize(tree)); eq(rt.children.length, 1, 'children'); @@ -312,6 +346,20 @@ function selfTest() { throw new Error('content mismatch'); }); + test('Flags field preserved on round-trip', () => { + const tree = { + kind: 'directory', name: 'WithFlags', revision: 0, flags: FLAGS.Hidden, category: 0, + children: [{ + kind: 'file', name: 'h.twin', + revision: 2, flags: FLAGS.Hidden | FLAGS.Virtual, category: 0, + content: Buffer.from('x'), revisions: [], + }], + }; + const rt = parse(serialize(tree)); + eq(rt.flags, FLAGS.Hidden, 'root flags'); + eq(rt.children[0].flags, FLAGS.Hidden | FLAGS.Virtual, 'file flags'); + }); + test('Bad magic rejected', () => { try { parse(Buffer.from('not a twinpack!!')); throw new Error('should have thrown'); } catch (e) { if (!e.message.includes('Bad magic')) throw e; } diff --git a/scripts/impexp.py b/scripts/impexp.py index 13d9ffaa..4f7cc97c 100644 --- a/scripts/impexp.py +++ b/scripts/impexp.py @@ -16,10 +16,33 @@ import sys MAGIC = 0xEA0BA51C - -DIR_MARK2 = { - 'Resources': 0x02, 'Sources': 0x03, 'ImportedTypeLibraries': 0x05, - 'Miscellaneous': 0x06, 'Packages': 0x07, +FORMAT_VERSION = 1 + +FLAGS_NONE = 0x00000000 +FLAGS_HIDDEN = 0x00000001 +FLAGS_SUPER_HIDDEN = 0x00000002 +FLAGS_VIRTUAL = 0x00000004 + +CATEGORY_DEFAULT = 0x00 +CATEGORY_REFERENCES = 0x01 # always virtual; never present in serialized files +CATEGORY_RESOURCES = 0x02 +CATEGORY_SOURCES = 0x03 +CATEGORY_SETTINGS = 0x04 +CATEGORY_IMPORTED_TYPE_LIBRARIES = 0x05 +CATEGORY_MISCELLANEOUS = 0x06 +CATEGORY_PACKAGES = 0x07 + +# Well-known entry names that get a non-default category on export. +# References is intentionally excluded -- it is materialised virtually by +# the IDE, never serialized, and tagging an on-disk folder with category +# 0x01 would confuse the IDE on import. +CATEGORY_BY_NAME = { + 'Resources': CATEGORY_RESOURCES, + 'Sources': CATEGORY_SOURCES, + 'Settings': CATEGORY_SETTINGS, + 'ImportedTypeLibraries': CATEGORY_IMPORTED_TYPE_LIBRARIES, + 'Miscellaneous': CATEGORY_MISCELLANEOUS, + 'Packages': CATEGORY_PACKAGES, } # -------------------------- Parser (binary -> tree) -------------------------- @@ -28,12 +51,12 @@ def parse(data): pos = [0] + def read_u64(): + v, = struct.unpack_from(' 1: + if kind == 1 and not is_root: content = read_blob() revision_count = read_u32() revisions = [read_u32() for _ in range(revision_count)] - return dict(kind='file', name=name, mark1=mark1, mark2=mark2, + return dict(kind='file', name=name, revision=revision, + flags=flags, category=category, content=content, revisions=revisions) count = read_u32() children = [read_entry() for _ in range(count)] - return dict(kind='directory', name=name, mark1=mark1, mark2=mark2, - children=children) + return dict(kind='directory', name=name, revision=revision, + flags=flags, category=category, children=children) return read_entry() @@ -90,15 +122,15 @@ def read_entry(): def serialize(root): chunks = [] + def write_u64(v): + chunks.append(struct.pack(' binary) -------------------------- -def _mark2_for(name, is_dir): - if is_dir: - return DIR_MARK2.get(name, 0x00) - return 0x04 if name == 'Settings' else 0x00 +def _category_for(name): + return CATEGORY_BY_NAME.get(name, CATEGORY_DEFAULT) def _build_tree(dir_path): @@ -204,12 +236,14 @@ def _build_tree(dir_path): content = fh.read() children.append(dict( kind='file', name=f, - mark1=0x0002, mark2=_mark2_for(f, False), + revision=0x0002, flags=FLAGS_NONE, + category=_category_for(f), content=content, revisions=[], )) return dict( kind='directory', name=name, - mark1=0x0000, mark2=_mark2_for(name, True), + revision=0x0000, flags=FLAGS_NONE, + category=_category_for(name), children=children, ) @@ -351,7 +385,7 @@ def t_disk(): def t_empty(): tree = dict(kind='directory', name='Empty', - mark1=0, mark2=0, children=[]) + revision=0, flags=0, category=0, children=[]) rt = parse(serialize(tree)) eq(rt['name'], 'Empty', 'name') eq(len(rt['children']), 0, 'children') @@ -359,10 +393,12 @@ def t_empty(): def t_single(): content = b'Hello twinBASIC' - tree = dict(kind='directory', name='Mini', mark1=0, mark2=0, + tree = dict(kind='directory', name='Mini', + revision=0, flags=0, category=0, children=[ - dict(kind='file', name='test.twin', mark1=2, - mark2=0, content=content, revisions=[]), + dict(kind='file', name='test.twin', + revision=2, flags=0, category=0, + content=content, revisions=[]), ]) rt = parse(serialize(tree)) eq(len(rt['children']), 1, 'children') @@ -371,6 +407,21 @@ def t_single(): raise AssertionError('content mismatch') test('Single-file project round-trip', t_single) + def t_flags(): + tree = dict( + kind='directory', name='WithFlags', + revision=0, flags=FLAGS_HIDDEN, category=0, + children=[ + dict(kind='file', name='h.twin', + revision=2, flags=FLAGS_HIDDEN | FLAGS_VIRTUAL, + category=0, content=b'x', revisions=[]), + ]) + rt = parse(serialize(tree)) + eq(rt['flags'], FLAGS_HIDDEN, 'root flags') + eq(rt['children'][0]['flags'], + FLAGS_HIDDEN | FLAGS_VIRTUAL, 'file flags') + test('Flags field preserved on round-trip', t_flags) + def t_bad_magic(): try: parse(b'not a twinpack!!')