I think other software leaves the RAM file system messed up to. Interacting with the RAM filesystem is extremely complicated. We should probably all be using some reusable libraries for interacting with it. If you wing it, you are very likely to get it wrong.
-- John. On Thu, Oct 15, 2026 at 5:07 PM Gary Weber <[email protected]> wrote: > I forgot to mention there's a third bug that's fixed in TS-DOS 4.20 for > the NEC. > > *Bug #3: SAVE"0:PROG.BA <http://PROG.BA>" after a RUN can save GOTO and > GOSUB targets as RAM addresses instead of line numbers* > > With DOS-ON, you RUN a program, SAVE"0:PROG.BA", and later LOAD"0:PROG.BA" > back. Most of the program is fine, but some GOTO, GOSUB or THEN line > numbers are wrong. LIST shows things like GOTO 12849 for lines that don't > exist, and RUN jumps to the wrong place. Fix the lines, run the program to > test it, save it again, and the next LOAD has them wrong again. If you've > ever had .BA files on your TPDD that seemed to "corrupt themselves", this > may be why. > > It's in every NEC TS-DOS image I have, starting with TS-DOS 4.00. The > SAVE code was byte-for-byte identical in 4.00 and 4.10, before this fix. > > So what happened here is, NEC BASIC has a speed-up that the Model 100's > BASIC doesn't. In tokenized BASIC a line-number reference after GOTO, > GOSUB, THEN and so on is stored as a 0Eh byte followed by the 2-byte line > number. The first time such a statement runs, the NEC BASIC looks the line > up and then overwrites those 3 bytes in your program with a 0Dh byte and > the line's RAM address, so the next time it can jump straight there without > searching: > > 0C38 CC 0760h ; FINDL2 / FINDLN: find the target line > 0C3B CNC 075Dh > 0C3E JNC 0C4Eh ; not found: ?UL > 0C41 DCX B ; BC = the line's address - 1 > 0C42 MVI A,0Dh > 0C44 STA FACDh ; PTRFLG: "some line numbers have been turned into > addresses" > ... > 0C48 CALL 173Ch ; write 0D lo hi over 0E lo hi, right in the > program text > > An address is only good while the program stays where it is in RAM, so the > system ROM turns every 0Dh address back into a 0Eh line number (routine > 16A8h) before anything that could move or keep the program. That includes > its own SAVE to RAM (25F6h), the MENU (5652h calls 2337h, which calls > 16A8h) and typing in or changing a program line (068Fh calls 1745h). > > TS-DOS's DOS-ON SAVE hook doesn't do that. It relinks the program and > sends it to the drive exactly as it sits in RAM: > > 453B POP PSW > 453C CPI 09h ; our device? > 453E JNZ 00A5h > 4541 LXI B,4142h ; "BA" > 4544 CALL 450Eh > 4547 JNZ 48D2h > 454A CALL 4CB6h ; RST 1 / DW 0714h = LINKER > 454D LHLD F45Dh ; TXTTAB: start of the program > ... > 455D CALL 411Fh ; send it to the drive, 0Dh addresses and all > > So if you RUN a program and then SAVE"0:PROG.BA", every GOTO or GOSUB > that ran is saved as an address. On LOAD"0:PROG.BA" the program usually > lands at a different address, because the program area starts right after > your RAM files (see Bug #1). Those saved addresses now point into the > middle of some other line. LIST follows the address and prints whatever 2 > bytes it finds there as the "line number". The "12849" above is 3231h: the > two text bytes "12". RUN jumps to the stale address and carries on from > whatever is there. > > My fix, in TS-DOS 4.20 stand-alone and SARDOS 1.80: the SAVE hook now > calls a 6-byte stub that turns the addresses back into line numbers first, > the same way the system ROM's own SAVE does, and then runs LINKER as before: > > 454A CALL 44F3h ; was CALL 4CB6h > ... > 44F3 RST 1 > 44F4 DW 16A8h ; system ROM: every 0Dh address back to a 0Eh line > number > 44F6 JMP 4CB6h ; then LINKER, as before > > 44F3h is the free space left behind when the Bug #2 helper moved to 4FF0h. > > Is a .BA saved from the TS-DOS menu screen (F1 Save) affected? No. You can > only get to TS-DOS's file screen through the MENU, and the MENU converts > the program back to line numbers on the way in. I checked this on the > emulator: a RAM .BA run from BASIC held addresses until it went back to the > MENU, and then held line numbers again. A plain SAVE to RAM is fine too. > Only SAVE"0:PROG.BA" from BASIC with DOS-ON is affected. > > Also, just like the other bugs I mentioned before, the M100's TS-DOS > doesn't have these problems. ANd the reason is the Model 100's BASIC never > turns line numbers into addresses: GOTO looks the line up by number every > time, so there is nothing to undo before saving. > > Why it stayed hidden so long: it takes all three of these together: > 1. the program ran (or at least its GOTOs or GOSUBs did) since its lines > were last typed in or changed, > 2. it was saved with SAVE"0:" before you went back to the MENU or changed > a line, and > 3. it was loaded back at a different address than it was saved from. > Run, test and save is about the most natural thing to do, and point 3 > happens whenever the RAM files in front of the program are different. If > they're the same, the stale addresses happen to still be right, just like > with Bug #1, so the bug only shows up sometimes, which makes it look like > random corruption. > > Already have a .BA on disk that was saved this way? The fix stops new bad > saves, but it doesn't repair old files on LOAD. LOAD it, retype the > GOTO/GOSUB lines that LIST shows with impossible line numbers, and SAVE it > again with TS-DOS 4.20 or SARDOS 1.80, which you can find in the NEC > Option ROM images download area > <https://www.web8201.net/default.asp?content=/Files/body.asp%3Fname=NEC%20Option%20ROM%20Images%26bknm=Back%20to%20Web8201%20Files%26bk=NEC-filedirlist.asp%26url=ROMS> > on Web8201. > > If you're stuck on an older TS-DOS for now, you stil can get around it: > just before SAVE"0:PROG.BA", re-enter any program line (retype it, or > LIST it, move the cursor onto it and press ENTER), or go to the MENU and > back into BASIC. Either one converts the addresses back to line numbers. > (LIST doesn't, because it just follows the addresses.) > > Anyway, that's three bugs that have existed in TS-DOS for the NEC since > its inception, finally fixed in 4.20. > > > On Wed, Oct 14, 2026 at 8:45 PM Gary Weber <[email protected]> wrote: > >> Believe it or not, there are some TS-DOS NEC-specific bugs from 1986. >> These have been there since the original NEC port that Traveling Software >> did of their Model 100 TS-DOS. >> >> They relate to how DOS-ON LOAD"0:PROG.BA" works from BASIC. >> 1) If you have "DOS-ON" and do a LOAD"0:PROG.BA", its line pointers are >> messed up and you have to return to the main menu and then back to BASIC to >> fix. >> 2) If you're in any other bank other than bank #1, LOAD"0:PROG.BA" >> simply hangs. You always have to be in RAM bank #1 with TS-DOS 4.10 or >> earlier. >> >> Both of these bugs are in the TS-DOS 4.10 stand-alone option ROM, as well >> as the one embedded in SARDOS. I'm putting out a new TS-DOS 4.20 option >> ROM image as well as SARDOS 1.80 with fixes, and they're available here >> <https://www.web8201.net/default.asp?content=/Files/body.asp%3Fname=NEC%20Option%20ROM%20Images%26bknm=Back%20to%20Web8201%20Files%26bk=NEC-filedirlist.asp%26url=ROMS> >> . >> >> If you want the nitty-gritty details, keep reading! >> >> *Bug #1: Any .BA that gets loaded from disk with LOAD"0:PROG.BA >> <http://PROG.BA>" keeps stale line links* >> >> Symptom: Wwith DOS-ON, LOAD"0:PROG.BA" followed by LIST shows only the >> first line and then garbage, or RUN fails. Going to the MENU and back into >> BASIC "fixes" it. >> >> Why: Every line of tokenized BASIC starts with a 2-byte link, the >> absolute RAM address of the next line. A .BA file keeps the links from the >> address it was saved at. If it loads anywhere else, BASIC must rebuild them >> with the system ROM's LINKER routine. The BASIC program area starts right >> after the RAM files, so any difference in the files present moves it. The >> normal native ROM LOAD calls LINKER, and so does entering BASIC from the >> MENU, which is why the round trip hides the problem. >> >> Now, if it just so happens that the .BA you are loading was the *first* >> .BA in RAM when it was saved to disk, and you're reloading it as the very >> first .BA in a freshly cleared RAM bank, the pointers will still be correct >> and you won't experience the problem. But if you already have some .BA >> programs in memory when you do this LOAD, the pointers will be off and you >> will see this issue. (Want to try reproduce it? Use TS-DOS 4.00 or 4.10, >> clear your RAM bank, create a small .BA program, SAVE"0:PROG.BA" to your >> TPDD drive or server, clear RAM again and then load up another program, >> i.e. LOAD"0:HELLO.BA" and SAVE"HELLO.BA" to your RAM directory, go back >> to BASIC and LOAD"0:PROG.BA" from your TPDD device that you had created >> earlier. Then do a LIST. Kaboom!) >> >> Okay, so what's going on here? In the original code, TS-DOS receives the >> file straight into the program area. Then it hands back to the system ROM's >> own LOAD code to finish: >> >> 44E8 CALL 4B1Bh ; restore the serial settings >> 44EB XRA A >> 44EC LXI H,27B8h ; system ROM LOAD tail... >> 44EF PUSH H >> 44F0 JMP 00A5h ; bank out and "return" into the system ROM at >> 27B8h >> >> The NEC system ROM's LOAD tail code in ROM: >> >> 27AC CALL 0714h ; LINKER <- skipped >> ... >> 27B5 CALL 280Bh ; NEC only: give back the extra space reserved for >> the load >> 27B8 XRA A ; <- TS-DOS comes back in here >> >> TS-DOS re-enters at 27B8h, past both LINKER and the space-trimming call. >> Skipping the trim is on purpose, because TS-DOS has already fixed the >> end-of-program pointer itself (44DA–44E5h), and doing it twice would >> corrupt it. Skipping LINKER is the side effect: nothing ever rebuilds the >> links. >> >> My fix is in TS-DOS 4.20 stand-alone, and SARDOS 1.80: the call to >> restore the serial settings now goes through a 6-byte stub in unused option >> ROM space, and the stub runs LINKER too: >> >> 44E8 CALL 4CCEh ; was CALL 4B1Bh >> ... >> 4CCE CALL 4B1Bh ; restore the serial settings, like it was already >> doing >> 4CD1 JMP 4CB6h ; TS-DOS's existing system call: RST 1 / DW 0714h >> / RET = LINKER >> >> LINKER runs on the new program, then control returns to 44EBh and the >> original hand-back carries on unchanged. >> >> And so.... Why didn't the Model 100 TS-DOS code have this bug?? The >> Model 100's TS-DOS ends its loader the same way (XRA A / LXI H,23F5h / PUSH >> H / JMP 00A6h), but the Model 100 ROM's LOAD tail is in a different order: >> >> 23F5 JNZ CLOADERR ; <- TS-DOS returns here (A = 0, so no error) >> ... >> 2403 CALL 05F0h ; LINKER: still ahead, so it runs >> >> On the Model 100, LINKER comes after the point where TS-DOS returns, so >> the program is always relinked. >> >> The NEC ROM loads differently, it first reserves extra space for the >> incoming program and gives back the excess afterwards (27B5h). It calls >> LINKER before that trim. When Traveling Software ported TS-DOS, the return >> point had to come after the trim, to avoid fixing the end pointer twice. On >> the NEC, that spot is also past LINKER. >> >> It stayed hidden for years because on a freshly set-up machine the >> program area usually starts at the same address (8017h, after the TS-DOS >> menu entry). Most .BA files were saved from there, so their links happened >> to be right. >> >> *Bug #2: Disk LOAD"0:FILE.BA <http://FILE.BA>" hangs from RAM bank #2 or >> #3 on the NEC PC-8201A* >> >> On the PC-8201A, port A1h is one latch for two things: bits 0–1 select >> the ROM at 0000–7FFFh (system or option ROM), and bits 2–3 select which RAM >> bank appears at 8000–FFFFh. >> >> After a disk .BA LOAD (LOAD"0:FILE.BA" with DOS-ON), TS-DOS has to call >> the system ROM's CLRPTR routine (22EEh) to reset BASIC's pointers. CLRPTR >> can't go through TS-DOS's normal bank-call path, so TS-DOS copies a small >> helper into RAM at FC8Ah and runs it there: >> >> 44FF LXI H,44F3h ; copy the 12-byte helper... >> LXI D,FC8Ah ; ...into RAM >> LXI B,000Ch >> CALL MOVE >> JMP FC8Ah ; and run it >> >> FC8A XRA A ; A = 00 >> OUT A1h ; system ROM in -- but RAM bank bits = 00 too! >> CALL 22EEh ; CLRPTR >> CALL F991h ; bank TS-DOS back in (restores the right RAM bank) >> JMP 44CCh >> >> The bug: XRA A / OUT A1h doesn't only switch to the system ROM. It also >> selects RAM bank #1. From bank #1 nothing goes wrong. But this helper runs >> from RAM, in the very area the bank switch replaces. From bank #2 or #3, >> the CPU's next instruction fetch, its stack, and CLRPTR itself all suddenly >> act on bank #1's memory. Bank #1's BASIC pointers get reset, the return >> address comes from the wrong bank, and the machine crashes into bank #1. >> >> The same 12 bytes are in TS-DOS 4.0 (1986), 4.1 and 4.2, and in every >> SARDOS. It went unnoticed for about 40 years because almost everyone works >> in bank #1. >> >> The fix: switch only the ROM and keep the current RAM bank, the same way >> TS-DOS's own bank-out routine does it: >> >> FC8A IN A0h ; read the current bank latch >> ANI 0Ch ; keep the RAM-bank bits; ROM bits 00 = system ROM >> OUT A1h ; switch the ROM, stay in our RAM bank >> CALL 22EEh ; CLRPTR >> CALL F991h ; bank TS-DOS back in >> JMP 44CCh >> >> The helper grew from 12 to 15 bytes, so it moved to free TS-DOS/SARDOS >> ROM space at 4FF0h, and the copy at 44FFh now copies 15 bytes from there. >> >> The usual option-ROM trick won't work here: write the latch, read bank >> #1's saved bank number at F3DBh, then write the latch again. That only >> works in code running from ROM. In this helper, which runs from RAM, the >> instruction after the first OUT would already be fetched from bank #1. >> >> --- >> >> Bottom line: On your NEC, if you're using the TS-DOS 4.10 or earlier >> option ROM, you should switch to using TS-DOS 4.20. Same for SARDOS 1.73 >> or earlier: switch to 1.80. >> >> Both are available in the ROMs download area >> <https://www.web8201.net/default.asp?content=/Files/body.asp%3Fname=NEC%20Option%20ROM%20Images%26bknm=Back%20to%20Web8201%20Files%26bk=NEC-filedirlist.asp%26url=ROMS> >> up on Web8201. >> >> -- >> Gary Weber >> [email protected] >> >
