6.4 KiB
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:Dis 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 viaLIBMANAGER(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:
- The scanner category (confirm the name/id for
p_getlibh— candidateoscanner). - The scanner class created by the app.
- The message ordinals for: init, set-parameters, enable/trigger, read.
- The parameter/result structs each message takes (esp. the read result buffer and its length).
- 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
- 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). - Start the debugger on the PC and connect to the remote (Workabout) target (Local/Remote CPU menu → Connect to Remote).
- 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.
- On the Workabout, start
DEMMANand enter its Scanner (Barcode) demo. - 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-functionsNMLIBCREATE(AH=5),NMLIBCREATEBYHANDLE(AH=6): captures the category/class and the object handle.MESSMANAGER(INT 0x83) —NMMESSSENDand the send/receive variants: captures each message ordinal and its argument pointer.- Optionally the
SCANNER.DYLmethod handlers we identified (init dispatch, trigger) as cross-checks.
- 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. - 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:
#include <plib.h>
#include <p_object.h>
/* 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.cwith the OO client:bcodeOpendoesp_getlibh+p_newsend(init) + params;bcodeReaddoesp_send(read) and returns the decoded string (strip the CR/LF postamble; strip any preamble byte0x80if 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_7ae3init dispatch — decoder type at[channel+10], case 4 = Symbol;FUN_7aa4trigger = the6/7ops).
Validation
- A valid UPC-A scan should return 12 digits (plus any pre/postamble), and
upcIsValidshould pass. The default postamble is CR LF (0x0d 0x0a). - Confirm repeated scans and clean shutdown (
p_senda destroy/close message, thenp_closeany 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.