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 | Enable/contact flag (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:
payload_len = 0x1000 * ((rows + 1) // 2 + 1)
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).
SCR row-topology blocks
SCR files store per-rung wire topology in structured blocks that precede each rung's instruction section. Each block encodes the right-wire and segment-flag data that clipboard stores per-cell in the grid.
[2B row_word][03 00 00] -- 5-byte header
[leading-row wire blocks...] -- rows before row 0 (count_down bridge)
[1B af_segment][1B entry_count][00] -- 3-byte trailer
[entry_count x (1B seg_flag, 1B col_idx)] -- row 0 flag table
[continuation-row wire blocks...] -- rows 1..N-1
[20 00] -- end marker
[wire_down data] -- per-column vertical wire indices
Each continuation-row wire block:
[1B seg][1B right_count][00 00][pairs of (col_idx, next_seg)...][final_col]
The block is parsed forward: leading-row blocks are consumed until the 3-byte trailer is found, then the row-0 flag table, then continuation rows, then the 0x0020 marker, then wire-down data.