Changes from NFS 3.34B

← Acorn NFS 3.35D disassembly

Changes from NFS 3.34B to NFS 3.35D

NFS 3.35D is a substantial revision of Acorn NFS 3.34B for the BBC Micro. The two 8 KB ROMs are 87.4% identical at the opcode level, with 161 structural change blocks reflecting significant new functionality and protocol changes.

Summary statistics

Metric Value
ROM size 8192 bytes
Identical bytes at same offset 272 (3.3%)
Byte-level similarity 83.0%
Opcode-level similarity 87.4%
Full instruction similarity 75.8%
Instructions (3.34B / 3.35D) 4045 / 4019
Structural change blocks 161

The very low identical-byte count (3.3%) reflects the extensive address shifting caused by multiple code insertions and deletions throughout the ROM. The opcode-level similarity (87.4%) is a better measure of structural similarity but still shows considerably more change than the 3.34 to 3.34B transition (98.7%).

ROM header

Field 3.34B 3.35D
Title "NET" "NET"
ROM type &82 &82
Binary version &03 &03
Copyright "(C)ROFF" "(C)ROFF"
Language entry &8099 &80D4
Service entry &80AF &80EA

The language and service entry points have moved due to new code inserted before them (the station number parser at &807D-&80B3 and the per-ROM disable check at &80EA-&80F3). All other header fields are unchanged.

Changes

1. Version string: "3.34B" to "3.35d"

The ROM identification string printed by *HELP (at &81BF in 3.34B, &81FA in 3.35D) changed from "NFS 3.34B" to "NFS 3.35d". The revision letter is now lowercase, an unusual convention for Acorn ROMs.

2. Command-line station number parsing

Entirely new code at &807D-&80B3 adds two capabilities to the *NET command:

Station number syntax: *NET 251 or *NET 3.251 directly sets the fileserver station and network numbers (stored at &0E00 and &0E01). In 3.34B, changing the fileserver required the *I AM command; *NET only dispatched internal sub-commands (*NET1 through *NET4), which manage file handles for Econet remote file operations. Despite the * prefix notation, these are not user-invocable commands — the MOS command parser requires a space or terminator after NET, so typing *NET1 at the command line does not match; the sub-commands are reached only through internal OSCLI calls within the ROM.

The parser at &8088 calls the existing parse_decimal routine (at &85FD) twice if a dot separator is present. The first number becomes the network (&0E01, via TAX pass-through in parse_decimal) and the second becomes the station (&0E00). It handles both station and network.station forms.

Colon command continuation: If a colon (:) is found in the command text, the code echoes the colon and reads interactive input character by character via OSRDCH (&80A7), appending it to the command buffer. After CR is received, the combined text is forwarded to the fileserver. This enables constructs like *NET 123:command where the text after the colon is typed interactively.

3. Per-ROM disable flag

The service handler at &80EA gains a new guard. Before dispatching any service call, 3.35D reads the byte at &0DF0+X (where X is the ROM bank number, within the NFS workspace page at &0D00-&0DFF). If bit 7 of that byte is set, the ROM returns immediately without processing the service call:

Runtime addr Instruction Purpose
&80EA PHA Save service number
&80EB LDA &0DF0,X Read per-ROM disable flag
&80EE ASL A Shift bit 7 into carry
&80EF PLA Restore service number
&80F0 BMI c80f4 If &FE/&FF, skip disable check
&80F2 BCS return_1 If carry set, ROM is disabled

Service calls &FE (Tube init) and &FF (full reset) bypass this check, since they are needed regardless of the disable state. This provides a mechanism for the MOS or another ROM to selectively disable NFS without removing the ROM physically.

4. Table-driven vector initialisation

The hardcoded LDA/STA pairs in 3.34B's init_vectors_and_copy (at &80C8) have been replaced by a data-driven loop at &810D-&8123. The loop reads four triplets from a table at &8177, each containing (low byte, high byte, vector offset), and stores the 16-bit addresses for BRKV, WRCHV, RDCHV, and EVNTV at &0200+offset:

Vector Address 3.34B value 3.35D value
BRKV &0202 &0016 &0016
WRCHV &020E &051C &051C
RDCHV &0210 &04E7 &04E7
EVNTV &0220 &06E8 &06E5

The EVNTV target changed from &06E8 to &06E5, reflecting a 3-byte shift in the page 6 event handler code. All other vector targets are unchanged.

5. Tube WRCH register: R4 back to R1

The most significant Tube protocol change, and a reversal of the 3.34 to 3.34B change. Five instructions in the Tube host code were modified to use Tube register R1 (&FEE0/&FEE1) instead of R4 (&FEE6/&FEE7) for WRCH character I/O:

Runtime addr 3.34B 3.35D Routine
&003A BIT &FEE6 BIT &FEE0 tube_main_loop
&003F LDA &FEE7 LDA &FEE1 tube_handle_wrch
&004A BIT &FEE6 BIT &FEE0 tube_poll_r2
&0527 BIT &FEE6 BIT &FEE0 tube_poll_r4/r1_wrch
&052C LDA &FEE7 LDA &FEE1 tube_poll_r4/r1_wrch

The routine at &0527 is labeled tube_poll_r4_wrch in 3.34B (polling R4) and tube_poll_r1_wrch in 3.35D (polling R1), reflecting the register reversion.

These are all in the relocated Tube host code (BRK handler block and page 5 block), which is copied from ROM to RAM during initialisation.

History: In NFS 3.34, WRCH polling used R1. NFS 3.34B moved it to R4 (the one-byte transfer register). NFS 3.35D reverses this, returning WRCH to R1. The tube_send_r4 routine continues to use R4 for outbound data in all three versions.

