And it's complicated by the fact that the NEC & M100 ROM entry points are
*almost* compatible, most of the time. ;) These discrepancies seemed to
bite software vendors who, back in the day, made certain assumptions, and
did a minimum of testing...

A common library sounds like a great idea.

On Thu, Oct 15, 2026, 5:17 PM John R. Hogerhuis <[email protected]> wrote:

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

Reply via email to