Binary Format¶
This page documents the Click clipboard binary format as reverse-engineered from native captures. All offsets are hexadecimal.
Buffer layout¶
The clipboard buffer has these regions:
0x0000 +-----------------------+
| Global header | Fixed template data. Not modified by the encoder.
0x0254 +-----------------------+
| Program header | Row count word at +0x00.
0x0260 +-----------------------+
| Rung 0 preamble | Comment flag +0x30, length +0x34.
0x0298 +-----------------------+
| Payload region | Comment RTF body (variable length, may be empty).
| | When empty, this region is zero-length and the
| | grid starts immediately at 0x0A60.
0x0A60 +-----------------------+ <-- grid start (in no-payload buffer)
| Cell grid | 32 cells/row. Wire-only rows: 0x800 bytes/row.
| | Instruction rows are larger (variable-length cells).
| | Pushed forward by payload_len when a comment exists.
+-----------------------+
| Page padding | Zero-filled to next 0x1000 (4096) boundary.
+-----------------------+
Global header (0x0000–0x0253)¶
Fixed template data loaded from the scaffold binary. Contains GUI state and format markers. The encoder does not modify this region.
Program header (0x0254–0x025F)¶
A 12-byte structure immediately before the rung 0 preamble:
| Offset | Size | Field | Value |
|---|---|---|---|
| +0x00 | 2B | row_word | total_grid_rows * 0x20 |
| +0x02 | ... | (other) | GUI state, not load-bearing for paste |
total_grid_rows includes data rows for all rungs, preamble rows for rungs 1+, and one terminal row. For a single N-row rung: total_grid_rows = N + 1.
Rung preamble¶
Every rung has a 0x40-byte preamble that holds its comment data:
| Rung | Location |
|---|---|
| Rung 0 | Fixed at 0x0260 (between program header and cell grid) |
| Rung N>0 | Cell 0 of the preamble row preceding the rung's data rows |
Comment fields within the preamble:
| Offset | Size | Field |
|---|---|---|
| +0x30 | 1B | Comment flag (1 = has comment) |
| +0x34 | 4B | Comment body length (uint32 LE) |
| +0x38 | var | Comment body (RTF) |
Payload region and the push model¶
When rung 0 has a comment, the RTF body is inserted at 0x0298 (preamble +0x38). This pushes the cell grid forward by payload_len bytes — everything after the insertion point shifts.
The encoder builds the grid as a byte blob (concatenated cell objects) appended to the header. The insertion at 0x0298 pushes the grid bytes forward by payload_len, so everything lands at the correct absolute addresses in the final buffer.
The final buffer is padded to the next 0x1000 boundary. For wire-only rungs, the pre-padding size is GRID_FIRST_ROW_START + rows * GRID_ROW_STRIDE + payload_len. Instruction cells are variable-length, so rows with instructions exceed the 0x800-byte baseline.
Comment sizing¶
- Maximum comment body: 1400 bytes (enforced by the encoder)
- The practical limit per row count depends on where
minimal_end + payload_lencrosses a page boundary - Example: 2-row rung — body up to 1324 bytes stays at 0x2000 total; 1325+ bumps to 0x3000
RTF envelope¶
Comments are stored as RTF with a fixed prefix and suffix:
{\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl{\f0\fnil\fcharset0 Arial;}}
\viewkind4\uc1\pard\fs20 <body>
\par }
Body encoding:
- Plain text: cp1252-encoded directly
- Bold:
{\b text} - Italic:
{\i text} - Underline:
{\ul text} - Multi-line:
\parbetween lines (not\line)
Cell grid¶
The cell grid starts at 0x0A60 (before any payload push). Each row has 32 cells. Wire-only rows are 0x800 bytes (32 x 0x40). Rows with instruction cells are larger because instruction cells are variable-length. Columns 0–30 are condition columns (A–AE); column 31 is the AF (output) column.
Cell structure (wire/blank cells)¶
Wire and blank cells are exactly 0x40 bytes: a 0x25-byte header, 0x0B bytes of padding, and a 16-byte tail.
Header (0x25 bytes):
| Offset | Size | Field |
|---|---|---|
| +0x00 | 1B | Always 0x00 |
| +0x01 | 4B | Column index (uint32 LE) |
| +0x05 | 4B | Row byte (uint32 LE, global_row + 1) |
| +0x09 | 1B | Row span (0x01 for single-row, 0x02+ for multi-row AF) |
| +0x0A | 1B | Visual sub-rows (0x01 for single-row, 0x02+ for timers) |
| +0x0B–0x0C | 2B | Structural: +0x0C = 0x01 in wire-only rungs, 0x00 in instruction-bearing rungs |
| +0x0D | 4B | Instruction index (int32 LE; 0xFFFFFFFF for data cells) |
| +0x11 | 4B | Structural flag (always 0x00000001) |
| +0x15 | 4B | Row-start flag: stored row bit 0 at column A, zero elsewhere (uint32 LE) |
| +0x19 | 4B | Segment flag (uint32 LE) |
| +0x1D | 4B | Right flag (uint32 LE) |
| +0x21 | 4B | Down flag (uint32 LE) |
Tail (16 bytes, at +0x30):
| Offset | Field |
|---|---|
| +0x08 | Marker (0x01 for condition cells and non-last-row AF data cells) |
| +0x09 | Rung index |
| +0x0C | Instruction count (AF data cell, last row of last rung only) |
| +0x0D | Row hint (condition: local_row + 1; AF last rung: local_row + 2) |
Cell structure (instruction cells)¶
Instruction cells are composite: they carry wire flags (right=1 for contacts, right+down for T-junction contacts) with instruction data layered on top. The cell is variable-length: 0x25-byte header + instruction blob + 16-byte tail. See instruction blobs for the blob format.
Cell boundary detection¶
To walk a variable-length grid, detect cell boundaries by signature:
+0x00 == 0x00+0x01 == col(expected column index)+0x05 == row_byte(expected row)+0x09 == 0x01+0x0A == 0x01
Do not use +0x0D for detection — it varies between 0x00, 0x01, and 0xFF across cell types.
Multi-rung format¶
Multi-rung buffers are not concatenated single-rung buffers. They share a single global header and program header, with interleaved data and preamble rows in one cell grid:
[rung 0 data rows] [rung 1 preamble row] [rung 1 data rows] [rung 2 preamble row] ... [terminal row]
The program header's row_word reflects the total grid row count across all rungs.
Only rung 0's comment payload lives in the payload region (pushed at 0x0298). Rung N>0 comments are stored inline in their preamble row's cell 0, at the same +0x30/+0x34/+0x38 offsets.
Page alignment¶
All buffers are padded with zero bytes to the next 0x1000 (4096 byte) boundary. The minimum buffer size for a 1-row rung with no comment is 0x2000 (8192 bytes).
Empty multi-row synthesis¶
Empty rung buffers for N rows (1..32) are synthesized deterministically from a minimal scaffold binary. Payload length formula:
This produces the correct buffer for any row count without needing 32 separate template files.
Instruction blob tag wire types¶
Instruction blobs use tagged fields where each tag is a 2-byte LE value. The tag's high byte encodes the wire type — i.e. how many bytes follow the tag and how to interpret them:
| High byte | Wire type | Payload |
|---|---|---|
| 0x11, 0x12 | flag | No payload (tag presence is the signal) |
| 0x20, 0x21, 0x22 | byte | 1 byte |
| 0x32 | u16 | 2 bytes (uint16 LE) |
| 0x3A | variant_u16 | Sequence of [uint16 index, uint16 value] pairs, terminated by 0xFFFF |
| 0x60, 0x61, 0x62 | string | [1B length][UTF-16LE value] |
| 0x68 | variant_string | Sequence of [uint16 index, 1B length, UTF-16LE value] entries, terminated by 0xFFFF |
This rule applies identically to both clipboard and SCR instruction blobs — the tag IDs, wire types, and operand values are the same in both formats. Only the framing differs (see below).
Clipboard vs SCR framing¶
The instruction blob content (tag IDs, operand values, wire types) is identical between clipboard and SCR formats. The difference is how blobs are framed:
Clipboard: blobs are embedded in the cell grid. Each instruction cell is a 0x25-byte cell header + blob + 16-byte cell tail. The blob boundary is found by scanning tagged fields (no explicit length). The 4-byte sentinel FFFFFFFF precedes each string value.
SCR (Scr*.tmp): blobs are stored in instruction sections with explicit framing. Each blob has embedded cell-header fields and an end_offset pointer:
[1B class_name_len][UTF-16LE class_name][2B type_code]
[1B row_span][1B pad][2B structural][2B instr_index][1B visual_sub_rows]
[visual_sub_rows counting bytes][4B end_offset]
[tagged fields...]
The embedded fields at offsets +0 through +6 after the type code correspond to clipboard cell header offsets +0x09 through +0x10. The end_offset is an absolute file pointer to the blob boundary — the same boundary that clipboard's find_blob_boundary() derives by scanning tags. Tags use length-prefixed strings (no FFFFFFFF sentinel).
The byte at end_offset is a 1-byte trailer length (observed 0 or 1): the next structure starts at end_offset + 2 + data[end_offset]. Certain instruction encodings (RD/SD always; Copy/Math/Out/Drum sometimes) emit a 1-byte trailer whose value is opaque.
SCR header¶
After the SC-SCR magic and opaque header bytes, offset 0x40 holds the u16 program index. Offset 0x42 is the byte length of the following UTF-16LE name. Immediately after the name:
[u16 column_count]
column_count x [u16 numeric column_width] -- includes AF
[u8 display_flags]
[u16 rung_count]
rung_count x RUNG
Widths resembling UTF-16 letters are numeric widths, not condition-family codes. The final width belongs to AF; it is not a file marker. The display byte contains independent settings:
| Mask | Setting |
|---|---|
0x01 |
Nicknames |
0x02 |
Address Comments |
0x04 |
Rung Comments |
0x08 |
Freeze Pane Coil Area |
The common value 0x0D enables nicknames, rung comments, and the frozen coil area. It is not a delimiter. Header parsing retains the byte internally without requiring a particular setting; the public Program model exposes the name, index, and semantic rungs, not editor display settings.
SCR rung records¶
Every rung, including rung zero, has the same framing:
RUNG = [u16 rung_index] -- zero-based ordinal, asserted sequential
[u32 rtf_len][rtf body]
TOPOLOGY
[u16 instr_count] -- 0 = no instruction section
[u32 section_marker] -- only when instr_count > 0; opaque
instr_count x ENTRY
ENTRY = [u8 stored_row][u8 col][u8 b2][u8 b3][u8 entry_seq][3B opaque] + blob
Instruction rows start at one because stored row zero is the special comment/preamble row. Blob pointers and trailer lengths determine the next entry position (see above). The apparent old u32 rung count was the u16 count followed by rung zero's u16 index.
Files with prog_idx == 1 have a two-byte tail after the last rung; subroutines end at the last record. The parser enforces the declared count, sequential indices, and final cursor. Malformed framing raises an offset-bearing error; it never searches for a later rung to resume decoding.
CLICK keeps trailing content-less editor rungs, sometimes fully wired. The decoder parses them and then omits trailing records with neither instructions nor a raw comment. This existing semantic-output policy is separate from structural parsing.
SCR row-topology blocks¶
[u16 stored_row_count] -- includes the special first row
stored_row_count x ROW
[u16 down_column_count] -- normally 32; a count, not an end marker
down_column_count x DOWN_LIST
ROW = [u8 row_flags][u16 entry_count]
entry_count x [u8 segment][u8 column]
DOWN_LIST = [u16 count][count x u8 stored_row_index]
The first stored row is the comment/preamble row. CLICK initializes it with flags 3 and the next ordinary row with flags 1. 03 00 00 means flags 3 and zero entries; 03 20 00 means flags 3 and 32 entries. Both are ordinary instances of the counted grammar. The latter occurs in tumbler/subroutines/Rotate.scr, where the former decoder incorrectly invoked recovery.
Rows retain their flags and placement-ordered entries in the internal parser. Native column orders include [6,1,0,3,2,4,5]; order must not be required to ascend. The semantic rung builder uses the column set for wire presence.
| SCR data | Clipboard field | Meaning established by native writer |
|---|---|---|
| Ordinary row flags bit 0 | +0x15 |
Set only in column A when the row bit is set |
| Entry segment bit 0 | +0x19 |
Copy the persisted bit for that column |
| Column occurs in row entries | +0x1D |
Right wire is present |
| Row occurs in column's down list | +0x21 |
Down wire is present |
The row flag is not the AF segment flag. Row flags bit 1 is also serialized; the special first row has this bit set, but its complete editing semantics remain uncharacterized. The parser accepts both low row bits. Entry flags currently support bit 0.
Down lists refer to stored row indices starting at one. The decoder subtracts one to obtain ordinary grid coordinates, and rejects zero or out-of-range references. These lists include down wires carried by contact cells, so they cover more than visible T/| wire tokens.
Stored rows and canonical output¶
Ordinary stored rows map directly to grid rows. The decoder preserves their positions and horizontal and vertical connections. Row and segment flags do not determine whether a row exists.
Instruction placement is separate from stored wire entries: an instruction can occupy a cell without a right-wire entry. An AF right-wire becomes a NOP only when no instruction occupies or covers that cell. Covered NOPs are omitted from the decoded instructions; the surrounding wiring remains.
The public Rung model retains instructions and wire geometry, but does not retain raw row or segment flags. Re-encoding constructs flags using the conventions in wire rendering, so it does not preserve every byte of native editor state. Trailing records without instructions or comments are omitted from the decoded program.
Native evidence¶
These corrections were checked against CLICK Ver3.92's native x86 CLICK.exe on 2026-09-10 (SHA-256 E31A8130BC6D6261BAC841AF3062DAB70DD68B4307A1B3B58DA37F994C6FE13A). Addresses are preferred-image virtual addresses, image base 0x400000:
- Program writer
0x5A77F0: numeric widths at0x5A791Cwith scaling at0x5A794B; display bits at0x5A79D5; u16 rung count backpatch at0x5A7B9B; u16 rung indices at0x5A7AD0. - Display toggle handlers
0x5BF360/0x5BF430/0x5BF500/0x5B1A00toggle the four serialized fields. MFC command IDs0x8036/0x802D/0x8095/0x8090and executable string resources supply the setting names above. - Rung serializer
0x61C540: stored row count at0x61C637, row flags at0x61C66F, u16 entry counts at0x61C6DD, pair bytes at0x61C754/0x61C795, down-column count at0x61C7F7. - Clipboard writer
0x5BC2F0: row-bit/column-A tests at0x5BC4DB/0x5BC4E1, entry bit at0x5BC54B, right presence at0x5BC552, down-list matching from0x5BC5B4. Native record origins are eight bytes before this document's clipboard cell origins.
The investigation walked 39 SCR files / 704 records without resynchronization. Comparing four flag mappings at 46,208 cell positions found no mismatches outside the synthetic time_drums pair. These are static-binary and fixture results, not a new live paste/reopen validation. InstCtl_Drum.dll confirms a four-row instruction footprint; it does not establish the disputed row-removal behavior.