The BBC Micro Tube ULA register pairs:

6. GSINIT/GSREAD filename parsing for FSCV

In 3.34B, FSCV codes 2 (*/), 3 (unrecognised * command), and 4 (*RUN) all dispatch to the same handler (fscv_star_handler). In 3.35D, codes 2 and 4 now dispatch through a new wrapper at &8DC7 (sub_c8dc7) which calls parse_filename_gs first, using the MOS GSINIT/GSREAD API (&FFC2/&FFC5) to parse the filename into the command buffer at &0E30 before forwarding it to the fileserver. A second GSINIT/GSREAD call at &8DE2 consumes the parsed filename and advances past trailing spaces.

This adds support for quoted filenames and |-escaped special characters in */ and *RUN commands. The * (unrecognised command) path via FSCV code 3 still bypasses this parsing, passing the raw command text to the fileserver.

3.34B contains one GSINIT/GSREAD call site (at &86A6); 3.35D contains two (at &86C8 and &8DE2).

7. Table-driven Tube address claim

The tube_addr_claim routine in the page 4 relocated code was restructured. In 3.34B, a series of compare-and-branch instructions selects the appropriate Tube control register value based on the claim type. In 3.35D, this is replaced by a lookup table containing the 8 possible control register values, indexed by the claim type in X. The lookup table approach is more compact and easier to extend.

8. OSBYTE &C6 replaces &C7 in error handler

In the error handling path (3.34B: &83E8, 3.35D: &8432), the OSBYTE call changed from &C7 (read/write *SPOOL file handle) to &C6 (read/write *EXEC file handle). The purpose is to check whether the currently open EXEC or SPOOL file is using the same handle as the one that just errored, and if so, to close it. 3.35D checks the EXEC handle instead of the SPOOL handle, which may fix a bug where errors during an *EXEC file were not properly detected.

9. Catalogue formatting

Several small changes to the *CAT display formatting:

10. Option name encoding reorganised

The boot option names ("Off", "Load", "Run", "Exec") displayed by *CAT are stored and accessed differently in 3.35D.

3.34B: The four strings are stored contiguously at option_name_strings (&8D3B), null-terminated, with a separate 4-byte offset table at option_name_offsets (&8D4C). The lookup code at &8C74 indexes the offset table, then reads characters until a zero byte:

    LDY option_name_offsets,X   ; &8C74: index into offset table
.loop
    LDA option_name_strings,Y   ; &8C77: read character
    BEQ done                    ; null terminator

3.35D: The strings are scattered through the code between instructions, addressed via base+offset from &8CDE. The first four bytes at &8CDE — starting with the ROR A opcode &6A — double as the offset table:

Offset byte Address String
&6A &8CDE+&6A=&8D48 "Off"
&7D &8CDE+&7D=&8D5B "Load"
&A5 &8CDE+&A5=&8D83 "Run"
&18 &8CDE+&18=&8CF6 "Exec"

Each string is terminated by the next byte having bit 7 set (the opcode of the following instruction, e.g. LDA #imm = &A9, RTS = &60) rather than by a null byte. The lookup code at &8C75 uses the same base address for both the offset table and the string data:

    LDY c8cde,X                 ; &8C75: read offset from base
.loop
    LDA c8cde,Y                 ; &8C78: read character from base+offset
    BMI done                    ; high-bit terminator (next opcode)

This eliminates the contiguous 21-byte data block (17 string bytes + 4 offset bytes) by reusing existing code bytes as the offset table and embedding the string data in gaps between instructions.

11. CSD check added to print_file_info

The print_file_info routine (&8CFA) gains a conditional check at entry that is not present in 3.34B:

3.35D (at &8D01-&8D09):

    LDX fs_cmd_csd              ; &8D01: load CSD handle from &0F03
    BEQ &8D0B                   ; if zero, skip to print filename
    JSR &8D61                   ; load fs_cmd_data[X], test bit 7
    BMI &8D23                   ; if set, skip filename — show hex only

When the CSD handle byte is non-zero and the corresponding byte in the FS command data buffer has bit 7 set, the routine skips the filename display and jumps directly to the load/exec/length hex fields. This check was subsequently removed in 3.35K.

12. Workspace variable relocation

Several workspace variables moved to different addresses:

Label 3.34B address 3.35D address
nfs_temp &CD &A8
rom_svc_num &CE &A9
tx_clear_flag &0D3A &0D62
Per-ROM disable flag (none) &0DF0+X

13. Shifted address operands

All code throughout the ROM has shifted addresses due to the cumulative effect of insertions and deletions. Unlike the 3.34 to 3.34B transition (a uniform +1 shift from &81C8 onwards), the shifts in 3.35D are non-uniform, varying by region as multiple code blocks were inserted, deleted, and reorganised.

Address map

The address mapping between 3.34B and 3.35D is non-linear, with 161 change blocks. Key entry point mappings:

Function 3.34B 3.35D Offset
Language entry &8099 &80D4 +&3B
Service entry &80AF &80EA +&3B
Init vectors &80C8 &810D +&45
Version string &81BF &81FA +&3B
Service dispatch &80B7 &8150 +&99

Relocated code block ROM sources (all +&12):

Block 3.34B 3.35D Runtime Size
BRK handler &9308 &931A &0016-&0076 97 bytes
Tube host (pg 4) &934D &935F &0400-&04FF 256 bytes
Tube host (pg 5) &944D &945F &0500-&05FF 256 bytes
Tube host (pg 6) &954D &955F &0600-&06FF 256 bytes