# Finishing the Workabout MX scanner — on-device continuation guide This document describes exactly how to continue from the current findings to a fully working scanner client, using on-device debugging. Read `SCANNER-API.md` first for the findings this builds on. ## Where we are Established (see `SCANNER-API.md` for detail): - The integral laser is driven as an **OO library object** in `SCANNER.DYL` (category token seen in ROM: **`oscanner`**), *not* by raw device I/O. - The app path is the standard OLIB one: `p_getlibh` → `p_newsend`/`f_newsend` (create + init) → `p_send` (configure / trigger / read). - Internals confirmed: `WL2:D` is the driver's channel; control ops **6** then **7** (`p_iow(chan,6)`, `p_iow(chan,7)`) fire the laser to a good decode (green LED) on the physical device. - The decompiled read (`FUN_858d`) creates the object via `LIBMANAGER` (`NMLIBCREATE`/`NMLIBCREATEBYHANDLE`), sets up a buffer, then reads; the decode lands CR/LF-terminated (default Symbol2 postamble). Missing, and why on-device: the **message ordinals** and **parameter structs** for the scanner's methods. OLIB assigns ordinals dynamically from the whole class hierarchy (including base classes in `olib`/`hwim`), so they cannot be read reliably from a static dump — but they resolve at runtime, where the debugger can capture them. MAME cannot inject a barcode, so validation must be on hardware anyway. ## What to capture For a working client you need five things: 1. The scanner **category** (confirm the name/id for `p_getlibh` — candidate `oscanner`). 2. The scanner **class** created by the app. 3. The **message ordinals** for: init, set-parameters, enable/trigger, read. 4. The **parameter/result structs** each message takes (esp. the read result buffer and its length). 5. The exact **call sequence** the working app uses. ## Tooling: the SIBO Debugger The SIBO Debugger (see manual `2-03`/`2-04`, "The SIBO Debugger") supports: - **Remote debugging**: development PC connected to the Workabout by a serial cable; debug up to 8 processes. - **Breakpoints in dynamic libraries / shared code** — required to break inside `SCANNER.DYL`. - Single-step, trace, register and memory display. Build your own code for source-level debugging with: ``` #pragma debug(vid=>full) /* in the .pr / source */ ``` and produce the `.sym`/`.dbd` symbol files with EMAKE. ### Set-up 1. Connect the PC to the Workabout with a serial cable (PC serial ↔ Workabout RS-232 port — note this is a *different* port from the barcode `TTY:D`). 2. Start the debugger on the PC and connect to the remote (Workabout) target (Local/Remote CPU menu → Connect to Remote). 3. Have the Workabout ready to run either the ROM `DEMMAN.APP`/`SCANAPP` (for Procedure A) or your test harness (Procedure B). ## Procedure A — trace the working ROM app (recommended first) Goal: watch `DEMMAN`/`SCANAPP` drive the real scanner and record the ordinals. 1. On the Workabout, start `DEMMAN` and enter its Scanner (Barcode) demo. 2. From the debugger, attach to that process and set breakpoints on the OLIB dispatch path so you catch the object creation and messages: - `LIBMANAGER` (`INT 0x84`) — sub-functions `NMLIBCREATE` (`AH=5`), `NMLIBCREATEBYHANDLE` (`AH=6`): captures the category/class and the object handle. - `MESSMANAGER` (`INT 0x83`) — `NMMESSSEND` and the send/receive variants: captures each message ordinal and its argument pointer. - Optionally the `SCANNER.DYL` method handlers we identified (init dispatch, trigger) as cross-checks. 3. For each captured call record: `AH`, the category/class in registers, the message **ordinal**, and the pointed-to **argument struct** (dump memory at the pointer). For the read, note where the decoded bytes land and the returned length. 4. Trigger a real scan and record the read message and its result buffer. Result: the exact category, class, ordinals, param structs, and sequence. ## Procedure B — iterate a C test harness Once Procedure A gives the ordinals (or to trial them), build a small OO client: ```c #include #include /* Fill these from Procedure A (placeholders until captured): */ #define SCAN_CATEGORY /* category id/handle for "oscanner" via p_getcat/p_getlibh */ #define C_SCANNER /* the scanner class */ #define O_SCAN_INIT /* init message ordinal */ #define O_SCAN_PARAMS /* set-parameters ordinal */ #define O_SCAN_READ /* read-a-barcode ordinal */ GLDEF_C INT main(VOID) { VOID *lib, *scanner; /* result/param structs per Procedure A */ lib = p_getlibh(SCAN_CATEGORY); scanner = p_newsend(SCAN_CATEGORY, C_SCANNER, O_SCAN_INIT, /*&initargs*/ 0); /* configure Symbol2 + the default 11-byte param block: 04 3f 01 15 06 04 1e 80 0d 0a 06 (CR/LF postamble) */ p_send3(scanner, O_SCAN_PARAMS, /*¶ms*/ 0); for (;;) { /* send the read message; result is the decoded barcode, CR/LF-terminated */ p_send3(scanner, O_SCAN_READ, /*&result*/ 0); /* display result ... */ } } ``` Build with `#pragma debug(vid=>full)`, run under the debugger, single-step the sends, and inspect return values / the result buffer. Adjust ordinals/structs until a real scan returns the UPC. ## Turning captures into the shipped reader - Replace `code/inventory/bcode.c` with the OO client: `bcodeOpen` does `p_getlibh` + `p_newsend`(init) + params; `bcodeRead` does `p_send`(read) and returns the decoded string (strip the CR/LF postamble; strip any preamble byte `0x80` if present). - `upc.c` (UPC-A check-digit validation) is unchanged and already correct. - Cross-check the captured ordinals against the decompiled handlers in `SCANNER-API.md` (`FUN_7ae3` init dispatch — decoder type at `[channel+10]`, case 4 = Symbol; `FUN_7aa4` trigger = the `6`/`7` ops). ## Validation - A valid UPC-A scan should return 12 digits (plus any pre/postamble), and `upcIsValid` should pass. The default postamble is CR LF (`0x0d 0x0a`). - Confirm repeated scans and clean shutdown (`p_send` a destroy/close message, then `p_close` any channel). ## Reproduce the RE environment (for further static/dynamic work) See `../mx-re/toolchain-and-plan.md`: MAME `psionwamx` (live trace/dasm), radare2 (static), and Ghidra headless (decompilation of the DYLs). These remain useful for reading further `SCANNER.DYL` methods, but the ordinals themselves come from the on-device debugger as above.