# XIE 2.3 — the minimum path to put one 1-bit image on the VT1200 > **See `GHIDRA_XIE_PIPELINE.md`** (final pass): the BadMatch gate is > resource byte 5 bit 0, set from the create request's level word at byte 28; > with a non-zero level entry the create (slot 8 op 2) produces an exportable > class-2 resource. Exact 12/8/16-word payloads are there. Third pass. Program `vt1200_v22.bin` (md5 `b46a3e442ccb86134d2356bd70d22fd2`). **The Ghidra session from passes 1–2 is gone**; I re-opened the ROM and this report's addresses come from the new session `a03f3d788b874b1a976a55417ab9f2d3` (same program, same base `rom:00000000`, same language `tms34010:LE:32:default`). All addresses are file/byte offsets, so they are identical to the earlier reports; `mame_bit = 0x3F200000 + off*8`. New raw material: `xie/ghidra-evidence/image_path/swapper_slotNN_HHHHHH.txt` — the 24 request byte-swappers, one per implemented slot. **These give the exact 32-bit field map of every request**, which is the main new evidence here. Read with passes 1 and 2 (`GHIDRA_XIE.md`, `GHIDRA_XIE_HANDLERS.md`). --- ## 1. Executive summary **Nothing in the ROM creates an image resource through a plain single-id request.** Every 2-word and 3-word resource slot (11, 12, 13, 15, 22, 24, 33, 40, 44) begins with `LookupPhotos` and answers `BadIDChoice` for an unused id — that was measured on the wire in pass 2 and it is confirmed structurally here. Creation must therefore live inside the **multi-operation classes 7, 8, 10**, and those are the only slots whose selector semantics are still open. What *is* now nailed down: * **Request field maps for all 25 slots**, from the byte-swappers (§3). Slots 7, 9 and 10 carry nothing but 32-bit words; slots 12 and 22 are literally `{header, id}`. * **Slot 19 is the tile-put request.** It clamps (x, y), adds (w, h) to get the right/bottom edges, rejects a fully clipped rectangle with `BadValue`, iterates `count` elements writing x and y into per-element records, and uses the request tail as pixel data. Layout: `{id@4, x@8, y@12, w@16, h@20, count@24}` + payload (§4). This is DEC's `XIEP_PUT_TILE` ("Transports random tiles of uncompressed image data from the client to the server") — confidence **high** on structure, **medium** on the name. * **Slot 21 is the export candidate**: 16 words, `{id@4, then ten 32-bit words at 8…44, two 16-bit sub-words}`, and it starts by resolving the id through `LookupPhotos` — i.e. "take this image resource and produce something from it". DEC: `XIEP_EXPORT` "Creates a new server PIXMAP resource object". * **Slots 20 and 43** are the only ones shaped like a *fixed* descriptor (35 words / 10 words); slot 20 additionally seeds a 25-element notion. **The biggest blocker** is a single missing fact: **which request creates the image resource, and what class code it wants.** Without it nothing can be put or exported. Second blocker: whether the export targets a client-created Pixmap (5.0 model) or creates one itself (DEC wording). §7 gives the exact probes that settle both in four requests. --- ## 2. Slot table — resource / transport / export family "fields" is the set of 32-bit request words proven by the byte-swapper (`w` = word index, byte offset = 4×w), so it is the *wire* layout, not an inference. | slot | best name | conf | 5.0 / DEC counterpart | words | 32-bit fields (byte off) | reply | |---|---|---|---|---|---|---| | 7 | **resource create class** — op 1/2 = create-by-value / by-reference | [I] | `XIEP_CREATE_BY_VALUE`, `XIEP_CREATE_BY_REFERENCE` | 4 | 4 (op), 8 (id), 12 (class) | no | | 8 | resource class, 4 sub-handlers (ops 1,2,4,5,6) | [I] | resource services | ≥3 | 4, 8, 12, 16, 20 | no | | 9 | 3-word op on a resource | [U] | — | 3 | 4, 8 | no | | 10 | 5-operation class (query/export shaped) | [I] | `XIEP_QUERY_RESOURCE` / `XIEP_QUERY_EXPORT` / `XIEP_EXPORT` | 3 | 4 (op), 8 | **yes** | | 11 | resource op with a second parameter | [U] | — | 3 | 4, 8 | no | | 12 | **delete / free** (id only) | [I] | `XIEP_DELETE_RESOURCE`, `XIEP_FREE_EXPORT` | 2 | 4 | no | | 13 | delete / free (id only) | [I] | as above | 2 | 4 | no | | 14 | resource op, id + one long + two half-words | [U] | — | 4 | 4, 8 | no | | 15 | pipeline op (photoflo family) | [U] | `XIEP_BIND_PHOTOFLO`? | 3 | 4, 8 | no | | 18 | pipeline op | [U] | `XIEP_EXECUTE_PHOTOFLO`? | ≥4 | 4, 8 | no | | 19 | **PUT_TILE** — x/y/w/h + counted payload | [C] structure | `XIEP_PUT_TILE` | ≥8 + data | 4, 8, 12, 16, 20, 24 | no | | 20 | fixed descriptor (photoflo definition) | [I] | `XIEP_EXECUTE_PHOTOFLO` / `CREATE_PHOTOFLO` | 35 | 4, 8, 16 (+16-bit at 12) | no | | 21 | **EXPORT** — id + big parameter block | [I] | `XIEP_EXPORT` | 16 | 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44 | no | | 22 | **delete / free** (id only) | [I] | `XIEP_DELETE_RESOURCE` | 2 | 4 | no | | 24 | **QUERY_RESOURCE** (only photo slot with a reply) | [C] | `XIEP_QUERY_RESOURCE` | 4 | 4, 8 (+16-bit at 12) | **yes** | | 33 | generic photo op, class from the request | [I] | `XIEP_QUERY_EXPORT`? | 4 | 4 (id), 8 (class), 12 | no | | 40 | generic photo op, class from the request | [I] | — | 4 | 4, 8 | no | | 43 | large photo op (10 words) | [U] | `XIEP_PUT_STREAM`? | 10 | 4, 8, 16, 20, 24 (+ one 16-bit field near 14) | no | | 44 | generic photo op, class from the request | [I] | — | 5 | 4, 8, 12, 16 | no | Session slots (1–6) are unchanged from pass 2: 1 `INIT_SESSION`, 2 `TERM_SESSION`, 3 `QUERY_OP_DEFAULTS`, 4 `SET_OP_DEFAULTS`, 5 mode-selecting setter, 6 `QUERY_EVENTS`. --- ## 3. How the field maps were recovered [C] For a byte-swapped client, `XieSProc` calls the per-slot stub; each stub byte-swaps `stuff->length` and then every 32-bit request word in place, then re-dispatches into `XieProc`. One 32-bit word at byte *B* produces three `MOVB` instructions: ``` rom:00069c04 MMTM SP,0x74 ; slot 11's swapper rom:00069c0a MOVE *A13(0xffe0),A11,0x1 rom:00069c0e MOVE *A11(0x40),A10,0x1 ; stuff rom:00069c12 MOVB *A10(0x10),*A9 ; ---- swap stuff->length rom:00069c16 MOVB *A10(0x18),*A10(0x10) rom:00069c1c MOVB *A9,*A10(0x18) rom:00069c20 MOVB *A10(0x20),*A9 ; ---- word at byte 4 rom:00069c24 MOVB *A10(0x38),*A10(0x20) rom:00069c2a MOVB *A9,*A10(0x38) rom:00069c2e MOVB *A10(0x28),*A9 ; (bytes 5/6 of the same word) rom:00069c32 MOVB *A10(0x30),*A10(0x28) rom:00069c38 MOVB *A9,*A10(0x30) rom:00069c3c MOVB *A10(0x40),*A9 ; ---- word at byte 8 ... rom:00069c5e MOVE *A10(0x8),A11,0x0 ; minor opcode rom:00069c64 ADDI 0xd3f90,A11 ; -> XieProc's proc table ``` Applied to all 24 stubs, the swapped-word sets are: | slot | 32-bit words at byte | reading | |---|---|---| | 1 | — (16-bit at 4 and 6 only) | version pair `{major, minor}` | | 2 | — | no arguments at all | | 3 | 4 | one 32-bit argument (the 2.3 handler ignores it) | | 4 | 4 | one 32-bit value → per-client record | | 5 | — | every argument is ≤16 bits | | 7 | 4, 8, 12 | `{op, id, class}` | | 8 | 4, 8, 12, 16, 20 | parameter run | | 9, 10 | 4, 8 | `{op, value}` | | 11 | 4, 8 | `{id, param}` | | 12, 13, 22 | 4 | `{id}` | | 14 | 4, 8 | `{id, param}` + half-words | | 15 | 4, 8 | `{a, b}` | | 18 | 4, 8 | `{a, b}` | | 19 | 4, 8, 12, 16, 20, 24 | `{id, x, y, w, h, count}` | | 20 | 4, 8, 16 | plus 16-bit fields at 12 | | 21 | 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44 | id + ten parameters | | 24 | 4, 8 | `{id, param}` | | 33 | 4, 8, 12 | `{id, class, param}` | | 40 | 4, 8 | `{id, class}` | | 43 | 4, 8, 16, 20, 24 (+16-bit at 14) | | | 44 | 4, 8, 12, 16 | | This is why slots 12/13/22 are "delete shaped" and 33/40/44 are "generic photo handle" slots: 33 passes `stuff->class` (byte 8) as the last argument to `LookupPhotos`, so the *client* names the resource class, while 11/12/13/14/15/ 21/22/24 hard-code it (`0xFFFF` = any, or `0`). --- ## 4. Slot 19 = the tile-put path [C for structure] Length check and the identification call: ``` rom:0006a782 MOVE *A9(0x10),A11,0x0 ; stuff->length rom:0006a786 SUBK 0x8,A11 rom:0006a788 JRNC 0x0006a790 ; length >= 8 words rom:0006a78a MOVK 0x10,A8 ; else BadLength rom:0006a790 MOVE A14,-*SP,0x1 rom:0006a792 MOVE *A9(0xc0),*A14+,0x1 ; arg = stuff word @ byte 24 rom:0006a796 MOVK 0x3,A11 rom:0006a798 MOVE A11,*A14+,0x1 ; arg = 3 rom:0006a79a MOVE *A9(0x20),*A14+,0x1 ; arg = id @ byte 4 rom:0006a7a2 MOVE A7,*A14+,0x1 rom:0006a7aa MOVE *A13(0xffe0),*A14+,0x1 rom:0006a7ae CALLA 0x07eab966 ; 0x6B966 (pipeline attach, arg 3) ``` Then the rectangle algebra, which is what makes this the put path: ``` rom:0006a7be MOVE *A9(0x40),A11,0x1 ; w1 = x (byte 8) rom:0006a7c2 JRLE 0x0006a7ca ; clamp to 0 rom:0006a7cc MOVE A11,*A13(0x80) ; local x rom:0006a7d0 MOVE *A9(0x60),A7,0x1 ; w2 = y (byte 12) rom:0006a7de MOVE A7,*A13(0xa0) ; local y rom:0006a7e2 MOVE *A9(0x80),A7,0x1 ; w3 = width (byte 16) rom:0006a7e6 MOVE *A9(0x40),A5,0x1 ; + x rom:0006a7ea ADD A5,A7 rom:0006a7ee MOVE *A5(0x80),A5,0x1 ; compare with dst width -> clip ... rom:0006a812 MOVE *A9(0xa0),A7,0x1 ; w4 = height (byte 20) rom:0006a816 MOVE *A9(0x60),A5,0x1 ; + y -> clip ... rom:0006a84e JRLE 0x0006a854 rom:0006a850 MOVE A5,A8 rom:0006a852 JRGT 0x0006a87c ; rectangle non-empty -> go on rom:0006a856 MOVK 0x2,A5 ; else error 2 = BadValue rom:0006a85a MOVE *A9(0x20),*A14+,0x1 ; (client, major, minor, id, 2) rom:0006a870 CALLA 0x07effa5e ; SendErrorToClient ``` and the payload loop: ``` rom:0006a88c MOVE *A9(0xc0),*A13(0x40) ; loop counter = word @ byte 24 rom:0006a896 JREQ 0x0006aa28 ; zero elements -> skip rom:0006a89e BTST 0x0,A11 ; bit 0 of that word = direction/flag rom:0006a8ac SLL 0x5,A5 ; element stride 32 bits rom:0006a8b0 MOVE *A5(0x160),A5,0x1 rom:0006a8b4 MOVE *A13(0x80),*A5(0xa8),0x1 ; element.x = x rom:0006a8c6 MOVE *A13(0xa0),*A5(0xe8),0x1 ; element.y = y rom:0006a8d8 MOVE *A13(0x80),A7,0x1 rom:0006a8dc MOVE *A13(0xc0),A11,0x1 rom:0006a8e0 ADD A11,A7 ; x += width ``` **Layout (bytes):** `0 hdr | 4 id | 8 x | 12 y | 16 width | 20 height | 24 count | 28 two 16-bit fields (not byte-swapped as a long) | 32… payload`. Minimum length 8 words = 32 bytes, i.e. header only; pixel bytes follow and are covered by the same `length`. Not proven: whether `count` counts bytes, rows, or tile records, and whether the payload is raw 1-bit rows packed 8-per-byte or bit-addressed. The `BTST 0x0` on the same word suggests a flags/count word; the loop's stride-32-bit element walk suggests it is a *count of tiles*, not bytes. --- ## 5. Export candidates **Slot 21** — 16 words, resolves the id then works on it: ``` rom:00062d58 SUBK 0x10,A11 / JREQ ; exactly 16 words rom:00062d68 MOVE *A9(0x20),*A14+,0x1 ; arg = id @ byte 4 rom:00062d6c MOVE A13,A7 / MOVE A7,*A14+ ; out pointer 1 rom:00062d72 MOVE A13,*A14+ ; out pointer 2 rom:00062d78 CALLA 0x07ea6fa0 ; LookupPhotos(client, id, &a, &b, 0) rom:00062d84 JRNE 0x000630ea ; lookup error -> return it rom:00062d88 MOVE *A13(0x20),A11,0x1 ; the resolved resource ``` It then walks the ten 32-bit parameters at bytes 8…44. A 44-byte parameter block after a resource id is exactly what an export descriptor looks like (destination pixmap, x, y, w, h, visual/format, stride, flags). **[I]** **Slot 10** — five operations, the only multi-op slot that replies, so if 2.3 has a "query export context" it is one of its five ops. Its request is only three words `{op@4, value@8}`, so its ops must be id-free or take the id in `value`. **[I]** **Slots 33/40/44** pass a client-chosen class to `LookupPhotos` and then a parameter; they are the "do something to resource of class " form and are the most likely home of export-by-class. **[I]** --- ## 6. Best sequence Everything is little-endian; `len` is in words including the 4-byte header. Step confidence in brackets. ``` [0] connect, QueryExtension("Xie") -> opcode 129 [C] [1] INIT_SESSION minor 1, len 2 02 00 03 00 (client speaks 2.3; any value accepted) -> 64-byte reply, major 2 minor 3 [C] [2] CREATE RESOURCE minor 7, len 4 [I] op = 01 00 00 00 id = 2a 00 00 00 (0x2A = our photomap id) class = ?? ?? ?? ?? (see probe P1) -> expect Success (no packet). BadRequest with bad=0x2a means the class word or op is wrong. [3] PUT_TILE minor 19, len 8 + payload [I-high] 00 00 00 00 | 2a 00 00 00 | 00 00 00 00 | 00 00 00 00 | 40 00 00 00 | 40 00 00 00 | | 00 00 00 00 | <512 bytes of 1-bit pixel data = 128 words> total len = 136 -> Success, or BadValue(2) if the rectangle clips away entirely, BadIDChoice(14) if the id did not resolve. [4] EXPORT minor 21, len 16 [I] id = 2a 00 00 00, then ten parameter words (probe P2) -> Success expected; the pixmap id is client-supplied (DEC wording) [5] TERM_SESSION minor 2, len 1 [C] -> no packet; just close. ``` ### 6.1 The 64×64 checkerboard 64 px × 64 rows at 1 bit = 8 bytes/row = 512 bytes = 128 words. A checkerboard of 8×8 blocks is a single repeated 32-bit pattern per byte-pair; the simplest generator is "row r, byte b: `0xAA` if ((r/8 + b) & 1) else `0x55`". ``` row 0..7 : AA 55 AA 55 AA 55 AA 55 (per byte, 8 bytes) row 8..15 : 55 AA 55 AA 55 AA 55 AA ... ``` Which of the two bit orders is "on" does not matter for a first display test. ### 6.2 Shorter path, if it exists If `XIEP_EXPORT` really creates the pixmap itself, step [4] returns the pixmap id in the slot-10 reply and the client can skip creating a pixmap. If instead the 5.0 model holds, the client must first `CreatePixmap(depth=1, 64x64)` and pass that XID in step [4]; then `XCopyArea(pixmap -> window)` displays it. Both variants use the same four XIE requests; only the pixmap's origin differs. --- ## 7. Probes that settle the open fields (for the worker with server access) Each probe is one connection: INIT (minor 1) → probe → TERM (minor 2). | id | request | variants | observation | |---|---|---|---| | P1 | create: minor 7, len 4, `{op, id=0x2A, class}` | class ∈ {0, 1, 2, 3, 4}, op ∈ {1, 2} | the pair that returns **Success** is create + the image class. `BadRequest` (code 1) with `bad=0x2a` ⇒ id rejected; `BadIDChoice` (14) ⇒ op is a lookup, not a create | | P2 | export: minor 21, len 16, `{id=0x2A, …}` | zero-fill the ten parameters, then vary word at byte 8 (likely the destination pixmap or a format) | `BadIDChoice` ⇒ slot 21 is not the export from this id; Success ⇒ export, and the reply/absence of a reply tells whether the pixmap comes from the client | | P3 | put: minor 19, len 8 (header only, no data) and len 136 with a checkerboard | count word at byte 24 ∈ {1, 64, 128, 512} | Success on the first, and `BadValue` on a zero-size rect, confirms the geometry; a length-related error identifies the count unit | | P4 | query after put: minor 24, len 4, `{id=0x2A, 0}` | — | a reply whose words change after P3 proves the data landed (the only photo slot that can reply) | | P5 | delete: minor 12 and 22, len 2, `{id=0x2A}` | — | whichever returns Success deletes the resource (the other is `FREE_EXPORT`) | Order matters: P1 first (nothing else can work without a resource), then P3, then P4 to prove the pixels landed, then P2. --- ## 8. Server-side constraints visible in the ROM * **Every handler checks `stuff->length` exactly** (or `>=` for 8, 18, 19); a wrong length always yields `BadLength` (16) with `minor_op = 0` (pass 1 §7). A 2.3 client must therefore get every length exactly right. * Slot 19's clipping uses the *destination* resource's width/height read from the resolved resource record (`*A5(0x80)`, `*A5(0xa0)`), so the image resource must carry real dimensions before a put. That is the strongest argument that the create step (P1) supplies width/height somewhere — note slot 7's request is only `{op, id, class}`, so dimensions most likely come from **slot 8 or 21** rather than 7. **This is the main open risk in the sequence above.** * Slot 19 rejects a fully clipped rectangle with `BadValue` (2) and a bad id with `BadIDChoice` (14), both carrying the real minor opcode. * Export target type (Pixmap vs Window) is not visible in any handler I read; DEC's manual says a *PIXMAP* resource is created. **[U]** * 1-bit only: the terminal is `StaticGray`, effective depth 1 (live probe), and DEC's restrictions say "resource-manipulation parameter values are restricted to those compatible with bitonal images". --- ## 9. Why this should display `XIE 2.3` on this server is the DEC/AGE-Logic image pipeline: a resource is created and filled by transport (`PUT_TILE`), then *exported* into a pixmap the X server owns. Once the pixels are in a pixmap, an ordinary X client can `XCopyArea` them into the Terminal Manager's window — no XIE involvement after the export. The three steps map one-to-one onto DEC's documented functions (`XIEP_CREATE_*` → `XIEP_PUT_TILE` → `XIEP_EXPORT`), the field maps come from the server's own byte-swappers rather than from guesswork, and slot 19's geometry code is unmistakably a clipped rectangle blit. The uncertainty is in *which* slot implements create, not in whether the pipeline exists. --- ## 10. Confirmed / Inferred / Unknown **Confirmed** 1. Request field maps for all 25 implemented slots (§3), from the 24 byte-swap stubs. 2. Slot 19 is a clipped-rectangle tile put with `{id, x, y, w, h, count}` and a payload, minimum 8 words (§4). 3. Slots 12, 13 and 22 are `{header, id}` only; 11/14/15/18/24 take `{id, one more long}`; 33 takes `{id, class, param}`. 4. Slot 21 resolves an id and then reads ten 32-bit parameters. 5. Slots 1–6 are the session/event group (pass 2). **Inferred** 1. Slot 7 (ops 1/2) is create-by-value / create-by-reference. 2. Slot 21 is export; slot 10 hosts query-export; 12/22 are delete/free. 3. Slot 19's `count` counts tile records, not bytes. **Unknown** 1. **Which slot creates the resource and where the dimensions live** — the blocker. 2. Pixmap vs Window as the export target, and who allocates the pixmap id. 3. Whether the payload of slot 19 is packed rows or bit-addressed data. 4. The class code values (`0` vs `0xFFFF` vs a client number). --- ## 11. Reproduction 1. `program_list_open` → empty; re-open with `program_open(path="/home/arti/code/k-space/vt1200/vt1200_v22.bin", language="tms34010:LE:32:default", project_location=…, read_only=false)` → session `a03f3d788b874b1a976a55417ab9f2d3`. 2. Swapper listings are pre-extracted in `xie/ghidra-evidence/image_path/swapper_slotNN_HHHHHH.txt`. 3. Handler bodies: `xie/ghidra-evidence/handlers/slotNN_HHHHHH.txt` (pass 2). 4. To re-derive a field map, collect the `MOVB *(A10|A11)(0xNN),*A9` offsets in a stub: a 32-bit word at byte *B* shows up as offsets `8B, 8B+8, 8B+16, 8B+24`; the length swap is the pair at `0x10`/`0x18` and must be ignored.