Skip to content

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_len crosses 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: \par between 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.