From 7efb4880f0c38c54bd1f926d637d053c7841f0b4 Mon Sep 17 00:00:00 2001 From: Arti Zirk Date: Fri, 16 Oct 2026 22:03:16 +0300 Subject: [PATCH] harness: the TFTP font 'Disk full' is the terminal's own tftp_close, not a storage or map bug A tcpdump on the tap plus a live execution trace settle the 'Disk full or allocation exceeded' abort: the TERMINAL sends TFTP error 3, from tftp_close (0x3F5EC860), which reports an incomplete read (the last DATA block read was a full 512-byte block) using its fixed text for code 3. It is not a capacity or disk condition. The font buffers (path/index read buffer bit 0x9316D0, fos parser stream buffer 0x227FD0, parsed-font cache 0x227F50) are all in main RAM (0x00000000-0x01FFFFFF), mapped and writable; the writes land in RAM dumps. No second map overlap exists, so the NVR fix did not cause this. vt1200.cpp is unchanged and every baseline is intact. New: fonttrace.lua, fontprobe.lua, two evidence pcaps, FONT_STORAGE_REPORT.md. --- harness/mame/FONT_STORAGE_REPORT.md | 213 ++++++++++++++++++++++++++++ harness/mame/README.md | 1 + harness/mame/font_tftp_crafted.pcap | Bin 0 -> 4196 bytes harness/mame/font_tftp_real.pcap | Bin 0 -> 2090 bytes harness/mame/fontprobe.lua | 51 +++++++ harness/mame/fonttrace.lua | 46 ++++++ 6 files changed, 311 insertions(+) create mode 100644 harness/mame/FONT_STORAGE_REPORT.md create mode 100644 harness/mame/font_tftp_crafted.pcap create mode 100644 harness/mame/font_tftp_real.pcap create mode 100644 harness/mame/fontprobe.lua create mode 100644 harness/mame/fonttrace.lua diff --git a/harness/mame/FONT_STORAGE_REPORT.md b/harness/mame/FONT_STORAGE_REPORT.md new file mode 100644 index 0000000..8c89a11 --- /dev/null +++ b/harness/mame/FONT_STORAGE_REPORT.md @@ -0,0 +1,213 @@ +# The TFTP font "Disk full" abort — who sends it, where the font goes, why it is spurious + +Date: 2026-10-16. Firmware `vt1200_v22.bin` md5 +`b46a3e442ccb86134d2356bd70d22fd2` (**unmodified**). **No MAME-tree change, and no +change to `harness/mame/vt1200.cpp` at all.** This report answers the two +questions in the brief with a live `tcpdump` and a live execution trace, and +records why the answer is *not* a second map overlap. + +## Verdict + +1. **The terminal sends the TFTP error 3, not the server.** The capture shows the + client emitting the ERROR packet after the server's DATA packets. The + server's only error path is "File not found" (code 1); `harness/serve.py` + never sends code 3. +2. **The error is not a storage condition.** It is the firmware's own + `tftp_close` routine reporting **"the connection was closed before the read + reached a short (final) block"** using its fixed text for error code 3, + `Disk full or allocation exceeded`. Nothing is full. +3. **The font storage region is plain main RAM, correctly mapped.** The receive + buffers and the parsed-font cache all sit inside the driver's RAM mapping + (`bit 0x00000000`–`0x01FFFFFF`); the writes land (verified in RAM dumps). + **There is no second map overlap.** The NVR fix is not the cause. +4. A **valid** font transfer completes and is parsed (verified). A font the + parser **rejects or abandons part-way** — such as the crafted 25 KB font — + reads only the first 1024 bytes and then closes, which is what produces the + spurious "Disk full". + +## 1. Who sends the error — capture + +`harness/mame/font_tftp_real.pcap` (the owner's exact `harness/serve.py`, on the +isolated tap; the server also has the same log line the owner sees): + +``` +192.168.99.2.1025 > 192.168.99.1.69: TFTP, length 11, RRQ "69" octet +192.168.99.1.69 > 192.168.99.2.1025: TFTP, length 516, DATA block 1 +192.168.99.2.1025 > 192.168.99.1.69: TFTP, length 4, ACK block 1 +192.168.99.1.69 > 192.168.99.2.1025: TFTP, length 516, DATA block 2 +192.168.99.2.1025 > 192.168.99.1.69: TFTP, length 36, ERROR ENOSPACE "Disk full or allocation exceeded" +``` + +The last packet is **from `192.168.99.2` (the terminal) to the server**. The +server's `_send_file()` receives it and prints +`tftp: client aborted transfer: '\x00\x03Disk full or allocation exceeded'`. +So the earlier reading of the log's wording was right and the "the terminal +prints a received error" reading was wrong: the terminal **generates and sends** +it. (`harness/mame/font_tftp_crafted.pcap` shows the same for the crafted font.) + +## 2. The firmware path that sends it + +The string `Disk full or allocation exceeded` is the terminal's fixed message +for **TFTP error code 3** (ROM file offset `0x7E225`, bit `0x3F5F1128`); the +per-code error table is built by `L3F5EF610` (`0x3F5EF610`, the send-error +routine). Code 3 is emitted from exactly one firmware site on this path: + +``` +tftp_close L3F5EC860 (bit 0x3F5EC860) +3f5ec8a0: move @0x1cb940, a11, 1 ; last-block flag +3f5ec8d0: jreq L3F5ECD10 ; flag == 0 -> send error 3 + ... build and send the ACK instead ... +L3F5ECD10: +3f5ecd10: move a14, -*sp, 1 +3f5ecd20: movk 3, a9 ; TFTP error code 3 +3f5ecd30: move a9, *a14+, 1 +3f5ecd40: move *a13(0xffe0), *a14+, 1 +3f5ecd60: calla L3F5EF610 ; tftp_send_error(...) +``` + +`@0x1cb940` (the "last block was short" flag) is set inside `tftp_read` +(`0x3F5ED230`): + +``` +3f5ef000: cmpi 0x200, a11 ; payload == 512 (a full block) ? +3f5ef020: movk 1, a9 +3f5ef030: jrne L3F5EF050 +3f5ef040: sub a9, a9 ; full block -> 0 (more may follow) +3f5ef050: move a9, @0x1cb940, 1 ; short block -> 1 (this was the end) +``` + +So the rule is mechanical: **if the connection is closed and the last DATA block +read was a full 512-byte block, `tftp_close` sends ERROR 3.** The separate +"bufsize too small" check in `tftp_read` (`0x3F5EED30`–`0x3F5EEDA0`) never fired +in any run — the payload was always ≤ the capacity. + +### What stops the read early + +Live trace (`harness/mame/fonttrace.lua`, `debug.log`), no-env + skip level 1: + +* the font-path object `69` (served as `system-10x20.pcf`, 25 860 B): + `tftp_read` reads **one** 512-byte block, returns 512, then `tftp_close` + fires and takes the `CLOSE->ERROR3` branch; +* the same with `baseline.pcf` (680 B) served for `69`: identical — one full + block, then close/error; +* a **400-byte** object (single short block, `<512`): DATA1 → ACK1, **no error**; +* the 680-byte `baseline.pcf` read as a **font** (the terminal derives a name + from the path object and re-fetches): DATA1 → ACK1 → DATA2 → ACK2, **no error** + — the read reached the short final block; +* the crafted `evil-real.pcf` (25 860 B) read as a **font**: DATA1 → ACK1 → + DATA2 → ACK2 → server sends DATA3, the terminal never ACKs it, and ~4 s later + the terminal sends ERROR 3 — the *parser* consumed the first 1024 bytes and + abandoned the font before EOF. + +So the abort tracks **"how much of the file the reader asked for"**, not any +size/capacity limit of a storage device. A reader that stops on a full block +(the path/index reader, one block; the crafted font's parser, two blocks) trips +`tftp_close`; a reader that reads to EOF does not. + +## 3. Where the terminal stores a fetched font — and its mapping + +Read from the live CPU and from RAM dumps (`VT1200_DUMP_RAM_FRAMES`); a RAM-dump +byte offset is `bit address >> 3` (verified against the NVR settings copy: +the font path `192.168.99.1:69` sits at byte `0x345EB`, which is the settings +copy base `bit 0x1A0020` plus offset `0x5E7`). + +| what | bit address | byte | mapping | +|---|---|---|---| +| font-path / index read buffer (`tftp_read` arg 2) | `0x9316D0` | `0x1262DA` | main RAM (`0x00000000`–`0x01FFFFFF`) | +| `fos` PCF parser stream buffer | `0x227FD0` | `0x44FFA` | main RAM | +| parsed-font cache record array | `0x227F50` | `0x44FEA` | main RAM | +| cache count / capacity | `0x227FB0` / `0x227FC0` | `0x44FF6` / `0x44FF8` | main RAM | +| settings copy (font path lives here) | `0x1A0020` | `0x34004` | main RAM | + +All of these are inside the driver's RAM region and are **writable**: the RAM +dump taken during a fetch contains the received file bytes at the read buffer's +address, and the settings copy contains the NVR font path. The only other +addresses the font/TFTP code touches are ROM. (A scan of every absolute operand +in `harness/dis/fontregion-v22.txt` finds 22 main-RAM addresses, 104 ROM +addresses, and nothing in the VRAM range, the board windows, or unmapped space.) + +### The "second map overlap" lead: checked, none exists + +The NVR fix repaired `0x6000000` sitting inside the video-memory mapping +(`0x04000000`–`0x1FFFFFFF`). I looked for the same shape in the font path and it +is not there: + +* every font/TFTP buffer is in RAM (`bit < 0x02000000`), far below the VRAM base + `0x04000000`, and the writes demonstrably land; +* the only region the font path reads that could have been swallowed is the NVR + itself, and that is now correctly mapped — the font path was read out of it + intact. + +### The NVRAM bisect + +Reverting the NVR mapping cannot change this, so I did not spend a rebuild on a +confounded experiment (with the NVR unmapped the font path reads garbage, so the +terminal would not even request `69`, and the comparison would be meaningless). +The direct evidence is stronger: the abort is driven by the **served content** +(the 400-byte object completes; the 680-byte object aborts; the same 680-byte +object read as a font completes) and by the **parser's early exit** on the +crafted font — neither of which touches `0x6000000`–`0x6007FFF`. The NVR fix did +not introduce the abort; it is upstream of it, because it is what lets the font +path be read at all. + +## 4. Does a font fetch now complete? Does the terminal parse it? + +* **Yes for a font the parser accepts.** Serving `baseline.pcf` as the font name + the terminal derives, the transfer reads to the short final block and + completes with no ERROR, and the parser runs: the font's internal name + `vt1200test` appears in RAM at byte `0x1316`, and the font-cache count moves. +* **No for the crafted 25 KB font, and the reason is the parser, not storage.** + `evil-real.pcf` reads DATA1/DATA2 (1024 bytes) and the parser then abandons it; + the inevitable `tftp_close` then reports ERROR 3. Any font the parser bails on + part-way will produce the same spurious "Disk full" — including, in the + owner's log, the `69` path object when `serve.py` returns a 25 KB font for it. + +So the "Disk full" does stop *close of connection*, but it is **downstream of +the parser's decision**, not a reason the font "cannot be stored". Fixing the +message would not make the crafted parse succeed; the parse already stops on its +own at 1024 bytes. + +## 5. Baselines and configurations + +`vt1200.cpp` was never changed, so every baseline is intact by construction; two +were re-run on the shipped binary to confirm: + +``` +no env, progress.lua 200 frames : PC=3FDCE400 samples=198 (3 distinct PCs) +VT1200_IO_PATH_STUB=1, 200 fr : PC=3FDCE400 samples=53 (75 distinct PCs) +ROM md5 : b46a3e442ccb86134d2356bd70d22fd2 +``` + +`VT1200_SKIP_LEVEL1=1` (OS + Terminal Manager) and `VT1200_MOUSE=1` are +untouched (no code change). + +## 6. What I refused to fake + +* No ROM patch; the ROM stays byte-identical. +* No change to `harness/serve.py` (another worker's file) and no invented font + capacity or patched firmware check. +* No "make the error go away" shortcut: the error is reported as the terminal's + own `tftp_close` behaviour, with the packet capture and the code path that + prove it. + +## 7. Reproduction + +The tests ran in an isolated network namespace so they do not disturb the +owner's `tap-mess-1000-0` (a second MAME cannot open it — `TUNSETIFF` returns +EBUSY; verified). Scratch lives in `harness/mame/fontwork/` (untracked). + +```sh +harness/mame/fontwork/netns_up.sh # private tap-mess-0-0 in netns vt1test +harness/mame/fontwork/run_trace_serve.sh 25 # owner's serve.py + tcpdump + fonttrace.lua +harness/mame/fontwork/run_trace5.sh 40 # crafted 25 KB font via the font reader +harness/mame/fontwork/run_custom.sh reference/fonts/baseline.pcf 30 +harness/mame/fontwork/run_custom.sh harness/mame/fontwork/small400.pcf 25 +``` + +New instrumentation in this directory: + +* `harness/mame/fonttrace.lua` — prints the control-flow milestones of one + fetch (`tftp_read`, `tftp_open`, `tftp_close`, the opcode/block checks, the + ACK/error branch) with registers, to `debug.log`. +* `harness/mame/fontprobe.lua` — prints the `bufsize`/`payload` values at the + capacity check (`0x3F5EED50`) and at the error path. diff --git a/harness/mame/README.md b/harness/mame/README.md index 4eee4fe..664821e 100644 --- a/harness/mame/README.md +++ b/harness/mame/README.md @@ -70,6 +70,7 @@ Reports in this directory: | `LANCE_REPORT.md` | the LANCE Ethernet model: the register file, the 8-byte address ROM and its checksum, the byte↔bit DMA, and the remaining loopback/CRC blocker | | `LANCE_LOOPBACK_REPORT.md` | `C — LANCE TEST`: the TX buffer **does** carry a correct FCS in the passing `DTCR=1` case and deliberately carries **none** in the failing one; the residual mismatch is MAME never setting `RMD1_FRAM` | | `NVRAM_REPORT.md` | the battery-backed NVR (bit `0x6000000`): previously unmodelled (its address was swallowed by the video-memory mapping), now a persisted `nvram_device`; the font path lives there, the font data does not | +| `FONT_STORAGE_REPORT.md` | the TFTP font "Disk full" abort: a capture proves the **terminal** sends TFTP error 3, and it is the firmware's `tftp_close` (`0x3F5EC860`) reporting an incomplete read, not a storage condition; the font buffers are in correctly-mapped RAM, so there is no second map overlap | | `RENDER_REPORT.md` | the active-pixel scanline renderer, and the geometry it derived | | `DUART_REPORT.md` | the DUARTs: wiring, the ISR-driven transmit, the datasheet-verified model edits | | `BRINGUP_LOG.md` | the whole bring-up narrative, **including withdrawn claims** — read it sceptically | diff --git a/harness/mame/font_tftp_crafted.pcap b/harness/mame/font_tftp_crafted.pcap new file mode 100644 index 0000000000000000000000000000000000000000..1492dca51d8addff23ff924c55b5fdec9c38a8f5 GIT binary patch literal 4196 zcmeHKYiL_#7=BMuXVYb~b@PJNdVUO?vnQsV?OGU;n4G4eX_Dn)FQQqRleDKxlQm7X z3SuXlmLa3wH~cgGA@c%?6hRSOQE=cdZ~LM1GSRVrB8Z46^Lf8>(j+Au_Cp*(zvMaJ z`+k@AobUUdyywZ6AHDOkjhd)hnn;3^EPpZm`l0b=Qi*PJ zEE9FK4gGlfRK^BKO%j^7t-M51Z$A|?tNrB`W)(J_JO^? zMfk1koBca5S!N&Lb~k*F>GOOK{6*8>jyeRNAE0u-{qT7%3g3o{UB+cI#)yC+)YJmR z*_Z?#U}Fe4P%#$R*aRG6gX26EjReDqR6IDY(m*7fAZ13M%P0?)ySm-(a(1q)%w#ov zenu%Ng<@$YomZ4TrO&PG>EUmNO?|G|*VogpbkT?!3=AeHrp6QTXx1gYqtLX~t(08D7? z;llgDMFY8=Wh?poG+zjZY%pSp;XSXCs|iy2b-GPOsLokybu? z4Q%8!dv3<%ZXjdgu^OA(j8z?~uF6YZG>c-g2CI~QtJ^3DX4 z$rFt-#1iuOdJQ4ls~SRsm^ci-q9Oc2Xo#z;8iM;f_u=Kl85?LtyyYZb4W`d?PmWYI z#Bm6i9rvLV*}bA6KG2N0XuSd8F1i-j#zs3Jv(X83v%xPMl?~Pq!)$Q82{vYd5j_7)H*b0q<}yG?wBaG@vhJ zHB+$|EyF_SP(0*?42n@)O$b$DbcTiaA+O(`iYLZGs?kc(;HVlh)KiU+VgbbLIXi2? zM%nSAU}II$G8>&McX5Wu9BjnKzYx!YjqQSs_t&#=dk1F-LyoQ2VDYHe7Yql;N8WqY zm^YxBI?OldjbZuKZ4AaEEb*w)p@w}CKhNx{>NuY_T9I_VNOUYFq+oP@BCls;tvHj` z3o^fiU9udXN|&;7x}eF~!gTRr7o`IC_y<&fDi|J!kgDnBtR_FCm#5^(2`vY^%eC8O zs#88kMVy^toR<<)jI&XcZa2OaY3ftDV4Dx#P@C2W&-EIJRUr5$N(<=LYI?OcXn5)G zOTUVd_3h@w^_9?Q-!C3Lx_tOQzc3q*9J6F2k`w-4ov+J{&XwI*xveo9-$pzOHg*U$ vj@PsC*0;b$`xV%Ljayz3-ZkF#F|hHGcQ;%8Q#c{mc)E^_ovYtmjt=+-xS+rm literal 0 HcmV?d00001 diff --git a/harness/mame/font_tftp_real.pcap b/harness/mame/font_tftp_real.pcap new file mode 100644 index 0000000000000000000000000000000000000000..a6fc0b60f750cc39df082182d927031bb6214ae0 GIT binary patch literal 2090 zcmcgtO>Em_7=E3EuCB_|(FqU;^a+rVMjhggb{m?ec(IdOP3$ao)@B@{)OJ#jG~Uuw z>yVIk*rD11mH(k#1_y*-C)5+-0OBwtI1R>)pP?}g4oqASAdT_9pPlq)TeK5ap7{Cx zp6~tM@A*D|`^Cra#vlRfPXYuiymS20(d4fKkVa0Jyk`8c|NTLD=E3PBgP;LCEv^DQ zzGM35&pvQs2q7V0_2}9;5GMCQ;B>qWq*5s;gFvH5ul(BpBxAr4Ds@#gh_U#IG^BU@ zE|3;!f|d|s!;Nzg2SKPi$3%>RBHNBwL6x)k#useIN*CWO4tyQFj0-xAg$TXrrg!JZ!97nVfuoLkzkuMP|M5yzdUGg4v{t$72YH)h^glfJ* zd_&|a;u?Z4`v#Hw5&vM5kU;DrvIk)ju@SFOpCKfCiUyMNQ4I(mWlh62l!8_*X*u1p zOBH~uSvFK81%J_jtWwbPl`3_CtiSBJFr(?YDLbqwc&@*+09IZxY$$=H*)zIoPlb&} z&|auDK~dGJWmocASPMmcR?Al{{fGudQ#b68mNd&G_cZ?O<3=XF%H4QGRnY$4-yKyH&H^%a1TQ`mBEKq1sp#oM>$>>H7GN2sRN=ifhh2qj&(|4E~EY$oKs|T%4k}+$( z*7jJf=gx{HGjXAfDTu$yz;!fhWNVJa<-#aPX zFR3Jjdm~BXDg33RJrnfHNHX#$a7m7Kdc*qRP+3u<1@jXk@Q2*Q4;}iCKSOA@5q?a` zcQ?m{l=9IUXQ`9{om2eD-RuO2Xmg} zdd_%qu~lb>Z5e%0ESU#2d5E}&q%;n{K%cVQkCsroD&0}5v;!>=` fYtP+1mlsog_3#slxuks$zu=+XZq$q2zBm5?2U_wx literal 0 HcmV?d00001 diff --git a/harness/mame/fontprobe.lua b/harness/mame/fontprobe.lua new file mode 100644 index 0000000..96de1c6 --- /dev/null +++ b/harness/mame/fontprobe.lua @@ -0,0 +1,51 @@ +-- fontprobe.lua -- live values for the TFTP font-read capacity check. +-- +-- Breaks inside the firmware's tftp_read (0x3f5ed230) at the point where the +-- per-datagram payload (a9) is compared with the buffer capacity (a11), and at +-- the error path. Prints the frame, PC, a9 (payload), a11 (capacity) and the +-- accumulated total (dword at a13+8) so the "disk full" verdict can be read +-- directly rather than inferred. +-- +-- FP_FRAMES= budget (default 20000) +-- vt1200 ... -debug -debugger none -debuglog \ +-- -autoboot_script harness/mame/fontprobe.lua -autoboot_delay 0 + +local machine = manager.machine +local dbg = machine.debugger +local cpu = machine.devices[':maincpu'] + +if not dbg then + print("ERROR: debugger not enabled; pass -debug") + machine:exit() + return +end + +FRAME = 0 + +-- tftp_read entry: dump the three arguments from the callee frame (a13-12/-8/-4) +dbg:command('bpset 3f5ed290,,{ printf "TFTP-ARGS fr=%d arg1(len)=%08X arg2(buf)=%08X arg3(sock)=%08X\\n", FRAME, d@(a13-12), d@(a13-8), d@(a13-4) ; g }') +-- capacity check, just after `cmp a11,a9` (a9 = payload, a11 = capacity) +dbg:command('bpset 3f5eed50,,{ printf "CHK fr=%d pc=%08X payload(a9)=%08X capacity(a11)=%08X a13=%08X [%08X %08X %08X | %08X %08X %08X]\\n", FRAME, PC, a9, a11, a13, d@(a13-12), d@(a13-8), d@(a13-4), d@(a13), d@(a13+4), d@(a13+8) ; g }') +-- error path just before the message is formatted (a9 = payload first arg, a11 = capacity second) +dbg:command('bpset 3f5eeda0,,{ printf "BUFSIZE-ERR fr=%d a9=%08X a11=%08X\\n", FRAME, a9, a11 ; g }') +-- ERROR code 3 being sent +dbg:command('bpset 3f5eee50,,{ printf "SEND-ERROR-3 fr=%d\\n", FRAME ; g }') +-- tftp_read entry, count calls +dbg:command('bpset 3f5ed230,,{ printf "TFTP-READ-ENTRY fr=%d a14=%08X\\n", FRAME, a14 ; g }') +-- received an ERROR packet from the peer +dbg:command('bpset 3f5edb30,,{ printf "GOT-ERROR-PKT fr=%d\\n", FRAME ; g }') + +local maxframes = tonumber(os.getenv("FP_FRAMES") or "20000") +emu.register_frame_done(function() + FRAME = FRAME + 1 + if FRAME % 2000 == 0 then + print(string.format("frame %d PC=%08X", FRAME, cpu.state['PC'].value)) + end + if FRAME >= maxframes then + print("done (" .. FRAME .. " frames)") + machine:exit() + end +end, "vt1200-fp") + +dbg.visible_cpu = cpu +dbg.execution_state = 'run' diff --git a/harness/mame/fonttrace.lua b/harness/mame/fonttrace.lua new file mode 100644 index 0000000..2593783 --- /dev/null +++ b/harness/mame/fonttrace.lua @@ -0,0 +1,46 @@ +-- fonttrace.lua -- step-by-step trace of one TFTP font fetch. +-- +-- Prints the control-flow milestones inside the firmware's TFTP client +-- (tftp_read 0x3f5ed230, tftp_open 0x3f5ebc40, tftp_close 0x3f5ec860) with the +-- key registers, so the reason a transfer is abandoned is read off the run +-- rather than inferred from the disassembly. +-- +-- FT_FRAMES= budget (default 20000) + +local machine = manager.machine +local dbg = machine.debugger +local cpu = machine.devices[':maincpu'] + +if not dbg then + print("ERROR: debugger not enabled; pass -debug") + machine:exit() + return +end + +FRAME = 0 + +dbg:command('bpset 3f5ed230,,{ printf "TFTP-ENTRY fr=%d a14=%08X\\n", FRAME, a14 ; g }') +dbg:command('bpset 3f5ed3f0,,{ printf " EXPECTED->%d fr=%d\\n", a11, FRAME ; g }') +dbg:command('bpset 3f5ed690,,{ printf " RECV ret=%08X fr=%d\\n", a8, FRAME ; g }') +dbg:command('bpset 3f5edb20,,{ printf " OPCODE=%04X fr=%d\\n", a11, FRAME ; g }') +dbg:command('bpset 3f5ee3d0,,{ printf " BLKREAD=%04X fr=%d\\n", a11, FRAME ; g }') +dbg:command('bpset 3f5ee420,,{ printf " BLKCHK blk=%04X exp=%08X fr=%d\\n", a11, a9, FRAME ; g }') +dbg:command('bpset 3f5ee720,,{ printf " ->ACK/send path fr=%d\\n", FRAME ; g }') +dbg:command('bpset 3f5eeae0,,{ printf " SEND-RET a8=%08X a11=%08X fr=%d\\n", a8, a11, FRAME ; g }') +dbg:command('bpset 3f5eecf0,,{ printf " COPY reclen=%08X fr=%d\\n", a9, FRAME ; g }') +dbg:command('bpset 3f5ef090,,{ printf " READ-RET a8=%08X fr=%d\\n", a8, FRAME ; g }') +dbg:command('bpset 3f5ec750,,{ printf "OPEN-OK fr=%d\\n", FRAME ; g }') +dbg:command('bpset 3f5ec860,,{ printf "CLOSE fr=%d\\n", FRAME ; g }') +dbg:command('bpset 3f5ecd10,,{ printf " CLOSE->ERROR3 fr=%d\\n", FRAME ; g }') + +local maxframes = tonumber(os.getenv("FT_FRAMES") or "20000") +emu.register_frame_done(function() + FRAME = FRAME + 1 + if FRAME >= maxframes then + print("done (" .. FRAME .. " frames)") + machine:exit() + end +end, "vt1200-ft") + +dbg.visible_cpu = cpu +dbg.execution_state = 'run'