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]
>

Reply via email to