Acorn NFS 3.62

Updated 31 Mar 2026

← All Acorn NFS and Acorn ANFS versions

; Sideways ROM header
; NFS ROM 3.62 disassembly (Acorn Econet filing system)
; ====================================================
8000 .rom_header←2← 04E6 LDA← 9B42 JSR
.language_entry←2← 04E6 LDA← 9B42 JSR
.pydis_start←2← 04E6 LDA← 9B42 JSR
JMP language_handler ; JMP language_handler
8003 .service_entry←1← 04F5 LDY
JMP service_handler ; JMP service_handler
8006 .rom_type←1← 04DA AND
EQUB &82 ; ROM type: service + language
8007 .copyright_offset←1← 04E2 LDX
EQUB copyright - rom_header ; Copyright string offset from &8000
8008 .binary_version←2← 836E CMP← 8377 LDA
EQUB &83 ; Binary version number
8009 .title
EQUS " NET" ; ROM title string " NET"
8010 .copyright
EQUB &00 ; Null terminator before copyright
; The 'ROFF' suffix (copyright_string+3) is reused by
; the *ROFF command matcher (svc_star_command) — a
; space-saving trick that shares ROM bytes between the
; copyright string and the star command table.
8011 .copyright_string
EQUS "(C)ROFF" ; Copyright string "(C)ROFF"
; Error message offset table (9 entries).
; Each byte is a Y offset into error_msg_table.
; Entry 0 (Y=0, "Line Jammed") doubles as the
; copyright string null terminator.
; Indexed by TXCB status (AND #7), or hardcoded 8.
8018 .error_offsets←1← 8515 LDY
EQUB &00 ; "Line Jammed"
8019 EQUB &0D ; "Net Error"
801A EQUB &18 ; "Not listening"
801B EQUB &27 ; "No Clock"
801C EQUB &31 ; "Escape"
801D EQUB &31 ; "Escape"
801E EQUB &31 ; "Escape"
801F EQUB &39 ; "Bad Option"
8020 EQUB &45 ; "No reply"
; Four bytes with unknown purpose.
8021 EQUB &01 ; Purpose unknown
8022 EQUB &00 ; Purpose unknown
8023 EQUB &62 ; Purpose unknown
8024 EQUB &03 ; Purpose unknown
; Dispatch table: low bytes of (handler_address - 1)
; Each entry stores the low byte of a handler address minus 1,
; for use with the PHA/PHA/RTS dispatch trick at &80E7.
; See dispatch_0_hi (&804A) for the corresponding high bytes.
; Five callers share this table via different Y base offsets:
; Y=&00 Service calls 0-12 (indices 0-13)
; Y=&0E Language entry reasons (indices 14-18)
; Y=&13 FSCV codes 0-7 (indices 19-26)
; Y=&17 FS reply handlers (indices 27-32)
; Y=&21 *NET1-4 sub-commands (indices 33-36)
; Lo bytes for the last 6 entries (indices 31-36) occupy
; &8044-&8049, immediately before the hi bytes. Their hi
; bytes are at &8069-&806E, after dispatch_0_hi.
8025 .dispatch_0_lo
EQUB <(return_1-1) ; lo - Svc 0: already claimed (no-op)
8026 EQUB <(svc_1_abs_workspace-1) ; lo - Svc 1: absolute workspace
8027 EQUB <(svc_2_private_workspace-1) ; lo - Svc 2: private workspace
8028 EQUB <(svc_3_autoboot-1) ; lo - Svc 3: auto-boot
8029 EQUB <(svc_4_star_command-1) ; lo - Svc 4: unrecognised star command
802A EQUB <(svc5_irq_check-1) ; lo - Svc 5: unrecognised interrupt
802B EQUB <(return_1-1) ; lo - Svc 6: BRK (no-op)
802C EQUB <(dispatch_net_cmd-1) ; lo - Svc 7: unrecognised OSBYTE
802D EQUB <(svc_8_osword-1) ; lo - Svc 8: unrecognised OSWORD
802E EQUB <(svc_9_help-1) ; lo - Svc 9: *HELP
802F EQUB <(return_1-1) ; lo - Svc 10: static workspace (no-op)
8030 EQUB <(econet_restore-1) ; lo - Svc 11: NMI release (reclaim NMIs)
8031 EQUB <(econet_save-1) ; lo - Svc 12: NMI claim (save NMI state)
8032 EQUB <(svc_13_select_nfs-1) ; lo - Svc 13: select NFS (intercepted before dispatch)
8033 EQUB <(lang_0_insert_remote_key-1) ; lo - Lang 0: no language / Tube
8034 EQUB <(lang_1_remote_boot-1) ; lo - Lang 1: normal startup
8035 EQUB <(lang_2_save_palette_vdu-1) ; lo - Lang 2: softkey byte (Electron)
8036 EQUB <(lang_3_execute_at_0100-1) ; lo - Lang 3: softkey length (Electron)
8037 EQUB <(lang_4_remote_validated-1) ; lo - Lang 4: remote validated
8038 EQUB <(fscv_0_opt_entry-1) ; lo - FSCV 0: *OPT
8039 EQUB <(fscv_1_eof-1) ; lo - FSCV 1: EOF check
803A EQUB <(fscv_2_star_run-1) ; lo - FSCV 2: */ (run)
803B EQUB <(fscv_3_star_cmd-1) ; lo - FSCV 3: unrecognised star command
803C EQUB <(fscv_2_star_run-1) ; lo - FSCV 4: *RUN
803D EQUB <(fscv_5_cat-1) ; lo - FSCV 5: *CAT
803E EQUB <(fscv_6_shutdown-1) ; lo - FSCV 6: shutdown
803F EQUB <(fscv_7_read_handles-1) ; lo - FSCV 7: read handle range
8040 EQUB <(fsreply_0_print_dir-1) ; lo - FS reply: print directory name
8041 EQUB <(fsreply_1_copy_handles_boot-1) ; lo - FS reply: copy handles + boot
8042 EQUB <(fsreply_2_copy_handles-1) ; lo - FS reply: copy handles
8043 EQUB <(fsreply_3_set_csd-1) ; lo - FS reply: set CSD handle
8044 EQUB <(fsreply_4_notify_exec-1) ; lo - FS reply: notify + execute
8045 EQUB <(fsreply_5_set_lib-1) ; lo - FS reply: set library handle
8046 EQUB <(net_1_read_handle-1) ; lo - *NET1: read handle from packet
8047 EQUB <(net_2_read_handle_entry-1) ; lo - *NET2: read handle from workspace
8048 EQUB <(net_3_close_handle-1) ; lo - *NET3: close handle
8049 EQUB <(net_4_resume_remote-1) ; lo - *NET4: resume remote
; Dispatch table: high bytes of (handler_address - 1)
; Paired with dispatch_0_lo (&8025). Together they form a table
; of 37 handler addresses, used via the PHA/PHA/RTS trick at
; &80E7.
804A .dispatch_0_hi
EQUB >(return_1-1) ; hi - Svc 0: already claimed (no-op)
804B EQUB >(svc_1_abs_workspace-1) ; hi - Svc 1: absolute workspace
804C EQUB >(svc_2_private_workspace-1) ; hi - Svc 2: private workspace
804D EQUB >(svc_3_autoboot-1) ; hi - Svc 3: auto-boot
804E EQUB >(svc_4_star_command-1) ; hi - Svc 4: unrecognised star command
804F EQUB >(svc5_irq_check-1) ; hi - Svc 5: unrecognised interrupt
8050 EQUB >(return_1-1) ; hi - Svc 6: BRK (no-op)
8051 EQUB >(dispatch_net_cmd-1) ; hi - Svc 7: unrecognised OSBYTE
8052 EQUB >(svc_8_osword-1) ; hi - Svc 8: unrecognised OSWORD
8053 EQUB >(svc_9_help-1) ; hi - Svc 9: *HELP
8054 EQUB >(return_1-1) ; hi - Svc 10: static workspace (no-op)
8055 EQUB >(econet_restore-1) ; hi - Svc 11: NMI release (reclaim NMIs)
8056 EQUB >(econet_save-1) ; hi - Svc 12: NMI claim (save NMI state)
8057 EQUB >(svc_13_select_nfs-1) ; hi - Svc 13: select NFS (intercepted before dispatch)
8058 EQUB >(lang_0_insert_remote_key-1) ; hi - Lang 0: no language / Tube
8059 EQUB >(lang_1_remote_boot-1) ; hi - Lang 1: normal startup
805A EQUB >(lang_2_save_palette_vdu-1) ; hi - Lang 2: softkey byte (Electron)
805B EQUB >(lang_3_execute_at_0100-1) ; hi - Lang 3: softkey length (Electron)
805C EQUB >(lang_4_remote_validated-1) ; hi - Lang 4: remote validated
805D EQUB >(fscv_0_opt_entry-1) ; hi - FSCV 0: *OPT
805E EQUB >(fscv_1_eof-1) ; hi - FSCV 1: EOF check
805F EQUB >(fscv_2_star_run-1) ; hi - FSCV 2: */ (run)
8060 EQUB >(fscv_3_star_cmd-1) ; hi - FSCV 3: unrecognised star command
8061 EQUB >(fscv_2_star_run-1) ; hi - FSCV 4: *RUN
8062 EQUB >(fscv_5_cat-1) ; hi - FSCV 5: *CAT
8063 EQUB >(fscv_6_shutdown-1) ; hi - FSCV 6: shutdown
8064 EQUB >(fscv_7_read_handles-1) ; hi - FSCV 7: read handle range
8065 EQUB >(fsreply_0_print_dir-1) ; hi - FS reply: print directory name
8066 EQUB >(fsreply_1_copy_handles_boot-1) ; hi - FS reply: copy handles + boot
8067 EQUB >(fsreply_2_copy_handles-1) ; hi - FS reply: copy handles
8068 EQUB >(fsreply_3_set_csd-1) ; hi - FS reply: set CSD handle
8069 EQUB >(fsreply_4_notify_exec-1) ; hi - FS reply: notify + execute
806A EQUB >(fsreply_5_set_lib-1) ; hi - FS reply: set library handle
806B EQUB >(net_1_read_handle-1) ; hi - *NET1: read handle from packet
806C EQUB >(net_2_read_handle_entry-1) ; hi - *NET2: read handle from workspace
806D EQUB >(net_3_close_handle-1) ; hi - *NET3: close handle
806E EQUB >(net_4_resume_remote-1) ; hi - *NET4: resume remote

*NET command dispatcher

Parses the character after *NET as '1'-'4', maps to table indices 33-36 via base offset Y=&21, and dispatches via &80E7. Characters outside '1'-'4' fall through to return_1 (RTS).

These are internal sub-commands used only by the ROM itself, not user-accessible star commands. The MOS command parser requires a space or terminator after 'NET', so *NET1 typed at the command line does not match; these are reached only via OSCLI calls within the ROM.

*NET1 (&8E67): read file handle from received packet (net_1_read_handle)

*NET2 (&8E6D): read handle entry from workspace (net_2_read_handle_entry)

*NET3 (&8E7D): close handle / mark as unused (net_3_close_handle)

*NET4 (&81BC): resume after remote operation (net_4_resume_remote)

806F .dispatch_net_cmd
LDA osbyte_a_copy ; Read command character following *NET
8071 SBC #&31 ; Subtract ASCII '1' to get 0-based command index
8073 CMP #4 ; Command index >= 4: invalid *NET sub-command
8075 BCS svc_dispatch_range ; Out of range: return via c80e3/RTS
8077 TAX ; X = command index (0-3)
8078 LDA #0 ; Clear &A9 (used by dispatch)
807A STA svc_state ; Store zero to &A9
807C TYA ; Preserve A before dispatch
807D LDY #&21 ; Y=&21: base offset for *NET commands (index 33+)
807F BNE dispatch ; ALWAYS branch to dispatch ALWAYS branch
8081 .skip_cmd_spaces←1← 8086 BEQ
INY ; Advance past matched command text
fall through ↓

"I AM" command handler

Dispatched from the command match table when the user types "*I AM <station>" or "*I AM <network>.<station>". Also used as the station number parser for "*NET <network>.<station>". Skips leading spaces, then calls parse_decimal for the first number. If a dot separator was found (carry set), it stores the result directly as the network (&0E01) and calls parse_decimal again for the station (&0E00). With a single number, it is stored as the station and the network defaults to 0 (local). If a colon follows, reads interactive input via OSRDCH and appends it to the command buffer. Finally jumps to forward_star_cmd.

8082 .i_am_handler
LDA (fs_options),y ; Load next char from command line
8084 CMP #&20 ; Skip spaces
8086 BEQ skip_cmd_spaces ; Loop back to skip leading spaces
8088 CMP #&3a ; Colon = interactive remote command prefix
808A BCS skip_stn_parse ; Char >= ':': skip number parsing
808C JSR parse_decimal ; Parse decimal number from (fs_options),Y (DECIN)
808F BCC got_station_num ; C=1: dot found, first number was network
8091 STA fs_server_net ; Store network number (n.s = network.station) A=parsed value (accumulated in &B2)
8094 INY ; Y=offset into (fs_options) buffer
8095 JSR parse_decimal ; Parse decimal number from (fs_options),Y (DECIN)
8098 .got_station_num←1← 808F BCC
BEQ skip_stn_parse ; Z=1: no station parsed (empty or non-numeric)
809A STA fs_server_stn ; A=parsed value (accumulated in &B2)
809D .skip_stn_parse←2← 808A BCS← 8098 BEQ
JSR infol2 ; Copy command text to FS buffer
80A0 .scan_for_colon←2← 80A8 BNE← 80BF BNE
DEY ; Scan backward for ':' (interactive prefix)
80A1 BEQ prepare_cmd_dispatch ; Y=0: no colon found, send command
80A3 LDA fs_cmd_data,y ; Read char from FS command buffer
80A6 CMP #&3a ; Test for colon separator
80A8 BNE scan_for_colon ; Not colon: keep scanning backward
80AA JSR oswrch ; Echo colon, then read user input from keyboard Write character
80AD .read_remote_cmd_line←1← 80BA BNE
JSR check_escape ; Check for escape condition
80B0 JSR osrdch ; Test escape flag before FS reply Read a character from the current input stream
80B3 STA fs_cmd_data,y ; Append typed character to command buffer A=character read
80B6 INY ; Advance write pointer
80B7 INX ; Increment character count
80B8 CMP #&0d ; Test for CR (end of line)
80BA BNE read_remote_cmd_line ; Not CR: continue reading input
80BC JSR osnewl ; Write newline (characters 10 and 13)
80BF BNE scan_for_colon ; After OSNEWL: loop back to scan for colon
fall through ↓

Forward unrecognised * command to fileserver (COMERR)

Copies command text from (fs_crc_lo) to &0F05+ via copy_filename, prepares an FS command with function code 0, and sends it to the fileserver to request decoding. The server returns a command code indicating what action to take (e.g. code 4=INFO, 7=DIR, 9=LIB, 5=load-as-command). This mechanism allows the fileserver to extend the client's command set without ROM updates. Called from the "I." and catch-all entries in the command match table at &8C4B, and from FSCV 2/3/4 indirectly. If CSD handle is zero (not logged in), returns without sending.

80C1 .forward_star_cmd←1← 9FBE JMP
JSR infol2 ; Copy command text to FS buffer
80C4 TAY ; Y=function code for HDRFN
80C5 .prepare_cmd_dispatch←1← 80A1 BEQ
JSR prepare_fs_cmd ; Prepare FS command buffer (12 references)
80C8 LDX fs_cmd_csd ; X=depends on function
80CB BEQ return_1 ; CSD handle zero: not logged in
80CD LDA fs_cmd_data ; A=function code (0-7)
80D0 LDY #&17 ; Y=depends on function
80D2 BNE dispatch ; ALWAYS branch

FSCV dispatch entry

Entered via the extended vector table when the MOS calls FSCV. Stores A/X/Y via save_fscv_args, compares A (function code) against 8, and dispatches codes 0-7 via the shared dispatch table at &8024 with base offset Y=&13 (table indices 20-27). Function codes: 0=*OPT, 1=EOF, 2=*/, 3=unrecognised *, 4=*RUN, 5=*CAT, 6=shutdown, 7=read handles.

On EntryAfunction code (0-7)
Xdepends on function
Ydepends on function
On ExitAdepends on handler (preserved if A >= 8)
Xdepends on handler (preserved if A >= 8)
Ydepends on handler (preserved if A >= 8)
80D4 .fscv_handler
JSR save_fscv_args_with_ptrs ; Store A/X/Y in FS workspace
80D7 CMP #8 ; FSCV function >= 8?
80D9 BCS return_1 ; Function code >= 8? Return (unsupported)
80DB TAX ; X = function code for dispatch
80DC TYA ; Save Y (command text ptr hi)
80DD LDY #&13 ; Y=&13: base offset for FSCV dispatch (indices 20+)
80DF BNE dispatch ; ALWAYS branch

Language entry dispatcher

Called when the NFS ROM is entered as a language. Although rom_type (&82) does not set the language bit, the MOS enters this point after NFS claims service &FE (Tube post-init). X = reason code (0-4). Dispatches via table indices 15-19 (base offset Y=&0E).

80E1 .language_handler←1← 8000 JMP
.lang_entry_dispatch←1← 8000 JMP
CPX #5 ; X >= 5: invalid reason code, return
80E3 .svc_dispatch_range←1← 8075 BCS
BCS return_1 ; Out of range: return via RTS
80E5 LDY #&0e ; Y=&0E: base offset for language handlers (index 15+)
fall through ↓

PHA/PHA/RTS computed dispatch

X = command index within caller's group (e.g. service number) Y = base offset into dispatch table (0, &0E, &13, &21, etc.) The loop adds Y+1 to X, so final X = command index + base + 1. Then high and low bytes of (handler-1) are pushed onto the stack, and RTS pops them and jumps to handler_address.

This is a standard 6502 trick: RTS increments the popped address by 1 before jumping, so the table stores (address - 1) to compensate. Multiple callers share one table via different Y base offsets.

80E7 .dispatch←5← 807F BNE← 80D2 BNE← 80DF BNE← 80E9 BPL← 81A1 JSR
INX ; Add base offset Y to index X (loop: X += Y+1)
80E8 DEY ; Decrement base offset counter
80E9 BPL dispatch ; Loop until Y exhausted
80EB TAY ; Y=&FF (no further use)
80EC LDA dispatch_0_hi-1,x ; Load high byte of (handler - 1) from table
80EF PHA ; Push high byte onto stack
80F0 LDA dispatch_0_lo-1,x ; Load low byte of (handler - 1) from table
80F3 PHA ; Push low byte onto stack
80F4 LDX fs_options ; Restore X (fileserver options) for use by handler
80F6 .return_1←3← 80CB BEQ← 80D9 BCS← 80E3 BCS
RTS ; RTS pops address, adds 1, jumps to handler
80F7 .service_handler←1← 8003 JMP
NOP ; 9 NOPs: bus settling time for ADLC probe
80F8 NOP ; (bus settling continued)
80F9 NOP ; (bus settling continued)
80FA NOP ; (bus settling continued)
80FB NOP ; (bus settling continued)
80FC NOP ; (bus settling continued)
80FD NOP ; (bus settling continued)
80FE NOP ; (bus settling continued)
80FF NOP ; (bus settling continued)
8100 PHA ; Save service call number
8101 CMP #1 ; Only probe ADLC on service 1 (workspace claim)
8103 BNE check_disable_flag ; Not service 1: skip probe
8105 LDA econet_control1_or_status1 ; Probe ADLC SR1: non-zero = absent (bus noise)
8108 AND #&ed ; Mask SR1 status bits (ignore bits 4,1)
810A BNE set_adlc_disable ; Non-zero: ADLC absent, set disable flag
810C LDA econet_control23_or_status2 ; Probe ADLC SR2 if SR1 was all zeros
810F AND #&db ; Mask SR2 status bits (ignore bits 5,2)
8111 BEQ check_disable_flag ; Both zero: ADLC present, skip
8113 .set_adlc_disable←1← 810A BNE
ROL rom_ws_table,x ; Set bit 7 of per-ROM workspace = disable flag
8116 SEC ; SEC for ROR to set bit 7
8117 ROR rom_ws_table,x ; Rotate carry into bit 7 of workspace
811A .check_disable_flag←2← 8103 BNE← 8111 BEQ
LDA rom_ws_table,x ; Read back flag; ASL puts bit 7 into carry
811D ASL ; C into bit 7 of A
811E PLA ; Restore service call number
811F BMI check_svc_high ; Service >= &80: always handle (Tube/init)
8121 BCS svc_unhandled_return ; C=1 (no ADLC): disable ROM, skip
fall through ↓

Service handler entry

Preamble at &80F7 (9 NOPs + ADLC probe): on service 1 only, probes ADLC status registers &FEA0/&FEA1 to detect whether Econet hardware is present. Non-zero reads indicate bus noise from absent hardware; sets bit 7 of per-ROM workspace as a disable flag. For services < &80, the flag causes an early return (disabling this ROM). Services >= &80 (&FE, &FF) are always handled regardless of flag.

Intercepts three service calls before normal dispatch: &FE: Tube init — explode character definitions &FF: Full init — vector setup, copy code to RAM, select NFS &12 (Y=5): Select NFS as active filing system All other service calls < &0D dispatch via c8146.

Probes ADLC status registers SR1 (&FEA0) and SR2 (&FEA1) to detect whether Econet hardware is present. Sets bit 7 of per-ROM workspace as a disable flag if not found. The 9 NOPs at &80F7 provide bus settling time after register access.

8123 .check_svc_high←1← 811F BMI
.service_handler_entry←1← 811F BMI
CMP #&fe ; Service >= &FE?
8125 BCC check_svc_12 ; Service < &FE: skip to &12/dispatch check
8127 BNE init_vectors_and_copy ; Service &FF: full init (vectors + RAM copy)
8129 CPY #0 ; Service &FE: Y=0?
812B BEQ check_svc_12 ; Y=0: no Tube data, skip to &12 check
812D LDX #6 ; X=6 extra pages for char definitions
812F LDA #osbyte_explode_chars ; OSBYTE &14: explode character RAM
8131 JSR osbyte ; Explode character definition RAM (six extra pages), can redefine all characters 32-255 (X=6)
8134 .poll_tube_ready←2← 8137 BPL← 8141 JMP
BIT tube_status_1_and_tube_control ; Poll Tube status register 1
8137 BPL poll_tube_ready ; Loop until Tube ready (bit 7 set)
8139 LDA tube_data_register_1 ; Read byte from Tube data register 1
813C BEQ tube_chars_done ; Zero byte: Tube transfer complete
813E JSR oswrch ; Send Tube char to screen via OSWRCH Write character
8141 JMP poll_tube_ready ; Loop for next Tube byte
8144 .init_vectors_and_copy←1← 8127 BNE
LDA #&ad ; EVNTV low = &AD (event handler address)
8146 STA evntv ; Set EVNTV low byte at &0220
8149 LDA #6 ; EVNTV high = &06 (page 6)
814B STA evntv+1 ; Set EVNTV high byte at &0221
814E LDA #&16 ; BRKV low = &16 (NMI workspace)
8150 STA brkv ; Set BRKV low byte at &0202
8153 LDA #0 ; BRKV high = &00 (zero page)
8155 STA brkv+1 ; Set BRKV high byte at &0203
8158 LDA #&8e ; Tube control register init value &8E
815A STA tube_status_1_and_tube_control ; Write to Tube control register
815D LDY #0 ; Y=0: copy 256 bytes per page
; Copy NMI handler code from ROM to RAM pages &04-&06
815F .cloop←1← 8172 BNE
LDA reloc_p4_src,y ; Load ROM byte from page &93
8162 STA tube_code_page4,y ; Store to page &04 (Tube code)
8165 LDA reloc_p5_src,y ; Load ROM byte from page &94
8168 STA tube_dispatch_table,y ; Store to page &05 (dispatch table)
816B LDA reloc_p6_src,y ; Load ROM byte from page &95
816E STA tube_page6_start,y ; Store to page &06
8171 DEY ; DEY wraps 0 -> &FF on first iteration
8172 BNE cloop ; Loop until 256 bytes copied per page
8174 JSR tube_post_init ; Run post-init routine in copied code
8177 LDX #&60 ; X=&60: copy 97 bytes (&60..&00)
; Copy NMI workspace initialiser from ROM to &0016-&0076
8179 .copy_nmi_workspace←1← 817F BPL
LDA reloc_zp_src,x ; Load NMI workspace init byte from ROM
817C STA nmi_workspace_start,x ; Store to zero page &16+X
817E DEX ; Next byte
817F BPL copy_nmi_workspace ; Loop until all workspace bytes copied
8181 .tube_chars_done←1← 813C BEQ
LDA #0 ; A=0: fall through to service &12 check
8183 .check_svc_12←2← 8125 BCC← 812B BEQ
CMP #&12 ; Is this service &12 (select FS)?
8185 BNE not_svc_12_nfs ; No: check if service < &0D
8187 CPY #5 ; Service &12: Y=5 (NFS)?
8189 BNE not_svc_12_nfs ; Not NFS: check if service < &0D
818B LDA #&0d ; A=&0D: dispatch index for svc_13_select_nfs
818D BNE do_svc_dispatch ; ALWAYS branch to dispatch ALWAYS branch
818F .not_svc_12_nfs←2← 8185 BNE← 8189 BNE
CMP #&0d ; Service >= &0D?
8191 .svc_unhandled_return←1← 8121 BCS
BCS return_2 ; Service >= &0D: not handled, return
8193 .do_svc_dispatch←1← 818D BNE
TAX ; X = service number (dispatch index)
8194 LDA svc_state ; Save &A9 (current service state)
8196 PHA ; Push saved &A9
8197 LDA ws_page ; Save &A8 (workspace page number)
8199 PHA ; Push saved &A8
819A STX svc_state ; Store service number to &A9
819C STY ws_page ; Store Y (page number) to &A8
819E TYA ; A = Y for dispatch table offset
819F LDY #0 ; Y=0: base offset for service dispatch
81A1 JSR dispatch ; Dispatch to service handler
81A4 LDX svc_state ; Recover service claim status from &A9
81A6 PLA ; Restore saved &A8 from stack
81A7 STA ws_page ; Write back &A8
fall through ↓

Service 4: unrecognised * command

The first 5 bytes (&81A9-&81AF) are the service handler epilogue: PLA/STA restores &A9, TXA/LDX retrieves romsel_copy, then RTS. This is the common return path reached after any dispatched service handler completes.

The service 4 handler entry at &81B5 (after 5 NOPs of padding) makes two match_rom_string calls against the ROM header, reusing header bytes as command strings:

  X=&0C: matches "ROFF" at copyright_string+3 — the
         suffix of "(C)ROFF" — *ROFF (Remote Off, end
         remote session) — falls through to net_4_resume_remote
  X=5: matches "NET" at the ROM title suffix
       — *NET (select NFS) — falls through to svc_13_select_nfs

If neither matches, returns with the service call unclaimed.

81A9 .svc_star_command
PLA ; Restore saved A from service dispatch
81AA STA svc_state ; Save to workspace &A9
81AC TXA ; Return ROM number in A
81AD LDX romsel_copy ; Restore X from MOS ROM select copy
81AF .return_2←1← 8191 BCS
RTS ; Return to MOS service handler
81B0 NOP ; Padding: dispatch targets &81B5
81B1 NOP ; NOP padding for command table
81B2 NOP ; NOP padding
81B3 NOP ; NOP padding
81B4 NOP ; NOP padding
81B5 .svc_4_star_command
LDX #&0c ; ROM offset for "ROFF" (copyright suffix)
81B7 JSR match_rom_string ; Try matching *ROFF command
81BA BNE match_net_cmd ; No match: try *NET
fall through ↓

Resume after remote operation / *ROFF handler (NROFF)

Checks byte 4 of (net_rx_ptr): if non-zero, the keyboard was disabled during a remote operation (peek/poke/boot). Clears the flag, re-enables the keyboard via OSBYTE &C9, and sends function &0A to notify completion. Also handles *ROFF and the triple-plus escape sequence (+++), which resets system masks via OSBYTE &CE and returns control to the MOS, providing an escape route when a remote session becomes unresponsive.

81BC .net_4_resume_remote
LDY #4 ; Y=4: offset of keyboard disable flag
81BE LDA (net_rx_ptr),y ; Read flag from RX buffer
81C0 BEQ skip_kbd_reenable ; Zero: keyboard not disabled, skip
81C2 LDA #0 ; A=0: value to clear flag and re-enable
81C4 TAX ; X=&00
81C5 STA (net_rx_ptr),y ; Clear keyboard disable flag in buffer
81C7 TAY ; Y=&00
81C8 LDA #osbyte_read_write_econet_keyboard_disable ; OSBYTE &C9: Econet keyboard disable
81CA JSR osbyte ; Re-enable keyboard (X=0, Y=0) Enable keyboard (for Econet)
81CD LDA #&0a ; Function &0A: remote operation complete
81CF JSR setup_tx_and_send ; Send notification to controlling station
81D2 .clear_osbyte_ce_cf←1← 84CE JSR
STX nfs_workspace ; Save X (return value from TX)
81D4 LDA #&ce ; OSBYTE &CE: first system mask to reset
81D6 .clear_osbyte_masks←1← 81E1 BEQ
LDX nfs_workspace ; Restore X for OSBYTE call
81D8 LDY #&7f ; Y=&7F: AND mask (clear bit 7)
81DA JSR osbyte ; Reset system mask byte
81DD ADC #1 ; Advance to next OSBYTE (&CE -> &CF)
81DF CMP #&d0 ; Reached &D0? (past &CF)
81E1 .cmd_name_matched
BEQ clear_osbyte_masks ; No: reset &CF too
81E3 .skip_kbd_reenable←1← 81C0 BEQ
LDA #0 ; A=0: clear remote state
81E5 STA svc_state ; Clear &A9 (service dispatch state)
81E7 .skpspi
STA nfs_workspace ; Clear workspace byte
81E9 RTS ; Return
81EA .match_net_cmd←1← 81BA BNE
LDX #5 ; X=5: ROM offset for "NET" match
81EC JSR match_rom_string ; Try matching *NET command
81EF BNE restore_ws_return ; No match: return unclaimed
fall through ↓

Select NFS as active filing system (INIT)

Reached from service &12 (select FS) with Y=5, or when *NET command selects NFS. Notifies the current FS of shutdown via FSCV A=6 — this triggers the outgoing FS to save its context back to its workspace page, allowing restoration if re-selected later (the FSDIE handoff mechanism). Then sets up the standard OS vector indirections (FILEV through FSCV) to NFS entry points, claims the extended vector table entries, and issues service &0F (vectors claimed) to notify other ROMs. If nfs_temp is zero (auto-boot not inhibited), injects the synthetic command "I .BOOT" through the command decoder to trigger auto-boot login.

81F1 .svc_13_select_nfs
JSR call_fscv_shutdown ; Notify current FS of shutdown (FSCV A=6)
81F4 SEC ; C=1 for ROR
81F5 ROR ws_page ; Set bit 7 of l00a8 (inhibit auto-boot)
81F7 JSR issue_vectors_claimed ; Claim OS vectors, issue service &0F
81FA LDY #&1d ; Y=&1D: top of FS state range
81FC .initl←1← 8204 BNE
LDA (net_rx_ptr),y ; Copy FS state from RX buffer...
81FE STA fs_state_deb,y ; ...to workspace (offsets &15-&1D)
8201 DEY ; Next byte (descending)
8202 CPY #&14 ; Loop until offset &14 done
8204 BNE initl ; Continue loop
8206 BEQ init_fs_vectors ; ALWAYS branch to init_fs_vectors ALWAYS branch

Service 9: *HELP

Prints the ROM identification string using print_inline.

On ExitYworkspace page number (from ws_page)
8208 .svc_9_help
JSR print_inline ; Print ROM identification string
820B EQUS ".NFS 3.62." ; Notify current FS of shutdown
8215 .restore_ws_return←2← 81EF BNE← 822A BNE
LDY ws_page ; Restore Y (workspace page number)
8217 RTS ; Return (service not claimed)

Notify filing system of shutdown

Loads A=6 (FS shutdown notification) and JMP (FSCV). The FSCV handler's RTS returns to the caller of this routine (JSR/JMP trick saves one level of stack).

8218 .call_fscv_shutdown←2← 81F1 JSR← 821D JSR
LDA #6 ; FSCV reason 6 = FS shutdown
821A JMP (fscv) ; Tail-call via filing system control vector

Service 3: auto-boot

Notifies current FS of shutdown via FSCV A=6. Scans keyboard (OSBYTE &7A): if no key is pressed, auto-boot proceeds directly via print_station_info. If a key is pressed, falls through to check_boot_key: the 'N' key (matrix address &55) proceeds with auto-boot, any other key causes the auto-boot to be declined.

821D .svc_3_autoboot
JSR call_fscv_shutdown ; Notify current FS of shutdown
8220 LDA #osbyte_scan_keyboard_from_16 ; OSBYTE &7A: scan keyboard
8222 JSR osbyte ; Keyboard scan starting from key 16
8225 TXA ; X is key number if key is pressed, or &ff otherwise
8226 BMI print_station_info ; No key pressed: proceed with auto-boot
fall through ↓

Check boot key

Checks if the pressed key (in A) is 'N' (matrix address &55). If not 'N', returns to the MOS without claiming the service call (another ROM may boot instead). If 'N', forgets the keypress via OSBYTE &78 and falls through to print_station_info.

8228 .check_boot_key
EOR #&55 ; XOR with &55: result=0 if key is 'N'
822A BNE restore_ws_return ; Not 'N': return without claiming
822C TAY ; Y=key
822D LDA #osbyte_write_keys_pressed ; OSBYTE &78: clear key-pressed state
822F JSR osbyte ; Write current keys pressed (X and Y)
fall through ↓

Print station identification

Prints "Econet Station <n>" using the station number from the net receive buffer, then tests ADLC SR2 for the network clock signal — prints " No Clock" if absent. Falls through to init_fs_vectors.

8232 .print_station_info←1← 8226 BMI
JSR print_inline ; Print 'Econet Station ' banner
8235 EQUS "Econet Station " ; Inline string "Econet Station "
8244 LDY #&14 ; Y=&14: station number offset in RX buf
8246 LDA (net_rx_ptr),y ; Load station number
8248 JSR print_decimal ; Print as 3-digit decimal
824B LDA #&20 ; BIT trick: bit 5 of SR2 = clock present
824D BIT econet_control23_or_status2 ; Test DCD: clock present if bit 5 clear
8250 .dofsl1
BEQ skip_no_clock_msg ; Clock present: skip warning
8252 JSR print_inline ; Print ' No Clock' warning
8255 EQUS " No Clock" ; Inline string " No Clock"
825E NOP ; NOP (padding after inline string)
825F .skip_no_clock_msg←1← 8250 BEQ
JSR print_inline ; Print two CRs (blank line)
8262 EQUS ".." ; 7 FS vectors to install
fall through ↓

Initialise filing system vectors

Copies 14 bytes from l829a (&829A) into FILEV-FSCV (&0212), setting all 7 filing system vectors to the extended vector dispatch addresses (&FF1B-&FF2D). Calls setup_rom_ptrs_netv to install the ROM pointer table entries with the actual NFS handler addresses. Also reached directly from svc_13_select_nfs, bypassing the station display. Falls through to issue_vectors_claimed.

8264 .init_fs_vectors←1← 8206 BEQ
LDY #&0d ; Copy 14 bytes: FS vector addresses to FILEV-FSCV
8266 .copy_fs_vectors←1← 826D BPL
LDA fs_dispatch_addrs,y ; Load extended vector dispatch address
8269 STA filev,y ; Write to FILEV-FSCV vector table
826C DEY ; Next byte (descending)
826D BPL copy_fs_vectors ; Loop until all 14 bytes copied
826F JSR setup_rom_ptrs_netv ; Read ROM ptr table addr, install NETV
8272 LDY #&1b ; Install 7 handler entries in ROM ptr table
8274 LDX #7 ; 7 FS vectors to install
8276 JSR store_rom_ptr_pair ; Install each 3-byte vector entry
8279 STX svc_state ; X=0 after loop; store as workspace offset
fall through ↓

Issue 'vectors claimed' service and optionally auto-boot

Issues service &0F (vectors claimed) via OSBYTE &8F, then service &0A. If l00a8 is zero (soft break — RXCBs already initialised), sets up the command string "I .BOOT" at &828E and jumps to the FSCV 3 unrecognised-command handler (which matches against the command table at &8C4B). The "I." prefix triggers the catch-all entry which forwards the command to the fileserver. Falls through to run_fscv_cmd.

827B .issue_vectors_claimed←1← 81F7 JSR
LDA #osbyte_issue_service_request ; A=&8F: issue service request
827D LDX #&0f ; X=&0F: 'vectors claimed' service
827F JSR osbyte ; Issue paged ROM service call, Reason X=15 - Vectors claimed
8282 LDX #&0a ; X=&0A: service &0A
8284 JSR osbyte ; Issue service &0A
8287 LDX ws_page ; Non-zero after hard reset: skip auto-boot
8289 BNE return_3 ; Non-zero: skip auto-boot
828B LDX #&92 ; X = lo byte of auto-boot string (run_fscv_cmd+5)
fall through ↓

Run FSCV command from ROM

Sets Y to the ROM page high byte (&82) and jumps to fscv_3_star_cmd to execute the command string at (X, Y). X is pre-loaded by the caller with the low byte of the string address. Also used as a data base address by store_rom_ptr_pair for Y-indexed access to the handler address table.

828D .run_fscv_cmd←2← 8339 LDA← 833F LDA
EQUB &A0 ; Y=&82: ROM page high byte
; Synthetic auto-boot command string. "I " does not match any
; entry in NFS's local command table — "I." requires a dot, and
; "I AM" requires 'A' after the space — so fscv_3_star_cmd
; forwards the entire string to the fileserver, which executes
; the .BOOT file.
828E EQUS ".L..I .B" ; Execute command string at (X, Y)
8296 EQUB &4F, &4F ; Auto-boot string tail / NETV handler data

FS vector dispatch and handler addresses (34 bytes)

Bytes 0-13: extended vector dispatch addresses, copied to FILEV-FSCV (&0212) by init_fs_vectors. Each 2-byte pair is a dispatch address (&FF1B-&FF2D) that the MOS uses to look up the handler in the ROM pointer table.

Bytes 14-33: handler address pairs read by store_rom_ptr_pair. Each entry has addr_lo, addr_hi, then a padding byte that is not read at runtime (store_rom_ptr_pair writes the current ROM bank number without reading). The last entry (FSCV) has no padding byte.

8298 .fs_vector_addrs
EQUB &54, &0D ; Auto-boot string tail / NETV handler data
829A .fs_dispatch_addrs←1← 8266 LDA
EQUW &FF1B ; FILEV dispatch (&FF1B)
829C EQUW &FF1E ; ARGSV dispatch (&FF1E) ARGSV dispatch lo
829E EQUW &FF21 ; BGETV dispatch (&FF21) BGETV dispatch hi
82A0 EQUW &FF24 ; BPUTV dispatch (&FF24) BPUTV dispatch lo
82A2 EQUW &FF27 ; GBPBV dispatch (&FF27) GBPBV dispatch lo GBPBV dispatch hi
82A4 EQUW &FF2A ; FINDV dispatch (&FF2A) FINDV dispatch lo FINDV dispatch hi
82A6 EQUW &FF2D ; FSCV dispatch (&FF2D) FSCV dispatch lo
82A8 EQUW &870C ; FILEV handler (&870C)
82AA EQUB &4A ; (ROM bank — not read)
82AB EQUW &8968 ; ARGSV handler (&8968)
82AD EQUB &44 ; (ROM bank — not read)
82AE EQUW &8563 ; BGETV handler (&8563)
82B0 EQUB &57 ; (ROM bank — not read)
82B1 EQUW &8413 ; BPUTV handler (&8413)
82B3 EQUB &42 ; (ROM bank — not read)
82B4 EQUW &8A72 ; GBPBV handler (&8A72)
82B6 EQUB &41 ; (ROM bank — not read)
82B7 EQUW &89D8 ; FINDV handler (&89D8)
82B9 EQUB &52 ; (ROM bank — not read)
82BA EQUW &80D4 ; FSCV handler (&80D4) FSCV handler hi

Service 1: claim absolute workspace

Claims pages up to &10 for NMI workspace (&0D), FS state (&0E), and FS command buffer (&0F). If Y >= &10, workspace already allocated — returns unchanged.

On EntryYcurrent top of absolute workspace
On ExitYupdated top of absolute workspace (max of input and &10)
82BC .svc_1_abs_workspace
CPY #&10 ; Already at page &10 or above?
82BE BCS return_3 ; Yes: nothing to claim
82C0 LDY #&10 ; Claim pages &0D-&0F (3 pages)
82C2 .return_3←2← 8289 BNE← 82BE BCS
RTS ; Return (workspace claim done)
82C3 EQUB &80, &90 ; FS page hi:lo for workspace pointer

Service 2: claim private workspace and initialise NFS

Y = next available workspace page on entry. Sets up net_rx_ptr (Y) and nfs_workspace (Y+1) page pointers. On soft break (OSBYTE &FD returns 0): skips FS state init, preserving existing login state, file server selection, and control block configuration — this is why pressing BREAK keeps the user logged in. On power-up/CTRL-BREAK (result non-zero): - Sets FS server station to &FE (no server selected) - Sets printer server station to &FE (no server selected) - Clears FS handles, OPT byte, message flag, SEQNOS - Initialises all RXCBs with &3F flag (available) In both cases: reads station ID from &FE18 (only valid during reset), calls adlc_init, enables user-level RX (LFLAG=&40).

On EntryYnext available workspace page
On ExitYnext available workspace page after NFS (input + 2)
82C5 .svc_2_private_workspace
STY net_rx_ptr_hi ; RX buffer page = first claimed page
82C7 INY ; Advance to next page
82C8 STY nfs_workspace_hi ; Workspace page = second claimed page
82CA LDA #0 ; A=0 for clearing workspace
82CC LDY #4 ; Y=4: remote status offset
82CE STA (net_rx_ptr),y ; Clear status byte in net receive buffer
82D0 LDY #&ff ; Y=&FF: used for later iteration
82D2 STA net_rx_ptr ; Clear RX ptr low byte
82D4 STA nfs_workspace ; Clear workspace ptr low byte
82D6 STA ws_page ; Clear RXCB iteration counter
82D8 STA tx_clear_flag ; Clear TX semaphore (no TX in progress)
82DB TAX ; X=0 for OSBYTE X=&00
82DC LDA #osbyte_read_write_last_break_type ; OSBYTE &FD: read type of last reset
82DE JSR osbyte ; Read type of last reset
82E1 TXA ; X = break type from OSBYTE result X=value of type of last reset
82E2 BEQ read_station_id ; Soft break (X=0): skip FS init
82E4 LDY #&15 ; Y=&15: printer station offset in RX buffer
82E6 LDA #&fe ; &FE = no server selected
82E8 STA fs_server_stn ; Station &FE = no server selected
82EB STA (net_rx_ptr),y ; Store &FE at printer station offset
82ED LDA #0 ; A=0 for clearing workspace fields
82EF STA fs_server_net ; Clear network number
82F2 STA prot_status ; Clear protection status
82F5 STA fs_messages_flag ; Clear message flag
82F8 STA fs_boot_option ; Clear boot option
82FB INY ; Y=&16
82FC STA (net_rx_ptr),y ; Clear net number at RX buffer offset &16
82FE LDY #3 ; Init printer server: station &FE, net 0
8300 STA (nfs_workspace),y ; Store net 0 at workspace offset 3
8302 DEY ; Y=2: printer station offset Y=&02
8303 LDA #&fe ; &FE = no printer server
8305 STA (nfs_workspace),y ; Store &FE at printer station in workspace
8307 .init_rxcb_entries←1← 8314 BNE
LDA ws_page ; Load RXCB counter
8309 JSR calc_handle_offset ; Convert to workspace byte offset
830C BCS read_station_id ; C=1: past max handles, done
830E LDA #&3f ; Mark RXCB as available
8310 STA (nfs_workspace),y ; Write &3F flag to workspace
8312 INC ws_page ; Next RXCB number
8314 BNE init_rxcb_entries ; Loop for all RXCBs
8316 .read_station_id←2← 82E2 BEQ← 830C BCS
LDA station_id_disable_net_nmis ; Read station ID (also INTOFF)
8319 LDY #&14 ; Y=&14: station ID offset in RX buffer
831B STA (net_rx_ptr),y ; Store our station number
831D JSR init_adlc_hw ; Initialise ADLC hardware
8320 LDA #&40 ; Enable user-level RX (LFLAG=&40)
8322 STA rx_flags ; Store to rx_flags
fall through ↓

Set up ROM pointer table and NETV

Reads the ROM pointer table base address via OSBYTE &A8, stores it in osrdsc_ptr (&F6). Sets NETV low byte to &36. Then copies one 3-byte extended vector entry (addr=&9080, rom=current) into the ROM pointer table at offset &36, installing osword_dispatch as the NETV handler.

8325 .setup_rom_ptrs_netv←1← 826F JSR
LDA #osbyte_read_rom_ptr_table_low ; OSBYTE &A8: read ROM pointer table address
8327 LDX #0 ; X=0: read low byte
8329 LDY #&ff ; Y=&FF: read high byte
832B JSR osbyte ; Returns table address in X (lo) Y (hi) Read address of ROM pointer table
832E STX osrdsc_ptr ; Store table base address low byte X=value of address of ROM pointer table (low byte)
8330 STY osrdsc_ptr_hi ; Store table base address high byte Y=value of address of ROM pointer table (high byte)
8332 LDY #&36 ; NETV extended vector offset in ROM ptr table
8334 STY netv ; Set NETV low byte = &36 (vector dispatch)
8337 LDX #1 ; Install 1 entry (NETV) in ROM ptr table
8339 .store_rom_ptr_pair←2← 8276 JSR← 834B BNE
LDA run_fscv_cmd,y ; Load handler address low byte from table
833C STA (osrdsc_ptr),y ; Store to ROM pointer table
833E INY ; Next byte
833F LDA run_fscv_cmd,y ; Load handler address high byte from table
8342 STA (osrdsc_ptr),y ; Store to ROM pointer table
8344 INY ; Next byte
8345 LDA romsel_copy ; Write current ROM bank number
8347 STA (osrdsc_ptr),y ; Store ROM number to ROM pointer table
8349 INY ; Advance to next entry position
834A DEX ; Count down entries
834B BNE store_rom_ptr_pair ; Loop until all entries installed
834D LDY nfs_workspace_hi ; Y = workspace high byte + 1 = next free page
834F INY ; Advance past workspace page
8350 RTS ; Return; Y = page after NFS workspace

FSCV 6: Filing system shutdown / save state (FSDIE)

Called when another filing system (e.g. DFS) is selected. Saves the current NFS context (FSLOCN station number, URD/CSD/LIB handles, OPT byte, etc.) from page &0E into the dynamic workspace backup area. This allows the state to be restored when *NET is re-issued later, without losing the login session. Finally calls OSBYTE &77 (close SPOOL/EXEC files) to release the Econet network printer on FS switch.

8351 .fscv_6_shutdown
LDY #&1d ; Copy 10 bytes: FS state to workspace backup
8353 .fsdiel←1← 835B BNE
LDA fs_state_deb,y ; Load FS state byte at offset Y
8356 STA (net_rx_ptr),y ; Store to workspace backup area
8358 DEY ; Next byte down
8359 CPY #&14 ; Offsets &15-&1D: server, handles, OPT, etc.
835B BNE fsdiel ; Loop for offsets &1D..&15
835D LDA #osbyte_close_spool_exec ; A=&77: OSBYTE close spool/exec
835F JMP osbyte ; Close any *SPOOL and *EXEC files
8362 .match_rom_string←2← 81B7 JSR← 81EC JSR
LDY ws_page ; Y = saved text pointer offset
8364 .match_cmd_chars←1← 8375 BNE
LDA (os_text_ptr),y ; Load next input character
8366 CMP #&2e ; Is it a '.' (abbreviation)?
8368 BEQ skip_space_next ; Yes: skip to space skipper (match)
836A AND #&df ; Force uppercase (clear bit 5)
836C BEQ check_rom_end ; Input char is NUL/space: check ROM byte
836E CMP binary_version,x ; Compare with ROM string byte
8371 BNE check_rom_end ; Mismatch: check if ROM string ended
8373 INY ; Advance input pointer
8374 INX ; Advance ROM string pointer
8375 BNE match_cmd_chars ; Continue matching (always taken)
8377 .check_rom_end←2← 836C BEQ← 8371 BNE
LDA binary_version,x ; Load ROM string byte at match point
837A BEQ skip_spaces ; Zero = end of ROM string = full match
837C RTS ; Non-zero = partial/no match; Z=0
837D .skip_space_next←2← 8368 BEQ← 8382 BEQ
INY ; Skip this space
fall through ↓

Skip spaces and test for end of line

Advances Y past leading spaces in the text at (os_text_ptr),Y. Returns Z=1 if the next non-space character is CR (end of line), Z=0 otherwise with A holding the character.

On EntryYoffset into (os_text_ptr) buffer
On ExitAcharacter EOR &0D (0 if CR)
Yoffset of first non-space character
Zset if end of line (CR)
837E .skip_spaces←2← 837A BEQ← 8DED JSR
LDA (os_text_ptr),y ; Load next input character
8380 CMP #&20 ; Is it a space?
8382 BEQ skip_space_next ; Yes: keep skipping
8384 EOR #&0d ; XOR with CR: Z=1 if end of line
8386 RTS ; Return with Z flag result

Initialise TX control block for FS reply on port &90

Loads port &90 (PREPLY) into A, calls init_tx_ctrl_block to set up the TX control block, stores the port and control bytes, then decrements the control flag. Used by send_fs_reply_cmd to prepare for receiving the fileserver's reply.

8387 .init_tx_reply_port←1← 83F4 JSR
LDA #&90 ; A=&90: FS reply port (PREPLY)
8389 .init_tx_ctrl_port←1← 889C JSR
JSR init_tx_ctrl_block ; Init TXCB from template
838C STA txcb_port ; Store port number in TXCB
838E LDA #3 ; Control byte: 3 = transmit
8390 STA txcb_start ; Store control byte in TXCB
8392 DEC txcb_ctrl ; Decrement TXCB flag to arm TX
8394 RTS ; Return after port setup

Initialise TX control block at &00C0 from template

Copies 12 bytes from tx_ctrl_template (&83AD) to &00C0. For the first 2 bytes (Y=0,1), also copies the fileserver station/network from &0E00/&0E01 to &00C2/&00C3. The template sets up: control=&80, port=&99 (FS command port), command data length=&0F, plus padding bytes.

On ExitApreserved
Y&FF (decremented past 0)
8395 .init_tx_ctrl_block←4← 8389 JSR← 83E3 JSR← 842F JSR← 8FF6 LDA
PHA ; Preserve A across call
8396 LDY #&0b ; Copy 12 bytes (Y=11..0)
8398 .fstxl1←1← 83A9 BPL
LDA tx_ctrl_template,y ; Load template byte
839B STA txcb_ctrl,y ; Store to TX control block at &00C0
839E CPY #2 ; Y < 2: also copy FS server station/network
83A0 BPL fstxl2 ; Skip station/network copy for Y >= 2
83A2 LDA fs_server_stn,y ; Load FS server station (Y=0) or network (Y=1)
83A5 STA txcb_dest,y ; Store to dest station/network at &00C2
83A8 .fstxl2←1← 83A0 BPL
DEY ; Next byte (descending)
83A9 BPL fstxl1 ; Loop until all 12 bytes copied
83AB PLA ; Restore A
83AC RTS ; Return

TX control block template (TXTAB, 12 bytes)

12-byte template copied to &00C0 by init_tx_ctrl. Defines the TX control block for FS commands: control flag, port, station/ network, and data buffer pointers (&0F00-&0FFF). The 4-byte Econet addresses use only the low 2 bytes; upper bytes are &FF.

83AD .tx_ctrl_template←1← 8398 LDA
EQUB &80, &99, ; Control flag Port (FS command = &99) &00, &00, ; Buffer start low Buffer start high (page &00, &0F ; &0F)
83B3 .tx_ctrl_upper←3← 891C BIT← 89FB BIT← 9183 BIT
EQUB &FF, &FF, ; Buffer start pad (4-byte Econet addr) &FF, &0F, ; Buffer start pad Buffer end low Buffer end &FF, &FF ; high (page &0F) Buffer end pad Buffer end ; pad

Prepare FS command with carry set

Alternate entry to prepare_fs_cmd that pushes A, loads &2A into fs_error_ptr, and enters with carry set (SEC). The carry flag is later tested by build_send_fs_cmd to select the byte-stream (BSXMIT) transmission path.

On EntryAflag byte to include in FS command
Yfunction code for FS header
83B9 .prepare_cmd_with_flag←1← 8AC3 JSR
PHA ; Save flag byte for command
83BA SEC ; C=1: include flag in FS command
83BB BCS store_fs_hdr_fn ; ALWAYS branch to prepare_fs_cmd ALWAYS branch
83BD .prepare_cmd_clv←2← 8729 JSR← 87CF JSR
CLV ; V=0: command has no flag byte
83BE BVC store_fs_hdr_clc ; ALWAYS branch to prepare_fs_cmd ALWAYS branch

*BYE handler (logoff)

Closes any open *SPOOL and *EXEC files via OSBYTE &77 (FXSPEX), then falls into prepare_fs_cmd with Y=&17 (FCBYE: logoff code). Dispatched from the command match table at &8C4B for "BYE".

83C0 .bye_handler
LDA #osbyte_close_spool_exec ; A=&77: OSBYTE close spool/exec
83C2 JSR osbyte ; Close any *SPOOL and *EXEC files
83C5 LDY #&17 ; Y=function code for HDRFN
fall through ↓

Prepare FS command buffer (12 references)

Builds the 5-byte FS protocol header at &0F00: &0F00 HDRREP = reply port (set downstream, typically &90/PREPLY) &0F01 HDRFN = Y parameter (function code) &0F02 HDRURD = URD handle (from &0E02) &0F03 HDRCSD = CSD handle (from &0E03) &0F04 HDRLIB = LIB handle (from &0E04) Command-specific data follows at &0F05 (TXBUF). Also clears V flag. Called before building specific FS commands for transmission.

On EntryYfunction code for HDRFN
Xpreserved through header build
On ExitA0 on success (from build_send_fs_cmd)
X0 on success, &D6 on not-found
Y1 (offset past command code in reply)
83C7 .prepare_fs_cmd←12← 80C5 JSR← 88C1 JSR← 8937 JSR← 8983 JSR← 89AA JSR← 8A21 JSR← 8A48 JSR← 8B1E JSR← 8BD9 JSR← 8C85 JSR← 8CBC JSR← 8D27 JSR
CLV ; V=0: standard FS command path
83C8 .init_tx_ctrl_data←2← 891F JSR← 89FE JSR
.prepare_fs_cmd_v←2← 891F JSR← 89FE JSR
LDA fs_urd_handle ; Copy URD handle from workspace to buffer
83CB STA fs_cmd_urd ; Store URD at &0F02
83CE .store_fs_hdr_clc←1← 83BE BVC
CLC ; CLC: no byte-stream path
83CF .store_fs_hdr_fn←1← 83BB BCS
STY fs_cmd_y_param ; Store function code at &0F01
83D2 LDY #1 ; Y=1: copy CSD (offset 1) then LIB (offset 0)
83D4 .copy_dir_handles←1← 83DB BPL
LDA fs_csd_handle,y ; Copy CSD and LIB handles to command buffer
83D7 STA fs_cmd_csd,y ; Store at &0F03 (CSD) and &0F04 (LIB)
83DA DEY ; Y=function code
83DB BPL copy_dir_handles ; Loop for both handles
fall through ↓

Build and send FS command (DOFSOP)

Sets reply port to &90 (PREPLY) at &0F00, initialises the TX control block, then adjusts TXCB's high pointer (HPTR) to X+5 -- the 5-byte FS header (reply port, function code, URD, CSD, LIB) plus the command data -- so only meaningful bytes are transmitted, conserving Econet bandwidth. If carry is set on entry (DOFSBX byte-stream path), takes the alternate path through econet_tx_retry for direct BSXMIT transmission. Otherwise sets up the TX pointer via setup_tx_ptr_c0 and falls through to send_fs_reply_cmd for reply handling. The carry flag is the sole discriminator between byte-stream and standard FS protocol paths -- set by SEC at the BPUTV/BGETV entry points. On return from WAITFS/BSXMIT, Y=0; INY advances past the command code to read the return code. Error &D6 ("not found") is detected via ADC #(&100-&D6) with C=0 -- if the return code was exactly &D6, the result wraps to zero (Z=1). This is a branchless comparison returning C=1, A=0 as a soft error that callers can handle, vs hard errors which go through FSERR.

On EntryXbuffer extent (command-specific data bytes)
Yfunction code
C0 for standard FS path, 1 for byte-stream (BSXMIT)
On ExitA0 on success
X0 on success, &D6 on not-found
Y1 (offset past command code in reply)
83DD .build_send_fs_cmd←1← 8B77 JSR
PHP ; Save carry (FS path vs byte-stream)
83DE LDA #&90 ; Reply port &90 (PREPLY)
83E0 STA fs_cmd_type ; Store at &0F00 (HDRREP)
83E3 JSR init_tx_ctrl_block ; Copy TX template to &00C0
83E6 TXA ; A = X (buffer extent)
83E7 ADC #5 ; HPTR = header (5) + data (X) bytes to send
83E9 STA txcb_end ; Store to TXCB end-pointer low
83EB PLP ; Restore carry flag
83EC BCS dofsl5 ; C=1: byte-stream path (BSXMIT)
83EE PHP ; Save flags for send_fs_reply_cmd
83EF JSR setup_tx_ptr_c0 ; Point net_tx_ptr to &00C0; transmit
83F2 PLP ; Restore flags
83F3 .send_fs_reply_cmd←2← 87DC JSR← 8AFB JSR
PHP ; Save flags (V flag state)
83F4 JSR init_tx_reply_port ; Set up RX wait for FS reply
83F7 JSR waitfs ; Transmit and wait (BRIANX)
83FA PLP ; Restore flags
83FB .dofsl7←1← 8411 BCC
INY ; Y=1: skip past command code byte
83FC LDA (txcb_start),y ; Load return code from FS reply
83FE TAX ; X = return code
83FF BEQ return_dofsl7 ; Zero: success, return
8401 BVC check_fs_error ; V=0: standard path, error is fatal
8403 ADC #&2a ; ADC #&2A: test for &D6 (not found)
8405 .check_fs_error←1← 8401 BVC
BNE store_fs_error ; Non-zero: hard error, go to FSERR
8407 .return_dofsl7←1← 83FF BEQ
RTS ; Return (success or soft &D6 error)
8408 .dofsl5←1← 83EC BCS
PLA ; Discard saved flags from stack
8409 LDX #&c0 ; X=&C0: TXCB address for byte-stream TX
840B INY ; Y++ past command code
840C JSR econet_tx_retry ; Byte-stream transmit with retry
840F STA fs_load_addr_3 ; Store result to &B3
8411 BCC dofsl7 ; C=0: success, check reply code
8413 .bputv_handler
CLC ; CLC for address addition
fall through ↓

BGETV entry point

Clears the escapable flag via clear_escapable, then falls through to handle_bput_bget with carry set (SEC by caller) to indicate a BGET operation.

On EntryYfile handle
C1 (set by MOS before calling BGETV)
On ExitAbyte read from file
C1 if EOF, 0 otherwise
8414 .bgetv_entry←1← 8564 JSR
JSR clear_escapable ; Clear escapable flag before BGET
fall through ↓

Handle BPUT/BGET file byte I/O

BPUTV enters at &8413 (CLC; fall through) and BGETV enters at &8563 (SEC; JSR here). The carry flag is preserved via PHP/PLP through the call chain and tested later (BCS) to select byte-stream transmission (BSXMIT) vs normal FS transmission (FSXMIT) -- a control-flow encoding using processor flags to avoid an extra flag variable.

BSXMIT uses handle=0 for print stream transactions (which sidestep the SEQNOS sequence number manipulation) and non-zero handles for file operations. After transmission, the high pointer bytes of the CB are reset to &FF -- "The BGET/PUT byte fix" which prevents stale buffer pointers corrupting subsequent byte-level operations.

On EntryC0 for BPUT (write byte), 1 for BGET (read byte)
Abyte to write (BPUT only)
Yfile handle
On ExitApreserved
Xpreserved
Ypreserved
8417 .handle_bput_bget
PHA ; Save A (BPUT byte) on stack
8418 STA fs_error_flags ; Also save byte at &0FDF for BSXMIT
841B TXA ; Transfer X for stack save
841C PHA ; Save X on stack
841D TYA ; Transfer Y (handle) for stack save
841E PHA ; Save Y (handle) on stack
841F PHP ; Save P (C = BPUT/BGET selector) on stack
8420 STY fs_spool_handle ; Save handle for SPOOL/EXEC comparison later
8422 JSR handle_to_mask_clc ; Convert handle Y to single-bit mask
8425 STY fs_handle_mask ; Store handle bitmask at &0FDE
8428 STY fs_spool0 ; Store handle bitmask for sequence tracking
842A LDY #&90 ; &90 = data port (PREPLY)
842C STY fs_putb_buf ; Store reply port in command buffer
842F JSR init_tx_ctrl_block ; Set up 12-byte TXCB from template
8432 LDA #&dc ; CB reply buffer at &0FDC
8434 STA txcb_start ; Store reply buffer ptr low in TXCB
8436 LDA #&e0 ; Error buffer at &0FE0
8438 STA txcb_end ; Store error buffer ptr low in TXCB
843A INY ; Y=1 (from init_tx_ctrl_block exit)
843B LDX #9 ; X=9: BPUT function code
843D PLP ; Restore C: selects BPUT (0) vs BGET (1)
843E BCC store_retry_count ; C=0 (BPUT): keep X=9
8440 DEX ; X=&08
8441 .store_retry_count←1← 843E BCC
STX fs_getb_buf ; Store function code at &0FDD
8444 LDA fs_spool0 ; Load handle bitmask for BSXMIT
8446 LDX #&c0 ; X=&C0: TXCB address for econet_tx_retry
8448 JSR econet_tx_retry ; Transmit via byte-stream protocol
844B LDX fs_getb_buf ; Load reply byte from buffer
844E BEQ update_sequence_return ; Zero reply = success, skip error handling
8450 LDY #&1f ; Copy 32-byte reply to error buffer at &0FE0
8452 .error1←1← 8459 BPL
LDA fs_putb_buf,y ; Load reply byte at offset Y
8455 STA fs_error_buf,y ; Store to error buffer at &0FE0+Y
8458 DEY ; Next byte (descending)
8459 BPL error1 ; Loop until all 32 bytes copied
845B TAX ; X=File handle
845C LDA #osbyte_read_write_exec_file_handle ; A=&C6: read *EXEC file handle
845E JSR osbyte ; Read/Write *EXEC file handle
8461 LDA #&29 ; ')': offset into "SP." string at &8529
8463 CPY fs_spool_handle ; Y=value of *SPOOL file handle
8465 BEQ close_spool_exec ; Handle matches SPOOL -- close it
8467 LDA #&2d ; '-': offset into "E." close-exec string
8469 CPX fs_spool_handle ; X=value of *EXEC file handle
846B BNE dispatch_fs_error ; No EXEC match -- skip close
846D .close_spool_exec←1← 8465 BEQ
TAX ; X = string offset for OSCLI close
846E LDY #&85 ; Y=&85: high byte of OSCLI string in ROM
8470 JSR oscli ; Close SPOOL/EXEC via "*SP." or "*E."
8473 .dispatch_fs_error←1← 846B BNE
LDA #&e0 ; Reset CB pointer to error buffer at &0FE0
8475 STA txcb_start ; Reset reply ptr to error buffer
8477 LDX fs_getb_buf ; Reload reply byte for error dispatch
fall through ↓

Handle fileserver error replies (FSERR)

The fileserver returns errors as: zero command code + error number + CR-terminated message string. This routine converts the reply buffer in-place to a standard MOS BRK error packet by: 1. Storing the error code at fs_last_error (&0E09) 2. Normalizing error codes below &A8 to &A8 (the standard FS error number), since the MOS error space below &A8 has other meanings 3. Scanning for the CR terminator and replacing it with &00 4. JMPing indirect through (l00c4) to execute the buffer as a BRK instruction — the zero command code serves as the BRK opcode N.B. This relies on the fileserver always returning a zero command code in position 0 of the reply buffer.

847A .store_fs_error←1← 8405 BNE
STX fs_last_error ; Remember raw FS error code
847D LDY #1 ; Y=1: point to error number byte in reply
847F CPX #&a8 ; Clamp FS errors below &A8 to standard &A8
8481 BCS error_code_clamped ; Error >= &A8: keep original value
8483 LDA #&a8 ; Error < &A8: override with standard &A8
8485 STA (txcb_start),y ; Write clamped error number to reply buffer
8487 .error_code_clamped←1← 8481 BCS
LDY #&ff ; Start scanning from offset &FF (will INY to 0)
8489 .copy_error_to_brk←1← 8491 BNE
INY ; Next byte in reply buffer
848A LDA (txcb_start),y ; Copy reply buffer to &0100 for BRK execution
848C STA error_block,y ; Build BRK error block at &0100
848F EOR #&0d ; Scan for CR terminator (&0D)
8491 BNE copy_error_to_brk ; Continue until CR found
8493 STA error_block,y ; Replace CR with zero = BRK error block end
8496 BEQ execute_downloaded ; Execute as BRK error block at &0100; ALWAYS ALWAYS branch
8498 .update_sequence_return←1← 844E BEQ
STA fs_sequence_nos ; Save updated sequence number
849B PLA ; Restore Y from stack
849C TAY ; Transfer A to Y for indexing
849D PLA ; Restore X from stack
849E TAX ; Transfer to X for return
849F PLA ; Restore A from stack
84A0 .return_remote_cmd←1← 84A5 BPL
RTS ; Return to caller

Check for pending escape condition

Tests bit 7 of the MOS escape flag (&FF) ANDed with the escapable flag. If no escape is pending, returns immediately. If escape is active, acknowledges it via OSBYTE &7E and jumps to the escape error handler.

On ExitAcorrupted (AND result on normal return)
84A1 .check_escape←2← 80AD JSR← 8627 JSR
LDA escape_flag ; Read escape flag from MOS workspace
84A3 AND escapable ; Mask with escapable: bit 7 set if active
84A5 BPL return_remote_cmd ; No escape pending: return
84A7 LDA #osbyte_acknowledge_escape ; OSBYTE &7E: acknowledge escape condition
84A9 JSR osbyte ; Clear escape condition and perform escape effects
84AC JMP nlisne ; Report escape error via error message table

Remote boot/execute handler

Checks byte 4 of the RX control block (remote status flag). If zero (not currently remoted), falls through to remot1 to set up a new remote session. If non-zero (already remoted), jumps to clear_jsr_protection and returns.

84AF .lang_1_remote_boot
LDY #4 ; Y=4: remote status flag offset
84B1 LDA (net_rx_ptr),y ; Read remote status from RX CB
84B3 BEQ remot1 ; Zero: not remoted, set up session
84B5 .rchex←1← 84FB BNE
JMP clear_jsr_protection ; Already remoted: clear and return
84B8 .remot1←2← 84B3 BEQ← 84F1 BEQ
ORA #9 ; Set remote status: bits 0+3 (ORA #9)
84BA STA (net_rx_ptr),y ; Store updated remote status
84BC LDX #&80 ; X=&80: RX data area offset
84BE LDY #&80 ; Y=&80: read source station low
84C0 LDA (net_rx_ptr),y ; Read source station lo from RX data at &80
84C2 PHA ; Save source station low byte
84C3 INY ; Y=&81
84C4 LDA (net_rx_ptr),y ; Read source station hi from RX data at &81
84C6 LDY #&0f ; Save controlling station to workspace &0E/&0F
84C8 STA (nfs_workspace),y ; Store station high to ws+&0F
84CA DEY ; Y=&0E Y=&0e
84CB PLA ; Restore source station low
84CC STA (nfs_workspace),y ; Store station low to ws+&0E
84CE JSR clear_osbyte_ce_cf ; Clear OSBYTE &CE/&CF flags
84D1 JSR ctrl_block_setup ; Set up TX control block
84D4 LDX #1 ; X=1: disable keyboard
84D6 LDY #0 ; Y=0 for OSBYTE
84D8 LDA #osbyte_read_write_econet_keyboard_disable ; Disable keyboard for remote session
84DA JSR osbyte ; Disable keyboard (for Econet)
fall through ↓

Execute code at &0100

Clears JSR protection, zeroes &0100-&0102 (creating a BRK instruction at &0100 as a safe default), then JMP &0100 to execute code received over the network. If no code was loaded, the BRK triggers an error handler.

84DD .lang_3_execute_at_0100
JSR clear_jsr_protection ; Allow JSR to page 1 (stack page)
84E0 LDX #2 ; Zero bytes &0100-&0102
84E2 LDA #0 ; A=0: zero execution header bytes
84E4 .zero_exec_header←1← 84E8 BPL
STA error_block,x ; BRK at &0100 as safe default
84E7 DEX ; Next byte
84E8 BPL zero_exec_header ; Loop until all zeroed
84EA .execute_downloaded←2← 8496 BEQ← 8523 BEQ
JMP error_block ; Execute downloaded code

Remote operation with source validation

Validates that the source station in the received packet matches the controlling station stored in the NFS workspace. If byte 4 of the RX control block is zero (not currently remoted), allows the new remote session via remot1. If non-zero, compares the source station at RX offset &80 against workspace offset &0E -- rejects mismatched stations via clear_jsr_protection, accepts matching stations by falling through to lang_0_insert_remote_key.

84ED .lang_4_remote_validated
LDY #4 ; Y=4: RX control block byte 4 (remote status)
84EF LDA (net_rx_ptr),y ; Read remote status flag
84F1 BEQ remot1 ; Zero = not remoted; allow new session
84F3 LDY #&80 ; Read source station from RX data at &80
84F5 LDA (net_rx_ptr),y ; A = source station number
84F7 LDY #&0e ; Compare against controlling station at &0E
84F9 CMP (nfs_workspace),y ; Check if source matches controller
84FB BNE rchex ; Reject: source != controlling station
fall through ↓

Insert remote keypress

Reads a character from RX block offset &82 and inserts it into keyboard input buffer 0 via OSBYTE &99.

84FD .lang_0_insert_remote_key
LDY #&82 ; Read keypress from RX data at &82
84FF LDA (net_rx_ptr),y ; Load character byte
8501 TAY ; Y = character to insert
8502 LDX #0 ; X = buffer 0 (keyboard input)
8504 JSR clear_jsr_protection ; Release JSR protection before inserting key
8507 LDA #osbyte_insert_input_buffer ; OSBYTE &99: insert char into input buffer
8509 JMP osbyte ; Tail call: insert character Y into buffer X Insert character Y into input buffer X
850C .error_not_listening←1← 8560 BEQ
LDA #8 ; Error code 8: "Not listening" error
850E BNE set_listen_offset ; ALWAYS branch to set_listen_offset ALWAYS branch
8510 .nlistn←1← 8640 JMP
LDA (net_tx_ptr,x) ; Load TX status byte for error lookup
8512 .nlisne←2← 84AC JMP← 8A40 JMP
AND #7 ; Mask to 3-bit error code (0-7)
8514 .set_listen_offset←1← 850E BNE
TAX ; X = error code index
8515 LDY error_offsets,x ; Look up error message offset from table
8518 LDX #0 ; X=0: start writing at &0101
851A STX error_block ; Store BRK opcode at &0100
851D .copy_error_message←1← 8527 BNE
LDA error_msg_table,y ; Load error message byte
8520 STA error_text,x ; Build error message at &0101+
8523 BEQ execute_downloaded ; Zero byte = end of message; go execute BRK
8525 INY ; Next source byte
8526 INX ; Next dest byte
8527 BNE copy_error_message ; Continue copying message
8529 EQUS "SP." ; Set bit7: FS transaction in progress
852C EQUB &0D, &45, &2E, &0D ; CR + E. + CR: *EXEC boot command
fall through ↓

Load '*' prefix and send FS command (WAITFS)

Loads A with &2A ('*') as the FS command prefix byte, then falls through to send_to_fs to perform a full fileserver transaction: transmit and wait for reply.

On ExitAreply command code
8530 .waitfs←5← 83F7 JSR← 877F JSR← 88A8 JSR← 903B JMP← 929B JSR
LDA #&2a ; A = '*' for FS command prefix
fall through ↓

Send command to fileserver and handle reply (WAITFS)

Performs a complete FS transaction: transmit then wait for reply. Sets bit 7 of rx_flags (mark FS transaction in progress), builds a TX frame from the data at (net_tx_ptr), and transmits it. The system RX flag (LFLAG bit 7) is only set when receiving into the page-zero control block — if RXCBP's high byte is non-zero, setting the system flag would interfere with other RX operations. The timeout counter uses the stack (indexed via TSX) rather than memory to avoid bus conflicts with Econet hardware during the tight polling loop. Handles multi-block replies and checks for escape conditions between blocks.

On EntryAfunction code / command prefix byte
On ExitAreply command code
8532 .send_to_fs
PHA ; Save function code on stack
8533 .send_to_fs_star
LDA rx_flags ; Load current rx_flags
8536 PHA ; Save rx_flags on stack for restore
8537 LDX net_tx_ptr_hi ; Only flag rx_flags if using page-zero CB
8539 BNE skip_rx_flag_set ; High byte != 0: skip flag set
853B ORA #&80 ; Set bit7: FS transaction in progress
853D STA rx_flags ; Write back updated rx_flags
8540 .skip_rx_flag_set←1← 8539 BNE
LDA #0 ; Push two zero bytes as timeout counters
8542 PHA ; First zero for timeout
8543 PHA ; Second zero for timeout
8544 TAY ; Y=0: index for flag byte check Y=&00
8545 TSX ; TSX: index stack-based timeout via X
8546 .incpx←3← 854D BNE← 8552 BNE← 8557 BNE
LDA (net_tx_ptr),y ; Read flag byte from TX control block
8548 BMI fs_wait_cleanup ; Bit 7 set = reply received
854A DEC error_text,x ; Three-stage nested timeout: inner loop
854D BNE incpx ; Inner not expired: keep polling
854F DEC stk_timeout_mid,x ; Middle timeout loop
8552 BNE incpx ; Middle not expired: keep polling
8554 DEC stk_timeout_hi,x ; Outer timeout loop (slowest)
8557 BNE incpx ; Outer not expired: keep polling
8559 .fs_wait_cleanup←1← 8548 BMI
PLA ; Pop first timeout byte
855A PLA ; Pop second timeout byte
855B PLA ; Pop saved rx_flags into A
855C STA rx_flags ; Restore saved rx_flags from stack
855F PLA ; Pop saved function code
8560 BEQ error_not_listening ; A=saved func code; zero would mean no reply
8562 RTS ; Return to caller
8563 .bgetv_handler
SEC ; C=1: flag for BGET mode
8564 JSR bgetv_entry ; Handle BGET via FS command
8567 SEC ; SEC: set carry for error check
8568 LDA #&fe ; A=&FE: mask for EOF check
856A BIT fs_error_flags ; BIT l0fdf: test error flags
856D BVS return_4 ; V=1: error, return early
856F CLC ; CLC: no error
8570 PHP ; Save flags for EOF check
8571 LDA fs_spool0 ; Load BGET result byte
8573 PLP ; Restore flags
8574 BMI bgetv_shared_jsr ; Bit7 set: skip FS flag clear
8576 JSR clear_fs_flag ; Clear FS flag for handle
8579 .bgetv_shared_jsr←1← 8574 BMI
EQUB &20 ; Set EOF flag for this handle
; Econet error message table (ERRTAB, 7 entries).
; Each entry: error number byte followed by NUL-terminated ; string.
; &A0: "Line Jammed" &A1: "Net Error"
; &A2: "Not listening" &A3: "No Clock"
; &11: "Escape" &CB: "Bad Option"
; &A5: "No reply"
; Indexed by the low 3 bits of the TXCB flag byte (AND #&07),
; which encode the specific Econet failure reason. The NREPLY
; and NLISTN routines build a MOS BRK error block at &100 on the
; stack page: NREPLY fires when the fileserver does not respond
; within the timeout period; NLISTN fires when the destination
; station actively refused the connection.
; Indexed via c850c/nlistn/nlisne at &850C-&8514.
857A .error_table_base
EQUS "P.-^.` Line Jammed." ; Load handle bitmask for caller Return with handle mask in A
858D EQUB &A1 ; Error &A1: Net Error
858E EQUS "Net Error." ; Error string "Net Error"
8598 EQUB &A2 ; Error &A2: Not listening
8599 EQUS "Not listening." ; Error string "Not listening"
85A7 EQUB &A3 ; Error &A3: No Clock
85A8 EQUS "No Clock." ; Error string "No Clock"
85B1 EQUB &11 ; Error &11: Escape
85B2 EQUS "Escape." ; Error string "Escape"
85B9 EQUB &CB ; Error &CB: Bad Option
85BA EQUS "Bad Option." ; Error string "Bad Option"
85C5 EQUB &A5 ; Error &A5: No reply
85C6 EQUS "No reply." ; Error string "No reply"
fall through ↓

Decode file attributes: FS → BBC format (FSBBC, 6-bit variant)

Reads attribute byte at offset &0E from the parameter block, masks to 6 bits, then falls through to the shared bitmask builder. Converts fileserver protection format (5-6 bits) to BBC OSFILE attribute format (8 bits) via the lookup table at &85EC. The two formats use different bit layouts for file protection attributes.

On ExitABBC attribute bitmask (8-bit)
Xcorrupted
Y&0E
85CF .decode_attribs_6bit←2← 88FB JSR← 8926 JSR
LDY #&0e ; Y=&0E: attribute byte offset in param block
85D1 LDA (fs_options),y ; Load FS attribute byte
85D3 AND #&3f ; Mask to 6 bits (FS → BBC direction)
85D5 LDX #4 ; X=4: skip first 4 table entries (BBC→FS half)
85D7 BNE attrib_shift_bits ; ALWAYS branch to shared bitmask builder ALWAYS branch

Decode file attributes: BBC → FS format (BBCFS, 5-bit variant)

Masks A to 5 bits and builds an access bitmask via the lookup table at &85EC. Each input bit position maps to a different output bit via the table. The conversion is done by iterating through the source bits and OR-ing in the corresponding destination bits from the table, translating between BBC (8-bit) and fileserver (5-bit) protection formats.

On EntryABBC attribute byte (bits 0-4 used)
On ExitAFS attribute bitmask (5-bit)
Xcorrupted
85D9 .decode_attribs_5bit←2← 881F JSR← 8943 JSR
AND #&1f ; Mask to 5 bits (BBC → FS direction)
85DB LDX #&ff ; X=&FF: INX makes 0; start from table index 0
85DD .attrib_shift_bits←1← 85D7 BNE
STA fs_error_ptr ; Temp storage for source bitmask to shift out
85DF LDA #0 ; A=0: accumulate destination bits here
85E1 .map_attrib_bits←1← 85E9 BNE
INX ; Next table entry
85E2 LSR fs_error_ptr ; Shift out source bits one at a time
85E4 BCC skip_set_attrib_bit ; Bit was 0: skip this destination bit
85E6 ORA access_bit_table,x ; OR in destination bit from lookup table
85E9 .skip_set_attrib_bit←1← 85E4 BCC
BNE map_attrib_bits ; Loop while source bits remain (A != 0)
85EB RTS ; Return; A = converted attribute bitmask
85EC .access_bit_table←1← 85E6 ORA
EQUB &50, &20, &05, &02, &88, &04, ; Attribute bit mapping &08, &80, &10, &01, &02 ; table (11 entries)

Set up TX pointer to control block at &00C0

Points net_tx_ptr to &00C0 where the TX control block has been built by init_tx_ctrl_block. Falls through to tx_poll_ff to initiate transmission with full retry.

On ExitA&FF (retry count, restored)
X0
Y0
85F7 .setup_tx_ptr_c0←2← 83EF JSR← 8897 JSR
LDX #&c0 ; TX control block low byte
85F9 STX net_tx_ptr ; Set net_tx_ptr = &00C0
85FB LDX #0 ; TX control block high byte
85FD STX net_tx_ptr_hi ; Set net_tx_ptr+1 = &00
fall through ↓

Transmit and poll for result (full retry)

Sets A=&FF (retry count) and Y=&60 (timeout parameter). Falls through to tx_poll_core.

On ExitA&FF (retry count, restored)
X0
Y0
85FF .tx_poll_ff←4← 9024 JSR← 907D JMP← 90DA JSR← 9279 JSR
LDA #&ff ; A=&FF: full retry count
8601 .tx_poll_timeout
LDY #&60 ; Y=timeout parameter (&60 = standard)
fall through ↓

Core transmit and poll routine (XMIT)

Claims the TX semaphore (tx_clear_flag) via ASL -- a busy-wait spinlock where carry=0 means the semaphore is held by another operation. Only after claiming the semaphore is the TX pointer copied to nmi_tx_block, ensuring the low-level transmit code sees a consistent pointer. Then calls the ADLC TX setup routine and polls the control byte for completion: bit 7 set = still busy (loop) bit 6 set = error (check escape or report) bit 6 clear = success (clean return) On error, checks for escape condition and handles retries. Two entry points: setup_tx_ptr_c0 (&85F7) always uses the standard TXCB; tx_poll_core (&8603) is general-purpose.

On EntryAretry count (&FF = full retry)
Ytimeout parameter (&60 = standard)
On ExitAentry A (retry count, restored from stack)
X0
Y0
8603 .tx_poll_core
PHA ; Save retry count and timeout on stack
8604 TYA ; Transfer timeout to A
8605 PHA ; Push timeout parameter
8606 LDX #0 ; X=0 for (zp,X) indirect addressing
8608 LDA (net_tx_ptr,x) ; Read control byte from TX block
860A .rearm_tx_attempt←1← 863D BEQ
STA (net_tx_ptr,x) ; Write back control byte (re-arm for TX)
860C PHA ; Save control byte for error recovery
860D .poll_tx_semaphore←1← 8610 BCC
ASL tx_clear_flag ; Spin until TX semaphore is free (C=1)
8610 BCC poll_tx_semaphore ; C=0: still held, keep spinning
8612 LDA net_tx_ptr ; Copy TX pointer to NMI block while locked
8614 STA nmi_tx_block ; Store low byte to NMI TX block
8616 LDA net_tx_ptr_hi ; Load TX pointer high byte
8618 STA nmi_tx_block_hi ; Store high byte to NMI TX block
861A JSR start_adlc_tx ; Initiate ADLC transmission
861D .poll_tx_complete←1← 861F BMI
LDA (net_tx_ptr,x) ; Poll: wait for bit 7 to clear (TX done)
861F BMI poll_tx_complete ; Bit 7 set: still busy, keep polling
8621 ASL ; Bit 6 into sign: 0=success, 1=error
8622 BPL tx_success_exit ; Success: clean up stack and exit
8624 ASL ; Bit 5: escape condition?
8625 BEQ tx_abort ; Yes (Z=1): abort via nlistn
8627 JSR check_escape ; Check for escape key pressed
862A PLA ; Recover saved control byte
862B TAX ; Move to X for retry
862C PLA ; Recover timeout parameter
862D TAY ; Move to Y for delay loop
862E PLA ; Recover retry count
862F BEQ tx_abort ; Retries exhausted: abort via nlistn
8631 SBC #1 ; Decrement retry count (C=1 from CMP)
8633 PHA ; Re-push retry count and timeout for retry
8634 TYA ; Transfer timeout to A
8635 PHA ; Push timeout for next attempt
8636 TXA ; Restore control byte for retry
8637 .tx_retry_delay←2← 8638 BNE← 863B BNE
DEX ; Delay loop: X*Y iterations before retry
8638 BNE tx_retry_delay ; Inner loop: decrement X
863A DEY ; Outer loop: decrement Y
863B BNE tx_retry_delay ; Continue delay until Y=0
863D BEQ rearm_tx_attempt ; ALWAYS branch
863F .tx_abort←2← 8625 BEQ← 862F BEQ
TAX ; A = error code for nlistn
8640 JMP nlistn ; Report net error via nlistn
8643 .tx_success_exit←1← 8622 BPL
PLA ; Success: discard 3 saved bytes from stack
8644 PLA ; Discard timeout
8645 PLA ; Discard retry count
8646 JMP clear_escapable ; Jump to clear escapable flag and return

Save FSCV arguments with text pointers

Extended entry used by FSCV, FINDV, and fscv_3_star_cmd. Copies X/Y into os_text_ptr/&F3 and fs_cmd_ptr/&0E11, then falls through to save_fscv_args to store A/X/Y in the FS workspace.

On EntryAfunction code
Xtext pointer low
Ytext pointer high
8649 .save_fscv_args_with_ptrs←3← 80D4 JSR← 89D8 JSR← 8C1B JSR
STX os_text_ptr ; Set os_text_ptr low = X
864B STY os_text_ptr_hi ; Set os_text_ptr high = Y
fall through ↓

Save FSCV/vector arguments

Stores A, X, Y into the filing system workspace. Called at the start of every FS vector handler (FILEV, ARGSV, BGETV, BPUTV, GBPBV, FINDV, FSCV). NFS repurposes CFS/RFS workspace locations: &BD (fs_last_byte_flag) = A (function code / command) &BB (fs_options) = X (control block ptr low) &BC (fs_block_offset) = Y (control block ptr high) &BE/&BF (fs_crc_lo/hi) = X/Y (duplicate for indexed access)

On EntryAfunction code
Xcontrol block pointer low
Ycontrol block pointer high
864D .save_fscv_args←3← 870C JSR← 8968 JSR← 8A72 JSR
STA fs_last_byte_flag ; Save A = function code / command
864F STX fs_options ; Save X = control block ptr low
8651 STY fs_block_offset ; Save Y = control block ptr high
8653 STX fs_crc_lo ; Duplicate X for indirect indexed access
8655 STY fs_crc_hi ; Duplicate Y for indirect indexed access
fall through ↓

Clear escapable flag preserving processor status

PHP/LSR escapable/PLP: clears bit 7 of the escapable flag while preserving the processor status register. Used at the start of FS vector operations to mark them as not yet escapable.

8657 .clear_escapable←2← 8414 JSR← 8646 JMP
PHP ; Clear escapable flag, preserving processor flags
8658 LSR escapable ; Reset: this operation is not escapable yet
865A PLP ; Restore flags (caller may need N/Z/C)
865B RTS ; Return

Print inline string, high-bit terminated (VSTRNG)

Pops the return address from the stack, prints each byte via OSASCI until a byte with bit 7 set is found, then jumps to that address. The high-bit byte serves as both the string terminator and the opcode of the first instruction after the string. N.B. Cannot be used for BRK error messages -- the stack manipulation means a BRK in the inline data would corrupt the stack rather than invoke the error handler.

On ExitAterminator byte (bit 7 set, also next opcode)
Xcorrupted (by OSASCI)
Y0
865C .print_inline←13← 8208 JSR← 8232 JSR← 8252 JSR← 825F JSR← 8C8D JSR← 8C97 JSR← 8CA5 JSR← 8CB0 JSR← 8CC5 JSR← 8CDA JSR← 8CED JSR← 8CFC JSR← 8DAB JSR
PLA ; Pop return address (low) — points to last byte of JSR
865D STA fs_load_addr ; Store return addr low as string ptr
865F PLA ; Pop return address (high)
8660 STA fs_load_addr_hi ; Store return addr high as string ptr
8662 LDY #0 ; Y=0: offset for indirect load
8664 .print_inline_char←1← 8671 JMP
INC fs_load_addr ; Advance pointer past return address / to next char
8666 BNE print_next_char ; No page wrap: skip high byte inc
8668 INC fs_load_addr_hi ; Handle page crossing in pointer
866A .print_next_char←1← 8666 BNE
LDA (fs_load_addr),y ; Load next byte from inline string
866C BMI jump_via_addr ; Bit 7 set? Done — this byte is the next opcode
866E JSR osasci ; Write character
8671 JMP print_inline_char ; Continue printing next character
8674 .jump_via_addr←1← 866C BMI
JMP (fs_load_addr) ; Jump to address of high-bit byte (resumes code after string)

Parse decimal number from (fs_options),Y (DECIN)

Reads ASCII digits and accumulates in &B2 (fs_load_addr_2). Multiplication by 10 uses the identity: n*10 = n*8 + n*2, computed as ASL &B2 (x2), then A = &B2*4 via two ASLs, then ADC &B2 gives x10. Terminates on "." (pathname separator), control chars, or space. The delimiter handling was revised to support dot-separated path components (e.g. "1.$.PROG") -- originally stopped on any char >= &40 (any letter), but the revision allows numbers followed by dots.

On EntryYoffset into (fs_options) buffer
On ExitAparsed value (accumulated in &B2)
Xpreserved
Yoffset past last digit parsed
8677 .parse_decimal←2← 808C JSR← 8095 JSR
LDA #0 ; Zero accumulator
8679 STA fs_load_addr_2 ; Initialise accumulator to zero
867B .scan_decimal_digit←1← 8694 BNE
LDA (fs_options),y ; Load next char from buffer
867D CMP #&2e ; Dot separator?
867F BEQ parse_decimal_rts ; Yes: exit with C=1 (dot found)
8681 BCC no_dot_exit ; Control char or space: done
8683 AND #&0f ; Mask ASCII digit to 0-9
8685 STA fs_load_addr_3 ; Save new digit
8687 ASL fs_load_addr_2 ; Running total * 2
8689 LDA fs_load_addr_2 ; A = running total * 2
868B ASL ; A = running total * 4
868C ASL ; A = running total * 8
868D ADC fs_load_addr_2 ; + total*2 = total * 10
868F ADC fs_load_addr_3 ; + digit = total*10 + digit
8691 STA fs_load_addr_2 ; Store new running total
8693 INY ; Advance to next char
8694 BNE scan_decimal_digit ; Loop (always: Y won't wrap to 0)
8696 .no_dot_exit←1← 8681 BCC
CLC ; No dot found: C=0
8697 .parse_decimal_rts←1← 867F BEQ
LDA fs_load_addr_2 ; Return result in A
8699 RTS ; Return with result in A

Convert handle in A to bitmask

Transfers A to Y via TAY, then falls through to handle_to_mask_clc to clear carry and convert.

On EntryAfile handle number (&20-&27, or 0)
On ExitApreserved
Xpreserved
Ybitmask (single bit set) or &FF if invalid
869A .handle_to_mask_a←3← 88AF JSR← 8A8D JSR← 8F5B JSR
TAY ; Handle number to Y for conversion
fall through ↓

Convert handle to bitmask (carry cleared)

Clears carry to ensure handle_to_mask converts unconditionally. Falls through to handle_to_mask.

On EntryYfile handle number (&20-&27, or 0)
On ExitApreserved
Xpreserved
Ybitmask (single bit set) or &FF if invalid
869B .handle_to_mask_clc←2← 8422 JSR← 8973 JSR
CLC ; Force unconditional conversion
fall through ↓

Convert file handle to bitmask (Y2FS)

Converts fileserver handles to single-bit masks segregated inside the BBC. NFS handles occupy the &20-&27 range (base HAND=&20), which cannot collide with local filing system or cassette handles -- the MOS routes OSFIND/OSBGET/OSBPUT to the correct filing system based on the handle value alone. The power-of-two encoding allows the EOF hint byte to track up to 8 files simultaneously with one bit per file, and enables fast set operations (ORA to add, EOR to toggle, AND to test) without loops. Handle 0 passes through unchanged (means "no file"). The bit-shift conversion loop has a built-in validity check: if the handle is out of range, the repeated ASL shifts all bits out, leaving A=0, which is converted to Y=&FF as a sentinel -- bad handles fail gracefully rather than indexing into garbage. Callers needing to move the handle from A use handle_to_mask_a; callers needing carry cleared use handle_to_mask_clc.

On EntryYhandle number
C0: convert, 1 with Y=0: skip, 1 with Y!=0: convert
On ExitApreserved
Xpreserved
Ybitmask (single bit set) or &FF if handle invalid
869C .handle_to_mask←1← 89DC JSR
PHA ; Save A (will be restored on exit)
869D TXA ; Save X (will be restored on exit)
869E PHA ; (second half of X save)
869F TYA ; A = handle from Y
86A0 BCC y2fsl5 ; C=0: always convert
86A2 BEQ handle_mask_exit ; C=1 and Y=0: skip (handle 0 = none)
86A4 .y2fsl5←1← 86A0 BCC
SEC ; C=1 and Y!=0: convert
86A5 SBC #&1f ; A = handle - &1F (1-based bit position)
86A7 TAX ; X = shift count
86A8 LDA #1 ; Start with bit 0 set
86AA .y2fsl2←1← 86AC BNE
ASL ; Shift bit left
86AB DEX ; Count down
86AC BNE y2fsl2 ; Loop until correct position
86AE ROR ; Undo final extra shift
86AF TAY ; Y = resulting bitmask
86B0 BNE handle_mask_exit ; Non-zero: valid mask, skip to exit
86B2 DEY ; Zero: invalid handle, set Y=&FF
86B3 .handle_mask_exit←2← 86A2 BEQ← 86B0 BNE
PLA ; Restore X
86B4 TAX ; Restore X from stack
86B5 PLA ; Restore A
86B6 RTS ; Return with mask in X

Convert bitmask to handle number (FS2A)

Inverse of Y2FS. Converts from the power-of-two FS format back to a sequential handle number by counting right shifts until A=0. Adds &1E to convert the 1-based bit position to a handle number (handles start at &1F+1 = &20). Used when receiving handle values from the fileserver in reply packets.

On EntryAsingle-bit bitmask
On ExitAhandle number (&20-&27)
Xcorrupted
Ypreserved
86B7 .mask_to_handle←2← 8A0B JSR← 8F73 JSR
LDX #&1f ; X = &1F (handle base - 1)
86B9 .fs2al1←1← 86BB BNE
INX ; Count this bit position
86BA LSR ; Shift mask right; C=0 when done
86BB BNE fs2al1 ; Loop until all bits shifted out
86BD TXA ; A = X = &1F + bit position = handle
86BE RTS ; Return with handle in A

Compare two 4-byte addresses

Compares bytes at &B0-&B3 against &B4-&B7 using EOR. Used by the OSFILE save handler to compare the current transfer address (&C8-&CB, copied to &B0) against the end address (&B4-&B7) during multi-block file data transfers.

On ExitAcorrupted (EOR result)
Xcorrupted
Ypreserved
86BF .compare_addresses←2← 8765 JSR← 8854 JSR
LDX #4 ; Compare 4 bytes (index 4,3,2,1)
86C1 .compare_addr_byte←1← 86C8 BNE
LDA addr_work,x ; Load byte from first address
86C3 EOR fs_load_addr_3,x ; XOR with corresponding byte
86C5 BNE return_compare ; Mismatch: Z=0, return unequal
86C7 DEX ; Next byte
86C8 BNE compare_addr_byte ; Continue comparing
86CA .return_compare←1← 86C5 BNE
RTS ; Return with Z flag result
86CB .fscv_7_read_handles
LDX #&20 ; X=first handle (&20)
86CD LDY #&27 ; Y=last handle (&27)
86CF .return_fscv_handles
RTS ; Return (FSCV 7 read handles)

Set bit(s) in EOF hint flags (&0E07)

ORs A into fs_eof_flags then stores the result via store_fs_flag. Each bit represents one of up to 8 open file handles. When clear, the file is definitely NOT at EOF. When set, the fileserver must be queried to confirm EOF status. This negative-cache optimisation avoids expensive network round-trips for the common case. The hint is cleared when the file pointer is updated (since seeking away from EOF invalidates the hint) and set after BGET/OPEN/EOF operations that might have reached the end.

On EntryAbitmask of bits to set
On ExitAupdated fs_eof_flags value
86D0 .set_fs_flag←4← 89B0 JSR← 8A07 JSR← 8A27 JSR← 8B08 JSR
ORA fs_eof_flags ; Merge new bits into flags
86D3 BNE store_fs_flag ; Store updated flags (always taken)
fall through ↓

Clear bit(s) in FS flags (&0E07)

Inverts A (EOR #&FF), then ANDs the result into fs_eof_flags to clear the specified bits.

On EntryAbitmask of bits to clear
On ExitAupdated fs_eof_flags value
86D5 .clear_fs_flag←3← 8576 JSR← 88CA JSR← 8B05 JSR
EOR #&ff ; Invert mask: set bits become clear bits
86D7 AND fs_eof_flags ; Clear specified bits in flags
86DA .store_fs_flag←1← 86D3 BNE
STA fs_eof_flags ; Write back updated flags
86DD RTS ; Return

Copy filename pointer to os_text_ptr and parse

Copies the 2-byte filename pointer from (fs_options),Y into os_text_ptr (&F2/&F3), then falls through to parse_filename_gs to parse the filename via GSINIT/GSREAD into the &0E30 buffer.

On ExitXlength of parsed string
Y0
86DE .copy_filename_ptr←1← 870F JSR
LDY #1 ; Y=1: copy 2 bytes (high then low)
86E0 .file1←1← 86E6 BPL
LDA (fs_options),y ; Load filename ptr from control block
86E2 STA os_text_ptr,y ; Store to MOS text pointer (&F2/&F3)
86E5 DEY ; Next byte (descending)
86E6 BPL file1 ; Loop for both bytes
fall through ↓

Parse filename using GSINIT/GSREAD into &0E30

Uses the MOS GSINIT/GSREAD API to parse a filename string from (os_text_ptr),Y, handling quoted strings and |-escaped characters. Stores the parsed result CR-terminated at &0E30 and sets up fs_crc_lo/hi to point to that buffer. Sub-entry at &86EA allows a non-zero starting Y offset.

On EntryYoffset into (os_text_ptr) buffer (0 at &86E8)
On ExitXlength of parsed string
Ypreserved
86E8 .parse_filename_gs←2← 89F1 JSR← 8DDC JSR
LDY #0 ; Start from beginning of string
fall through ↓

Parse filename via GSINIT/GSREAD from offset Y

Sub-entry of parse_filename_gs that accepts a non-zero Y offset into the (os_text_ptr) string. Initialises GSINIT, reads chars via GSREAD into &0E30, CR-terminates the result, and sets up fs_crc_lo/hi to point at the buffer.

On EntryYoffset into (os_text_ptr) string
On ExitXlength of parsed string
Ypreserved
86EA .parse_filename_gs_y←1← 8C7B JSR
LDX #&ff ; X=&FF: next INX wraps to first char index
86EC CLC ; C=0 for GSINIT: parse from current position
86ED JSR gsinit ; Initialise GS string parser
86F0 BEQ terminate_filename ; Empty string: skip to CR terminator
86F2 .quote1←1← 86FB BCC
JSR gsread ; Read next character via GSREAD
86F5 BCS terminate_filename ; C=1 from GSREAD: end of string reached
86F7 INX ; Advance buffer index
86F8 STA fs_filename_buf,x ; Store parsed character to &0E30+X
86FB BCC quote1 ; ALWAYS loop (GSREAD clears C on success) ALWAYS branch
86FD .terminate_filename←2← 86F0 BEQ← 86F5 BCS
INX ; Terminate parsed string with CR
86FE LDA #&0d ; CR = &0D
8700 STA fs_filename_buf,x ; Store CR terminator at end of string
8703 LDA #&30 ; Point fs_crc_lo/hi at &0E30 parse buffer
8705 STA fs_crc_lo ; fs_crc_lo = &30
8707 LDA #&0e ; fs_crc_hi = &0E → buffer at &0E30
8709 STA fs_crc_hi ; Store high byte
870B RTS ; Return; X = string length

FILEV handler (OSFILE entry point)

Calls save_fscv_args (&864D) to preserve A/X/Y, then JSR &86DE to copy the 2-byte filename pointer from the parameter block to os_text_ptr and fall through to parse_filename_gs (&86E8) which parses the filename into &0E30+. Sets fs_crc_lo/hi to point at the parsed filename buffer. Dispatches by function code A: A=&FF: load file (send_fs_examine at &8722) A=&00: save file (filev_save at &8795) A=&01-&06: attribute operations (filev_attrib_dispatch at &88D1) Other: restore_args_return (unsupported, no-op)

On EntryAfunction code (&FF=load, &00=save, &01-&06=attrs)
Xparameter block address low byte
Yparameter block address high byte
On ExitArestored
Xrestored
Yrestored
870C .filev_handler
JSR save_fscv_args ; Save A/X/Y in FS workspace
870F JSR copy_filename_ptr ; Copy filename ptr from param block to os_text_ptr
8712 LDA fs_last_byte_flag ; Recover function code from saved A
8714 BPL saveop ; A >= 0: save (&00) or attribs (&01-&06)
8716 CMP #&ff ; A=&FF? Only &FF is valid for load
8718 BEQ loadop ; A=&FF: branch to load path
871A JMP restore_args_return ; Unknown negative code: no-op return
871D .loadop←1← 8718 BEQ
JSR infol2 ; Copy parsed filename to cmd buffer
8720 LDY #2 ; Y=2: FS function code offset
fall through ↓

Send FS examine command

Sends an FS examine/load command to the fileserver. The function code in Y is set by the caller (Y=2 for load, Y=5 for examine). Overwrites fs_cmd_urd (&0F02) with &92 (PLDATA port number) to repurpose the URD header field for the data transfer port. Sets escapable to &92 so escape checking is active during the transfer. Calls prepare_cmd_clv to build the FS header (which skips the normal URD copy, preserving &92). The FS reply contains load/exec addresses and file length used to set up the data transfer. Byte 6 of the parameter block selects load address handling: non-zero uses the address from the FS reply (load to file's own address); zero uses the caller-supplied address.

On EntryYFS function code (2=load, 5=examine)
XTX buffer extent
8722 .send_fs_examine←1← 8E0D JSR
LDA #&92 ; Port &92 = PLDATA (data transfer port)
8724 STA escapable ; Mark transfer as escapable
8726 STA fs_cmd_urd ; Overwrite URD field with data port number
8729 JSR prepare_cmd_clv ; Build FS header (V=1: CLV path)
872C LDY #6 ; Y=6: param block byte 6
872E LDA (fs_options),y ; Byte 6: use file's own load address?
8730 BNE lodfil ; Non-zero: use FS reply address (lodfil)
8732 JSR copy_load_addr_from_params ; Zero: copy caller's load addr first
8735 JSR copy_reply_to_params ; Then copy FS reply to param block
8738 BCC skip_lodfil ; Carry clear from prepare_cmd_clv: skip lodfil
873A .lodfil←1← 8730 BNE
JSR copy_reply_to_params ; Copy FS reply addresses to param block
873D JSR copy_load_addr_from_params ; Then copy load addr from param block
8740 .skip_lodfil←1← 8738 BCC
LDY #4 ; Compute end address = load + file length
8742 .copy_load_end_addr←1← 874D BNE
LDA fs_load_addr,x ; Load address byte
8744 STA txcb_end,x ; Store as current transfer position
8746 ADC fs_file_len,x ; Add file length byte
8749 STA fs_work_4,x ; Store as end position
874B INX ; Next address byte
874C DEY ; Decrement byte counter
874D BNE copy_load_end_addr ; Loop for all 4 address bytes
874F SEC ; Adjust high byte for 3-byte length overflow
8750 SBC fs_file_len_3 ; Subtract 4th length byte from end addr
8753 STA fs_work_7 ; Store adjusted end address high byte
8755 JSR send_data_blocks ; Transfer file data in &80-byte blocks
8758 LDX #2 ; Copy 3-byte file length to FS reply cmd buffer
875A .floop←1← 8761 BPL
LDA fs_file_len_3,x ; Load file length byte
875D STA fs_cmd_data,x ; Store in FS command data buffer
8760 DEX ; Next byte (count down)
8761 BPL floop ; Loop for 3 bytes (X=2,1,0)
8763 BMI save_csd_display ; ALWAYS branch

Send file data in multi-block chunks

Repeatedly sends &80-byte (128-byte) blocks of file data to the fileserver using command &7F. Compares current address (&C8-&CB) against end address (&B4-&B7) via compare_addresses, and loops until the entire file has been transferred. Each block is sent via send_to_fs_star.

8765 .send_data_blocks←2← 8755 JSR← 8AF8 JSR
JSR compare_addresses ; Compare two 4-byte addresses
8768 BEQ return_lodchk ; Addresses match: transfer complete
876A LDA #&92 ; Port &92 for data block transfer
876C STA txcb_port ; Store port to TXCB command byte
876E .send_block_loop←1← 878A BNE
LDX #3 ; Set up next &80-byte block for transfer
8770 .copy_block_addrs←1← 8779 BPL
LDA txcb_end,x ; Swap: current addr -> source, end -> current
8772 STA txcb_start,x ; Source addr = current position
8774 LDA fs_work_4,x ; Load end address byte
8776 STA txcb_end,x ; Dest = end address (will be clamped)
8778 DEX ; Next address byte
8779 BPL copy_block_addrs ; Loop for all 4 bytes
877B LDA #&7f ; Command &7F = data block transfer
877D STA txcb_ctrl ; Store to TXCB control byte
877F JSR waitfs ; Send this block to the fileserver
8782 LDY #3 ; Y=3: compare 4 bytes (3..0)
8784 .lodchk←1← 878D BPL
LDA txcb_end,y ; Compare current vs end address (4 bytes)
8787 EOR fs_work_4,y ; XOR with end address byte
878A BNE send_block_loop ; Not equal: more blocks to send
878C DEY ; Next byte
878D BPL lodchk ; Loop for all 4 address bytes
878F .return_lodchk←1← 8768 BEQ
RTS ; All equal: transfer complete
8790 .saveop←1← 8714 BPL
BEQ filev_save ; A=0: SAVE handler
8792 JMP filev_attrib_dispatch ; A!=0: attribute dispatch (A=1-6)

OSFILE save handler (A=&00)

Copies 4-byte load/exec/length addresses from the parameter block to the FS command buffer, along with the filename. The savsiz loop computes data-end minus data-start for each address byte to derive the transfer length, saving both the original address and the difference. Sends FS command with port &91, function code Y=1, and the filename via copy_string_to_cmd. After transfer_file_blocks sends the data, calls send_fs_reply_cmd for the final handshake. If fs_messages_flag is set, prints the catalogue line inline: filename (padded to 12 chars), load address, exec address, and file length. Finally decodes the FS attributes and copies the reply data back into the caller's parameter block.

8795 .filev_save←1← 8790 BEQ
LDX #4 ; Process 4 address bytes (load/exec/start/end)
8797 LDY #&0e ; Y=&0E: start from end-address in param block
8799 .savsiz←1← 87B3 BNE
LDA (fs_options),y ; Read end-address byte from param block
879B STA port_ws_offset,y ; Save to port workspace for transfer setup
879E JSR sub_4_from_y ; Y = Y-4: point to start-address byte
87A1 SBC (fs_options),y ; end - start = transfer length byte
87A3 STA fs_cmd_csd,y ; Store length byte in FS command buffer
87A6 PHA ; Save length byte for param block restore
87A7 LDA (fs_options),y ; Read corresponding start-address byte
87A9 STA port_ws_offset,y ; Save to port workspace
87AC PLA ; Restore length byte from stack
87AD STA (fs_options),y ; Replace param block entry with length
87AF JSR add_5_to_y ; Y = Y+5: advance to next address group
87B2 DEX ; Decrement address byte counter
87B3 BNE savsiz ; Loop for all 4 address bytes
87B5 LDY #9 ; Copy load/exec addresses to FS command buffer
87B7 .copy_save_params←1← 87BD BNE
LDA (fs_options),y ; Read load/exec address byte from params
87B9 STA fs_cmd_csd,y ; Copy to FS command buffer
87BC DEY ; Next byte (descending)
87BD BNE copy_save_params ; Loop for bytes 9..1
87BF LDA #&91 ; Port &91 for save command
87C1 STA escapable ; Mark as escapable during save
87C3 STA fs_cmd_urd ; Overwrite URD field with port number
87C6 STA fs_error_ptr ; Save port &91 for flow control ACK
87C8 LDX #&0b ; Append filename at offset &0B in cmd buffer
87CA JSR copy_string_to_cmd ; Append filename to cmd buffer at offset X
87CD LDY #1 ; Y=1: function code for save
87CF JSR prepare_cmd_clv ; Build header and send FS save command
87D2 LDA fs_cmd_data ; Read FS reply command code for transfer type
87D5 JSR transfer_file_blocks ; Send file data blocks to server
87D8 .save_csd_display←1← 8763 BMI
LDA fs_cmd_csd ; Save CSD from reply for catalogue display
87DB PHA ; Save CSD byte from reply for display
87DC JSR send_fs_reply_cmd ; Send final reply acknowledgement
87DF PLA ; Restore CSD byte after reply command
87E0 LDY fs_messages_flag ; Check if file info messages enabled
87E3 BEQ skip_catalogue_msg ; Messages off: skip catalogue display
87E5 LDY #0 ; Y=0: start of filename in reply
87E7 TAX ; A = CSD; test for directory prefix
87E8 BEQ print_filename_char ; CSD=0: no directory prefix
87EA JSR print_dir_from_offset ; Print directory prefix from reply
87ED BMI print_addresses ; Dir printed: skip to address display
87EF .print_filename_char←2← 87E8 BEQ← 87F9 BNE
LDA (fs_crc_lo),y ; Load filename character from reply
87F1 CMP #&21 ; Check for control character or space
87F3 BCC pad_filename_space ; Below &21: pad with spaces to column 12
87F5 JSR osasci ; Write character
87F8 INY ; Next character in filename
87F9 BNE print_filename_char ; Loop for more filename characters
87FB .pad_filename_space←2← 87F3 BCC← 8801 BCC
JSR print_space ; Print space to pad filename to 12 chars
87FE INY ; Advance column counter
87FF CPY #&0c ; Reached column 12?
8801 BCC pad_filename_space ; No: keep padding with spaces
8803 .print_addresses←1← 87ED BMI
LDY #5 ; Y=5: load address offset in reply
8805 JSR print_hex_bytes ; Print 4-byte load address in hex
8808 LDY #9 ; Y=9: exec address offset in reply
880A JSR print_hex_bytes ; Print 4-byte exec address in hex
880D LDY #&0c ; Y=&0C: file length offset in reply
880F LDX #3 ; X=3: print 3 bytes of length
8811 JSR num01 ; Print file length in hex
8814 .send_fs_reply
JSR osnewl ; Send FS reply acknowledgement Write newline (characters 10 and 13)
8817 .skip_catalogue_msg←1← 87E3 BEQ
STX fs_reply_cmd ; Store reply command for attr decode
881A LDY #&0e ; Y=&0E: access byte offset in param block
881C LDA fs_cmd_data ; Load access byte from FS reply
881F JSR decode_attribs_5bit ; Convert FS access to BBC attribute format
8822 .copy_attribs_reply←1← 882A BNE
STA (fs_options),y ; Store decoded access in param block
8824 INY ; Next attribute byte
8825 LDA fs_reply_data,y ; Load remaining reply data for param block
8828 CPY #&12 ; Copied all 4 bytes? (Y=&0E..&11)
882A BNE copy_attribs_reply ; Loop for 4 attribute bytes
882C JMP restore_args_return ; Restore A/X/Y and return to caller

Copy load address from parameter block

Copies 4 bytes from (fs_options)+2..5 (the load address in the OSFILE parameter block) to &AE-&B3 (local workspace).

On ExitY8 (final offset)
Alast byte copied
882F .copy_load_addr_from_params←2← 8732 JSR← 873D JSR
LDY #5 ; Start at offset 5 (top of 4-byte addr)
8831 .lodrl1←1← 8839 BCS
LDA (fs_options),y ; Read from parameter block
8833 STA work_ae,y ; Store to local workspace
8836 DEY ; Next byte (descending)
8837 CPY #2 ; Copy offsets 5,4,3,2 (4 bytes)
8839 BCS lodrl1 ; Loop while Y >= 2
883B .add_5_to_y←1← 87AF JSR
INY ; Y += 5
883C .add_4_to_y←1← 8AD5 JSR
INY ; Y += 4
883D INY ; (continued)
883E INY ; (continued)
883F INY ; (continued)
8840 RTS ; Return

Copy FS reply data to parameter block

Copies bytes from the FS command reply buffer (&0F02+) into the parameter block at (fs_options)+2..13. Used to fill in the OSFILE parameter block with information returned by the fileserver.

On EntryXattribute byte (stored first at offset &0D)
8841 .copy_reply_to_params←2← 8735 JSR← 873A JSR
LDY #&0d ; Start at offset &0D (top of range)
8843 TXA ; First store uses X (attrib byte)
8844 .lodrl2←1← 884C BCS
STA (fs_options),y ; Write to parameter block
8846 LDA fs_cmd_urd,y ; Read next byte from reply buffer
8849 DEY ; Next byte (descending)
884A CPY #2 ; Copy offsets &0D down to 2
884C BCS lodrl2 ; Loop until offset 2 reached
884E .sub_4_from_y←1← 879E JSR
DEY ; Y -= 4
884F .sub_3_from_y←2← 88E9 JSR← 8ADD JSR
DEY ; Y -= 3
8850 DEY ; (continued)
8851 DEY ; (continued)
8852 RTS ; Return to caller

Multi-block file data transfer

Manages the transfer of file data in chunks between the local machine and the fileserver. Entry conditions: WORK (&B0-&B3) and WORK+4 (&B4-&B7) hold the low and high addresses of the data being sent/received. Sets up source (&C4-&C7) and destination (&C8-&CB) from the FS reply, sends &80-byte (128-byte) blocks with command &91, and continues until all data has been transferred. Handles address overflow and Tube co-processor transfers. For SAVE, WORK+8 holds the port on which to receive byte-level ACKs for each data block (flow control).

8853 .transfer_file_blocks←2← 87D5 JSR← 8AF3 JSR
PHA ; Save FS command byte on stack
8854 JSR compare_addresses ; Compare two 4-byte addresses
8857 BEQ restore_ay_return ; Addresses equal: nothing to transfer
8859 .transfer_loop_top←1← 88AB BNE
LDA #0 ; A=0: high bytes of block size
885B PHA ; Push 4-byte block size: 0, 0, hi, lo
885C PHA ; Push second zero byte
885D TAX ; X=&00
885E LDA fs_data_count ; Load block size high byte from &0F07
8861 PHA ; Push block size high
8862 LDA fs_func_code ; Load block size low byte from &0F06
8865 PHA ; Push block size low
8866 LDY #4 ; Y=4: process 4 address bytes
8868 CLC ; CLC for ADC in loop
8869 .setup_block_addrs←1← 8876 BNE
LDA fs_load_addr,x ; Source = current position
886B STA txcb_start,x ; Store source address byte
886D PLA ; Pop block size byte from stack
886E ADC fs_load_addr,x ; Dest = current pos + block size
8870 STA txcb_end,x ; Store dest address byte
8872 STA fs_load_addr,x ; Advance current position
8874 INX ; Next address byte
8875 DEY ; Decrement byte counter
8876 BNE setup_block_addrs ; Loop for all 4 bytes
8878 SEC ; SEC for SBC in overshoot check
8879 .savchk←1← 8881 BNE
LDA fs_load_addr,y ; Check if new pos overshot end addr
887C SBC fs_work_4,y ; Subtract end address byte
887F INY ; Next byte
8880 DEX ; Decrement counter
8881 BNE savchk ; Loop for 4-byte comparison
8883 BCC send_block ; C=0: no overshoot, proceed
8885 .clamp_dest_setup
LDX #3 ; Overshot: clamp dest to end address
8887 .clamp_dest_addr←1← 888C BPL
LDA fs_work_4,x ; Load end address byte
8889 STA txcb_end,x ; Replace dest with end address
888B DEX ; Next byte
888C BPL clamp_dest_addr ; Loop for all 4 bytes
888E .send_block←1← 8883 BCC
PLA ; Recover original FS command byte
888F PHA ; Re-push for next iteration
8890 PHP ; Save processor flags (C from cmp)
8891 STA txcb_port ; Store command byte in TXCB
8893 LDA #&80 ; 128-byte block size for data transfer
8895 STA txcb_ctrl ; Store size in TXCB control byte
8897 JSR setup_tx_ptr_c0 ; Point TX ptr to &00C0; transmit
889A LDA fs_error_ptr ; ACK port for flow control
889C JSR init_tx_ctrl_port ; Set reply port for ACK receive
889F PLP ; Restore flags (C=overshoot status)
88A0 BCS restore_ay_return ; C=1: all data sent (overshot), done
88A2 LDA #&91 ; Command &91 = data block transfer
88A4 STA txcb_port ; Store command &91 in TXCB
88A6 INC txcb_start ; Skip command code byte in TX buffer
88A8 JSR waitfs ; Transmit block and wait (BRIANX)
88AB BNE transfer_loop_top ; More blocks? Loop back
fall through ↓

FSCV 1: EOF handler

Checks whether a file handle has reached end-of-file. Converts the handle via handle_to_mask_clc, tests the result against the EOF hint byte (&0E07). If the hint bit is clear, returns X=0 immediately (definitely not at EOF — no network call needed). If the hint bit is set, sends FS command &11 (FCEOF) to query the fileserver for definitive EOF status. Returns X=&FF if at EOF, X=&00 if not. This two-level check avoids an expensive network round-trip when the file is known to not be at EOF.

On EntryXfile handle to check
On ExitX&FF if at EOF, &00 if not
88AD .fscv_1_eof
PHA ; Save A (function code)
88AE TXA ; X = file handle to check
88AF JSR handle_to_mask_a ; Convert handle to bitmask in A
88B2 TYA ; Y = handle bitmask from conversion
88B3 AND fs_eof_flags ; Local hint: is EOF possible for this handle?
88B6 TAX ; X = result of AND (0 = not at EOF)
88B7 BEQ restore_ay_return ; Hint clear: definitely not at EOF
88B9 PHA ; Save bitmask for clear_fs_flag
88BA STY fs_cmd_data ; Handle byte in FS command buffer
88BD LDY #&11 ; Y=&11: FS function code FCEOF Y=function code for HDRFN
88BF LDX #1 ; X=preserved through header build
88C1 JSR prepare_fs_cmd ; Prepare FS command buffer (12 references)
88C4 PLA ; Restore bitmask
88C5 LDX fs_cmd_data ; FS reply: non-zero = at EOF
88C8 BNE restore_ay_return ; At EOF: skip flag clear
88CA JSR clear_fs_flag ; Not at EOF: clear the hint bit
88CD .restore_ay_return←4← 8857 BEQ← 88A0 BCS← 88B7 BEQ← 88C8 BNE
PLA ; Restore A
88CE LDY fs_block_offset ; Restore Y
88D0 RTS ; Return; X=0 (not EOF) or X=&FF (EOF)

FILEV attribute dispatch (A=1-6)

Dispatches OSFILE operations by function code: A=1: write catalogue info (load/exec/length/attrs) — FS &14 A=2: write load address only A=3: write exec address only A=4: write file attributes A=5: read catalogue info, returns type in A — FS &12 A=6: delete named object — FS &14 (FCDEL) A>=7: falls through to restore_args_return (no-op) Each handler builds the appropriate FS command, sends it to the fileserver, and copies the reply into the parameter block. The control block layout uses dual-purpose fields: the 'data start' field doubles as 'length' and 'data end' doubles as 'protection' depending on whether reading or writing attrs.

On EntryAfunction code (1-6)
On ExitAobject type (A=5 read info) or restored
88D1 .filev_attrib_dispatch←1← 8792 JMP
STA fs_cmd_data ; Store function code in FS cmd buffer
88D4 CMP #6 ; A=6? (delete)
88D6 BEQ cha6 ; Yes: jump to delete handler
88D8 BCS check_attrib_result ; A>=7: unsupported, fall through to return
88DA CMP #5 ; A=5? (read catalogue info)
88DC BEQ cha5 ; Yes: jump to read info handler
88DE CMP #4 ; A=4? (write attributes only)
88E0 BEQ cha4 ; Yes: jump to write attrs handler
88E2 CMP #1 ; A=1? (write all catalogue info)
88E4 BEQ get_file_protection ; Yes: jump to write-all handler
88E6 ASL ; A=2 or 3: convert to param block offset
88E7 ASL ; A*4: 2->8, 3->12
88E8 TAY ; Y = A*4
88E9 JSR sub_3_from_y ; Y = A*4 - 3 (load addr offset for A=2)
88EC LDX #3 ; X=3: copy 4 bytes
88EE .chalp1←1← 88F5 BPL
LDA (fs_options),y ; Load address byte from param block
88F0 STA fs_func_code,x ; Store to FS cmd data area
88F3 DEY ; Next source byte (descending)
88F4 DEX ; Next dest byte
88F5 BPL chalp1 ; Loop for 4 bytes
88F7 LDX #5 ; X=5: data extent for filename copy
88F9 BNE copy_filename_to_cmd ; ALWAYS branch
88FB .get_file_protection←1← 88E4 BEQ
JSR decode_attribs_6bit ; A=1: encode protection from param block
88FE STA fs_file_attrs ; Store encoded attrs at &0F0E
8901 LDY #9 ; Y=9: source offset in param block
8903 LDX #8 ; X=8: dest offset in cmd buffer
8905 .chalp2←1← 890C BNE
LDA (fs_options),y ; Load byte from param block
8907 STA fs_cmd_data,x ; Store to FS cmd buffer
890A DEY ; Next source byte (descending)
890B DEX ; Next dest byte
890C BNE chalp2 ; Loop until X=0 (8 bytes copied)
890E LDX #&0a ; X=&0A: data extent past attrs+addrs
8910 .copy_filename_to_cmd←2← 88F9 BNE← 892E BNE
JSR copy_string_to_cmd ; Append filename to cmd buffer
8913 LDY #&13 ; Y=&13: fn code for FCSAVE (write attrs)
8915 BNE send_fs_cmd_v1 ; ALWAYS branch to send command ALWAYS branch
8917 .cha6←1← 88D6 BEQ
JSR infol2 ; A=6: copy filename (delete)
891A LDY #&14 ; Y=&14: fn code for FCDEL (delete)
891C .send_fs_cmd_v1←1← 8915 BNE
BIT tx_ctrl_upper ; Set V=1 (BIT trick: &B3 has bit 6 set)
891F JSR init_tx_ctrl_data ; Send via prepare_fs_cmd_v (V=1 path)
8922 .check_attrib_result←1← 88D8 BCS
BCS attrib_error_exit ; C=1: &D6 not-found, skip to return
8924 BCC argsv_check_return ; C=0: success, copy reply to param block ALWAYS branch
8926 .cha4←1← 88E0 BEQ
JSR decode_attribs_6bit ; A=4: encode attrs from param block
8929 STA fs_func_code ; Store encoded attrs at &0F06
892C LDX #2 ; X=2: data extent (1 attr byte + fn)
892E BNE copy_filename_to_cmd ; ALWAYS branch to append filename ALWAYS branch
8930 .cha5←1← 88DC BEQ
LDX #1 ; X=1: filename only, no data extent
8932 JSR copy_string_to_cmd ; Copy filename to cmd buffer
8935 LDY #&12 ; Y=&12: fn code for FCEXAM (read info) Y=function code for HDRFN
8937 JSR prepare_fs_cmd ; Prepare FS command buffer (12 references)
893A LDA fs_obj_type ; Save object type from FS reply
893D STX fs_obj_type ; Clear reply byte (X=0 on success) X=0 on success, &D6 on not-found
8940 STX fs_len_clear ; Clear length high byte in reply
8943 JSR decode_attribs_5bit ; Decode 5-bit access byte from FS reply
8946 LDY #&0e ; Y=&0E: attrs offset in param block
8948 STA (fs_options),y ; Store decoded attrs at param block +&0E
894A DEY ; Y=&0D: start copy below attrs Y=&0d
894B LDX #&0c ; X=&0C: copy from reply offset &0C down
894D .copy_fs_reply_to_cb←1← 8954 BNE
LDA fs_cmd_data,x ; Load reply byte (load/exec/length)
8950 STA (fs_options),y ; Store to param block
8952 DEY ; Next dest byte (descending)
8953 DEX ; Next source byte
8954 BNE copy_fs_reply_to_cb ; Loop until X=0 (12 bytes copied)
8956 INX ; X=0 -> X=2 for length high copy
8957 INX ; INX again: X=2
8958 LDY #&11 ; Y=&11: length high dest in param block
895A .cha5lp←1← 8961 BPL
LDA fs_access_level,x ; Load length high byte from reply
895D STA (fs_options),y ; Store to param block
895F DEY ; Next dest byte (descending)
8960 DEX ; Next source byte
8961 BPL cha5lp ; Loop for 3 length-high bytes
8963 LDA fs_cmd_data ; Return object type in A
8966 .attrib_error_exit←1← 8922 BCS
BPL restore_xy_return ; A>=0: branch to restore_args_return
fall through ↓

ARGSV handler (OSARGS entry point)

A=0, Y=0: return filing system number (5 = network FS) A=0, Y>0: read file pointer via FS command &0C (FCRDSE) A=1, Y>0: write file pointer via FS command &0D (FCWRSE) A=2, Y=0: return &01 (command-line tail supported) A>=3 (ensure): silently returns -- NFS has no local write buffer to flush, since all data is sent to the fileserver immediately The handle in Y is converted via handle_to_mask_clc. For writes (A=1), the carry flag from the mask conversion is used to branch to save_args_handle, which records the handle for later use.

On EntryAfunction code (0=query, 1=write ptr, >=3=ensure)
Yfile handle (0=FS-level query, >0=per-file)
On ExitAfiling system number if A=0/Y=0 query, else restored
Xrestored
Yrestored
8968 .argsv_handler
JSR save_fscv_args ; Save A/X/Y registers for later restore
896B CMP #3 ; Function >= 3?
896D BCS restore_args_return ; A>=3 (ensure/flush): no-op for NFS
896F CPY #0 ; Test file handle
8971 BEQ argsv_fs_query ; Y=0: FS-level query, not per-file
8973 JSR handle_to_mask_clc ; Convert handle to bitmask
8976 STY fs_cmd_data ; Store bitmask as first cmd data byte
8979 LSR ; LSR splits A: C=1 means write (A=1)
897A STA fs_func_code ; Store function code to cmd data byte 2
897D BCS save_args_handle ; C=1: write path, copy ptr from caller
897F LDY #&0c ; Y=&0C: FCRDSE (read sequential pointer) Y=function code for HDRFN
8981 LDX #2 ; X=2: 3 data bytes in command X=preserved through header build
8983 JSR prepare_fs_cmd ; Build and send FS command Prepare FS command buffer (12 references)
8986 STA fs_last_byte_flag ; Clear last-byte flag on success A=0 on success (from build_send_fs_cmd)
8988 LDX fs_options ; X = saved control block ptr low
898A LDY #2 ; Y=2: copy 3 bytes of file pointer
898C STA zp_work_3,x ; Zero high byte of 3-byte pointer
898E .copy_fileptr_reply←1← 8995 BPL
LDA fs_cmd_data,y ; Read reply byte from FS cmd data
8991 STA zp_work_2,x ; Store to caller's control block
8993 DEX ; Next byte (descending)
8994 DEY ; Next source byte
8995 BPL copy_fileptr_reply ; Loop for all 3 bytes
8997 .argsv_check_return←1← 8924 BCC
BCC restore_args_return ; C=0 (read): return to caller
8999 .save_args_handle←1← 897D BCS
TYA ; Save bitmask for set_fs_flag later
899A PHA ; Push bitmask
899B LDY #3 ; Y=3: copy 4 bytes of file pointer
899D .copy_fileptr_to_cmd←1← 89A4 BPL
LDA zp_work_3,x ; Read caller's pointer byte
899F STA fs_data_count,y ; Store to FS command data area
89A2 DEX ; Next source byte
89A3 DEY ; Next destination byte
89A4 BPL copy_fileptr_to_cmd ; Loop for all 4 bytes
89A6 LDY #&0d ; Y=&0D: FCWRSE (write sequential pointer) Y=function code for HDRFN
89A8 LDX #5 ; X=5: 6 data bytes in command X=preserved through header build
89AA JSR prepare_fs_cmd ; Build and send FS command Prepare FS command buffer (12 references)
89AD STX fs_last_byte_flag ; Save not-found status from X X=0 on success, &D6 on not-found
89AF PLA ; Recover bitmask for EOF hint update
89B0 JSR set_fs_flag ; Set EOF hint bit for this handle
fall through ↓

Restore arguments and return

Common exit point for FS vector handlers. Reloads A from fs_last_byte_flag (&BD), X from fs_options (&BB), and Y from fs_block_offset (&BC) — the values saved at entry by save_fscv_args — and returns to the caller.

On ExitArestored from fs_last_byte_flag (&BD)
Xrestored from fs_options (&BB)
Yrestored from fs_block_offset (&BC)
89B3 .restore_args_return←7← 871A JMP← 882C JMP← 896D BCS← 8997 BCC← 8A2A BCC← 8A7D JMP← 8E35 JMP
LDA fs_last_byte_flag ; A = saved function code / command
89B5 .restore_xy_return←5← 8966 BPL← 89C6 BNE← 89D6 BPL← 8A01 BCS← 8A0E BNE
LDX fs_options ; X = saved control block ptr low
89B7 LDY fs_block_offset ; Y = saved control block ptr high
89B9 RTS ; Return to MOS with registers restored
89BA .argsv_fs_query←1← 8971 BEQ
CMP #2 ; Y=0: FS-level queries (no file handle)
89BC BEQ halve_args_a ; A=2: FS-level ensure (write extent)
89BE BCS return_a_zero ; A>=3: FS command (ARGSV write)
89C0 TAY ; Y = A = byte count for copy loop
89C1 BNE osarg1 ; A!=0: copy command context block
89C3 LDA #&0a ; FS number 5 (loaded as &0A, LSR'd)
89C5 .halve_args_a←1← 89BC BEQ
LSR ; Shared: halve A (A=0 or A=2 paths)
89C6 BNE restore_xy_return ; Return with A = FS number or 1
89C8 .osarg1←2← 89C1 BNE← 89CE BPL
LDA fs_cmd_context,y ; Copy command context to caller's block
89CB STA (fs_options),y ; Store to caller's parameter block
89CD DEY ; Next byte (descending)
89CE BPL osarg1 ; Loop until all bytes copied
89D0 STY zp_work_2,x ; Y=&FF after loop; fill high bytes
89D2 STY zp_work_3,x ; Set 32-bit result bytes 2-3 to &FF
fall through ↓

Return with A=0 via register restore

Loads A=0 and branches (always taken) to the common register restore exit at restore_args_return. Used as a shared exit point by ARGSV, FINDV, and GBPBV when an operation is unsupported or should return zero.

On ExitA0
Xrestored from fs_options (&BB)
Yrestored from fs_block_offset (&BC)
89D4 .return_a_zero←4← 89BE BCS← 89E4 BNE← 8B19 JMP← 8BBB JMP
LDA #0 ; A=operation (0=close, &40=read, &80=write, &C0=R/W)
89D6 BPL restore_xy_return ; ALWAYS branch

FINDV handler (OSFIND entry point)

A=0: close file -- delegates to close_handle (&8A10) A>0: open file -- modes &40=read, &80=write/update, &C0=read/write For open: the mode byte is converted to the fileserver's two-flag format by flipping bit 7 (EOR #&80) and shifting. This produces Flag 1 (read/write direction) and Flag 2 (create/existing), matching the fileserver protocol. After a successful open, the new handle's bit is OR'd into the EOF hint byte (marks it as "might be at EOF, query the server"), and into the sequence number tracking byte for the byte-stream protocol.

On EntryAoperation (0=close, &40=read, &80=write, &C0=R/W)
Xfilename pointer low (open)
Yfile handle (close) or filename pointer high (open)
On ExitAhandle on open, 0 on close-all, restored on close-one
Xrestored
Yrestored
89D8 .findv_handler
JSR save_fscv_args_with_ptrs ; Save A/X/Y and set up pointers
89DB SEC ; SEC distinguishes open (A>0) from close
89DC JSR handle_to_mask ; Convert file handle to bitmask (Y2FS)
89DF TAX ; A=preserved
89E0 BEQ close_handle ; A=0: close file(s)
89E2 AND #&3f ; Valid open modes: &40, &80, &C0 only
89E4 BNE return_a_zero ; Invalid mode bits: return
89E6 TXA ; A = original mode byte
89E7 EOR #&80 ; Convert MOS mode to FS protocol flags
89E9 ASL ; ASL: shift mode bits left
89EA STA fs_cmd_data ; Flag 1: read/write direction
89ED ROL ; ROL: Flag 2 into bit 0
89EE STA fs_func_code ; Flag 2: create vs existing file
89F1 JSR parse_filename_gs ; Parse filename from command line
89F4 LDX #2 ; X=2: copy after 2-byte flags
89F6 JSR copy_string_to_cmd ; Copy filename to FS command buffer
89F9 LDY #6 ; Y=6: FS function code FCOPEN
89FB BIT tx_ctrl_upper ; Set V flag from l83b3 bit 6
89FE JSR init_tx_ctrl_data ; Build and send FS open command
8A01 BCS restore_xy_return ; Error: restore and return
8A03 LDA fs_cmd_data ; Load reply handle from FS
8A06 TAX ; X = new file handle
8A07 JSR set_fs_flag ; Set EOF hint + sequence bits
; OR handle bit into fs_sequence_nos
; (&0E08). Without this, a newly opened file could
; inherit a stale sequence number from a previous
; file using the same handle, causing byte-stream
; protocol errors.
8A0A TXA ; A=handle bitmask for new file A=single-bit bitmask
8A0B JSR mask_to_handle ; Convert bitmask to handle number (FS2A)
8A0E BNE restore_xy_return ; ALWAYS branch to restore and return

Close file handle(s) (CLOSE)

  Y=0: close all files — first calls OSBYTE &77 (close SPOOL and
       EXEC files) to coordinate with the MOS before sending the
       close-all command to the fileserver. This ensures locally-
       managed file handles are released before the server-side
       handles are invalidated, preventing the MOS from writing to
       a closed spool file.
  Y>0: close single handle — sends FS close command and clears
       the handle's bit in both the EOF hint byte and the sequence
       number tracking byte.
On EntryYfile handle (0=close all, >0=close single)
8A10 .close_handle←1← 89E0 BEQ
TYA ; A = handle (Y preserved in A) Y=preserved
8A11 BNE close_single_handle ; Y>0: close single file
8A13 LDA #osbyte_close_spool_exec ; Close SPOOL/EXEC before FS close-all
8A15 JSR osbyte ; Close any *SPOOL and *EXEC files
8A18 LDY #0 ; Y=0: close all handles on server
8A1A .close_single_handle←1← 8A11 BNE
STY fs_cmd_data ; Handle byte in FS command buffer
8A1D LDX #1 ; X=preserved through header build
8A1F LDY #7 ; Y=function code for HDRFN
8A21 JSR prepare_fs_cmd ; Prepare FS command buffer (12 references)
8A24 LDA fs_cmd_data ; Reply handle for flag update
8A27 JSR set_fs_flag ; Update EOF/sequence tracking bits
8A2A .close_opt_return←1← 8A50 BCC
BCC restore_args_return ; C=0: restore A/X/Y and return
8A2C .fscv_0_opt_entry
BEQ set_messages_flag ; Entry from fscv_0_opt (close-all path)
fall through ↓

FSCV 0: *OPT handler (OPTION)

Handles *OPT X,Y to set filing system options: *OPT 1,Y (Y=0/1): set local user option in &0E06 (OPT) *OPT 4,Y (Y=0-3): set boot option via FS command &16 (FCOPT) Other combinations generate error &CB (OPTER: "bad option").

On EntryXoption number (1 or 4)
Yoption value
8A2E .fscv_0_opt
CPX #4 ; Is it *OPT 4,Y?
8A30 BNE check_opt1 ; No: check for *OPT 1
8A32 CPY #4 ; Y must be 0-3 for boot option
8A34 BCC optl1 ; Y < 4: valid boot option
8A36 .check_opt1←1← 8A30 BNE
DEX ; Not *OPT 4: check for *OPT 1
8A37 BNE opter1 ; Not *OPT 1 either: bad option
8A39 .set_messages_flag←1← 8A2C BEQ
STY fs_messages_flag ; Set local messages flag (*OPT 1,Y)
8A3C BCC opt_return ; Return via restore_args_return
8A3E .opter1←1← 8A37 BNE
LDA #7 ; Error index 7 (Bad option)
8A40 JMP nlisne ; Generate BRK error
8A43 .optl1←1← 8A34 BCC
STY fs_cmd_data ; Boot option value in FS command
8A46 LDY #&16 ; Y=&16: FS function code FCOPT Y=function code for HDRFN
8A48 JSR prepare_fs_cmd ; Prepare FS command buffer (12 references)
8A4B LDY fs_block_offset ; Restore Y from saved value
8A4D STY fs_boot_option ; Cache boot option locally
8A50 .opt_return←1← 8A3C BCC
BCC close_opt_return ; Return via restore_args_return
8A52 .adjust_addrs_9←1← 8B0D JSR
LDY #9 ; Y=9: adjust 9 address bytes
8A54 JSR adjust_addrs_clc ; Adjust with carry clear
8A57 .adjust_addrs_1←1← 8C02 JSR
LDY #1 ; Y=1: adjust 1 address byte
8A59 .adjust_addrs_clc←1← 8A54 JSR
CLC ; C=0 for address adjustment
fall through ↓

Bidirectional 4-byte address adjustment

Adjusts a 4-byte value in the parameter block at (fs_options)+Y: If fs_load_addr_2 (&B2) is positive: adds fs_lib_handle+X values If fs_load_addr_2 (&B2) is negative: subtracts fs_lib_handle+X Starting offset X=&FC means it reads from &0E06-&0E09 area. Used to convert between absolute and relative file positions.

On EntryYstarting offset into (fs_options) parameter block
On ExitAcorrupted (last adjusted byte)
X0
Yentry Y + 4
8A5A .adjust_addrs←2← 8B13 JSR← 8C0E JSR
LDX #&fc ; X=&FC: index into &0E06 area (wraps to 0)
8A5C .adjust_addr_byte←1← 8A6F BNE
LDA (fs_options),y ; Load byte from param block
8A5E BIT fs_load_addr_2 ; Test sign of adjustment direction
8A60 BMI subtract_adjust ; Negative: subtract instead
8A62 ADC fs_cmd_context,x ; Add adjustment value
8A65 JMP gbpbx ; Skip to store result
8A68 .subtract_adjust←1← 8A60 BMI
SBC fs_cmd_context,x ; Subtract adjustment value
8A6B .gbpbx←1← 8A65 JMP
STA (fs_options),y ; Store adjusted byte back
8A6D INY ; Next param block byte
8A6E INX ; Next adjustment byte (X wraps &FC->&00)
8A6F BNE adjust_addr_byte ; Loop 4 times (X=&FC,&FD,&FE,&FF,done)
8A71 RTS ; Return (unsupported function)

GBPBV handler (OSGBPB entry point)

A=1-4: file read/write operations (handle-based) A=5-8: info queries (disc title, current dir, lib, filenames) Calls 1-4 are standard file data transfers via the fileserver. Calls 5-8 were a late addition to the MOS spec and are the only NFS operations requiring Tube data transfer -- described in the original source as "untidy but useful in theory." The data format uses length-prefixed strings (<name length><object name>) rather than the CR-terminated strings used elsewhere in the FS.

On EntryAcall number (1-8)
Xparameter block address low byte
Yparameter block address high byte
On ExitA0 after FS operation, else restored
Xrestored
Yrestored
8A72 .gbpbv_handler
JSR save_fscv_args ; Save A/X/Y to FS workspace
8A75 TAX ; X = call number for range check
8A76 BEQ gbpb_invalid_exit ; A=0: invalid, restore and return
8A78 DEX ; Convert to 0-based (A=0..7)
8A79 CPX #8 ; Range check: must be 0-7
8A7B BCC gbpbx1 ; In range: continue to handler
8A7D .gbpb_invalid_exit←1← 8A76 BEQ
JMP restore_args_return ; Out of range: restore args and return
8A80 .gbpbx1←1← 8A7B BCC
TXA ; Recover 0-based function code
8A81 LDY #0 ; Y=0: param block byte 0 (file handle)
8A83 PHA ; Save function code on stack
8A84 CMP #4 ; A>=4: info queries, dispatch separately
8A86 BCC gbpbe1 ; A<4: file read/write operations
8A88 JMP osgbpb_info ; Dispatch to OSGBPB 5-8 info handler
8A8B .gbpbe1←1← 8A86 BCC
LDA (fs_options),y ; Get file handle from param block byte 0
8A8D JSR handle_to_mask_a ; Convert handle to bitmask for EOF flags
8A90 STY fs_cmd_data ; Store handle in FS command data
8A93 LDY #&0b ; Y=&0B: start at param block byte 11
8A95 LDX #6 ; X=6: copy 6 bytes of transfer params
8A97 .gbpbf1←1← 8AA3 BNE
LDA (fs_options),y ; Load param block byte
8A99 STA fs_func_code,x ; Store to FS command buffer at &0F06+X
8A9C DEY ; Previous param block byte
8A9D CPY #8 ; Skip param block offset 8 (the handle)
8A9F BNE gbpbx0 ; Not at handle offset: continue
8AA1 DEY ; Extra DEY to skip handle byte
8AA2 .gbpbx0←1← 8A9F BNE
.gbpbf2←1← 8A9F BNE
DEX ; Decrement copy counter
8AA3 BNE gbpbf1 ; Loop for all 6 bytes
8AA5 PLA ; Recover function code from stack
8AA6 LSR ; LSR: odd=read (C=1), even=write (C=0)
8AA7 PHA ; Save function code again (need C later)
8AA8 BCC gbpbl1 ; Even (write): X stays 0
8AAA INX ; Odd (read): X=1
8AAB .gbpbl1←1← 8AA8 BCC
STX fs_func_code ; Store FS direction flag
8AAE LDY #&0b ; Y=&0B: command data extent
8AB0 LDX #&91 ; Command &91=put, &92=get
8AB2 PLA ; Recover function code
8AB3 PHA ; Save again for later direction check
8AB4 BEQ gbpb_write_path ; Even (write): keep &91 and Y=&0B
8AB6 LDX #&92 ; Odd (read): use &92 (get) instead
8AB8 DEY ; Read: one fewer data byte in command Y=&0a
8AB9 .gbpb_write_path←1← 8AB4 BEQ
STX fs_cmd_urd ; Store port to FS command URD field
8ABC STX fs_error_ptr ; Save port for error recovery
8ABE LDX #8 ; X=8: command data bytes
8AC0 LDA fs_cmd_data ; Load handle from FS command data
8AC3 JSR prepare_cmd_with_flag ; Build FS command with handle+flag
8AC6 LDA fs_load_addr_3 ; Save seq# for byte-stream flow control
8AC8 STA fs_sequence_nos ; Store to FS sequence number workspace
8ACB LDX #4 ; X=4: copy 4 address bytes
8ACD .gbpbl3←1← 8AE1 BNE
LDA (fs_options),y ; Set up source/dest from param block
8ACF STA addr_work,y ; Store as source address
8AD2 STA txcb_pos,y ; Store as current transfer position
8AD5 JSR add_4_to_y ; Skip 4 bytes to reach transfer length
8AD8 ADC (fs_options),y ; Dest = source + length
8ADA STA addr_work,y ; Store as end address
8ADD JSR sub_3_from_y ; Back 3 to align for next iteration
8AE0 DEX ; Decrement byte counter
8AE1 BNE gbpbl3 ; Loop for all 4 address bytes
8AE3 INX ; X=1 after loop
8AE4 .gbpbf3←1← 8AEB BPL
LDA fs_cmd_csd,x ; Copy CSD data to command buffer
8AE7 STA fs_func_code,x ; Store at &0F06+X
8AEA DEX ; Decrement counter
8AEB BPL gbpbf3 ; Loop for X=1,0
8AED PLA ; Odd (read): send data to FS first
8AEE BNE gbpb_read_path ; Non-zero: skip write path
8AF0 LDA fs_cmd_urd ; Load port for transfer setup
8AF3 JSR transfer_file_blocks ; Transfer data blocks to fileserver
8AF6 BCS wait_fs_reply ; Carry set: transfer error
8AF8 .gbpb_read_path←1← 8AEE BNE
JSR send_data_blocks ; Read path: receive data blocks from FS
8AFB .wait_fs_reply←1← 8AF6 BCS
JSR send_fs_reply_cmd ; Wait for FS reply command
8AFE LDA (fs_options,x) ; Load handle mask for EOF flag update
8B00 BIT fs_cmd_data ; Check FS reply: bit 7 = not at EOF
8B03 BMI skip_clear_flag ; Bit 7 set: not EOF, skip clear
8B05 JSR clear_fs_flag ; At EOF: clear EOF hint for this handle
8B08 .skip_clear_flag←1← 8B03 BMI
JSR set_fs_flag ; Set EOF hint flag (may be at EOF)
8B0B STX fs_load_addr_2 ; Direction=0: forward adjustment
8B0D JSR adjust_addrs_9 ; Adjust param block addrs by +9 bytes
8B10 DEC fs_load_addr_2 ; Direction=&FF: reverse adjustment
8B12 SEC ; SEC for reverse subtraction
8B13 JSR adjust_addrs ; Adjust param block addrs (reverse)
8B16 ASL fs_cmd_data ; Shift bit 7 into C for return flag
8B19 JMP return_a_zero ; Return via restore_args path
8B1C .get_disc_title←1← 8B4C BEQ
LDY #&15 ; Y=&15: function code for disc title Y=function code for HDRFN
8B1E JSR prepare_fs_cmd ; Build and send FS command Prepare FS command buffer (12 references)
8B21 LDA fs_boot_option ; Load boot option from FS workspace
8B24 STA fs_boot_data ; Store boot option in reply area
8B27 STX fs_load_addr ; X=0: reply data start offset X=0 on success, &D6 on not-found
8B29 STX fs_load_addr_hi ; Clear reply buffer high byte
8B2B LDA #&12 ; A=&12: 18 bytes of reply data
8B2D STA fs_load_addr_2 ; Store as byte count for copy
8B2F BNE copy_reply_to_caller ; ALWAYS branch to copy_reply_to_caller ALWAYS branch

OSGBPB 5-8 info handler (OSINFO)

Handles the "messy interface tacked onto OSGBPB" (original source). Checks whether the destination address is in Tube space by comparing the high bytes against TBFLAG. If in Tube space, data must be copied via the Tube FIFO registers (with delays to accommodate the slow 16032 co-processor) instead of direct memory access.

8B31 .osgbpb_info←1← 8A88 JMP
LDY #4 ; Y=4: check param block byte 4
8B33 LDA tube_flag ; Check if destination is in Tube space
8B36 BEQ store_tube_flag ; No Tube: skip Tube address check
8B38 CMP (fs_options),y ; Compare Tube flag with addr byte 4
8B3A BNE store_tube_flag ; Mismatch: not Tube space
8B3C DEY ; Y=&03
8B3D SBC (fs_options),y ; Y=3: subtract addr byte 3 from flag
8B3F .store_tube_flag←2← 8B36 BEQ← 8B3A BNE
STA svc_state ; Non-zero = Tube transfer required
8B41 .info2←1← 8B47 BNE
LDA (fs_options),y ; Copy param block bytes 1-4 to workspace
8B43 STA fs_last_byte_flag,y ; Store to &BD+Y workspace area
8B46 DEY ; Previous byte
8B47 BNE info2 ; Loop for bytes 3,2,1
8B49 PLA ; Sub-function: AND #3 of (original A - 4)
8B4A AND #3 ; Mask to 0-3 (OSGBPB 5-8 → 0-3)
8B4C BEQ get_disc_title ; A=0 (OSGBPB 5): read disc title
8B4E LSR ; LSR: A=0 (OSGBPB 6) or A=1 (OSGBPB 7)
8B4F BEQ gbpb6_read_name ; A=0 (OSGBPB 6): read CSD/LIB name
8B51 BCS gbpb8_read_dir ; C=1 (OSGBPB 8): read filenames from dir
8B53 .gbpb6_read_name←1← 8B4F BEQ
TAY ; Y=0 for CSD or carry for fn code select Y=function code
8B54 LDA fs_csd_handle,y ; Get CSD/LIB/URD handles for FS command
8B57 STA fs_cmd_csd ; Store CSD handle in command buffer
8B5A LDA fs_lib_handle ; Load LIB handle from workspace
8B5D STA fs_cmd_lib ; Store LIB handle in command buffer
8B60 LDA fs_urd_handle ; Load URD handle from workspace
8B63 STA fs_cmd_urd ; Store URD handle in command buffer
8B66 LDX #&12 ; X=&12: buffer extent for command data X=buffer extent (command-specific data bytes)
8B68 STX fs_cmd_y_param ; Store X as function code in header
8B6B LDA #&0d ; &0D = 13 bytes of reply data expected
8B6D STA fs_func_code ; Store reply length in command buffer
8B70 STA fs_load_addr_2 ; Store as byte count for copy loop
8B72 LSR ; LSR: &0D >> 1 = 6
8B73 STA fs_cmd_data ; Store as command data byte
8B76 CLC ; CLC for standard FS path
8B77 JSR build_send_fs_cmd ; Build and send FS command (DOFSOP)
8B7A STX fs_load_addr_hi ; X=0 on success, &D6 on not-found
8B7C INX ; INX: X=1 after build_send_fs_cmd
8B7D STX fs_load_addr ; Store X as reply start offset
8B7F .copy_reply_to_caller←2← 8B2F BNE← 8BF7 JSR
LDA svc_state ; Copy FS reply to caller's buffer
8B81 BNE tube_transfer ; Non-zero: use Tube transfer path
8B83 LDX fs_load_addr ; X = reply start offset
8B85 LDY fs_load_addr_hi ; Y = reply buffer high byte
8B87 .copy_reply_bytes←1← 8B90 BNE
LDA fs_cmd_data,x ; Load reply data byte
8B8A STA (fs_crc_lo),y ; Store to caller's buffer
8B8C INX ; Next source byte
8B8D INY ; Next destination byte
8B8E DEC fs_load_addr_2 ; Decrement remaining bytes
8B90 BNE copy_reply_bytes ; Loop until all bytes copied
8B92 BEQ gbpb_done ; ALWAYS branch to exit ALWAYS branch
8B94 .tube_transfer←1← 8B81 BNE
JSR tube_claim_loop ; Claim Tube transfer channel
8B97 LDA #1 ; A=1: Tube claim type 1 (write)
8B99 LDX fs_options ; X = param block address low
8B9B LDY fs_block_offset ; Y = param block address high
8B9D INX ; INX: advance past byte 0
8B9E BNE no_page_wrap ; No page wrap: keep Y
8BA0 INY ; Page wrap: increment high byte
8BA1 .no_page_wrap←1← 8B9E BNE
JSR tube_addr_claim ; Claim Tube address for transfer
8BA4 LDX fs_load_addr ; X = reply data start offset
8BA6 .tbcop1←1← 8BB4 BNE
LDA fs_cmd_data,x ; Load reply data byte
8BA9 STA tube_data_register_3 ; Send byte to Tube via R3
8BAC INX ; Next source byte
8BAD LDY #6 ; Delay loop for slow Tube co-processor
8BAF .wait_tube_delay←1← 8BB0 BNE
DEY ; Decrement delay counter
8BB0 BNE wait_tube_delay ; Loop until delay complete
8BB2 DEC fs_load_addr_2 ; Decrement remaining bytes
8BB4 BNE tbcop1 ; Loop until all bytes sent to Tube
8BB6 LDA #&83 ; Release Tube after transfer complete
8BB8 JSR tube_addr_claim ; Release Tube address claim
8BBB .gbpb_done←2← 8B92 BEQ← 8C11 BEQ
JMP return_a_zero ; Return via restore_args path
8BBE .gbpb8_read_dir←1← 8B51 BCS
LDY #9 ; OSGBPB 8: read filenames from dir
8BC0 LDA (fs_options),y ; Byte 9: number of entries to read
8BC2 STA fs_func_code ; Store as reply count in command buffer
8BC5 LDY #5 ; Y=5: byte 5 = starting entry number
8BC7 LDA (fs_options),y ; Load starting entry number
8BC9 STA fs_data_count ; Store in command buffer
8BCC LDX #&0d ; X=&0D: command data extent X=preserved through header build
8BCE STX fs_reply_cmd ; Store extent in command buffer
8BD1 LDY #2 ; Y=2: function code for dir read
8BD3 STY fs_load_addr ; Store 2 as reply data start offset
8BD5 STY fs_cmd_data ; Store 2 as command data byte
8BD8 INY ; Y=3: function code for header read Y=function code for HDRFN Y=&03
8BD9 JSR prepare_fs_cmd ; Prepare FS command buffer (12 references)
8BDC STX fs_load_addr_hi ; X=0 after FS command completes X=0 on success, &D6 on not-found
8BDE LDA fs_func_code ; Load reply entry count
8BE1 STA (fs_options,x) ; Store at param block byte 0 (X=0)
8BE3 LDA fs_cmd_data ; Load entries-read count from reply
8BE6 LDY #9 ; Y=9: param block byte 9
8BE8 ADC (fs_options),y ; Add to starting entry number
8BEA STA (fs_options),y ; Update param block with new position
8BEC LDA txcb_end ; Load total reply length
8BEE SBC #7 ; Subtract header (7 bytes) from reply len
8BF0 STA fs_func_code ; Store adjusted length in command buffer
8BF3 STA fs_load_addr_2 ; Store as byte count for copy loop
8BF5 BEQ skip_copy_reply ; Zero bytes: skip copy
8BF7 JSR copy_reply_to_caller ; Copy reply data to caller's buffer
8BFA .skip_copy_reply←1← 8BF5 BEQ
LDX #2 ; X=2: clear 3 bytes
8BFC .zero_cmd_bytes←1← 8C00 BPL
STA fs_data_count,x ; Zero out &0F07+X area
8BFF DEX ; Next byte
8C00 BPL zero_cmd_bytes ; Loop for X=2,1,0
8C02 JSR adjust_addrs_1 ; Adjust pointer by +1 (one filename read)
8C05 SEC ; SEC for reverse adjustment
8C06 DEC fs_load_addr_2 ; Reverse adjustment for updated counter
8C08 LDA fs_cmd_data ; Load entries-read count
8C0B STA fs_func_code ; Store in command buffer
8C0E JSR adjust_addrs ; Adjust param block addresses
8C11 BEQ gbpb_done ; Z=1: all done, exit
8C13 .tube_claim_loop←3← 8B94 JSR← 8C18 BCC← 8E1D JSR
LDA #&c3 ; A=&C3: Tube claim with retry
8C15 JSR tube_addr_claim ; Request Tube address claim
8C18 BCC tube_claim_loop ; C=0: claim failed, retry
8C1A RTS ; Tube claimed successfully

FSCV 2/3/4: unrecognised * command handler (DECODE)

CLI parser originally by Sophie Wilson (co-designer of ARM). Matches command text against the table at &8C4B using case-insensitive comparison with abbreviation support — commands can be shortened with '.' (e.g. "I." for "INFO"). The "I." entry is a special fudge placed first in the table: since "I." could match multiple commands, it jumps to forward_star_cmd to let the fileserver resolve the ambiguity.

The matching loop compares input characters against table entries. On mismatch, it skips to the next entry. On match of all table characters, or when '.' abbreviation is found, it dispatches via PHA/PHA/RTS to the entry's handler address.

After matching, adjusts fs_crc_lo/fs_crc_hi to point past the matched command text.

8C1B .fscv_3_star_cmd
JSR save_fscv_args_with_ptrs ; Save A/X/Y and set up command ptr
8C1E LDX #&ff ; X=&FF: table index (pre-incremented)
8C20 STX fs_crflag ; Disable column formatting
8C22 STX escapable ; Enable escape checking
8C24 .scan_cmd_table←1← 8C3F BNE
LDY #&ff ; Y=&FF: input index (pre-incremented)
8C26 .decfir←1← 8C31 BEQ
INY ; Advance input pointer
8C27 INX ; Advance table pointer
8C28 .decmor←1← 8C43 BCS
LDA fs_cmd_match_table,x ; Load table character
8C2B BMI dispatch_cmd ; Bit 7: end of name, dispatch
8C2D EOR (fs_crc_lo),y ; XOR input char with table char
8C2F AND #&df ; Case-insensitive (clear bit 5)
8C31 BEQ decfir ; Match: continue comparing
8C33 DEX ; Mismatch: back up table pointer
8C34 .decmin←1← 8C38 BPL
INX ; Skip to end of table entry
8C35 LDA fs_cmd_match_table,x ; Load table byte
8C38 BPL decmin ; Loop until bit 7 set (end marker)
8C3A LDA (fs_crc_lo),y ; Check input for '.' abbreviation
8C3C INX ; Skip past handler high byte
8C3D CMP #&2e ; Is input '.' (abbreviation)?
8C3F BNE scan_cmd_table ; No: try next table entry
8C41 INY ; Yes: skip '.' in input
8C42 DEX ; Back to handler high byte
8C43 BCS decmor ; ALWAYS branch; dispatch via BMI
8C45 .dispatch_cmd←1← 8C2B BMI
PHA ; Push handler address high byte
8C46 LDA cmd_match_data,x ; Load handler address low byte
8C49 PHA ; Push handler address low byte
8C4A RTS ; Dispatch via RTS (addr-1 on stack)

FS command match table (COMTAB)

Format: command letters (bit 7 clear), then dispatch address as two big-endian bytes: high|(bit 7 set), low. The bit 7 set on the high byte marks the end of the command string. The PHA/PHA/RTS trick adds 1 to the stored (address-1). Matching is case-insensitive (AND &DF) and supports '.' abbreviation.

Entries: "I." → &80C1 (forward_star_cmd) — placed first as a fudge to catch *I. abbreviation before matching *I AM "I AM" → &8082 (i_am_handler: parse station.net, logon) "EX" → &9FB5 (ex_trampoline: checks next char, dispatches to ex_handler or forward_star_cmd for *EXEC etc.) "BYE"\r → &83C0 (bye_handler: logoff) <catch-all> → &80C1 (forward anything else to FS)

8C4B .fs_cmd_match_table←2← 8C28 LDA← 8C35 LDA
EOR #&2e ; Match last char against '.' for *I. abbreviation
8C4D EQUB &80 ; I. handler hi → &80C1 (forward_star_cmd)
8C4E EQUB &C0 ; I. handler lo
8C4F EQUS "I AM" ; "I AM" command string
8C53 EQUB &80 ; I AM handler hi → &8082 (i_am_handler)
8C54 EQUB &81 ; I AM handler lo
8C55 EQUS "EX" ; "EX" command string
8C57 EQUB &9F ; EX handler hi → &9FB5 (ex_trampoline)
8C58 EQUB &B4 ; EX handler lo
8C59 EQUS "BYE" ; "BYE" command string
8C5C EQUB &0D ; CR terminator for BYE
8C5D EQUB &83 ; BYE handler hi → &83C0 (bye_handler)
8C5E EQUB &BF ; BYE handler lo
8C5F EQUB &80 ; Catch-all hi → &80C1 (forward_star_cmd)
8C60 EQUB &C0 ; Catch-all lo
8C61 .ex_handler←1← 9FBB JMP
LDX #1 ; X=1: force one entry per line for *EX
8C63 LDA #3 ; A=3: examine format code
8C65 BNE init_cat_params ; ALWAYS branch

*CAT handler (directory catalogue)

Initialises &B5=&0B (examine arg count) and &B7=&03 (column count). The catalogue protocol is multi-step: first sends FCREAD (&12: examine) to get the directory header, then sends FCUSER (&15: read user environment) to get CSD, disc, and library names, then sends FC &03 (examine entries) repeatedly to fetch entries in batches until zero are returned (end of dir). The receive buffer abuts the examine request buffer and ends at RXBUFE, allowing seamless data handling across request cycles.

The command code byte in the fileserver reply indicates FS type: zero means an old-format FS (client must format data locally), non-zero means new-format (server returns pre-formatted strings). This enables backward compatibility with older Acorn fileservers.

Display format: - Station number in parentheses - "Owner" or "Public" access level - Boot option with name (Off/Load/Run/Exec) - Current directory and library paths - Directory entries: CRFLAG (&B9) cycles 0-3 for multi-column layout; at count 0 a newline is printed, others get spaces. *EX sets CRFLAG=&FF to force one entry per line.

8C67 .fscv_5_cat
LDX #3 ; X=3: column count for multi-column layout
8C69 STX fs_crflag ; CRFLAG=3: first entry will trigger newline
8C6B LDY #&ff ; Y=&FF: mark as escapable
8C6D STY escapable ; Store escapable flag for Escape checking
8C6F INY ; Y=&00
8C70 LDA #&0b ; A=&0B: examine argument count
8C72 .init_cat_params←1← 8C65 BNE
STA fs_work_5 ; Store examine argument count
8C74 STX fs_work_7 ; Store column count
8C76 LDA #6 ; A=6: examine format type in command
8C78 STA fs_cmd_data ; Store format type at &0F05
8C7B JSR parse_filename_gs_y ; Set up command parameter pointers
8C7E LDX #1 ; X=1: copy dir name at cmd offset 1
8C80 JSR copy_string_to_cmd ; Copy directory name to command buffer
8C83 LDY #&12 ; Y=function code for HDRFN
8C85 JSR prepare_fs_cmd ; Prepare FS command buffer (12 references)
8C88 LDX #3 ; X=3: start printing from reply offset 3
8C8A JSR print_reply_bytes ; Print directory title (10 chars)
8C8D JSR print_inline ; Print '('
8C90 EQUS "(" ; Inline string "("
8C91 LDA fs_reply_stn ; Load station number from FS reply
8C94 JSR print_decimal ; Print station number as decimal
8C97 JSR print_inline ; Print ') '
8C9A EQUS ") " ; Inline string ") "
8CA0 LDY fs_access_level ; Access level byte: 0=Owner, non-zero=Public
8CA3 BNE print_public ; Non-zero: Public access
8CA5 JSR print_inline ; Print 'Owner' + CR
8CA8 EQUS "Owner." ; Inline string "Owner" + CR
8CAE BNE print_user_env ; Always branches (print_inline sets N=1)
8CB0 .print_public←1← 8CA3 BNE
JSR print_inline ; Print 'Public' + CR
8CB3 EQUS "Public." ; Print ' Option '
8CBA .print_user_env←1← 8CAE BNE
LDY #&15 ; Y=function code for HDRFN
8CBC JSR prepare_fs_cmd ; Prepare FS command buffer (12 references)
8CBF INX ; X=1: past command code byte
8CC0 LDY #&10 ; Y=&10: print 16 characters
8CC2 JSR print_reply_counted ; Print disc/CSD name from reply
8CC5 JSR print_inline ; Print ' Option '
8CC8 EQUS " Option " ; Print ' ('
8CD3 LDA fs_boot_option ; Load boot option from workspace
8CD6 TAX ; X = boot option for name table lookup
8CD7 JSR print_hex ; Print boot option as hex digit
8CDA JSR print_inline ; Print ' ('
8CDD EQUS " (" ; Inline string " ("
8CDF LDY option_name_offsets,x ; Load string offset for option name
8CE2 .print_option_name←1← 8CEB BNE
LDA option_name_offsets,y ; Load character from option name string
8CE5 BMI done_option_name ; Bit7 set: string terminated, done
8CE7 JSR osasci ; Write character
8CEA INY ; Next character
8CEB BNE print_option_name ; Continue printing option name
8CED .done_option_name←1← 8CE5 BMI
JSR print_inline ; Print ')' + CR + 'Dir. '
8CF0 EQUS ").Dir. " ; Inline string ")" + CR + "Dir. "
8CF7 LDX #&11 ; X=&11: directory name offset in reply
8CF9 JSR print_reply_bytes ; Print current directory name
8CFC JSR print_inline ; Print ' Lib. '
8CFF EQUS " Lib. " ; Store entry start offset for request
8D09 LDX #&1b ; X=&1B: library name offset in reply
8D0B JSR print_reply_bytes ; Print library name
8D0E JSR osnewl ; Print two CRs (blank line) Write newline (characters 10 and 13)
8D11 .fetch_dir_batch←1← 8D45 BNE
STY fs_func_code ; Store entry start offset for request
8D14 STY fs_work_4 ; Save start offset in zero page for loop
8D16 LDX fs_work_5 ; Load examine arg count for batch size
8D18 STX fs_data_count ; Store as request count at &0F07
8D1B .cat_examine_loop
LDX fs_work_7 ; Load column count for display format
8D1D STX fs_cmd_data ; Store column count in command data
8D20 LDX #3 ; X=3: copy directory name at offset 3
8D22 JSR copy_string_to_cmd ; Append directory name to examine command
8D25 LDY #3 ; Y=function code for HDRFN
8D27 JSR prepare_fs_cmd ; Prepare FS command buffer (12 references)
8D2A INX ; X past command code byte in reply
8D2B LDA fs_cmd_data ; Load entry count from reply
8D2E BNE process_entries ; Zero entries returned = end of directory
8D30 JMP osnewl ; Write newline (characters 10 and 13)
8D33 .process_entries←1← 8D2E BNE
PHA ; Save entry count for batch processing
8D34 .scan_entry_terminator←1← 8D38 BPL
INY ; Advance Y past entry data bytes
8D35 LDA fs_cmd_data,y ; Read entry byte from reply buffer
8D38 BPL scan_entry_terminator ; Loop until high-bit terminator found
8D3A STA fs_cmd_lib,y ; Store terminator as print boundary
8D3D JSR cat_column_separator ; Print/format this directory entry
8D40 PLA ; Restore entry count from stack
8D41 CLC ; CLC for addition
8D42 ADC fs_work_4 ; Advance start offset by entry count
8D44 TAY ; Y = new entry start offset
8D45 BNE fetch_dir_batch ; More entries: fetch next batch
8D47 .print_reply_bytes←3← 8C8A JSR← 8CF9 JSR← 8D0B JSR
LDY #&0a ; Y=&0A: default print 10 characters
8D49 .print_reply_counted←2← 8CC2 JSR← 8D51 BNE
LDA fs_cmd_data,x ; Load reply byte at offset X
8D4C JSR osasci ; Write character
8D4F INX ; Next reply byte
8D50 DEY ; Decrement character count
8D51 BNE print_reply_counted ; Loop for remaining characters
; Option name encoding: the boot option names ("Off", "Load",
; "Run", "Exec") are scattered through the code rather than
; stored as a contiguous table. They are addressed via ; base+offset
; from l8d54 (&8D54), whose four bytes are offsets into page ; &8D:
; &2B→&8D7F "Off", &3E→&8D92 "Load",
; &66→&8DBA "Run", &18→&8D6C "Exec"
; Each string is terminated by the next instruction's opcode
; having bit 7 set (e.g. LDA #imm = &A9, RTS = &60).
8D53 .return_9
RTS ; Return from column separator
8D54 .option_name_offsets←2← 8CDF LDY← 8CE2 LDA
EQUS "+>f" ; Option name offsets (4 entries)
8D57 EQUB &18 ; Opt 3 (Exec) name offset: &18 -> &8D6C
8D58 EQUS "L.!" ; Opt 1 (Load): "L.!" command prefix

Boot command strings for auto-boot

The four boot options use OSCLI strings at offsets within page &8D. The offset table at boot_option_offsets+1 (&8D68) is indexed by the boot option value (0-3); each byte is the low byte of the string address, with the page high byte &8D loaded separately: Option 0 (Off): offset &67 → &8D67 = bare CR (empty command) Option 1 (Load): offset &58 → &8D58 = "L.!BOOT" (the bytes &4C='L', &2E='.', &21='!' precede "BOOT" + CR at &8D5F) Option 2 (Run): offset &5A → boot_cmd_strings-1 = "!BOOT" (*RUN) Option 3 (Exec): offset &60 → &8D60 = "E.!BOOT"

This is a classic BBC ROM space optimisation: the string data overlaps with other byte sequences to save space. The &0D byte at &8D67 terminates "E.!BOOT" AND doubles as the bare-CR command for boot option 0.

8D5B .boot_cmd_strings
EQUS "BOOT" ; "BOOT" string for boot option 2
8D5F EQUB &0D ; CR terminator for BOOT string
8D60 EQUS "E.!BOOT" ; Opt 3 (Exec): "E.!BOOT" command string

Boot option → OSCLI string offset table

Five bytes: the first byte (&0D) is the bare-CR target for boot option 0; bytes 1-4 are the offset table indexed by boot option (0-3). Each offset is the low byte of a pointer into page &8D. The code reads from boot_option_offsets+1 (&8D68) via LDX l8d68,Y with Y=boot_option, then LDY #&8D, JMP oscli. See boot_cmd_strings for the target strings.

8D67 .boot_option_offsets
EQUB &0D ; CR terminator / Opt 0 bare CR command
8D68 .boot_string_offsets←1← 8E4B LDX
EQUB &67 ; Opt 0 (Off): bare CR at &8D67
8D69 EQUB &58 ; Opt 1 (Load): L.!BOOT at &8D58
8D6A EQUB &5A ; Opt 2 (Run): !BOOT at &8D5A
8D6B EQUB &60 ; Opt 3 (Exec): E.!BOOT at &8D60
8D6C EQUS "Exec" ; "Exec" option name string
8D70 .print_hex_bytes←2← 8805 JSR← 880A JSR
LDX #4 ; X=4: print 4 hex bytes
8D72 .num01←2← 8811 JSR← 8D79 BNE
LDA (fs_options),y ; Load byte from parameter block
8D74 JSR print_hex ; Print as two hex digits
8D77 DEY ; Next byte (descending)
8D78 DEX ; Count down
8D79 BNE num01 ; Loop until 4 bytes printed
8D7B .print_space←1← 87FB JSR
LDA #&20 ; A=space character
8D7D BNE print_digit ; ALWAYS branch
8D7F EQUS "Off" ; Return; X = next free position in buffer

Copy filename to FS command buffer

Entry with X=0: copies from (fs_crc_lo),Y to &0F05+X until CR. Used to place a filename into the FS command buffer before sending to the fileserver. Falls through to copy_string_to_cmd.

On ExitXnext free position in cmd buffer
Ystring length (incl CR)
A0 (from EOR &0D with final CR)
8D82 .infol2←5← 809D JSR← 80C1 JSR← 871D JSR← 8917 JSR← 8DDF JSR
.copy_filename←5← 809D JSR← 80C1 JSR← 871D JSR← 8917 JSR← 8DDF JSR
LDX #0 ; Start writing at &0F05 (after cmd header)
fall through ↓

Copy string to FS command buffer

Entry with X and Y specified: copies bytes from (fs_crc_lo),Y to &0F05+X, stopping when a CR (&0D) is encountered. The CR itself is also copied. Returns with X pointing past the last byte written.

On EntryXdestination offset in fs_cmd_data (&0F05+X)
On ExitXnext free position past CR
Ystring length (incl CR)
A0 (from EOR &0D with final CR)
8D84 .copy_string_to_cmd←6← 87CA JSR← 8910 JSR← 8932 JSR← 89F6 JSR← 8C80 JSR← 8D22 JSR
LDY #0 ; Start copying from offset 0
8D86 .copy_string_from_offset←1← 8D8F BNE
LDA (fs_crc_lo),y ; Load next byte from source string
8D88 STA fs_cmd_data,x ; Store to FS command buffer (&0F05+X)
8D8B INX ; Advance write position
8D8C INY ; Advance source pointer
8D8D EOR #&0d ; XOR with CR: result=0 if byte was CR
8D8F BNE copy_string_from_offset ; Loop until CR copied
8D91 .return_5←1← 8D9B BMI
RTS ; Return; X = next free position in buffer
8D92 EQUS "Load" ; Transfer to A for modulo

Print directory name from reply buffer

Prints characters from the FS reply buffer (&0F05+X onwards). Null bytes (&00) are replaced with CR (&0D) for display. Stops when a byte with bit 7 set is encountered (high-bit terminator). Used by fscv_5_cat to display Dir. and Lib. paths.

8D96 .fsreply_0_print_dir
LDX #0 ; X=0: start from first reply byte
8D98 .print_dir_from_offset←2← 87EA JSR← 8DB8 BNE
LDA fs_cmd_data,x ; Load byte from FS reply buffer
8D9B BMI return_5 ; Bit 7 set: end of string, return
8D9D BNE print_newline ; Non-zero: print character
fall through ↓

Print catalogue column separator or newline

Handles column formatting for *CAT display. On a null byte separator, advances the column counter modulo 4: prints a 2-space separator between columns, or a CR at column 0. Called from fsreply_0_print_dir.

8D9F .cat_column_separator←1← 8D3D JSR
LDY fs_crflag ; Null byte: check column counter
8DA1 BMI print_cr ; Negative: print CR (no columns)
8DA3 INY ; Advance column counter
8DA4 TYA ; Transfer to A for modulo
8DA5 AND #3 ; Modulo 4 columns
8DA7 STA fs_crflag ; Update column counter
8DA9 BEQ print_cr ; Column 0: start new line
8DAB JSR print_inline ; Print 2-space column separator
8DAE EQUS " " ; Print hundreds digit
8DB0 BNE next_dir_entry ; ALWAYS branch to next byte
8DB2 .print_cr←2← 8DA1 BMI← 8DA9 BEQ
LDA #&0d ; CR = carriage return
8DB4 .print_newline←1← 8D9D BNE
JSR osasci ; Write character 13
8DB7 .next_dir_entry←1← 8DB0 BNE
INX ; Next byte in reply buffer
8DB8 BNE print_dir_from_offset ; Loop until end of buffer
8DBA EQUS "Run" ; A = dividend (from Y)
fall through ↓

Print byte as 3-digit decimal number

Prints A as a decimal number using repeated subtraction for each digit position (100, 10, 1). Leading zeros are printed (no suppression). Used to display station numbers.

On EntryAbyte value to print
On ExitAlast digit character
Xcorrupted
Y0 (remainder after last division)
8DBD .print_decimal←2← 8248 JSR← 8C94 JSR
TAY ; Y = value to print
8DBE LDA #&64 ; Divisor = 100 (hundreds digit)
8DC0 JSR print_decimal_digit ; Print hundreds digit
8DC3 LDA #&0a ; Divisor = 10 (tens digit)
8DC5 JSR print_decimal_digit ; Print tens digit
8DC8 LDA #1 ; Divisor = 1; fall through to units
fall through ↓

Print one decimal digit by repeated subtraction

Divides Y by A using repeated subtraction. Prints the quotient as an ASCII digit ('0'-'9') via OSASCI. Returns with the remainder in Y. X starts at &2F ('0'-1) and increments once per subtraction, giving the ASCII digit directly.

On EntryAdivisor (stored to &B8)
Ydividend
On ExitYremainder
8DCA .print_decimal_digit←2← 8DC0 JSR← 8DC5 JSR
STA fs_error_ptr ; Save divisor to workspace
8DCC TYA ; A = dividend (from Y)
8DCD LDX #&2f ; X = &2F = ASCII '0' - 1
8DCF SEC ; Prepare for subtraction
8DD0 .divide_subtract←1← 8DD3 BCS
INX ; Count one subtraction (next digit value)
8DD1 SBC fs_error_ptr ; A = A - divisor
8DD3 BCS divide_subtract ; Loop while A >= 0 (borrow clear)
8DD5 ADC fs_error_ptr ; Undo last subtraction: A = remainder
8DD7 TAY ; Y = remainder for caller
8DD8 TXA ; A = X = ASCII digit character
8DD9 .print_digit←1← 8D7D BNE
JMP osasci ; Write character

FSCV 2/4: */ (run) and *RUN handler

Parses the filename via parse_filename_gs and calls infol2, then falls through to fsreply_4_notify_exec to set up and send the FS load-as-command request.

8DDC .fscv_2_star_run
JSR parse_filename_gs ; Parse filename from command line
8DDF JSR infol2 ; Copy filename to FS command buffer
fall through ↓

FS reply 4: send FS load-as-command and execute response

Initialises a GS reader to skip past the filename and calculate the command context address, then sets up an FS command with function code &05 (FCCMND: load as command) using send_fs_examine. If a Tube co-processor is present (tube_flag != 0), transfers the response data to the Tube via tube_addr_claim. Otherwise jumps via the indirect pointer at (&0F09) to execute at the load address.

8DE2 .fsreply_4_notify_exec
LDY #0 ; Y=0: start of text for GSINIT
8DE4 CLC ; CLC before GSINIT call
8DE5 JSR gsinit ; GSINIT/GSREAD: skip past the filename
8DE8 .skip_gs_filename←1← 8DEB BCC
JSR gsread ; Read next filename character
8DEB BCC skip_gs_filename ; C=0: more characters, keep reading
8DED JSR skip_spaces ; Skip spaces after filename
8DF0 CLC ; Calculate context addr = text ptr + Y
8DF1 TYA ; Y = offset past filename end
8DF2 ADC os_text_ptr ; Add text pointer low byte
8DF4 STA fs_cmd_context ; Store context address low byte
8DF7 LDA os_text_ptr_hi ; Load text pointer high byte
8DF9 ADC #0 ; Add carry from low byte addition
8DFB STA fs_context_hi ; Store context address high byte
8DFE LDX #&0e ; X=&0E: FS command buffer offset
8E00 STX fs_block_offset ; Store block offset for FS command
8E02 LDA #&10 ; A=&10: 16 bytes of command data
8E04 STA fs_options ; Store options byte
8E06 STA fs_work_16 ; Store to FS workspace
8E09 LDX #&4a ; X=&4A: TXCB size for load command
8E0B LDY #5 ; Y=5: FCCMND (load as command)
8E0D JSR send_fs_examine ; Send FS examine/load command
8E10 LDA tube_flag ; Check for Tube co-processor
8E13 BEQ exec_local ; No Tube: execute locally
8E15 ADC fs_load_upper ; Check load address upper bytes
8E18 ADC fs_addr_check ; Continue address range check
8E1B BCS exec_local ; Carry set: not Tube space, exec locally
8E1D JSR tube_claim_loop ; Claim Tube transfer channel
8E20 LDX #9 ; X=9: source offset in FS reply
8E22 LDY #&0f ; Y=&0F: page &0F (FS command buffer)
8E24 LDA #4 ; A=4: Tube transfer type 4 (256-byte)
8E26 JMP tube_addr_claim ; Transfer data to Tube co-processor
8E29 .exec_local←2← 8E13 BEQ← 8E1B BCS
ROL ; ROL: restore A (undo ADC carry)
8E2A .exec_at_load_addr
JMP (fs_load_vector) ; Execute at load address via indirect JMP

Set library handle

Stores Y into &0E04 (library directory handle in FS workspace). Falls through to JMP restore_args_return if Y is non-zero.

On EntryYlibrary handle from FS reply
8E2D .fsreply_5_set_lib
STY fs_lib_handle ; Save library handle from FS reply
8E30 BCC jmp_restore_args ; SDISC path: skip CSD, jump to return
fall through ↓

Set CSD handle

Stores Y into &0E03 (current selected directory handle). Falls through to JMP restore_args_return.

On EntryYCSD handle from FS reply
8E32 .fsreply_3_set_csd
STY fs_csd_handle ; Store CSD handle from FS reply
8E35 .jmp_restore_args←2← 8E30 BCC← 8E46 BCC
JMP restore_args_return ; Restore A/X/Y and return to caller

Copy FS reply handles to workspace and execute boot command

SEC entry (LOGIN): copies 4 bytes from &0F05-&0F08 (FS reply) to &0E02-&0E05 (URD, CSD, LIB handles and boot option), then looks up the boot option in boot_option_offsets to get the OSCLI command string and executes it via JMP oscli. The carry flag distinguishes LOGIN (SEC) from SDISC (CLC) — both share the handle-copying code, but only LOGIN executes the boot command. This use of the carry flag to select behaviour between two callers avoids duplicating the handle-copy loop.

8E38 .fsreply_1_copy_handles_boot
SEC ; Set carry: LOGIN path (copy + boot)
fall through ↓

Copy FS reply handles to workspace (no boot)

CLC entry (SDISC): copies handles only, then jumps to restore_args_return via jmp_restore_args. Called when the FS reply contains updated handle values but no boot action is needed.

8E39 .fsreply_2_copy_handles
LDX #3 ; Copy 4 bytes: boot option + 3 handles
8E3B BCC copy_handles_loop ; SDISC: skip boot option, copy handles only
8E3D .logon2←1← 8E44 BPL
LDA fs_cmd_data,x ; Load from FS reply (&0F05+X)
8E40 STA fs_urd_handle,x ; Store to handle workspace (&0E02+X)
8E43 .copy_handles_loop←1← 8E3B BCC
DEX ; Next handle (descending)
8E44 BPL logon2 ; Loop while X >= 0
8E46 BCC jmp_restore_args ; SDISC: done, restore args and return
fall through ↓

Execute boot command via OSCLI

Reached from fsreply_1_copy_handles_boot when carry is set (LOGIN path). Reads the boot option from fs_boot_option (&0E05), looks up the OSCLI command string offset from boot_option_offsets+1, and executes the boot command via JMP oscli with page &8D.

8E48 .boot_cmd_execute
LDY fs_boot_option ; Y = boot option from FS workspace
8E4B LDX boot_string_offsets,y ; X = command string offset from table
8E4E LDY #&8d ; Y = &8D (high byte of command address)
8E50 JMP oscli ; Execute boot command string via OSCLI

Load handle from &F0 and calculate workspace offset

Loads the file handle byte from &F0, then falls through to calc_handle_offset which converts handle * 12 to a workspace byte offset. Validates offset < &48.

On ExitAhandle*12 or 0 if invalid
Yworkspace offset or 0 if invalid
Cclear if valid, set if invalid
8E53 .load_handle_calc_offset←2← 8E6D JSR← 8E7D JSR
LDA osword_pb_ptr ; Load handle from &F0
fall through ↓

Calculate handle workspace offset

Converts a file handle number (in A) to a byte offset (in Y) into the NFS handle workspace. The calculation is A*12: ASL A (A*2), ASL A (A*4), PHA, ASL A (A*8), ADC stack (A*8 + A*4 = A*12). Validates that the offset is < &48 (max 6 handles × 12 bytes per handle entry = 72 bytes). If invalid (>= &48), returns with C set and Y=0, A=0 as an error indicator.

On EntryAfile handle number
On ExitAhandle*12 or 0 if invalid
Yworkspace offset or 0 if invalid
Cclear if valid, set if invalid
8E55 .calc_handle_offset←3← 8309 JSR← 8F8D JSR← 8FA6 JSR
ASL ; A = handle * 2
8E56 ASL ; A = handle * 4
8E57 PHA ; Push handle*4 onto stack
8E58 ASL ; A = handle * 8
8E59 TSX ; X = stack pointer
8E5A ADC error_text,x ; A = handle*8 + handle*4 = handle*12
8E5D TAY ; Y = offset into handle workspace
8E5E PLA ; Clean up stack (discard handle*4)
8E5F CMP #&48 ; Offset >= &48? (6 handles max)
8E61 BCC return_6 ; Valid: return with C clear
8E63 LDY #0 ; Invalid: Y = 0
8E65 TYA ; A = 0, C set (error) A=&00
8E66 .return_6←1← 8E61 BCC
.return_calc_handle←1← 8E61 BCC
RTS ; Return after calculation
; *NET1: read file handle from received packet.
; Reads a byte from offset &6F of the RX buffer (net_rx_ptr)
; and falls through to net_2_read_handle_entry's common path.
8E67 .net_1_read_handle
LDY #&6f ; Y=&6F: RX buffer handle offset
8E69 LDA (net_rx_ptr),y ; Read handle from RX packet
8E6B BCC store_handle_return ; Valid handle: store and return
fall through ↓

*NET2: read handle entry from workspace

Looks up the handle in &F0 via calc_handle_offset. If the workspace slot contains &3F ('?', meaning unused/closed), returns 0. Otherwise returns the stored handle value. Clears rom_svc_num on exit.

On ExitAhandle value (0 if closed/invalid)
8E6D .net_2_read_handle_entry
JSR load_handle_calc_offset ; Look up handle &F0 in workspace
8E70 BCS rxpol2 ; Invalid handle: return 0
8E72 LDA (nfs_workspace),y ; Load stored handle value
8E74 CMP #&3f ; &3F = unused/closed slot marker
8E76 BNE store_handle_return ; Slot in use: return actual value
8E78 .rxpol2←2← 8E70 BCS← 8E80 BCS
LDA #0 ; Return 0 for closed/invalid handle
8E7A .store_handle_return←2← 8E6B BCC← 8E76 BNE
STA osword_pb_ptr ; Store result back to &F0
8E7C RTS ; Return

*NET3: close handle (mark as unused)

Looks up the handle in &F0 via calc_handle_offset. Writes &3F ('?') to mark the handle slot as closed in the NFS workspace. Returns via RTS (earlier versions preserved the carry flag across the write using ROL/ROR on rx_flags, but 3.60 simplified this).

On ExitA&3F (close marker) or 0 if invalid
8E7D .net_3_close_handle
JSR load_handle_calc_offset ; Look up handle &F0 in workspace
8E80 BCS rxpol2 ; Invalid handle: return 0
8E82 LDA #&3f ; &3F = '?' marks slot as unused
8E84 STA (nfs_workspace),y ; Write close marker to workspace slot
8E86 RTS ; Return

Filing system OSWORD entry

Subtracts &0F from the command code in &EF, giving a 0-4 index for OSWORD calls &0F-&13 (15-19). Falls through to the range check and dispatch at osword_12_handler (&8E8D).

8E87 .svc_8_osword
LDA osbyte_a_copy ; Command code from &EF
8E89 SBC #&0f ; Subtract &0F: OSWORD &0F-&13 become indices 0-4
8E8B BMI return_7 ; Outside our OSWORD range, exit
fall through ↓

OSWORD range check, dispatch, and register restore

Reached by fall-through from svc_8_osword with A = OSWORD number minus &0F. Rejects indices >= 5 (only OSWORDs &0F-&13 are handled). Dispatches to the appropriate handler via fs_osword_dispatch, then on return copies 3 bytes from (net_rx_ptr)+0..2 back to &AA-&AC (restoring the param block pointer that was saved by fs_osword_dispatch before dispatch).

The actual OSWORD &12 sub-function dispatch (read/set station, protection, handles etc.) lives in sub_c8f01.

8E8D .osword_12_handler
CMP #5 ; Only OSWORDs &0F-&13 (index 0-4)
8E8F BCS return_7 ; Index >= 5: not ours, return
8E91 JSR fs_osword_dispatch ; Dispatch via PHA/PHA/RTS table
8E94 LDY #2 ; Y=2: restore 3 bytes (&AA-&AC)
8E96 .copy_param_ptr←1← 8E9C BPL
LDA (net_rx_ptr),y ; Load saved param block byte
8E98 STA osword_flag,y ; Restore to &AA-&AC
8E9B DEY ; Next byte (descending)
8E9C BPL copy_param_ptr ; Loop for all 3 bytes
8E9E RTS ; Return to service handler

PHA/PHA/RTS dispatch for filing system OSWORDs

Saves the param block pointer (&AA-&AC) to (net_rx_ptr) and reads the sub-function code from (&F0)+1, then dispatches via the 5-entry table at &8EB8 (low) / &8EBD (high) using PHA/PHA/RTS. The RTS at the end of the dispatched handler returns here, after which the caller restores &AA-&AC.

8E9F .fs_osword_dispatch←1← 8E91 JSR
TAX ; X = sub-function code for table lookup
8EA0 LDA fs_osword_tbl_hi,x ; Load handler address high byte from table
8EA3 PHA ; Push high byte for RTS dispatch
8EA4 LDA osword_handler_lo,x ; Load handler address low byte from table
8EA7 .fs_osword_tbl_lo
PHA ; Push low byte for RTS dispatch
8EA8 LDY #2 ; Y=2: save 3 bytes (&AA-&AC)
8EAA .save1←1← 8EB0 BPL
LDA osword_flag,y ; Load param block pointer byte
8EAD STA (net_rx_ptr),y ; Save to NFS workspace via (net_rx_ptr)
8EAF DEY ; Next byte (descending)
8EB0 BPL save1 ; Loop for all 3 bytes
8EB2 INY ; Y=0 after BPL exit; INY makes Y=1
8EB3 LDA (osword_pb_ptr),y ; Read sub-function code from (&F0)+1
8EB5 STY svc_state ; Store Y=1 to &A9
8EB7 .return_7←2← 8E8B BMI← 8E8F BCS
RTS ; RTS dispatches to pushed handler address
8EB8 .osword_handler_lo←1← 8EA4 LDA
EQUB <(osword_0f_handler-1) ; lo(osword_0f_handler-1): OSWORD &0F
8EB9 EQUB <(osword_10_handler-1) ; lo(osword_10_handler-1): OSWORD &10
8EBA EQUB <(osword_11_handler-1) ; lo(osword_11_handler-1): OSWORD &11
8EBB EQUB <(osword_12_dispatch-1) ; lo(osword_12_dispatch-1): OSWORD &12
8EBC EQUB <(econet_tx_rx-1) ; lo(econet_tx_rx-1): OSWORD &13
8EBD .fs_osword_tbl_hi←1← 8EA0 LDA
EQUB >(osword_0f_handler-1) ; hi(osword_0f_handler-1): OSWORD &0F
8EBE EQUB >(osword_10_handler-1) ; hi(osword_10_handler-1): OSWORD &10
8EBF EQUB >(osword_11_handler-1) ; hi(osword_11_handler-1): OSWORD &11
8EC0 EQUB >(osword_12_dispatch-1) ; hi(osword_12_dispatch-1): OSWORD &12
8EC1 EQUB >(econet_tx_rx-1) ; hi(econet_tx_rx-1): OSWORD &13

OSWORD &0F handler: initiate transmit (CALLTX)

Checks the TX semaphore (TXCLR at &0D62) via ASL -- if carry is clear, a TX is already in progress and the call returns an error, preventing user code from corrupting a system transmit. Otherwise copies 16 bytes from the caller's OSWORD parameter block into the user TX control block (UTXCB) in static workspace. The TXCB pointer is copied to LTXCBP only after the semaphore is claimed, ensuring the low-level transmit code (BRIANX) sees a consistent pointer -- if copied before claiming, another transmitter could modify TXCBP between the copy and the claim.

On EntryXparameter block address low byte
Yparameter block address high byte
On ExitAcorrupted
Xcorrupted
Y&FF
8EC2 .osword_0f_handler
ASL tx_clear_flag ; ASL TXCLR: C=1 means TX free to claim
8EC5 TYA ; Save Y (param block high) for later
8EC6 BCC readry ; C=0: TX busy, return error status
8EC8 LDA net_rx_ptr_hi ; User TX CB in workspace page (high byte)
8ECA STA ws_ptr_hi ; Set param block high byte
8ECC STA nmi_tx_block_hi ; Set LTXCBP high byte for low-level TX
8ECE LDA #&6f ; &6F: offset into workspace for user TXCB
8ED0 STA ws_ptr_lo ; Set param block low byte
8ED2 STA nmi_tx_block ; Set LTXCBP low byte for low-level TX
8ED4 LDX #&0f ; X=15: copy 16 bytes (OSWORD param block)
8ED6 JSR copy_param_workspace ; Copy param block to user TX control block
8ED9 JMP start_adlc_tx ; Start user transmit via BRIANX

OSWORD &11 handler: read JSR arguments (READRA)

Copies the JSR (remote procedure call) argument buffer from the static workspace page back to the caller's OSWORD parameter block. Reads the buffer size from workspace offset JSRSIZ, then copies that many bytes. After the copy, clears the old LSTAT byte via CLRJSR to reset the protection status. Also provides READRB as a sub-entry (&8EE7) to return just the buffer size and args size without copying the data.

8EDC .osword_11_handler
LDA net_rx_ptr_hi ; Set source high byte from workspace page
8EDE STA ws_ptr_hi ; Store as copy source high byte in &AC
8EE0 LDY #&7f ; JSRSIZ at workspace offset &7F
8EE2 LDA (net_rx_ptr),y ; Load buffer size from workspace
8EE4 INY ; Y=&80: start of JSR argument data Y=&80
8EE5 STY ws_ptr_lo ; Store &80 as copy source low byte
8EE7 TAX ; X = buffer size (loop counter)
8EE8 DEX ; X = size-1 (0-based count for copy)
8EE9 LDY #0 ; Y=0: start of destination param block
8EEB JSR copy_param_workspace ; Copy X+1 bytes from workspace to param
8EEE JMP clear_jsr_protection ; Clear JSR protection status (CLRJSR)
8EF1 .read_args_size←1← 8F4C BEQ
LDY #&7f ; Y=&7F: JSRSIZ offset (READRB entry)
8EF3 LDA (net_rx_ptr),y ; Load buffer size from workspace
8EF5 LDY #1 ; Y=1: param block offset for size byte
8EF7 STA (osword_pb_ptr),y ; Store buffer size to (&F0)+1
8EF9 INY ; Y=2: param block offset for args size Y=&02
8EFA LDA #&80 ; A=&80: argument data starts at offset &80
8EFC .readry←1← 8EC6 BCC
STA (osword_pb_ptr),y ; Store args start offset to (&F0)+2
8EFE RTS ; Return
8EFF .osword_12_offsets←1← 8F13 LDA
EQUB &FF, &01 ; Store args start offset to (&F0)+2

OSWORD &12 handler: dispatch sub-functions 0-5

Range-checks the sub-function code from the param block. Sub-functions 4-5 go to read/set station number. Sub-functions 0-3 select the appropriate workspace page (static &0D or dynamic) and offset, then fall through to the bidirectional param block copy loop.

8F01 .osword_12_dispatch
CMP #6 ; OSWORD &12: range check sub-function
8F03 BCS rsl1 ; Sub-function >= 6: not supported
8F05 CMP #4 ; Check for sub-functions 4-5
8F07 BCS rssl1 ; Sub-function 4 or 5: read/set protection
8F09 LSR ; LSR: 0->0, 1->0, 2->1, 3->1
8F0A LDX #&0d ; X=&0D: default to static workspace page
8F0C TAY ; Transfer LSR result to Y for indexing
8F0D BEQ set_workspace_page ; Y=0 (sub 0-1): use page &0D
8F0F LDX nfs_workspace_hi ; Y=1 (sub 2-3): use dynamic workspace
8F11 .set_workspace_page←1← 8F0D BEQ
STX ws_ptr_hi ; Store workspace page in &AC (hi byte)
8F13 LDA osword_12_offsets,y ; Load offset: &FF (sub 0-1) or &01 (sub 2-3)
8F16 STA ws_ptr_lo ; Store offset in &AB (lo byte)
8F18 LDX #1 ; X=1: copy 2 bytes
8F1A LDY #1 ; Y=1: start at param block offset 1
fall through ↓

Bidirectional copy loop between param block and workspace

If C=1, copies from OSWORD param block (&F0),Y to workspace (&AB),Y. In either case, loads from workspace and stores to param block. Loops for X+1 bytes. Used by OSWORD &0F, &10, &11, and &12 handlers.

On EntryC1=copy param to workspace first, 0=workspace to param only
Xbyte count minus 1
Ystarting offset
On ExitAlast byte copied
X&FF
Ystart + count + 1
8F1C .copy_param_workspace←4← 8ED6 JSR← 8EEB JSR← 8F28 BPL← 8FBD JSR
BCC skip_param_write ; C=0: skip param-to-workspace copy
8F1E LDA (osword_pb_ptr),y ; C=1: copy from param to workspace
8F20 STA (ws_ptr_lo),y ; Store param byte to workspace
8F22 .skip_param_write←1← 8F1C BCC
LDA (ws_ptr_lo),y ; Load byte from workspace
fall through ↓

Bidirectional block copy between OSWORD param block and workspace.

C=1: copy X+1 bytes from (&F0),Y to (&AB),Y (param to workspace) C=0: copy X+1 bytes from (&AB),Y to (&F0),Y (workspace to param)

On EntryC1=param to workspace, 0=workspace to param
Xbyte count minus 1
Ystarting offset
On ExitAlast byte copied
X&FF
Ystart + count + 1
8F24 .copy_param_block
STA (osword_pb_ptr),y ; Store to param block (no-op if C=1)
8F26 INY ; Advance to next byte
8F27 DEX ; Decrement remaining count
8F28 BPL copy_param_workspace ; Loop while bytes remain
8F2A .logon3
.return_copy_param
RTS ; Return
8F2B .rssl1←1← 8F07 BCS
LSR ; LSR A: test bit 0 of sub-function
8F2C INY ; Y=1: offset for protection byte
8F2D LDA (osword_pb_ptr),y ; Load protection byte from param block
8F2F BCS rssl2 ; C=1 (odd sub): set protection
8F31 LDA prot_status ; C=0 (even sub): read current status
8F34 STA (osword_pb_ptr),y ; Return current value to param block
8F36 .rssl2←1← 8F2F BCS
STA prot_status ; Update protection status
8F39 STA saved_jsr_mask ; Also save as JSR mask backup
8F3C RTS ; Return
8F3D .read_fs_handle←1← 8F48 BEQ
LDY #&14 ; Y=&14: RX buffer offset for FS handle
8F3F LDA (net_rx_ptr),y ; Read FS reply handle from RX data
8F41 LDY #1 ; Y=1: param block byte 1
8F43 STA (osword_pb_ptr),y ; Return handle to caller's param block
8F45 RTS ; Return
8F46 .rsl1←1← 8F03 BCS
CMP #8 ; Sub-function 8: read FS handle
8F48 BEQ read_fs_handle ; Match: read handle from RX buffer
8F4A CMP #9 ; Sub-function 9: read args size
8F4C BEQ read_args_size ; Match: read ARGS buffer info
8F4E BPL return_last_error ; Sub >= 10 (bit 7 clear): read error
8F50 LDY #3 ; Y=3: start from handle 3 (descending)
8F52 LSR ; LSR: test read/write bit
8F53 BCC readc1 ; C=0: read handles from workspace
8F55 STY ws_page ; Init loop counter at Y=3
8F57 .copy_handles_to_ws←1← 8F66 BNE
LDY ws_page ; Reload loop counter
8F59 LDA (osword_pb_ptr),y ; Read handle from caller's param block
8F5B JSR handle_to_mask_a ; Convert handle number to bitmask
8F5E TYA ; TYA: get bitmask result
8F5F LDY ws_page ; Reload loop counter
8F61 STA fs_server_net,y ; Store bitmask to FS server table
8F64 DEC ws_page ; Next handle (descending)
8F66 BNE copy_handles_to_ws ; Loop for handles 3,2,1
8F68 RTS ; Return
8F69 .return_last_error←1← 8F4E BPL
INY ; Y=1 (post-INY): param block byte 1
8F6A LDA fs_last_error ; Read last FS error code
8F6D STA (osword_pb_ptr),y ; Return error to caller's param block
8F6F RTS ; Return
8F70 .readc1←2← 8F53 BCC← 8F79 BNE
LDA fs_server_net,y ; A=single-bit bitmask
8F73 JSR mask_to_handle ; Convert bitmask to handle number (FS2A)
8F76 STA (osword_pb_ptr),y ; A=handle number (&20-&27) Y=preserved
8F78 DEY ; Next handle (descending)
8F79 BNE readc1 ; Loop for handles 3,2,1
8F7B RTS ; Return

OSWORD &10 handler: open/read RX control block (OPENRX)

If the first byte of the caller's parameter block is zero, scans for a free RXCB (flag byte = &3F = deleted) starting from RXCB #3 (RXCBs 0-2 are dedicated: printer, remote, FS). Returns the RXCB number in the first byte, or zero if none free. If the first byte is non-zero, reads the specified RXCB's data back into the caller's parameter block (12 bytes) and then deletes the RXCB by setting its flag byte to &3F -- a consume-once semantic so user code reads received data and frees the CB in a single atomic operation, preventing double-reads. The low-level user RX flag (LFLAG) is temporarily disabled via ROR/ROL during the operation to prevent the interrupt-driven receive code from modifying a CB that is being read or opened.

On EntryXparameter block address low byte
Yparameter block address high byte
On ExitAcorrupted
Xcorrupted
Y&FF
8F7C .osword_10_handler
LDX nfs_workspace_hi ; Workspace page high byte
8F7E STX ws_ptr_hi ; Set up pointer high byte in &AC
8F80 STY ws_ptr_lo ; Save param block high byte in &AB
8F82 ROR rx_flags ; Disable user RX during CB operation
8F85 LDA (osword_pb_ptr),y ; Read first byte of param block
8F87 STA osword_flag ; Save: 0=open new, non-zero=read RXCB
8F89 BNE read_rxcb ; Non-zero: read specified RXCB
8F8B LDA #3 ; Start scan from RXCB #3 (0-2 reserved)
8F8D .scan0←1← 8F9F BNE
JSR calc_handle_offset ; Convert RXCB number to workspace offset
8F90 BCS openl4 ; Invalid RXCB: return zero
8F92 LSR ; LSR twice: byte offset / 4
8F93 LSR ; Yields RXCB number from offset
8F94 TAX ; X = RXCB number for iteration
8F95 LDA (ws_ptr_lo),y ; Read flag byte from RXCB workspace
8F97 BEQ openl4 ; Zero = end of CB list
8F99 CMP #&3f ; &3F = deleted slot, free for reuse
8F9B BEQ scan1 ; Found free slot
8F9D INX ; Try next RXCB
8F9E TXA ; A = next RXCB number
8F9F BNE scan0 ; Continue scan (always branches)
8FA1 .scan1←1← 8F9B BEQ
TXA ; A = free RXCB number
8FA2 LDX #0 ; X=0 for indexed indirect store
8FA4 STA (osword_pb_ptr,x) ; Return RXCB number to caller's byte 0
8FA6 .read_rxcb←1← 8F89 BNE
JSR calc_handle_offset ; Convert RXCB number to workspace offset
8FA9 BCS openl4 ; Invalid: write zero to param block
8FAB DEY ; Y = offset-1: points to flag byte
8FAC STY ws_ptr_lo ; Set &AB = workspace ptr low byte
8FAE LDA #&c0 ; &C0: test mask for flag byte
8FB0 LDY #1 ; Y=1: flag byte offset in RXCB
8FB2 LDX #&0b ; Enable interrupts before transmit
8FB4 CPY osword_flag ; Compare Y(1) with saved byte (open/read)
8FB6 ADC (ws_ptr_lo),y ; ADC flag: test if slot is in use
8FB8 BEQ openl6 ; Dest station = &FFFF (accept reply from any station)
8FBA BMI openl7 ; Negative: slot has received data
8FBC .copy_rxcb_to_param←1← 8FCC BNE
CLC ; C=0: workspace-to-param direction
8FBD .openl6←1← 8FB8 BEQ
JSR copy_param_workspace ; Copy RXCB data to param block
8FC0 BCS reenable_rx ; Done: skip deletion on error
8FC2 LDA #&3f ; Mark CB as consumed (consume-once)
8FC4 LDY #1 ; Y=1: flag byte offset
8FC6 STA (ws_ptr_lo),y ; Write &3F to mark slot deleted
8FC8 BNE reenable_rx ; Branch to exit (always taken) ALWAYS branch
8FCA .openl7←1← 8FBA BMI
ADC #1 ; Advance through multi-byte field
8FCC BNE copy_rxcb_to_param ; Loop until all bytes processed
8FCE DEY ; Y=-1 → Y=0 after STA below
8FCF .openl4←3← 8F90 BCS← 8F97 BEQ← 8FA9 BCS
STA (osword_pb_ptr),y ; Return zero (no free RXCB found)
8FD1 .reenable_rx←2← 8FC0 BCS← 8FC8 BNE
ROL rx_flags ; Re-enable user RX
8FD4 RTS ; Return

Set up RX buffer pointers in NFS workspace

Calculates the start address of the RX data area (&F0+1) and stores it at workspace offset &1C. Also reads the data length from (&F0)+1 and adds it to &F0 to compute the end address at offset &20.

On EntryCclear for ADC
8FD5 .setup_rx_buffer_ptrs←1← 9008 JSR
LDY #&1c ; Y=&1C: workspace offset for RX data start
8FD7 LDA osword_pb_ptr ; A = base address low byte
8FD9 ADC #1 ; A = base + 1 (skip length byte)
8FDB JSR store_16bit_at_y ; Receive data blocks until command byte = &00 or &0D
8FDE LDY #1 ; Read data length from (&F0)+1
8FE0 LDA (osword_pb_ptr),y ; A = data length byte
8FE2 LDY #&20 ; Workspace offset &20 = RX data end
8FE4 ADC osword_pb_ptr ; A = base + length = end address low
8FE6 .store_16bit_at_y←1← 8FDB JSR
STA (nfs_workspace),y ; Store low byte of 16-bit address
8FE8 INY ; Advance to high byte offset
8FE9 LDA osword_pb_ptr_hi ; A = high byte of base address
8FEB ADC #0 ; Add carry for 16-bit addition
8FED STA (nfs_workspace),y ; Store high byte
8FEF RTS ; Return

Econet transmit/receive handler

A=0: Initialise TX control block from ROM template at &8395 (init_tx_ctrl_block+Y, zero entries substituted from NMI workspace &0DE6), transmit it, set up RX control block, and receive reply. A>=1: Handle transmit result (branch to cleanup at &903E).

On EntryA0=set up and transmit, >=1=handle TX result
8FF0 .econet_tx_rx
CMP #1 ; A=0: set up and transmit; A>=1: handle result
8FF2 BCS handle_tx_result ; A >= 1: handle TX result
8FF4 LDY #&23 ; Y=&23: start of template (descending)
8FF6 .dofs01←1← 9003 BNE
LDA init_tx_ctrl_block,y ; Load ROM template byte
8FF9 BNE store_txcb_byte ; Non-zero = use ROM template byte as-is
8FFB LDA nmi_sub_table,y ; Zero = substitute from NMI workspace
8FFE .store_txcb_byte←1← 8FF9 BNE
STA (nfs_workspace),y ; Store to dynamic workspace
9000 DEY ; Descend through template
9001 CPY #&17 ; Stop at offset &17
9003 BNE dofs01 ; Loop until all bytes copied
9005 INY ; Y=&18: TX block starts here
9006 STY net_tx_ptr ; Point net_tx_ptr at workspace+&18
9008 JSR setup_rx_buffer_ptrs ; Set up RX buffer start/end pointers
900B LDY #2 ; Y=2: port byte offset in RXCB
900D LDA #&90 ; A=&90: FS reply port
900F STA escapable ; Mark as escapable operation
9011 STA (osword_pb_ptr),y ; Store port &90 at (&F0)+2
9013 INY ; Y=&03
9014 INY ; Y=&04: advance to station address Y=&04
9015 .copy_fs_addr←1← 901D BNE
LDA fs_context_base,y ; Copy FS station addr from workspace
9018 STA (osword_pb_ptr),y ; Store to RX param block
901A INY ; Next byte
901B CPY #7 ; Done 3 bytes (Y=4,5,6)?
901D BNE copy_fs_addr ; No: continue copying
901F LDA nfs_workspace_hi ; High byte of workspace for TX ptr
9021 STA net_tx_ptr_hi ; Store as TX pointer high byte
9023 CLI ; Enable interrupts before transmit
9024 JSR tx_poll_ff ; Transmit with full retry
9027 LDY #&20 ; Y=&20: RX end address offset
9029 LDA #&ff ; Set RX end address to &FFFF (accept any length)
902B STA (nfs_workspace),y ; Store end address low byte (&FF)
902D INY ; Y=&21
902E STA (nfs_workspace),y ; Store end address high byte (&FF)
9030 LDY #&19 ; Y=&19: port byte in workspace RXCB
9032 LDA #&90 ; A=&90: FS reply port
9034 STA (nfs_workspace),y ; Store port to workspace RXCB
9036 DEY ; Y=&18
9037 LDA #&7f ; A=&7F: flag byte = waiting for reply
9039 STA (nfs_workspace),y ; Store flag byte to workspace RXCB
903B JMP waitfs ; Jump to RX poll (BRIANX)
903E .handle_tx_result←1← 8FF2 BCS
PHP ; Save processor flags
903F LDY #1 ; Y=1: first data byte offset
9041 LDA (osword_pb_ptr),y ; Load first data byte from RX buffer
fall through ↓

FS response data relay (DOFS)

Entered from the econet_tx_rx response handler at &903E after loading the first data byte from the RX buffer. Saves the command byte and station address from the received packet into (net_rx_ptr)+&71/&72, then iterates through remaining data bytes. Each byte is stored at (net_rx_ptr)+&7D, the control block is set up via ctrl_block_setup_alt, and the packet is transmitted. Loops until a &0D terminator or &00 null is found. The branch at &9053 (BNE dofs2) handles the first-packet case where the data length field at (net_rx_ptr)+&7B is adjusted.

9043 .net_write_char
TAX ; X = first data byte (command code)
9044 INY ; Advance to next data byte
9045 LDA (osword_pb_ptr),y ; Load station address high byte
9047 INY ; Advance past station addr
9048 STY ws_ptr_lo ; Save Y as data index
904A LDY #&72 ; Store station addr hi at (net_rx_ptr)+&72
904C STA (net_rx_ptr),y ; Store to workspace
904E DEY ; Y=&71
904F TXA ; A = command code (from X)
9050 STA (net_rx_ptr),y ; Store station addr lo at (net_rx_ptr)+&71
9052 PLP ; Restore flags from earlier PHP
9053 BNE dofs2 ; First call: adjust data length
9055 .send_data_bytes←1← 906E BNE
LDY ws_ptr_lo ; Reload data index
9057 INC ws_ptr_lo ; Advance data index for next iteration
9059 LDA (osword_pb_ptr),y ; Load next data byte
905B BEQ return_8 ; Zero byte: end of data, return
905D LDY #&7d ; Y=&7D: store byte for TX at offset &7D
905F STA (net_rx_ptr),y ; Store data byte at (net_rx_ptr)+&7D for TX
9061 PHA ; Save data byte for &0D check after TX
9062 JSR ctrl_block_setup_alt ; Set up TX control block
9065 JSR enable_irq_and_tx ; Enable IRQs and transmit
9068 .delay_between_tx←1← 9069 BNE
DEX ; Short delay loop between TX packets
9069 BNE delay_between_tx ; Spin until X reaches 0
906B PLA ; Restore data byte for terminator check
906C EOR #&0d ; Test for end-of-data marker (&0D)
906E BNE send_data_bytes ; Not &0D: continue with next byte
9070 .return_8←1← 905B BEQ
RTS ; Return (data complete)
9071 .dofs2←1← 9053 BNE
JSR ctrl_block_setup_alt ; First-packet: set up control block
9074 LDY #&7b ; Y=&7B: data length offset
9076 LDA (net_rx_ptr),y ; Load current data length
9078 ADC #3 ; Adjust data length by 3 for header bytes
907A STA (net_rx_ptr),y ; Store adjusted length
fall through ↓

Enable interrupts and transmit via tx_poll_ff

CLI to enable interrupts, then JMP tx_poll_ff. A short tail-call wrapper used after building the TX control block.

907C .enable_irq_and_tx←1← 9065 JSR
CLI ; Enable interrupts
907D JMP tx_poll_ff ; Transmit via tx_poll_ff

NETVEC dispatch handler (ENTRY)

Indirected from NETVEC at &0224. Saves all registers and flags, retrieves the reason code from the stacked A, and dispatches to one of 9 handlers (codes 0-8) via the PHA/PHA/RTS trampoline at &9099. Reason codes >= 9 are ignored.

Dispatch targets (from NFS09): 0: no-op (RTS) 1-3: PRINT -- chars in printer buffer / Ctrl-B / Ctrl-C 4: NWRCH -- write character to screen (net write char) 5: SELECT -- printer selection changed 6: no-op (net read char -- not implemented) 7: NBYTE -- remote OSBYTE call 8: NWORD -- remote OSWORD call

On EntryAreason code (0-8)
On ExitApreserved
Xpreserved
Ypreserved
9080 .osword_dispatch
PHP ; Save processor status
9081 PHA ; Save A (reason code)
9082 TXA ; Save X
9083 PHA ; Push X to stack
9084 TYA ; Save Y
9085 PHA ; Push Y to stack
9086 TSX ; Get stack pointer for indexed access
9087 LDA stk_frame_3,x ; Retrieve original A (reason code) from stack
908A CMP #9 ; Reason codes 0-8 only
908C BCS entry1 ; Code >= 9: skip dispatch, restore regs
908E TAX ; X = reason code for table lookup
908F JSR osword_trampoline ; Dispatch to handler via trampoline
9092 .entry1←1← 908C BCS
PLA ; Restore Y
9093 TAY ; Transfer to Y register
9094 PLA ; Restore X
9095 TAX ; Transfer to X register
9096 PLA ; Restore A
9097 PLP ; Restore processor status flags
9098 RTS ; Return with all registers preserved
9099 .osword_trampoline←1← 908F JSR
LDA osword_tbl_hi,x ; PHA/PHA/RTS trampoline: push handler addr-1, RTS jumps to it
909C PHA ; Push high byte of handler address
909D LDA osword_tbl_lo,x ; Load handler low byte from table
90A0 PHA ; Push low byte of handler address
90A1 LDA osbyte_a_copy ; Load workspace byte &EF for handler
90A3 RTS ; RTS dispatches to pushed handler
90A4 .osword_tbl_lo←1← 909D LDA
EQUB <(return_1-1) ; Load workspace byte &EF for handler
90A5 EQUB <(remote_print_handler-1) ; Push low byte of handler address
90A6 EQUB <(remote_print_handler-1) ; RTS dispatches to pushed handler
90A7 EQUB <(remote_print_handler-1) ; lo(return_1-1): fn 0 (null handler)
90A8 EQUB <(net_write_char_handler-1) ; lo(remote_print_handler- 1): fn 1
90A9 EQUB <(printer_select_handler-1) ; lo(remote_print_handler- 1): fn 2
90AA EQUB <(return_1-1) ; lo(remote_print_handler- 1): fn 3
90AB EQUB <(remote_cmd_dispatch-1) ; lo(net_write_char_ handler-1): fn 4
90AC EQUB <(remote_osword_handler-1) ; lo(printer_select_ handler-1): fn 5
90AD .osword_tbl_hi←1← 9099 LDA
EQUB >(return_1-1) ; lo(return_1-1): fn 6 (null handler)
90AE EQUB >(remote_print_handler-1) ; lo(remote_cmd_dispatch- 1): fn 7
90AF EQUB >(remote_print_handler-1) ; lo(remote_osword_ handler-1): fn 8
90B0 EQUB >(remote_print_handler-1) ; hi(return_1-1): fn 0 (null handler)
90B1 EQUB >(net_write_char_handler-1) ; hi(remote_print_handler- 1): fn 1
90B2 EQUB >(printer_select_handler-1) ; hi(remote_print_handler- 1): fn 2
90B3 EQUB >(return_1-1) ; hi(remote_print_handler- 1): fn 3
90B4 EQUB >(remote_cmd_dispatch-1) ; hi(net_write_char_ handler-1): fn 4
90B5 EQUB >(remote_osword_handler-1) ; hi(printer_select_ handler-1): fn 5

NETVEC fn 4: handle net write character (NWRCH)

Zeros the carry flag in the stacked processor status to signal success, stores the character from Y into workspace offset &DA, loads A=0 as the command type, and falls through to setup_tx_and_send.

On EntryYcharacter to write
90B6 .net_write_char_handler
TSX ; Get stack pointer for P register access
90B7 ROR stk_frame_p,x ; ROR/ASL on stacked P: zeros carry to signal success
90BA ASL stk_frame_p,x ; ASL: restore P after ROR zeroed carry
90BD TYA ; Y = character to write
90BE LDY #&da ; Store character at workspace offset &DA
90C0 STA (nfs_workspace),y ; Store char at workspace offset &DA
90C2 LDA #0 ; A=0: command type for net write char
fall through ↓

Set up TX control block and send

Stores A at workspace offset &D9 (command type), then sets byte &0C to &80 (TX active flag). Saves the current net_tx_ptr, temporarily redirects it to (nfs_workspace)+&0C so tx_poll_ff transmits from the workspace TX control block. After transmission completes, writes &3F (TX deleted) at (net_tx_ptr)+&00 to mark the control block as free, then restores net_tx_ptr to its original value.

On EntryAcommand type byte
90C4 .setup_tx_and_send←3← 81CF JSR← 9117 JSR← 917A JSR
LDY #&d9 ; Y=&D9: command type offset
90C6 STA (nfs_workspace),y ; Store command type at ws+&D9
90C8 LDA #&80 ; Mark TX control block as active (&80)
90CA LDY #&0c ; Y=&0C: TXCB start offset
90CC STA (nfs_workspace),y ; Set TX active flag at ws+&0C
90CE LDA net_tx_ptr ; Save net_tx_ptr; redirect to workspace TXCB
90D0 PHA ; Save net_tx_ptr low
90D1 LDA net_tx_ptr_hi ; Load net_tx_ptr high
90D3 PHA ; Save net_tx_ptr high
90D4 STY net_tx_ptr ; Redirect net_tx_ptr low to workspace
90D6 LDX nfs_workspace_hi ; Load workspace page high byte
90D8 STX net_tx_ptr_hi ; Complete ptr redirect
90DA JSR tx_poll_ff ; Transmit with full retry
90DD LDA #&3f ; Mark TXCB as deleted (&3F) after transmit
90DF STA (net_tx_ptr,x) ; Write &3F to TXCB byte 0
90E1 PLA ; Restore net_tx_ptr high
90E2 STA net_tx_ptr_hi ; Write back
90E4 PLA ; Restore net_tx_ptr low
90E5 STA net_tx_ptr ; Write back
90E7 RTS ; Return

Fn 7: remote OSBYTE handler (NBYTE)

Full RPC mechanism for OSBYTE calls across the network. When a machine is remoted, OSBYTE/OSWORD calls that affect terminal-side hardware (keyboard scanning, flash rates, etc.) must be indirected across the net. OSBYTE calls are classified into three categories: Y>0 (NCTBPL table): executed on BOTH machines (flash rates etc.) Y<0 (NCTBMI table): executed on terminal only, result sent back Y=0: not recognised, passed through unhandled Results returned via stack manipulation: the saved processor status byte at &0106 has V-flag (bit 6) forced on to tell the MOS the call was claimed (preventing dispatch to other ROMs), and the I-bit (bit 2) forced on to disable interrupts during register restoration, preventing race conditions. The carry flag in the saved P is also manipulated via ROR/ASL to zero it, signaling success to the caller. OSBYTE &81 (INKEY) gets special handling as it must read the terminal's keyboard.

90E8 .remote_cmd_dispatch
LDY osword_pb_ptr_hi ; Load original Y (OSBYTE secondary param)
90EA CMP #&81 ; OSBYTE &81 (INKEY): always forward to terminal
90EC BEQ dispatch_remote_osbyte ; Forward &81 to terminal for keyboard read
90EE LDY #1 ; Y=1: search NCTBPL table (execute on both)
90F0 LDX #9 ; X=9: 10-entry NCTBPL table size
90F2 JSR match_osbyte_code ; Search for OSBYTE code in NCTBPL table
90F5 BEQ dispatch_remote_osbyte ; Match found: dispatch with Y=1 (both)
90F7 DEY ; Y=-1: search NCTBMI table (terminal only)
90F8 DEY ; Second DEY: Y=&FF (from 1 via 0)
90F9 LDX #&0e ; X=&0E: 15-entry NCTBMI table size
90FB JSR match_osbyte_code ; Search for OSBYTE code in NCTBMI table
90FE BEQ dispatch_remote_osbyte ; Match found: dispatch with Y=&FF (terminal)
9100 INY ; Y=0: OSBYTE not recognised, ignore
9101 .dispatch_remote_osbyte←3← 90EC BEQ← 90F5 BEQ← 90FE BEQ
LDX #2 ; X=2 bytes to copy (default for RBYTE)
9103 TYA ; A=Y: check table match result
9104 BEQ return_nbyte ; Y=0: not recognised, return unhandled
9106 PHP ; Y>0 (NCTBPL): send only, no result expected
9107 BPL nbyte6 ; Y>0 (NCTBPL): no result expected, skip RX
9109 INX ; Y<0 (NCTBMI): X=3 bytes (result + P flags) X=&03
910A .nbyte6←1← 9107 BPL
LDY #&dc ; Y=&DC: top of 3-byte stack frame region
910C .nbyte1←1← 9114 BPL
LDA tube_claimed_id,y ; Copy OSBYTE args from stack frame to workspace
910F STA (nfs_workspace),y ; Store to NFS workspace for transmission
9111 DEY ; Next byte (descending)
9112 CPY #&da ; Copied all 3 bytes? (&DC, &DB, &DA)
9114 BPL nbyte1 ; Loop for remaining bytes
9116 TXA ; A = byte count for setup_tx_and_send
9117 JSR setup_tx_and_send ; Build TXCB and transmit to terminal
911A PLP ; Restore N flag from table match type
911B BPL return_nbyte ; Y was positive (NCTBPL): done, no result
911D LDA #&7f ; Set up RX control block to wait for reply
911F LDY #&0c ; Y=&0C: RX control block offset in workspace
9121 STA (nfs_workspace),y ; Write &7F (waiting) to RXCB flag byte
9123 .poll_rxcb_flag←1← 9125 BPL
LDA (nfs_workspace),y ; Poll for TX completion (wait for bit 7 set)
9125 BPL poll_rxcb_flag ; Bit7 clear: still waiting, poll again
9127 TSX ; X = stack pointer for register restoration
9128 LDY #&dd ; Y=&DD: saved P byte offset in workspace
912A LDA (nfs_workspace),y ; Load remote processor status from reply
912C ORA #&44 ; Force V=1 (claimed) and I=1 (no IRQ) in saved P
912E BNE nbyte5 ; ALWAYS branch (ORA #&44 never zero) ALWAYS branch
9130 .nbyte4←1← 9139 BNE
DEY ; Previous workspace offset
9131 DEX ; Previous stack register slot
9132 LDA (nfs_workspace),y ; Load next result byte (X, then Y)
9134 .nbyte5←1← 912E BNE
STA stk_frame_p,x ; Write result bytes to stacked registers
9137 CPY #&da ; Copied all result bytes? (P at &DA)
9139 BNE nbyte4 ; Loop for remaining result bytes
913B .return_nbyte←2← 9104 BEQ← 911B BPL
RTS ; Return to OSBYTE dispatcher

Search remote OSBYTE table for match (NCALLP)

Searches remote_osbyte_table for OSBYTE code A. X indexes the last entry to check (table is scanned X..0). Returns Z=1 if found. Called twice by remote_cmd_dispatch:

  X=9  → first 10 entries (NCTBPL: execute on both machines)
  X=14 → all 15 entries (NCTBMI: execute on terminal only)

The last 5 entries (&0B, &0C, &0F, &79, &7A) are terminal-only because they affect the local keyboard or buffers.

On entry: A = OSBYTE code, X = table size - 1 On exit: Z=1 if match found, Z=0 if not

913C .match_osbyte_code←3← 90F2 JSR← 90FB JSR← 9142 BPL
CMP remote_osbyte_table,x ; Compare OSBYTE code with table entry
913F BEQ return_match_osbyte ; Match found: return with Z=1
9141 DEX ; Next table entry (descending)
9142 BPL match_osbyte_code ; Loop for remaining entries
9144 .return_match_osbyte←2← 913F BEQ← 915C BNE
RTS ; Return; Z=1 if match, Z=0 if not
9145 .remote_osbyte_table←1← 913C CMP
EQUB &04 ; OSBYTE &04: cursor key status
9146 EQUB &09 ; OSBYTE &09: flash duration (1st colour)
9147 EQUB &0A ; OSBYTE &0A: flash duration (2nd colour)
9148 EQUB &15 ; OSBYTE &15: flush specific buffer
9149 EQUB &9A ; OSBYTE &9A: video ULA control register
914A EQUB &9B ; OSBYTE &9B: video ULA palette
914B EQUB &E1 ; OSBYTE &E1: function key &C0-&CF
914C EQUB &E2 ; OSBYTE &E2: function key &D0-&DF
914D EQUB &E3 ; OSBYTE &E3: function key &E0-&EF
914E EQUB &E4 ; OSBYTE &E4: function key &F0-&FF
914F EQUB &0B ; OSBYTE &0B: auto-repeat delay
9150 EQUB &0C ; OSBYTE &0C: auto-repeat rate
9151 EQUB &0F ; OSBYTE &0F: flush buffer class
9152 EQUB &79 ; OSBYTE &79: keyboard scan from X
9153 EQUB &7A ; OSBYTE &7A: keyboard scan from 16

NETVEC fn 8: remote OSWORD dispatch (NWORD)

Only accepts OSWORD 7 (make a sound) and OSWORD 8 (define an envelope), rejecting all others. Sets Y=14 as the maximum parameter byte count, then falls through to remote_cmd_data.

9154 .remote_osword_handler
LDY #&0e ; Y=&0E: max 14 parameter bytes for OSWORD
9156 CMP #7 ; OSWORD 7 = make a sound
9158 BEQ copy_params_rword ; OSWORD 7 (sound): handle via common path
915A CMP #8 ; OSWORD 8 = define an envelope
fall through ↓

Fn 8: remote OSWORD handler (NWORD)

Only intercepts OSWORD 7 (make a sound) and OSWORD 8 (define an envelope). Unlike NBYTE which returns results, NWORD is entirely fire-and-forget -- no return path is implemented. The developer explicitly noted this was acceptable since sound/envelope commands don't return meaningful results. Copies up to 14 parameter bytes from the RX buffer to workspace, tags the message as RWORD, and transmits.

915C .remote_cmd_data
BNE return_match_osbyte ; Not OSWORD 7 or 8: ignore (BNE exits)
915E .copy_params_rword←1← 9158 BEQ
LDX #&db ; Point workspace to offset &DB for params
9160 STX nfs_workspace ; Store workspace ptr offset &DB
9162 .copy_osword_params←1← 9167 BPL
LDA (osword_pb_ptr),y ; Load param byte from OSWORD param block
9164 STA (nfs_workspace),y ; Write param byte to workspace
9166 DEY ; Next byte (descending)
9167 BPL copy_osword_params ; Loop for all parameter bytes
9169 INY ; Y=0 after loop
916A DEC nfs_workspace ; Point workspace to offset &DA
916C LDA osbyte_a_copy ; Load original OSWORD code
916E STA (nfs_workspace),y ; Store OSWORD code at ws+0
9170 STY nfs_workspace ; Reset workspace ptr to base
9172 LDY #&14 ; Y=&14: command type offset
9174 LDA #&e9 ; Tag as RWORD (port &E9)
9176 STA (nfs_workspace),y ; Store port tag at ws+&14
9178 LDA #1 ; A=1: single-byte TX
917A JSR setup_tx_and_send ; Load template byte from ctrl_block_template[X]
917D STX nfs_workspace ; Restore workspace ptr
fall through ↓

Alternate entry into control block setup

Sets X=&0D, Y=&7C. Tests bit 6 of &83B3 to choose target: V=0 (bit 6 clear): stores to (nfs_workspace) V=1 (bit 6 set): stores to (net_rx_ptr)

917F .ctrl_block_setup_alt←2← 9062 JSR← 9071 JSR
LDX #&0d ; X=&0D: template offset for alt entry
9181 LDY #&7c ; Y=&7C: target workspace offset for alt entry
9183 BIT tx_ctrl_upper ; BIT test: V flag = bit 6 of &83B3
9186 BVS cbset2 ; V=1: store to (net_rx_ptr) instead
fall through ↓

Control block setup — main entry

Sets X=&1A, Y=&17, clears V (stores to nfs_workspace). Reads the template table at &91B4 indexed by X, storing each value into the target workspace at offset Y. Both X and Y are decremented on each iteration.

Template sentinel values: &FE = stop (end of template for this entry path) &FD = skip (leave existing value unchanged) &FC = use page high byte of target pointer

9188 .ctrl_block_setup←1← 84D1 JSR
LDY #&17 ; Y=&17: workspace target offset (main entry)
918A LDX #&1a ; X=&1A: template table index (main entry)
918C .ctrl_block_setup_clv←1← 924B JSR
CLV ; V=0: target is (nfs_workspace)
918D .cbset2←2← 9186 BVS← 91AE BPL
LDA ctrl_block_template,x ; Load template byte from ctrl_block_template[X]
9190 CMP #&fe ; &FE = stop sentinel
9192 BEQ cb_template_tail ; End of template: jump to exit
9194 CMP #&fd ; &FD = skip sentinel
9196 BEQ cb_template_main_start ; Skip: don't store, just decrement Y
9198 CMP #&fc ; &FC = page byte sentinel
919A BNE cbset3 ; Not sentinel: store template value directly
919C LDA net_rx_ptr_hi ; V=1: use (net_rx_ptr) page
919E BVS rxcb_matched ; V=1: skip to net_rx_ptr page
91A0 LDA nfs_workspace_hi ; V=0: use (nfs_workspace) page
91A2 .rxcb_matched←1← 919E BVS
STA net_tx_ptr_hi ; PAGE byte → Y=&02 / Y=&74
91A4 .cbset3←1← 919A BNE
BVS cbset4 ; → Y=&04 / Y=&76
91A6 STA (nfs_workspace),y ; PAGE byte → Y=&06 / Y=&78
91A8 BVC cb_template_main_start ; → Y=&08 / Y=&7A ALWAYS branch
91AA .cbset4←1← 91A4 BVS
STA (net_rx_ptr),y ; Alt-path only → Y=&70
91AC .cb_template_main_start←2← 9196 BEQ← 91A8 BVC
DEY ; → Y=&0C (main only)
91AD DEX ; → Y=&0D (main only)
91AE BPL cbset2 ; Loop until all template bytes done
91B0 .cb_template_tail←1← 9192 BEQ
INY ; → Y=&10 (main only)
91B1 STY net_tx_ptr ; Store final offset as net_tx_ptr
91B3 RTS ; → Y=&07 / Y=&79

Control block initialisation template

Read by the loop at &918D, indexed by X from a starting value down to 0. Values are stored into either (nfs_workspace) or (net_rx_ptr) at offset Y, depending on the V flag.

Two entry paths read different slices of this table: ctrl_block_setup: X=&1A (26) down, Y=&17 (23) down, V=0 ctrl_block_setup_alt: X=&0D (13) down, Y=&7C (124) down, V from BIT &83B3

Sentinel values: &FE = stop processing &FD = skip this offset (decrement Y but don't store) &FC = substitute the page byte (net_rx_ptr_hi or nfs_workspace_hi)

91B4 .ctrl_block_template←1← 918D LDA
STA zp_ptr_lo ; Alt-path only → Y=&6F
91B6 SBC l7dfd,x
91B9 EQUB &FC, &FF, &FF, &7E, ; → Y=&0D (main only) → Y=&03 / &FC, &FF, &FF, &00, ; Y=&75 SKIP (main only) → Y=&10 &00, &FE, &80, &93, ; (main only) → Y=&08 / Y=&7A → &FD, &FD, &D9, &FC, ; Y=&09 / Y=&7B PAGE byte → Y=&15 &FF, &FF, &DE, &FC, ; (main only) → Y=&16 (main only) &FF, &FF, &FE, &D1, ; SKIP (main only) PAGE byte → &FD, &FD, &1F, &FD, ; Y=&11 (main only) → Y=&12 (main &FF, &FF, &FD, &FD, ; only) → Y=&13 (main only) → &FF, &FF ; Y=&14 (main only) → Y=&17 (main ; only)
fall through ↓

Fn 5: printer selection changed (SELECT)

Called when the printer selection changes. Compares X against the network printer buffer number (&F0). If it matches, initialises the printer buffer pointer (&0D61 = &1F) and sets the initial flag byte (&0D60 = &41). Otherwise falls through to return.

On EntryX1-based buffer number
91DB .printer_select_handler
DEX ; X-1: convert 1-based buffer to 0-based
91DC CPX osword_pb_ptr ; Is this the network printer buffer?
91DE BNE setup1 ; No: skip printer init
91E0 LDA #&1f ; &1F = initial buffer pointer offset
91E2 STA printer_buf_ptr ; Reset printer buffer write position
91E5 LDA #&41 ; &41 = initial PFLAGS (bit 6 set, bit 0 set)
91E7 .setup1←1← 91DE BNE
STA prot_flags ; Store A to printer status byte
91E9 .return_printer_select←2← 91EC BNE← 9200 BCS
RTS ; Return

Fn 1/2/3: network printer handler (PRINT)

Handles network printer output. Reason 1 = chars in buffer (extract from MOS buffer 3 and accumulate), reason 2 = Ctrl-B (start print), reason 3 = Ctrl-C (end print). The printer status byte PFLAGS uses: bit 7 = sequence number (toggles per packet for dup detection) bit 6 = always 1 (validity marker) bit 0 = 0 when print active Print streams reuse the BSXMIT (byte-stream transmit) code with handle=0, which causes the AND SEQNOS to produce zero and sidestep per-file sequence tracking. After transmission, TXCB pointer bytes are filled with &FF to prevent stale values corrupting subsequent BGET/BPUT operations (a historically significant bug fix). N.B. The printer and REMOTE facility share the same dynamically allocated static workspace page via WORKP1 (&9E,&9F) — care must be taken to never leave the pointer corrupted, as corruption would cause one subsystem to overwrite the other's data. Only handles buffer 4 (network printer); others are ignored.

On EntryXreason code (1=chars, 2=Ctrl-B, 3=Ctrl-C)
Ybuffer number (must be 4 for network printer)
91EA .remote_print_handler
CPY #4 ; Only handle buffer 4 (network printer)
91EC BNE return_printer_select ; Not buffer 4: ignore
91EE TXA ; A = reason code
91EF DEX ; Reason 1? (DEX: 1->0)
91F0 BNE toggle_print_flag ; Not reason 1: handle Ctrl-B/C
91F2 TSX ; Get stack pointer for P register
91F3 ORA stk_frame_p,x ; Force I flag in stacked P to block IRQs
91F6 STA stk_frame_p,x ; Write back modified P register
91F9 .prlp1←2← 9208 BCC← 920D BCC
LDA #osbyte_read_buffer ; OSBYTE &91: extract char from MOS buffer
91FB LDX #buffer_printer ; X=3: printer buffer number
91FD JSR osbyte ; Get character from input buffer (C is set if the buffer is empty, otherwise Y=extracted character)
9200 BCS return_printer_select ; Buffer empty: return
9202 TYA ; Y = extracted character Y is the character extracted from the buffer
9203 JSR store_output_byte ; Store char in output buffer
9206 CPY #&6e ; Buffer nearly full? (&6E = threshold)
9208 BCC prlp1 ; Not full: get next char
920A JSR flush_output_block ; Buffer full: flush to network
920D BCC prlp1 ; Continue after flush
fall through ↓

Store output byte to network buffer

Stores byte A at the current output offset in the RX buffer pointed to by (net_rx_ptr). Advances the offset counter and triggers a flush if the buffer is full.

On EntryAbyte to store
On ExitYbuffer offset before store
920F .store_output_byte←2← 9203 JSR← 921C JSR
LDY printer_buf_ptr ; Load current buffer offset
9212 STA (net_rx_ptr),y ; Store byte at current position
9214 INC printer_buf_ptr ; Advance buffer pointer
9217 RTS ; Return; Y = buffer offset
9218 .toggle_print_flag←1← 91F0 BNE
PHA ; Save reason code
9219 TXA ; A = reason code
921A EOR #1 ; EOR #1: toggle print-active flag (bit 0)
921C JSR store_output_byte ; Store toggled flag as output byte
921F EOR prot_flags ; XOR with PFLAGS
9221 ROR ; Test if sequence changed (bit 7 mismatch)
9222 BCC skip_flush ; Sequence unchanged: skip flush
9224 ROL ; Undo ROR
9225 STA prot_flags ; Update PFLAGS
9227 JSR flush_output_block ; Flush current output block
922A .skip_flush←1← 9222 BCC
LDA prot_flags ; Load PFLAGS
922C AND #&f0 ; Extract upper nibble of PFLAGS
922E ROR ; Shift for bit extraction
922F TAX ; Save in X
9230 PLA ; Restore original reason code
9231 ROR ; Merge print-active bit from original A
9232 TXA ; Retrieve shifted PFLAGS
9233 ROL ; Recombine into new PFLAGS value
9234 STA prot_flags ; Update PFLAGS
9236 RTS ; Return

Flush output block

Sends the accumulated output block over the network, resets the buffer pointer, and prepares for the next block of output data.

9237 .flush_output_block←2← 920A JSR← 9227 JSR
LDY #8 ; Store buffer length at workspace offset &08
9239 LDA printer_buf_ptr ; Current buffer fill position
923C STA (nfs_workspace),y ; Write to workspace offset &08
923E LDA net_rx_ptr_hi ; Store page high byte at offset &09
9240 INY ; Y=&09
9241 STA (nfs_workspace),y ; Write page high byte at offset &09
9243 LDY #5 ; Also store at offset &05
9245 STA (nfs_workspace),y ; (end address high byte)
9247 LDY #&0b ; Y=&0B: flag byte offset
9249 LDX #&26 ; X=&26: start from template entry &26
924B JSR ctrl_block_setup_clv ; Reuse ctrl_block_setup with CLV entry
924E DEY ; Y=&0A: sequence flag byte offset
924F LDA prot_flags ; Load protocol flags (PFLAGS)
9251 PHA ; Save current PFLAGS
9252 ROL ; Carry = current sequence (bit 7)
9253 PLA ; Restore original PFLAGS
9254 EOR #&80 ; Toggle sequence number (bit 7 of PFLAGS)
9256 STA prot_flags ; Save toggled PFLAGS
9258 ROL ; Old sequence bit into bit 0
9259 STA (nfs_workspace),y ; Store sequence flag at offset &0A
925B LDY #&1f ; Y=&1F: buffer start offset
925D STY printer_buf_ptr ; Reset printer buffer to start (&1F)
9260 LDA #0 ; A=0: printer output flag
9262 TAX ; X=0: workspace low byte X=&00
9263 LDY nfs_workspace_hi ; Y = workspace page high byte
9265 CLI ; Enable interrupts before TX
fall through ↓

Byte-stream transmit (BSXMIT/BSPSX)

Transmits a data packet over econet with sequence number tracking. Sets up the TX control block pointer from X/Y, computes the sequence bit from A AND fs_sequence_nos (handle-based tracking), merges it into the flag byte at (net_tx_ptr)+0, then initiates transmit via tx_poll_ff. Sets end addresses (offsets 8/9) to &FF to allow unlimited data. Selects port byte &D1 (print) or &90 (FS) based on the original A value. Polls the TX result in a loop via BRIANX (c8530), retrying while the result bit differs from the expected sequence. On success, toggles the sequence tracking bit in fs_sequence_nos.

On EntryAhandle bitmask (0=printer, non-zero=file)
XTX control block address low
YTX control block address high
9266 .econet_tx_retry←2← 840C JSR← 8448 JSR
STX net_tx_ptr ; Set TX control block ptr low byte
9268 STY net_tx_ptr_hi ; Set TX control block ptr high byte
926A PHA ; Save A (handle bitmask) for later
926B AND fs_sequence_nos ; Compute sequence bit from handle
926E BEQ bsxl1 ; Zero: no sequence bit set
9270 LDA #1 ; Non-zero: normalise to bit 0
9272 .bsxl1←1← 926E BEQ
LDY #0 ; Y=0: flag byte offset in TXCB
9274 ORA (net_tx_ptr),y ; Merge sequence into existing flag byte
9276 PHA ; Save merged flag byte
9277 STA (net_tx_ptr),y ; Write flag+sequence to TXCB byte 0
9279 JSR tx_poll_ff ; Transmit with full retry
927C LDA #&ff ; End address &FFFF = unlimited data length
927E LDY #8 ; Y=8: end address low offset in TXCB
9280 STA (net_tx_ptr),y ; Store &FF to end addr low
9282 INY ; Y=&09
9283 STA (net_tx_ptr),y ; Store &FF to end addr high (Y=9)
9285 PLA ; Recover merged flag byte
9286 TAX ; Save in X for sequence compare
9287 LDY #&d1 ; Y=&D1: printer port number
9289 PLA ; Recover saved handle bitmask
928A PHA ; Re-save for later consumption
928B BEQ bspsx ; A=0: port &D1 (print); A!=0: port &90 (FS)
928D LDY #&90 ; Y=&90: FS data port
928F .bspsx←1← 928B BEQ
TYA ; A = selected port number
9290 LDY #1 ; Y=1: port byte offset in TXCB
9292 STA (net_tx_ptr),y ; Write port to TXCB byte 1
9294 TXA ; A = saved flag byte (expected sequence)
9295 DEY ; Y=&00
9296 PHA ; Push expected sequence for retry loop
9297 .bsxl0←1← 92A3 BCS
LDA #&7f ; Flag byte &7F = waiting for reply
9299 STA (net_tx_ptr),y ; Write to TXCB flag byte (Y=0)
929B JSR waitfs ; Transmit and wait for reply (BRIANX)
929E PLA ; Recover expected sequence
929F PHA ; Keep on stack for next iteration
92A0 EOR (net_tx_ptr),y ; Check if TX result matches expected sequence
92A2 ROR ; Bit 0 to carry (sequence mismatch?)
92A3 BCS bsxl0 ; C=1: mismatch, retry transmit
92A5 PLA ; Clean up: discard expected sequence
92A6 PLA ; Discard saved handle bitmask
92A7 EOR fs_sequence_nos ; Toggle sequence bit on success
92AA .return_bspsx
RTS ; Return

Save palette and VDU state (CVIEW)

Part of the VIEW facility (second iteration, started 27/7/82). Uses dynamically allocated buffer store. The WORKP1 pointer (&9E,&9F) serves double duty: non-zero indicates data ready AND provides the buffer address — an efficient use of scarce zero- page space. This code must be user-transparent as the NFS may not be the dominant filing system. Reads all 16 palette entries using OSWORD &0B (read palette) and stores the results. Then reads cursor position (OSBYTE &85), shadow RAM allocation (OSBYTE &C2), and screen start address (OSBYTE &C3) using the 3-entry table at &931E. On completion, restores the JSR buffer protection bits (LSTAT) from OLDJSR to re-enable JSR reception, which was disabled during the screen data capture to prevent interference.

92AB .lang_2_save_palette_vdu
LDA table_idx ; Save current table index
92AD PHA ; Push for later restore
92AE LDA #&e9 ; Point workspace to palette save area (&E9)
92B0 STA nfs_workspace ; Set workspace low byte
92B2 LDY #0 ; Y=0: first palette entry
92B4 STY table_idx ; Clear table index counter
92B6 LDA vdu_screen_mode ; Save current screen MODE to workspace
92B9 STA (nfs_workspace),y ; Store MODE at workspace[0]
92BB INC nfs_workspace ; Advance workspace pointer past MODE byte
92BD LDA vdu_colours ; Read colour count (from &0351)
92C0 PHA ; Push for iteration count tracking
92C1 TYA ; A=0: logical colour number for OSWORD A=&00
92C2 .save_palette_entry←1← 92E1 BNE
STA (nfs_workspace),y ; Store logical colour at workspace[0]
92C4 LDX nfs_workspace ; X = workspace ptr low (param block addr)
92C6 LDY nfs_workspace_hi ; Y = workspace ptr high
92C8 LDA #osword_read_palette ; OSWORD &0B: read palette for logical colour
92CA JSR osword ; Read palette
92CD PLA ; Recover colour count
92CE LDY #0 ; Y=0: access workspace[0]
92D0 STA (nfs_workspace),y ; Write colour count back to workspace[0]
92D2 INY ; Y=1: access workspace[1] (palette result) Y=&01
92D3 LDA (nfs_workspace),y ; Read palette value returned by OSWORD
92D5 PHA ; Push palette value for next iteration
92D6 LDX nfs_workspace ; X = current workspace ptr low
92D8 INC nfs_workspace ; Advance workspace pointer
92DA INC table_idx ; Increment table index
92DC DEY ; Y=0 for next store Y=&00
92DD LDA table_idx ; Load table index as logical colour
92DF CPX #&f9 ; Loop until workspace wraps past &F9
92E1 BNE save_palette_entry ; Continue for all 16 palette entries
92E3 PLA ; Discard last palette value from stack
92E4 STY table_idx ; Reset table index to 0
92E6 INC nfs_workspace ; Advance workspace past palette data
92E8 JSR save_vdu_state ; Save cursor pos and OSBYTE state values
92EB INC nfs_workspace ; Advance workspace past VDU state data
92ED PLA ; Recover saved table index
92EE STA table_idx ; Restore table index
92F0 .clear_jsr_protection←4← 84B5 JMP← 84DD JSR← 8504 JSR← 8EEE JMP
LDA saved_jsr_mask ; Restore LSTAT from saved OLDJSR value
92F3 STA prot_status ; Write to protection status
92F6 RTS ; Return

Save VDU workspace state

Stores the cursor position value from &0355 into NFS workspace, then reads cursor position (OSBYTE &85), shadow RAM (OSBYTE &C2), and screen start (OSBYTE &C3) via read_vdu_osbyte, storing each result into consecutive workspace bytes. The JSR to read_vdu_osbyte_x0 is a self-calling trick: it executes read_vdu_osbyte twice (once for &C2, once for &C3) because the RTS returns to the instruction at read_vdu_osbyte_x0 itself.

ROMExec
92F7 .save_vdu_state←1← 92E8 JSR
LDA vdu_cursor_edit ; Read cursor editing state
92FA STA (nfs_workspace),y ; Store to workspace[Y]
92FC TAX ; Preserve in X for OSBYTE
92FD JSR read_vdu_osbyte ; OSBYTE &85: read cursor position
9300 INC nfs_workspace ; Advance workspace pointer
9302 TYA ; Y result from OSBYTE &85
9303 STA (nfs_workspace,x) ; Store Y pos to workspace (X=0)
9305 JSR read_vdu_osbyte_x0 ; Self-call trick: executes twice
9308 .read_vdu_osbyte_x0←1← 9305 JSR
LDX #0 ; X=0 for (zp,X) addressing
930A .read_vdu_osbyte←1← 92FD JSR
LDY table_idx ; Index into OSBYTE number table
930C INC table_idx ; Next table entry next time
930E INC nfs_workspace ; Advance workspace pointer
9310 LDA vdu_osbyte_table,y ; Read OSBYTE number from table
9313 LDY #&ff ; Y=&FF: read current value
9315 JSR osbyte ; Call OSBYTE
9318 TXA ; Result in X to A
9319 LDX #0 ; X=0 for indexed indirect store
931B STA (nfs_workspace,x) ; Store result to workspace
931D RTS ; Return after storing result
931E .vdu_osbyte_table←1← 9310 LDA
EQUB &85, ; OSBYTE &85: read cursor position OSBYTE &C2, ; &C3: read screen start address &C3
; Tube BRK handler (BRKV target) — reference: NFS11 NEWBR
; Sends error information to the Tube co-processor via R2 ; and R4:
; 1. Sends &FF to R4 (WRIFOR) to signal error
; 2. Reads R2 data (flush any pending byte)
; 3. Sends &00 via R2, then error number from (&FD),0
; 4. Loops sending error string bytes via R2 until zero terminator
; 5. Falls through to tube_reset_stack → tube_main_loop
; The main loop continuously polls R1 for WRCH requests ; (forwarded
; to OSWRITCH &FFCB) and R2 for command bytes (dispatched ; via the
; 12-entry table at &0500). The R2 command byte is stored ; at &51
; (self-modifying the JMP indirect low byte) before ; dispatch.
9321 0016 .nmi_workspace_start←1← 817C STA
.tube_brk_handler←1← 817C STA
LDA #&ff ; A=&FF: signal error to co-processor via R4
9323 0018 JSR tube_send_r4 ; Send &FF error signal to Tube R4
9326 001B LDA tube_data_register_2 ; Flush any pending R2 byte
9329 001E LDA #0 ; A=0: send zero prefix to R2
932B 0020 .tube_send_zero_r2
JSR tube_send_r2 ; Send zero prefix byte via R2
932E 0023 TAY ; Y=0: start of error block at (&FD)
932F 0024 LDA (brk_ptr),y ; Load error number from (&FD),0
9331 0026 JSR tube_send_r2 ; Send error number via R2
9334 0029 .tube_brk_send_loop←1← 0030 BNE
INY ; Advance to next error string byte
9335 002A .tube_send_error_byte
LDA (brk_ptr),y ; Load next error string byte
9337 002C JSR tube_send_r2 ; Send error string byte via R2
933A 002F TAX ; Zero byte = end of error string
933B 0030 BNE tube_brk_send_loop ; Loop until zero terminator sent
933D 0032 .tube_reset_stack←1← 0477 JMP
LDX #&ff ; Reset stack pointer to top
933F 0034 TXS ; TXS: set stack pointer from X
9340 0035 CLI ; Enable interrupts for main loop
9341 0036 .tube_main_loop←6← 0044 BPL← 057F JMP← 05A6 JMP← 0604 JMP← 0665 JMP← 0692 JMP
BIT tube_status_1_and_tube_control ; BIT R1 status: check WRCH request
9344 0039 BPL tube_poll_r2 ; R1 not ready: check R2 instead
9346 003B .tube_handle_wrch←1← 0049 BMI
LDA tube_data_register_1 ; Read character from Tube R1 data
9349 003E JSR oswrch ; Write character
934C 0041 .tube_poll_r2←1← 0039 BPL
BIT tube_status_register_2 ; BIT R2 status: check command byte
934F 0044 BPL tube_main_loop ; R2 not ready: loop back to R1 check
9351 0046 BIT tube_status_1_and_tube_control ; Re-check R1: WRCH has priority over R2
9354 0049 BMI tube_handle_wrch ; R1 ready: handle WRCH first
9356 004B LDX tube_data_register_2 ; Read command byte from Tube R2 data
9359 004E STX tube_cmd_lo ; Self-modify JMP low byte for dispatch
935B 0050 .tube_dispatch_cmd
JMP (tube_dispatch_table) ; Dispatch to handler via indirect JMP
935E 0053 .tube_transfer_addr←2← 04DE STY← 04EE STA
EQUB &00 ; Tube transfer address low byte
935F 0054 .tube_xfer_page←3← 04B6 INC← 04D4 STA← 04F3 STA
EQUB &80 ; Tube transfer page (default &80)
9360 0055 .tube_xfer_addr_2←2← 04BA INC← 04FD STY
EQUB &00 ; Tube transfer address byte 2
9361 0056 .tube_xfer_addr_3←2← 04BE INC← 04FB STA
EQUB &00 ; Tube transfer address byte 3
; Tube host code page 4 — reference: NFS12 (BEGIN, ADRR, ; SENDW)
; Copied from ROM at reloc_p4_src during init. The first ; 28 bytes (&0400-&041B)
; overlap with the end of the ZP block (the same ROM bytes ; serve both
; the ZP copy at &005B-&0076 and this page at ; &0400-&041B). Contains:
; &0400: JMP &0484 (BEGIN — startup/CLI entry, break type check)
; &0403: JMP &06E2 (tube_escape_check)
; &0406: tube_addr_claim — Tube address claim (ADRR protocol)
; &0414: tube_release_claim — release address claim via R4 cmd 5
; &0421: tube_post_init — reset claimed-address state to &80
; &0435: data transfer setup (SENDW protocol) — &0435-&0483
; &0484: BEGIN — startup entry, sends ROM contents to Tube
; &04CB: tube_claim_default — claim default transfer address
; &04D2: tube_init_reloc — extract relocation address from ROM
9362 0400 .tube_code_page4←1← 8162 STA
JMP tube_begin ; JMP to BEGIN startup entry
9365 0403 .tube_escape_entry
JMP tube_escape_check ; JMP to tube_escape_check (&06A7)
9368 0406 .tube_addr_claim←10← 049A JSR← 04CF JMP← 8BA1 JSR← 8BB8 JSR← 8C15 JSR← 8E26 JMP← 997B JSR← 9A31 JSR← 9F07 JSR← 9F0F JSR
CMP #&80 ; A>=&80: address claim; A<&80: data transfer
936A 0408 BCC tube_transfer_setup ; A<&80: data transfer setup (SENDW)
936C 040A CMP #&c0 ; A>=&C0: new address claim from another host
936E 040C BCS addr_claim_external ; C=1: external claim, check ownership
9370 040E ORA #&40 ; Map &80-&BF range to &C0-&FF for comparison
9372 0410 CMP tube_claimed_id ; Is this for our currently-claimed address?
9374 0412 BNE return_tube_init ; Not our address: return
; Release Tube address claim via R4 command 5
; Saves interrupt state (PHP/SEI), sends R4 command 5 ; (release)
; followed by the currently-claimed address, then restores
; interrupts. Falls through to tube_post_init to reset the
; claimed-address state to &80.
9376 0414 .tube_release_claim←1← 0471 JSR
PHP ; PHP: save interrupt state for release
9377 0415 SEI ; SEI: disable interrupts during R4 protocol
9378 0416 LDA #5 ; R4 cmd 5: release our address claim
937A 0418 JSR tube_send_r4 ; Send release command to co-processor
937D 041B LDA tube_claimed_id ; Load our currently-claimed address
937F 041D JSR tube_send_r4 ; Send our address as release parameter
9382 0420 PLP ; Restore interrupt state
9383 0421 .tube_post_init←1← 8174 JSR
LDA #&80 ; &80 sentinel: clear address claim
9385 0423 STA tube_claimed_id ; &80 sentinel = no address currently claimed
9387 0425 STA tube_claim_flag ; Store to claim-in-progress flag
9389 0427 RTS ; Return from tube_post_init
938A 0428 .addr_claim_external←1← 040C BCS
ASL tube_claim_flag ; Another host claiming; check if we're owner
938C 042A BCS accept_new_claim ; C=1: we have an active claim
938E 042C CMP tube_claimed_id ; Compare with our claimed address
9390 042E BEQ return_tube_init ; Match: return (we already have it)
9392 0430 CLC ; Not ours: CLC = we don't own this address
9393 0431 RTS ; Return with C=0 (claim denied)
9394 0432 .accept_new_claim←1← 042A BCS
STA tube_claimed_id ; Accept new claim: update our address
9396 0434 .return_tube_init←2← 0412 BNE← 042E BEQ
RTS ; Return with address updated
9397 0435 .tube_transfer_setup←1← 0408 BCC
PHP ; PHP: save interrupt state
9398 0436 SEI ; SEI: disable interrupts for R4 protocol
9399 0437 .setup_data_transfer
STY tube_data_ptr_hi ; Save 16-bit transfer address from (X,Y)
939B 0439 STX tube_data_ptr ; Store address pointer low byte
939D 043B JSR tube_send_r4 ; Send transfer type byte to co-processor
93A0 043E TAX ; X = transfer type for table lookup
93A1 043F LDY #3 ; Y=3: send 4 bytes (address + claimed addr)
93A3 0441 LDA tube_claimed_id ; Send our claimed address + 4-byte xfer addr
93A5 0443 JSR tube_send_r4 ; Send transfer address byte
93A8 0446 .send_xfer_addr_bytes←1← 044C BPL
LDA (tube_data_ptr),y ; Load transfer address byte from (X,Y)
93AA 0448 JSR tube_send_r4 ; Send address byte to co-processor via R4
93AD 044B DEY ; Previous byte (big-endian: 3,2,1,0)
93AE 044C BPL send_xfer_addr_bytes ; Loop for all 4 address bytes
93B0 044E LDY #&18 ; Y=&18: enable Tube control register
93B2 0450 STY tube_status_1_and_tube_control ; Enable Tube interrupt generation
93B5 0453 LDA tube_ctrl_values,x ; Look up Tube control bits for this xfer type
93B8 0456 STA tube_status_1_and_tube_control ; Apply transfer- specific control bits
93BB 0459 LSR ; LSR: check bit 2 (2-byte flush needed?)
93BC 045A LSR ; LSR: shift bit 2 to carry
93BD 045B BCC skip_r3_flush ; C=0: no flush needed, skip R3 reads
93BF 045D BIT tube_data_register_3 ; Dummy R3 reads: flush for 2-byte transfers
93C2 0460 BIT tube_data_register_3 ; Second dummy read to flush R3 FIFO
93C5 0463 .skip_r3_flush←1← 045B BCC
JSR tube_send_r4 ; Trigger co-processor ack via R4
93C8 0466 .poll_r4_copro_ack←1← 0469 BVC
BIT tube_status_register_4_and_cpu_control ; Poll R4 status for co- processo r response
93CB 0469 BVC poll_r4_copro_ack ; Bit 6 clear: not ready, keep polling
93CD 046B BCS copro_ack_nmi_check ; R4 bit 7: co-processor acknowledged transfer
93CF 046D CPX #4 ; Type 4 = SENDW (host-to-parasite word xfer)
93D1 046F BNE skip_nmi_release ; Not SENDW type: skip release path
93D3 0471 .tube_sendw_complete←1← 0496 BEQ
JSR tube_release_claim ; SENDW complete: release, sync, restart
93D6 0474 JSR tube_send_r2 ; Sync via R2 send
93D9 0477 JMP tube_reset_stack ; Restart Tube main loop
93DC 047A .copro_ack_nmi_check←1← 046B BCS
LSR ; LSR: check bit 0 (NMI used?)
93DD 047B BCC skip_nmi_release ; C=0: NMI not used, skip NMI release
93DF 047D LDY #&88 ; Release Tube NMI (transfer used interrupts)
93E1 047F STY tube_status_1_and_tube_control ; Write &88 to Tube control to release NMI
93E4 0482 .skip_nmi_release←2← 046F BNE← 047B BCC
PLP ; Restore interrupt state
93E5 0483 .return_tube_xfer
RTS ; Return from transfer setup
; Tube host startup entry (BEGIN)
; Entry point via JMP from &0400. Enables interrupts, ; checks
; break type via OSBYTE &FD: soft break re-initialises ; Tube and
; restarts, hard break claims address &FF. Sends ROM ; contents
; to co-processor page by page via SENDW, then claims the ; final
; transfer address.
93E6 0484 .tube_begin←1← 0400 JMP
CLI ; BEGIN: enable interrupts for Tube host code
93E7 0485 BCS claim_addr_ff ; C=1: hard break, claim addr &FF
93E9 0487 BNE check_break_type ; C=0, A!=0: re-init path
93EB 0489 JMP tube_reply_ack ; Z=1 from C=0 path: just acknowledge
93EE 048C .check_break_type←1← 0487 BNE
LDX #0 ; X=0 for OSBYTE
93F0 048E LDY #&ff ; Y=&FF for OSBYTE
93F2 0490 LDA #osbyte_read_write_last_break_type ; OSBYTE &FD: what type of reset was this?
93F4 0492 JSR osbyte ; Read type of last reset
93F7 0495 TXA ; X=value of type of last reset
93F8 0496 BEQ tube_sendw_complete ; Soft break (X=0): re-init Tube and restart
93FA 0498 .claim_addr_ff←2← 0485 BCS← 049D BCC
LDA #&ff ; Claim address &FF (startup = highest prio)
93FC 049A JSR tube_addr_claim ; Request address claim from Tube system
93FF 049D BCC claim_addr_ff ; C=0: claim failed, retry
9401 049F JSR tube_init_reloc ; Init reloc pointers from ROM header
9404 04A2 .next_rom_page←1← 04C4 BVC
LDA #7 ; R4 cmd 7: SENDW to send ROM to parasite
9406 04A4 JSR tube_claim_default ; Set up Tube for SENDW transfer
9409 04A7 LDY #0 ; Y=0: start at beginning of page
940B 04A9 STY zp_ptr_lo ; Store to zero page pointer low byte
940D 04AB .send_rom_page_bytes←1← 04B4 BNE
LDA (zp_ptr_lo),y ; Send 256-byte page via R3, byte at a time
940F 04AD STA tube_data_register_3 ; Write byte to Tube R3 data register
9412 04B0 NOP ; Timing delay: Tube data register needs NOPs
9413 04B1 NOP ; NOP delay (2)
9414 04B2 NOP ; NOP delay (3)
9415 04B3 INY ; Next byte in page
9416 04B4 BNE send_rom_page_bytes ; Loop for all 256 bytes
9418 04B6 INC tube_xfer_page ; Increment 24-bit destination addr
941A 04B8 BNE skip_addr_carry ; No carry: skip higher bytes
941C 04BA INC tube_xfer_addr_2 ; Carry into second byte
941E 04BC BNE skip_addr_carry ; No carry: skip third byte
9420 04BE INC tube_xfer_addr_3 ; Carry into third byte
9422 04C0 .skip_addr_carry←2← 04B8 BNE← 04BC BNE
INC zp_ptr_hi ; Increment page counter
9424 04C2 BIT zp_ptr_hi ; Bit 6 set = all pages transferred
9426 04C4 BVC next_rom_page ; More pages: loop back to SENDW
9428 04C6 JSR tube_init_reloc ; Re-init reloc pointers for final claim
942B 04C9 LDA #4 ; A=4: transfer type for final address claim
; Claim default Tube transfer address
; Sets Y=0, X=&53 (address &0053), then JMP ; tube_addr_claim
; to initiate a Tube address claim for the default ; transfer
; address. Called from the BEGIN startup path and after ; the
; page transfer loop completes.
942D 04CB .tube_claim_default←1← 04A4 JSR
LDY #0 ; Y=0: transfer address low byte
942F 04CD LDX #&53 ; X=&53: transfer address high byte (&0053)
9431 04CF JMP tube_addr_claim ; Claim Tube address for transfer
; Initialise relocation address for ROM transfer
; Sets source page to &8000 and page counter to &80. ; Checks
; ROM type bit 5 for a relocation address in the ROM ; header;
; if present, extracts the 4-byte address from after the
; copyright string. Otherwise uses default &8000 start.
9434 04D2 .tube_init_reloc←2← 049F JSR← 04C6 JSR
LDA #&80 ; Init: start sending from &8000
9436 04D4 STA tube_xfer_page ; Store &80 as source page high byte
9438 04D6 STA zp_ptr_hi ; Store &80 as page counter initial value
943A 04D8 LDA #&20 ; A=&20: bit 5 mask for ROM type check
943C 04DA AND rom_type ; ROM type bit 5: reloc address in header?
943F 04DD TAY ; Y = 0 or &20 (reloc flag)
9440 04DE STY tube_transfer_addr ; Store as transfer address selector
9442 04E0 BEQ store_xfer_end_addr ; No reloc addr: use defaults
9444 04E2 LDX copyright_offset ; Skip past copyright string to find reloc addr
9447 04E5 .scan_copyright_end←1← 04E9 BNE
INX ; Skip past null-terminated copyright string
9448 04E6 LDA rom_header,x ; Load next byte from ROM header
944B 04E9 BNE scan_copyright_end ; Loop until null terminator found
944D 04EB LDA language_handler_lo,x ; Read 4-byte reloc address from ROM header
9450 04EE STA tube_transfer_addr ; Store reloc addr byte 1 as transfer addr
9452 04F0 LDA language_handler_hi,x ; Load reloc addr byte 2
9455 04F3 STA tube_xfer_page ; Store as source page start
9457 04F5 LDY service_entry,x ; Load reloc addr byte 3
945A 04F8 LDA service_handler_lo,x ; Load reloc addr byte 4 (highest)
945D 04FB .store_xfer_end_addr←1← 04E0 BEQ
STA tube_xfer_addr_3 ; Store high byte of end address
945F 04FD STY tube_xfer_addr_2 ; Store byte 3 of end address
9461 04FF RTS ; Return with pointers initialised
; Tube host code page 5 — reference: NFS13 (TASKS, ; BPUT-FILE)
; Copied from ROM at reloc_p5_src during init. Contains:
; &0500: 12-entry dispatch table (&0500-&0517)
; &0518: 8-byte Tube control register value table
; &0520: tube_osbput — write byte to file
; &052D: tube_osbget — read byte from file
; &0537: tube_osrdch — read character
; &053A: tube_rdch_reply — send carry+data as reply
; &0542: tube_osfind — open/close file
; &055E: tube_osargs — file argument read/write
; &0582: tube_read_string — read string from R2 into &0700
; &0596: tube_oscli — execute * command
; &059C: tube_reply_ack — send &7F acknowledge
; &05A9: tube_osfile — whole file operation
; &05D1: tube_osgbpb — multi-byte file read/write
; Code continues seamlessly into page 6 (tube_osbyte_short ; at &05F2
; straddles the page boundary with a BVC at &05FF/&0600).
9462 0500 .tube_dispatch_table←2← 0050 JMP← 8168 STA
EQUW tube_osrdch ; 12-entry Tube R2 command dispatch table R2 cmd 0: OSRDCH
9464 0502 EQUW tube_oscli ; R2 cmd 1: OSCLI
9466 0504 EQUW tube_osbyte_2param ; R2 cmd 2: OSBYTE (2-param)
9468 0506 EQUW tube_osbyte_long ; R2 cmd 3: OSBYTE (3-param)
946A 0508 EQUW tube_osword ; R2 cmd 4: OSWORD
946C 050A EQUW tube_osword_rdln ; R2 cmd 5: OSWORD 0 (read line)
946E 050C EQUW tube_osargs ; R2 cmd 6: OSARGS
9470 050E EQUW tube_osbget ; R2 cmd 7: OSBGET
9472 0510 EQUW tube_osbput ; R2 cmd 8: OSBPUT
9474 0512 EQUW tube_osfind ; R2 cmd 9: OSFIND
9476 0514 EQUW tube_osfile ; R2 cmd 10: OSFILE
9478 0516 EQUW tube_osgbpb ; R2 cmd 11: OSGBPB
; Tube ULA control register values, indexed by transfer
; type (0-7). Written to &FEE0 after clearing V+M with
; &18. Bit layout: S=set/clear, T=reset regs, P=PRST,
; V=2-byte R3, M=PNMI(R3), J=PIRQ(R4), I=PIRQ(R1),
; Q=HIRQ(R4). Bits 1-7 select flags; bit 0 (S) is the
; value to set or clear.
947A 0518 .tube_ctrl_values←1← 0453 LDA
EQUB &86 ; Type 0: set I+J (1-byte R3, parasite to host)
947B 0519 EQUB &88 ; Type 1: set M (1-byte R3, host to parasite)
947C 051A EQUB &96 ; Type 2: set V+I+J (2-byte R3, parasite to host)
947D 051B EQUB &98 ; Type 3: set V+M (2-byte R3, host to parasite)
947E 051C EQUB &18 ; Type 4: clear V+M (execute code at address)
947F 051D EQUB &18 ; Type 5: clear V+M (release address claim)
9480 051E EQUB &82 ; Type 6: set I (define event handler)
9481 051F EQUB &18 ; Type 7: clear V+M (transfer and release)
; Tube OSBPUT handler (R2 cmd 8)
; Reads file handle and data byte from R2, then
; calls OSBPUT (&FFD4) to write the byte. Falls through
; to tube_reply_ack to send &7F acknowledgement.
9482 0520 .tube_osbput
JSR tube_read_r2 ; Read channel handle from R2 for BPUT
9485 0523 TAY ; Y=channel handle from R2
9486 0524 JSR tube_read_r2 ; Read data byte from R2 for BPUT
9489 0527 .tube_poll_r1_wrch
JSR osbput ; Write a single byte A to an open file Y
948C 052A JMP tube_reply_ack ; BPUT done: send acknowledge, return
; Tube OSBGET handler (R2 cmd 7)
; Reads file handle from R2, calls OSBGET (&FFD7)
; to read a byte, then falls through to tube_rdch_reply
; which encodes the carry flag (error) into bit 7 and
; sends the result byte via R2.
948F 052D .tube_osbget
JSR tube_read_r2 ; Read channel handle from R2 for BGET
9492 0530 TAY ; Y=channel handle for OSBGET Y=file handle
9493 0531 JSR osbget ; Read a single byte from an open file Y
9496 0534 JMP tube_rdch_reply ; Send carry+byte reply (BGET result)
; Tube OSRDCH handler (R2 cmd 0)
; Calls OSRDCH (&FFE0) to read a character from
; the current input stream, then falls through to
; tube_rdch_reply which encodes the carry flag (error)
; into bit 7 and sends the result byte via R2.
9499 0537 .tube_osrdch
JSR osrdch ; Read a character from the current input stream
949C 053A .tube_rdch_reply←2← 0534 JMP← 05EF JMP
ROR ; ROR A: encode carry (error flag) into bit 7
949D 053B JSR tube_send_r2 ; Send carry+data byte to Tube R2
94A0 053E ROL ; ROL A: restore carry flag
94A1 053F JMP tube_reply_byte ; Return via tube_reply_byte
; Tube OSFIND handler (R2 cmd 9)
; Reads open mode from R2. If zero, reads a file
; handle and closes that file. Otherwise saves the mode,
; reads a filename string into &0700 via tube_read_string,
; then calls OSFIND (&FFCE) to open the file. Sends the
; resulting file handle (or &00) via tube_reply_byte.
94A4 0542 .tube_osfind
JSR tube_read_r2 ; Read open mode from R2 for OSFIND
94A7 0545 BEQ tube_osfind_close ; A=0: close file, else open with filename
94A9 0547 PHA ; Save open mode while reading filename
94AA 0548 JSR tube_read_string ; Read filename string from R2 into &0700
94AD 054B PLA ; Recover open mode from stack
94AE 054C JSR osfind ; Open or close file(s)
94B1 054F JMP tube_reply_byte ; Send file handle result to co-processor
94B4 0552 .tube_osfind_close←1← 0545 BEQ
JSR tube_read_r2 ; OSFIND close: read handle from R2
94B7 0555 TAY ; Y=handle to close
94B8 0556 LDA #osfind_close ; A=0: close command for OSFIND
94BA 0558 JSR osfind ; Close one or all files
94BD 055B JMP tube_reply_ack ; Close done: send acknowledge, return
; Tube OSARGS handler (R2 cmd 6)
; Reads file handle from R2 into Y, then reads
; a 4-byte argument and reason code into zero page.
; Calls OSARGS (&FFDA), sends the result A and 4-byte
; return value via R2, then returns to the main loop.
94C0 055E .tube_osargs
JSR tube_read_r2 ; Read file handle from R2 for OSARGS
94C3 0561 TAY ; Y=file handle for OSARGS
94C4 0562 .tube_read_params
LDX #4 ; Read 4-byte arg + reason from R2 into ZP
94C6 0564 .read_osargs_params←1← 056A BNE
JSR tube_read_r2 ; Read next param byte from R2
94C9 0567 STA escape_flag,x ; Params stored at &00-&03 (little-endian)
94CB 0569 DEX ; Decrement byte counter
94CC 056A BNE read_osargs_params ; Loop for 4 bytes
94CE 056C JSR tube_read_r2 ; Read OSARGS reason code from R2
94D1 056F JSR osargs ; Read or write a file's attributes
94D4 0572 JSR tube_send_r2 ; Send result A back to co-processor
94D7 0575 LDX #3 ; Return 4-byte result from ZP &00-&03
94D9 0577 .send_osargs_result←1← 057D BPL
LDA zp_ptr_lo,x ; Load result byte from zero page
94DB 0579 JSR tube_send_r2 ; Send byte to co-processor via R2
94DE 057C DEX ; Previous byte (count down)
94DF 057D BPL send_osargs_result ; Loop for all 4 bytes
94E1 057F JMP tube_main_loop ; Return to Tube main loop
; Read string from Tube R2 into buffer
; Loops reading bytes from tube_read_r2 into the
; string buffer at &0700, storing at string_buf+Y.
; Terminates on CR (&0D) or when Y wraps to zero
; (256-byte overflow). Returns with X=0, Y=7 so that
; XY = &0700, ready for OSCLI or OSFIND dispatch.
; Called by the Tube OSCLI and OSFIND handlers.
94E4 0582 .tube_read_string←3← 0548 JSR← 0596 JSR← 05B3 JSR
LDX #0 ; X=0: initialise string buffer index
94E6 0584 LDY #0 ; Y=0: string buffer offset 0
94E8 0586 .strnh←1← 0591 BNE
JSR tube_read_r2 ; Read next string byte from R2
94EB 0589 STA string_buf,y ; Store byte in string buffer at &0700+Y
94EE 058C INY ; Next buffer position
94EF 058D BEQ string_buf_done ; Y overflow: string too long, truncate
94F1 058F CMP #&0d ; Check for CR terminator
94F3 0591 BNE strnh ; Not CR: continue reading string
94F5 0593 .string_buf_done←1← 058D BEQ
LDY #7 ; Y=7: set XY=&0700 for OSCLI/OSFIND
94F7 0595 RTS ; Return with XY pointing to &0700
; Tube OSCLI handler (R2 cmd 1)
; Reads a command string from R2 into &0700 via
; tube_read_string, then calls OSCLI (&FFF7) to execute
; it. Falls through to tube_reply_ack to send &7F
; acknowledgement.
94F8 0596 .tube_oscli
JSR tube_read_string ; Read command string from R2 into &0700
94FB 0599 JSR oscli ; Execute * command via OSCLI
94FE 059C .tube_reply_ack←3← 0489 JMP← 052A JMP← 055B JMP
LDA #&7f ; &7F = success acknowledgement
9500 059E .tube_reply_byte←4← 053F JMP← 054F JMP← 05A1 BVC← 067D JMP
BIT tube_status_register_2 ; Poll R2 status until ready
9503 05A1 BVC tube_reply_byte ; Bit 6 clear: not ready, loop
9505 05A3 STA tube_data_register_2 ; Write byte to R2 data register
9508 05A6 .mj←1← 05CF BEQ
JMP tube_main_loop ; Return to Tube main loop
; Tube OSFILE handler (R2 cmd 10)
; Reads a 16-byte control block into zero page,
; a filename string into &0700 via tube_read_string,
; and a reason code from R2. Calls OSFILE (&FFDD),
; then sends the result A and updated 16-byte control
; block back via R2. Returns to the main loop via mj.
950B 05A9 .tube_osfile
LDX #&10 ; Read 16-byte OSFILE control block from R2
950D 05AB .argsw←1← 05B1 BNE
JSR tube_read_r2 ; Read next control block byte from R2
9510 05AE STA zp_ptr_hi,x ; Store at &01+X (descending)
9512 05B0 DEX ; Decrement byte counter
9513 05B1 BNE argsw ; Loop for all 16 bytes
9515 05B3 JSR tube_read_string ; Read filename string from R2 into &0700
9518 05B6 STX zp_ptr_lo ; XY=&0700: filename pointer for OSFILE
951A 05B8 STY zp_ptr_hi ; Store Y=7 as pointer high byte
951C 05BA LDY #0 ; Y=0 for OSFILE control block offset
951E 05BC JSR tube_read_r2 ; Read OSFILE reason code from R2
9521 05BF JSR osfile ; Execute OSFILE operation
9524 05C2 JSR tube_send_r2 ; Send result A (object type) to co-processor
9527 05C5 LDX #&10 ; Return 16-byte control block to co-processor
9529 05C7 .send_osfile_ctrl_blk←1← 05CD BNE
LDA zp_ptr_hi,x ; Load control block byte
952B 05C9 JSR tube_send_r2 ; Send byte to co-processor via R2
952E 05CC DEX ; Decrement byte counter
952F 05CD BNE send_osfile_ctrl_blk ; Loop for all 16 bytes
9531 05CF BEQ mj ; ALWAYS branch to main loop ALWAYS branch
; Tube OSGBPB handler (R2 cmd 11)
; Reads a 13-byte control block and reason code
; from R2 into zero page. Calls OSGBPB (&FFD1), then
; sends 12 result bytes and the carry+result byte
; (via tube_rdch_reply) back via R2.
9533 05D1 .tube_osgbpb
LDX #&0d ; Read 13-byte OSGBPB control block from R2
9535 05D3 .read_osgbpb_ctrl_blk←1← 05D9 BNE
JSR tube_read_r2 ; Read next control block byte from R2
9538 05D6 STA escape_flag,x ; Store at &FF+X (descending into &00-&0C)
953A 05D8 DEX ; Decrement byte counter
953B 05D9 BNE read_osgbpb_ctrl_blk ; Loop for all 13 bytes
953D 05DB JSR tube_read_r2 ; Read OSGBPB reason code from R2
9540 05DE LDY #0 ; Y=0 for OSGBPB control block
9542 05E0 JSR osgbpb ; Read or write multiple bytes to an open file
9545 05E3 PHA ; Save A (completion status) for later
9546 05E4 LDX #&0c ; Return 13-byte result block to co-processor
9548 05E6 .send_osgbpb_result←1← 05EC BPL
LDA zp_ptr_lo,x ; Load result byte from zero page
954A 05E8 JSR tube_send_r2 ; Send byte to co-processor via R2
954D 05EB DEX ; Decrement byte counter
954E 05EC BPL send_osgbpb_result ; Loop for 13 bytes (X=12..0)
9550 05EE PLA ; Recover completion status from stack
9551 05EF JMP tube_rdch_reply ; Send carry+status as RDCH-style reply
; Tube OSBYTE 2-param handler (R2 cmd 2)
; Reads X and A from R2, calls OSBYTE (&FFF4)
; with Y=0, then sends the result X via
; tube_reply_byte. Used for OSBYTE calls that take
; only A and X parameters.
9554 05F2 .tube_osbyte_2param
JSR tube_read_r2 ; Read X param from R2 for 2-param OSBYTE
9557 05F5 TAX ; X = first parameter
9558 05F6 JSR tube_read_r2 ; Read A (OSBYTE number) from R2
955B 05F9 JSR osbyte ; Execute OSBYTE call
955E 05FC .tube_poll_r2_result←2← 05FF ref← 0625 BVS
BIT tube_status_register_2 ; Poll R2 status for result send
9561 05FF EQUB &50 ; BVC: page 5/6 boundary straddle
9562 0600 .tube_page6_start←1← 816E STA
EQUB &FB ; Send carry+status to co-processor via R2
9563 0601 STX tube_data_register_2 ; Send X result for 2-param OSBYTE
9566 0604 .bytex←1← 0617 BEQ
JMP tube_main_loop ; Return to main event loop
; Tube OSBYTE 3-param handler (R2 cmd 3)
; Reads X, Y, and A from R2, calls OSBYTE
; (&FFF4), then sends carry+Y and X as result bytes
; via R2. Used for OSBYTE calls needing all three
; parameters and returning both X and Y results.
9569 0607 .tube_osbyte_long
JSR tube_read_r2 ; Read X, Y, A from R2 for 3-param OSBYTE
956C 060A TAX ; Save in X
956D 060B JSR tube_read_r2 ; Read Y parameter from co-processor
9570 060E TAY ; Save in Y
9571 060F JSR tube_read_r2 ; Read A (OSBYTE function code)
9574 0612 JSR osbyte ; Execute OSBYTE A,X,Y
9577 0615 EOR #&9d ; Test for OSBYTE &9D (fast Tube BPUT)
9579 0617 BEQ bytex ; OSBYTE &9D (fast Tube BPUT): no result needed
957B 0619 ROR ; Encode carry (error flag) into bit 7
957C 061A JSR tube_send_r2 ; Send carry+status byte via R2
957F 061D .tube_osbyte_send_y←1← 0620 BVC
BIT tube_status_register_2 ; Poll R2 status for ready
9582 0620 BVC tube_osbyte_send_y ; Not ready: keep polling
9584 0622 STY tube_data_register_2 ; Send Y result, then fall through to send X
9587 0625 .tube_osbyte_short
BVS tube_poll_r2_result ; BVS &05FC: overlapping code — loops back to page 5 R2 poll to send X after Y
; Tube OSWORD handler (R2 cmd 4)
; Reads OSWORD number A and in-length from R2,
; then reads the parameter block into &0128. Calls
; OSWORD (&FFF1), then sends the out-length result
; bytes from the parameter block back via R2.
; Returns to the main loop via tube_return_main.
9589 0627 .tube_osword
JSR tube_read_r2 ; Overlapping entry: &20 = JSR c06c5 (OSWORD)
958C 062A TAY ; Save OSWORD number in Y
958D 062B .tube_osword_read←1← 062E BPL
BIT tube_status_register_2 ; Poll R2 status for data ready
9590 062E BPL tube_osword_read ; Not ready: keep polling
9592 0630 .tube_osbyte_send_x
LDX tube_data_register_2 ; Read param block length from R2
9595 0633 DEX ; DEX: length 0 means no params to read
9596 0634 BMI skip_param_read ; No params (length=0): skip read loop
9598 0636 .tube_osword_read_lp←2← 0639 BPL← 0642 BPL
BIT tube_status_register_2 ; Poll R2 status for data ready
959B 0639 BPL tube_osword_read_lp ; Not ready: keep polling
959D 063B LDA tube_data_register_2 ; Read param byte from R2
95A0 063E STA tube_osword_pb,x ; Store param bytes into block at &0128
95A3 0641 DEX ; Next param byte (descending)
95A4 0642 BPL tube_osword_read_lp ; Loop until all params read
95A6 0644 TYA ; Restore OSWORD number from Y
95A7 0645 .skip_param_read←1← 0634 BMI
LDX #<(tube_osword_pb) ; XY=&0128: param block address for OSWORD
95A9 0647 LDY #>(tube_osword_pb) ; Y=&01: param block at &0128
95AB 0649 JSR osword ; Execute OSWORD with XY=&0128
95AE 064C .poll_r2_osword_result←1← 064F BPL
BIT tube_status_register_2 ; Poll R2 status for ready
95B1 064F BPL poll_r2_osword_result ; Not ready: keep polling
95B3 0651 LDX tube_data_register_2 ; Read result block length from R2
95B6 0654 DEX ; Decrement result byte counter
95B7 0655 BMI tube_return_main ; No results to send: return to main loop
95B9 0657 .tube_osword_write←1← 0663 BPL
LDY tube_osword_pb,x ; Send result block bytes from &0128 via R2
95BC 065A .tube_osword_write_lp←1← 065D BVC
BIT tube_status_register_2 ; Poll R2 status for ready
95BF 065D BVC tube_osword_write_lp ; Not ready: keep polling
95C1 065F STY tube_data_register_2 ; Send result byte via R2
95C4 0662 DEX ; Next result byte (descending)
95C5 0663 BPL tube_osword_write ; Loop until all results sent
95C7 0665 .tube_return_main←1← 0655 BMI
JMP tube_main_loop ; Return to main event loop
; Tube OSWORD 0 handler (R2 cmd 5)
; Handles OSWORD 0 (read line) specially. Reads
; 4 parameter bytes from R2 into &0128 (max length,
; min char, max char, flags). Calls OSWORD 0 (&FFF1)
; to read a line, then sends &7F+CR or the input line
; byte-by-byte via R2, followed by &80 (error/escape)
; or &7F (success).
95CA 0668 .tube_osword_rdln
LDX #4 ; Read 5-byte OSWORD 0 control block from R2
95CC 066A .read_rdln_ctrl_block←1← 0670 BPL
JSR tube_read_r2 ; Read control block byte from R2
95CF 066D STA zp_ptr_lo,x ; Store in zero page params
95D1 066F DEX ; Next byte (descending)
95D2 0670 BPL read_rdln_ctrl_block ; Loop until all 5 bytes read
95D4 0672 INX ; X=0 after loop, A=0 for OSWORD 0 (read line)
95D5 0673 LDY #0 ; Y=0 for OSWORD 0
95D7 0675 TXA ; A=0: OSWORD 0 (read line)
95D8 0676 JSR osword ; Read input line from keyboard
95DB 0679 BCC tube_rdln_send_line ; C=0: line read OK; C=1: escape pressed
95DD 067B LDA #&ff ; &FF = escape/error signal to co-processor
95DF 067D JMP tube_reply_byte ; Escape: send &FF error to co-processor
95E2 0680 .tube_rdln_send_line←1← 0679 BCC
LDX #0 ; X=0: start of input buffer at &0700
95E4 0682 LDA #&7f ; &7F = line read successfully
95E6 0684 JSR tube_send_r2 ; Send &7F (success) to co-processor
95E9 0687 .tube_rdln_send_loop←1← 0690 BNE
LDA string_buf,x ; Load char from input buffer
95EC 068A .tube_rdln_send_byte
JSR tube_send_r2 ; Send char to co-processor
95EF 068D INX ; Next character
95F0 068E CMP #&0d ; Check for CR terminator
95F2 0690 BNE tube_rdln_send_loop ; Loop until CR terminator sent
95F4 0692 JMP tube_main_loop ; Return to main event loop
; Send byte to Tube data register R2
; Polls Tube status register 2 until bit 6 (TDRA)
; is set, then writes A to the data register. Uses a
; tight BIT/BVC polling loop. Called by 12 sites
; across the Tube host code for all R2 data
; transmission: command responses, file data, OSBYTE
; results, and control block bytes.
95F7 0695 .tube_send_r2←14← 0020 JSR← 0026 JSR← 002C JSR← 0474 JSR← 053B JSR← 0572 JSR← 0579 JSR← 05C2 JSR← 05C9 JSR← 05E8 JSR← 061A JSR← 0684 JSR← 068A JSR← 0698 BVC
BIT tube_status_register_2 ; Poll R2 status (bit 6 = ready)
95FA 0698 BVC tube_send_r2 ; Not ready: keep polling
95FC 069A STA tube_data_register_2 ; Write A to Tube R2 data register
95FF 069D RTS ; Return to caller
; Send byte to Tube data register R4
; Polls Tube status register 4 until bit 6 is set,
; then writes A to the data register. Uses a tight
; BIT/BVC polling loop. R4 is the command/control
; channel used for address claims (ADRR), data transfer
; setup (SENDW), and release commands. Called by 7
; sites, primarily during tube_release_claim and
; tube_transfer_setup sequences.
9600 069E .tube_send_r4←8← 0018 JSR← 0418 JSR← 041D JSR← 043B JSR← 0443 JSR← 0448 JSR← 0463 JSR← 06A1 BVC
BIT tube_status_register_4_and_cpu_control ; Poll R4 status (bit 6 = ready)
9603 06A1 BVC tube_send_r4 ; Not ready: keep polling
9605 06A3 STA tube_data_register_4 ; Write A to Tube R4 data register
9608 06A6 RTS ; Return to caller
9609 06A7 .tube_escape_check←1← 0403 JMP
LDA escape_flag ; Check OS escape flag at &FF
960B 06A9 SEC ; SEC+ROR: put bit 7 of &FF into carry+bit 7
960C 06AA ROR ; ROR: shift escape bit 7 to carry
960D 06AB BMI tube_send_r1 ; Escape set: forward to co-processor via R1
960F 06AD .tube_event_handler
PHA ; EVNTV: forward event A, Y, X to co-processor
9610 06AE LDA #0 ; Send &00 prefix (event notification)
9612 06B0 JSR tube_send_r1 ; Send zero prefix via R1
9615 06B3 TYA ; Y value for event
9616 06B4 JSR tube_send_r1 ; Send Y via R1
9619 06B7 TXA ; X value for event
961A 06B8 JSR tube_send_r1 ; Send X via R1
961D 06BB PLA ; Restore A (event type)
; Send byte to Tube data register R1
; Polls Tube status register 1 until bit 6 is set,
; then writes A to the data register. Uses a tight
; BIT/BVC polling loop. R1 is used for asynchronous
; event and escape notification to the co-processor.
; Called by tube_event_handler to forward event type,
; Y, and X parameters, and reached via BMI from
; tube_escape_check when the escape flag is set.
961E 06BC .tube_send_r1←5← 06AB BMI← 06B0 JSR← 06B4 JSR← 06B8 JSR← 06BF BVC
BIT tube_status_1_and_tube_control ; Poll R1 status (bit 6 = ready)
9621 06BF BVC tube_send_r1 ; Not ready: keep polling
9623 06C1 STA tube_data_register_1 ; Write A to Tube R1 data register
9626 06C4 RTS ; Return to caller
; Read a byte from Tube data register R2
; Polls Tube status register 2 until data is available
; (bit 7 set), then loads A from Tube data register 2.
; Called by all Tube dispatch handlers that receive data
; or parameters from the co-processor.
9627 06C5 .tube_read_r2←21← 0520 JSR← 0524 JSR← 052D JSR← 0542 JSR← 0552 JSR← 055E JSR← 0564 JSR← 056C JSR← 0586 JSR← 05AB JSR← 05BC JSR← 05D3 JSR← 05DB JSR← 05F2 JSR← 05F6 JSR← 0607 JSR← 060B JSR← 060F JSR← 0627 JSR← 066A JSR← 06C8 BPL
BIT tube_status_register_2 ; Poll R2 status (bit 7 = ready)
962A 06C8 BPL tube_read_r2 ; Not ready: keep polling
962C 06CA LDA tube_data_register_2 ; Read data byte from R2
962F 06CD RTS ; Return with byte in A
9630 06CE .trampoline_tx_setup
JMP tx_begin ; Trampoline: begin TX operation
9633 06D1 .trampoline_adlc_init
JMP adlc_init ; Trampoline: full ADLC init
9636 06D4 .svc_12_nmi_release
JMP wait_idle_and_reset ; Trampoline: wait idle and reset
9639 06D7 .svc_11_nmi_claim
JMP init_nmi_workspace ; Trampoline: init NMI workspace
963C 06DA LDA #4 ; A=4: SR interrupt bit mask
963E 06DC BIT system_via_ifr ; Test SR flag in VIA IFR
9641 06DF BNE handle_sr_intr ; SR active: handle interrupt
9643 06E1 LDA #5 ; A=5: NMI not for us
9645 06E3 RTS ; Return (NMI not claimed)
9646 06E4 .handle_sr_intr←1← 06DF BNE
TXA ; Save X on stack
9647 06E5 PHA ; Push X for later restore
9648 06E6 TYA ; Save Y on stack
9649 06E7 PHA ; Push Y for later restore
964A 06E8 LDA system_via_acr ; Read VIA auxiliary control reg
964D 06EB AND #&e3 ; Mask shift register bits
964F 06ED ORA tx_work_51 ; OR in TX shift register mode
9652 06F0 STA system_via_acr ; Write back ACR with SR mode
9655 06F3 LDA system_via_sr ; Read SR to clear shift complete
9658 06F6 LDA #4 ; A=4: SR interrupt bit
965A 06F8 STA system_via_ifr ; Clear SR interrupt flag
965D 06FB STA system_via_ier ; Disable SR interrupt
9660 06FE EQUB &AC, &57 ; A=5: NMI not for us
9662 EQUB &0D ; CR terminator for VDU state data
9663 CPY #&86 ; Y >= &86: above dispatch range
9665 BCS dispatch_svc5 ; Out of range: skip protection
9667 LDA prot_status ; Save current JSR protection mask
966A STA saved_jsr_mask ; Backup to saved_jsr_mask
966D ORA #&1c ; Set protection bits 2-4
966F STA prot_status ; Apply protection during dispatch
9672 .dispatch_svc5←1← 9665 BCS
LDA #&9b ; Push return addr high (&9B)
9674 PHA ; High byte on stack for RTS
9675 LDA svc5_dispatch_lo,y ; Load dispatch target low byte
9678 PHA ; Low byte on stack for RTS
9679 .svc_5_unknown_irq
RTS ; RTS = dispatch to PHA'd address

ADLC initialisation

Reads station ID (INTOFF side effect), performs full ADLC reset, checks for Tube presence (OSBYTE &EA), then falls through to adlc_init_workspace.

967A .adlc_init←1← 06D1 JMP
BIT station_id_disable_net_nmis ; INTOFF: read station ID, disable NMIs
967D JSR adlc_full_reset ; Full ADLC hardware reset
9680 LDA #osbyte_read_tube_presence ; OSBYTE &EA: check Tube co-processor
9682 LDX #0 ; X=0 for OSBYTE
9684 STX econet_init_flag ; Clear Econet init flag before setup
9687 LDY #&ff ; Y=&FF for OSBYTE
9689 JSR osbyte ; Read Tube present flag
968C STX tube_flag ; X=value of Tube present flag
968F LDA #osbyte_issue_service_request ; OSBYTE &8F: issue service request
9691 LDX #&0c ; X=&0C: NMI claim service
9693 LDY #&ff ; Y=&FF: pass to adlc_init_workspace
fall through ↓

Initialise NMI workspace

Issues OSBYTE &8F with X=&0C (NMI claim service request) before copying the NMI shim. Sub-entry at &9698 skips the service request for quick re-init. Then copies 32 bytes of NMI shim from ROM (&9F7D) to RAM (&0D00), patches the current ROM bank number into the shim's self-modifying code at &0D07, sets TX clear flag and econet_init_flag to &80, reads station ID from &FE18 (INTOFF side effect), stores it in the TX scout buffer, and re-enables NMIs by reading &FE20 (INTON side effect).

9695 .adlc_init_workspace
JSR osbyte ; Issue paged ROM service call, Reason X=12 - NMI claim
fall through ↓

Initialise NMI workspace (skip service request)

Sub-entry of adlc_init_workspace that skips the OSBYTE &8F service request. Copies 32 bytes of NMI shim from ROM to &0D00, patches the ROM bank number, sets init flags, reads station ID, and re-enables NMIs.

9698 .init_nmi_workspace←1← 06D7 JMP
LDY #&20 ; Copy 32 bytes of NMI shim from ROM to &0D00
969A .copy_nmi_shim←1← 96A1 BNE
LDA listen_jmp_hi,y ; Read byte from NMI shim ROM source
969D STA nmi_code_base,y ; Write to NMI shim RAM at &0D00
96A0 DEY ; Next byte (descending)
96A1 BNE copy_nmi_shim ; Loop until all 32 bytes copied
96A3 LDA romsel_copy ; Patch current ROM bank into NMI shim
96A5 STA nmi_shim_07 ; Self-modifying code: ROM bank at &0D07
96A8 LDA #&80 ; &80 = Econet initialised
96AA STA tx_clear_flag ; Mark TX as complete (ready)
96AD STA econet_init_flag ; Mark Econet as initialised
96B0 LDA station_id_disable_net_nmis ; Read station ID (&FE18 = INTOFF side effect)
96B3 STA tx_src_stn ; Store our station ID in TX scout
96B6 STY tx_src_net ; Y=0 after copy loop: net = local
96B9 STY need_release_tube ; Clear Tube release flag
96BB BIT video_ula_control ; INTON: re-enable NMIs (&FE20 read side effect)
96BE RTS ; Return

NMI RX scout handler (initial byte)

Default NMI handler for incoming scout frames. Checks if the frame is addressed to us or is a broadcast. Installed as the NMI target during idle RX listen mode. Tests SR2 bit0 (AP = Address Present) to detect incoming data. Reads the first byte (destination station) from the RX FIFO and compares against our station ID. Reading &FE18 also disables NMIs (INTOFF side effect).

96BF .nmi_rx_scout←1← 9F88 JMP
LDA #1 ; A=&01: mask for SR2 bit0 (AP = Address Present)
96C1 BIT econet_control23_or_status2 ; BIT SR2: Z = A AND SR2 -- tests if AP is set
96C4 BEQ scout_error ; AP not set, no incoming data -- check for errors
96C6 LDA econet_data_continue_frame ; Read first RX byte (destination station address)
96C9 CMP station_id_disable_net_nmis ; Compare to our station ID (&FE18 read = INTOFF, disables NMIs)
96CC BEQ accept_frame ; Match -- accept frame
96CE CMP #&ff ; Check for broadcast address (&FF)
96D0 BNE scout_reject ; Neither our address nor broadcast -- reject frame
96D2 LDA #&40 ; Flag &40 = broadcast frame
96D4 STA tx_flags ; Store broadcast flag in TX flags
96D7 .accept_frame←1← 96CC BEQ
LDA #&dc ; Install next NMI handler at &96DC (RX scout net byte)
96D9 JMP install_nmi_handler ; Install next handler and RTI

RX scout second byte handler

Reads the second byte of an incoming scout (destination network). Checks for network match: 0 = local network (accept), &FF = broadcast (accept and flag), anything else = reject. Installs the scout data reading loop handler at &970E.

96DC .nmi_rx_scout_net
BIT econet_control23_or_status2 ; BIT SR2: test for RDA (bit7 = data available)
96DF BPL scout_error ; No RDA -- check errors
96E1 LDA econet_data_continue_frame ; Read destination network byte
96E4 BEQ accept_local_net ; Network = 0 -- local network, accept
96E6 EOR #&ff ; EOR &FF: test if network = &FF (broadcast)
96E8 BEQ accept_scout_net ; Broadcast network -- accept
96EA .scout_reject←1← 96D0 BNE
LDA #&a2 ; Reject: wrong network. CR1=&A2: RIE|RX_DISCONTINUE
96EC STA econet_control1_or_status1 ; Write CR1 to discontinue RX
96EF JMP install_rx_scout_handler ; Return to idle scout listening
96F2 .accept_local_net←1← 96E4 BEQ
STA tx_flags ; Network = 0 (local): clear tx_flags
96F5 .accept_scout_net←1← 96E8 BEQ
STA port_buf_len ; Store Y offset for scout data buffer
96F7 LDA #&0e ; Install scout data reading loop at &970E
96F9 LDY #&97 ; High byte of scout data handler
96FB JMP set_nmi_vector ; Install scout data loop and RTI

Scout error/discard handler

Reached when the scout data loop sees no RDA (BPL at &9713) or when scout completion finds unexpected SR2 state. If SR2 & &81 is non-zero (AP or RDA still active), performs full ADLC reset and discards. If zero (clean end), discards via &99E8. This path is a common landing for any unexpected ADLC state during scout reception.

96FE .scout_error←5← 96C4 BEQ← 96DF BPL← 9713 BPL← 9747 BEQ← 9749 BPL
LDA econet_control23_or_status2 ; Read SR2
9701 AND #&81 ; Test AP (b0) | RDA (b7)
9703 BEQ scout_discard ; Neither set -- clean end, discard via &970B
9705 JSR adlc_full_reset ; Unexpected data/status: full ADLC reset
9708 JMP install_rx_scout_handler ; Discard and return to idle
970B .scout_discard←1← 9703 BEQ
JMP discard_listen ; Gentle discard: RX_DISCONTINUE

Scout data reading loop

Reads the body of a scout frame, two bytes per iteration. Stores bytes at &0D3D+Y (scout buffer: src_stn, src_net, ctrl, port, ...). Between each pair it checks SR2: - At entry (&9710): SR2 read, BPL tests RDA (bit7) - No RDA (BPL) -> error (&96FE) - RDA set (BMI) -> read byte - After first byte (&971C): full SR2 tested - SR2 non-zero (BNE) -> scout completion (&9738) This is the FV detection point: when FV is set (by inline refill of the last byte during the preceding RX FIFO read), SR2 is non-zero and the branch is taken. - SR2 = 0 -> read second byte and loop - After second byte (&9730): re-test full SR2 - SR2 non-zero (BNE) -> loop back to &9713 - SR2 = 0 -> RTI, wait for next NMI The loop ends at Y=&0C (12 bytes max in scout buffer).

970E .scout_data_loop
LDY port_buf_len ; Y = buffer offset
9710 LDA econet_control23_or_status2 ; Read SR2
9713 .scout_loop_rda←1← 9733 BNE
BPL scout_error ; No RDA -- error handler &96FE
9715 LDA econet_data_continue_frame ; Read data byte from RX FIFO
9718 STA rx_src_stn,y ; Store at &0D3D+Y (scout buffer)
971B INY ; Advance buffer index
971C LDA econet_control23_or_status2 ; Read SR2 again (FV detection point)
971F BMI scout_loop_second ; RDA set -- more data, read second byte
9721 BNE scout_complete ; SR2 non-zero (FV or other) -- scout completion
9723 .scout_loop_second←1← 971F BMI
LDA econet_data_continue_frame ; Read second byte of pair
9726 STA rx_src_stn,y ; Store at &0D3D+Y
9729 INY ; Advance and check buffer limit
972A CPY #&0c ; Copied all 12 scout bytes?
972C BEQ scout_complete ; Buffer full (Y=12) -- force completion
972E STY port_buf_len ; Save final buffer offset
9730 LDA econet_control23_or_status2 ; Read SR2 for next pair
9733 BNE scout_loop_rda ; SR2 non-zero -- loop back for more bytes
9735 JMP nmi_rti ; SR2 = 0 -- RTI, wait for next NMI

Scout completion handler

Reached from the scout data loop when SR2 is non-zero (FV detected). Disables PSE to allow individual SR2 bit testing: CR1=&00 (clear all enables) CR2=&84 (RDA_SUPPRESS_FV | FC_TDRA) -- no PSE, no CLR bits Then checks FV (bit1) and RDA (bit7): - No FV (BEQ) -> error &96FE (not a valid frame end) - FV set, no RDA (BPL) -> error &96FE (missing last byte) - FV set, RDA set -> read last byte, process scout After reading the last byte, the complete scout buffer (&0D3D-&0D48) contains: src_stn, src_net, ctrl, port [, extra_data...]. The port byte at &0D40 determines further processing: - Port = 0 -> immediate operation (&9A46) - Port non-zero -> check if it matches an open receive block

9738 .scout_complete←2← 9721 BNE← 972C BEQ
LDA #0 ; CR1=&00: disable all interrupts
973A STA econet_control1_or_status1 ; Write CR1
973D LDA #&84 ; CR2=&84: disable PSE, enable RDA_SUPPRESS_FV
973F STA econet_control23_or_status2 ; Write CR2
9742 LDA #2 ; A=&02: FV mask for SR2 bit1
9744 BIT econet_control23_or_status2 ; BIT SR2: test FV (Z) and RDA (N)
9747 BEQ scout_error ; No FV -- not a valid frame end, error
9749 BPL scout_error ; FV set but no RDA -- missing last byte, error
974B LDA econet_data_continue_frame ; Read last byte from RX FIFO
974E STA rx_src_stn,y ; Store last byte at &0D3D+Y
9751 LDA #&44 ; CR1=&44: RX_RESET | TIE (switch to TX for ACK)
9753 STA econet_control1_or_status1 ; Write CR1: switch to TX mode
9756 SEC ; Set bit7 of need_release_tube flag
9757 ROR need_release_tube ; Rotate C=1 into bit7: mark Tube release needed
9759 LDA rx_port ; Check port byte: 0 = immediate op, non-zero = data transfer
975C BNE scout_match_port ; Port non-zero -- look for matching receive block
975E .scout_no_match
JMP immediate_op ; Port = 0 -- immediate operation handler
9761 .scout_match_port←1← 975C BNE
BIT tx_flags ; Check if broadcast (bit6 of tx_flags)
9764 BVC scan_port_list ; Not broadcast -- skip CR2 setup
9766 LDA #7 ; CR2=&07: broadcast prep
9768 STA econet_control23_or_status2 ; Write CR2: broadcast frame prep
976B .scan_port_list←1← 9764 BVC
BIT rx_flags ; Check if RX port list active (bit7)
976E BPL try_nfs_port_list ; No active ports -- try NFS workspace
9770 LDA #&c0 ; Start scanning port list at page &C0
9772 LDY #0 ; Y=0: start offset within each port slot
9774 .scan_nfs_port_list←1← 97B7 BNE
STA port_ws_offset ; Store page to workspace pointer low
9776 STY rx_buf_offset ; Store page high byte for slot scanning
9778 .check_port_slot←1← 97A9 BCC
LDY #0 ; Y=0: read control byte from start of slot
977A .scout_ctrl_check
LDA (port_ws_offset),y ; Read port control byte from slot
977C BEQ discard_no_match ; Zero = end of port list, no match
977E CMP #&7f ; &7F = any-port wildcard
9780 BNE next_port_slot ; Not wildcard -- check specific port match
9782 INY ; Y=1: advance to port byte in slot
9783 LDA (port_ws_offset),y ; Read port number from slot (offset 1)
9785 BEQ check_station_filter ; Zero port in slot = match any port
9787 CMP rx_port ; Check if port matches this slot
978A BNE next_port_slot ; Port mismatch -- try next slot
978C .check_station_filter←1← 9785 BEQ
INY ; Y=2: advance to station byte
978D LDA (port_ws_offset),y ; Read station filter from slot (offset 2)
978F BEQ port_match_found ; Zero station = match any station, accept
9791 CMP rx_src_stn ; Check if source station matches
9794 BNE next_port_slot ; Station mismatch -- try next slot
9796 .scout_port_match
INY ; Y=3: advance to network byte
9797 LDA (port_ws_offset),y ; Read network filter from slot (offset 3)
9799 CMP rx_src_net ; Check if source network matches
979C BEQ port_match_found ; Network matches or zero = accept
979E .next_port_slot←3← 9780 BNE← 978A BNE← 9794 BNE
LDA rx_buf_offset ; Check if NFS workspace search pending
97A0 BEQ try_nfs_port_list ; No NFS workspace -- try fallback path
97A2 LDA port_ws_offset ; Load current slot base address
97A4 CLC ; CLC for 12-byte slot advance
97A5 ADC #&0c ; Advance to next 12-byte port slot
97A7 STA port_ws_offset ; Update workspace pointer to next slot
97A9 BCC check_port_slot ; Always branches (page &C0 won't overflow)
97AB .discard_no_match←2← 977C BEQ← 97B1 BVC
JMP nmi_error_dispatch ; No match found -- discard frame
97AE .try_nfs_port_list←2← 976E BPL← 97A0 BEQ
BIT rx_flags ; Try NFS workspace if paged list exhausted
97B1 BVC discard_no_match ; No NFS workspace RX (bit6 clear) -- discard
97B3 LDA #0 ; NFS workspace starts at offset 0 in page
97B5 LDY nfs_workspace_hi ; NFS workspace high byte for port list
97B7 BNE scan_nfs_port_list ; Scan NFS workspace port list
97B9 .port_match_found←3← 978F BEQ← 979C BEQ← 9AA7 JMP
LDA #3 ; Match found: set scout_status = 3
97BB STA scout_status ; Record match for completion handler
97BE JSR tx_calc_transfer ; Calculate transfer parameters
97C1 BCC nmi_error_dispatch ; C=0: no Tube claimed -- discard
97C3 BIT tx_flags ; Check broadcast flag for ACK path
97C6 BVC send_data_rx_ack ; Not broadcast -- normal ACK path
97C8 JMP copy_scout_to_buffer ; Broadcast: different completion path
97CB .send_data_rx_ack←2← 97C6 BVC← 9A9C JMP
LDA #&44 ; CR1=&44: RX_RESET | TIE
97CD STA econet_control1_or_status1 ; Write CR1: TX mode for ACK
97D0 LDA #&a7 ; CR2=&A7: RTS | CLR_TX_ST | FC_TDRA | PSE
97D2 STA econet_control23_or_status2 ; Write CR2: enable TX with PSE
97D5 LDA #&dc ; Install data_rx_setup at &97DC
97D7 LDY #&97 ; High byte of data_rx_setup handler
97D9 JMP ack_tx_write_dest ; Send ACK with data_rx_setup as next NMI
97DC .data_rx_setup
LDA #&82 ; CR1=&82: TX_RESET | RIE (switch to RX for data frame)
97DE STA econet_control1_or_status1 ; Write CR1: switch to RX for data frame
97E1 LDA #&e6 ; Install nmi_data_rx at &97E6
97E3 JMP install_nmi_handler ; Install nmi_data_rx and return from NMI

Data frame RX handler (four-way handshake)

Receives the data frame after the scout ACK has been sent. First checks AP (Address Present) for the start of the data frame. Reads and validates the first two address bytes (dest_stn, dest_net) against our station address, then installs continuation handlers to read the remaining data payload into the open port buffer.

Handler chain: &97E6 (AP+addr check) -> &97FA (net=0 check) -> &9810 (skip ctrl+port) -> &9843 (bulk data read) -> &9877 (completion)

97E6 .nmi_data_rx
LDA #1 ; A=&01: mask for AP (Address Present)
97E8 BIT econet_control23_or_status2 ; BIT SR2: test AP bit
97EB BEQ nmi_error_dispatch ; No AP: wrong frame or error
97ED LDA econet_data_continue_frame ; Read first byte (dest station)
97F0 CMP station_id_disable_net_nmis ; Compare to our station ID (INTOFF)
97F3 BNE nmi_error_dispatch ; Not for us: error path
97F5 LDA #&fa ; Install net check handler at &97FA
97F7 JMP install_nmi_handler ; Set NMI vector via RAM shim
97FA .nmi_data_rx_net
BIT econet_control23_or_status2 ; Validate source network = 0
97FD BPL nmi_error_dispatch ; SR2 bit7 clear: no data ready -- error
97FF LDA econet_data_continue_frame ; Read dest network byte
9802 BNE nmi_error_dispatch ; Network != 0: wrong network -- error
9804 LDA #&10 ; Install skip handler at &9810
9806 LDY #&98 ; High byte of &9810 handler
9808 BIT econet_control1_or_status1 ; SR1 bit7: IRQ, data already waiting
980B BMI nmi_data_rx_skip ; Data ready: skip directly, no RTI
980D JMP set_nmi_vector ; Install handler and return via RTI
9810 .nmi_data_rx_skip←1← 980B BMI
BIT econet_control23_or_status2 ; Skip control and port bytes (already known from scout)
9813 BPL nmi_error_dispatch ; SR2 bit7 clear: error
9815 LDA econet_data_continue_frame ; Discard control byte
9818 LDA econet_data_continue_frame ; Discard port byte
fall through ↓

Install data RX bulk or Tube handler

Selects either the normal bulk RX handler (&9843) or the Tube RX handler (&98A0) based on the Tube transfer flag in tx_flags, and installs the appropriate NMI handler.

981B .install_data_rx_handler←1← 9E9E JMP
LDA #2 ; A=2: Tube transfer flag mask
981D BIT tx_flags ; Check if Tube transfer active
9820 BNE install_tube_rx ; Tube active: use Tube RX path
9822 LDA #&43 ; Install bulk read at &9843
9824 LDY #&98 ; High byte of &9843 handler
9826 BIT econet_control1_or_status1 ; SR1 bit7: more data already waiting?
9829 BMI nmi_data_rx_bulk ; Yes: enter bulk read directly
982B JMP set_nmi_vector ; No: install handler and RTI
982E .install_tube_rx←1← 9820 BNE
LDA #&a0 ; Tube: install Tube RX at &98A0
9830 LDY #&98 ; High byte of &98A0 handler
9832 JMP set_nmi_vector ; Install Tube handler and RTI

NMI error handler dispatch

Common error/abort entry used by 12 call sites. Checks tx_flags bit 7: if clear, does a full ADLC reset and returns to idle listen (RX error path); if set, jumps to tx_result_fail (TX not-listening path).

9835 .nmi_error_dispatch←12← 97AB JMP← 97C1 BCC← 97EB BEQ← 97F3 BNE← 97FD BPL← 9802 BNE← 9813 BPL← 9856 BEQ← 9888 BEQ← 988E BEQ← 994B JMP← 9A76 JMP
LDA tx_flags ; Check tx_flags for error path
9838 BPL rx_error ; Bit7 clear: RX error path
983A JMP tx_result_fail ; Bit7 set: TX result = not listening
983D .rx_error←1← 9838 BPL
.rx_error_reset←1← 9838 BPL
JSR adlc_full_reset ; Full ADLC reset on RX error
9840 JMP discard_reset_listen ; Discard and return to idle listen

Data frame bulk read loop

Reads data payload bytes from the RX FIFO and stores them into the open port buffer at (open_port_buf),Y. Reads bytes in pairs (like the scout data loop), checking SR2 between each pair. SR2 non-zero (FV or other) -> frame completion at &9877. SR2 = 0 -> RTI, wait for next NMI to continue.

9843 .nmi_data_rx_bulk←1← 9829 BMI
LDY port_buf_len ; Y = buffer offset, resume from last position
9845 LDA econet_control23_or_status2 ; Read SR2 for next pair
9848 .data_rx_loop←1← 9872 BNE
BPL data_rx_complete ; SR2 bit7 clear: frame complete (FV)
984A LDA econet_data_continue_frame ; Read first byte of pair from RX FIFO
984D STA (open_port_buf),y ; Store byte to buffer
984F INY ; Advance buffer offset
9850 BNE read_sr2_between_pairs ; Y != 0: no page boundary crossing
9852 INC open_port_buf_hi ; Crossed page: increment buffer high byte
9854 DEC port_buf_len_hi ; Decrement remaining page count
9856 BEQ nmi_error_dispatch ; No pages left: handle as complete
9858 .read_sr2_between_pairs←1← 9850 BNE
LDA econet_control23_or_status2 ; Read SR2 between byte pairs
985B BMI read_second_rx_byte ; SR2 bit7 set: more data available
985D BNE data_rx_complete ; SR2 non-zero, bit7 clear: frame done
985F .read_second_rx_byte←1← 985B BMI
LDA econet_data_continue_frame ; Read second byte of pair from RX FIFO
9862 STA (open_port_buf),y ; Store byte to buffer
9864 INY ; Advance buffer offset
9865 STY port_buf_len ; Save updated buffer position
9867 BNE check_sr2_loop_again ; Y != 0: no page boundary crossing
9869 INC open_port_buf_hi ; Crossed page: increment buffer high byte
986B DEC port_buf_len_hi ; Decrement remaining page count
986D BEQ data_rx_complete ; No pages left: frame complete
986F .check_sr2_loop_again←1← 9867 BNE
LDA econet_control23_or_status2 ; Read SR2 for next iteration
9872 BNE data_rx_loop ; SR2 non-zero: more data, loop back
9874 JMP nmi_rti ; SR2=0: no more data yet, wait for NMI

Data frame completion

Reached when SR2 non-zero during data RX (FV detected). Same pattern as scout completion (&9738): disables PSE (CR2=&84, CR1=&00), then tests FV and RDA. If FV+RDA, reads the last byte. If extra data available and buffer space remains, stores it. Proceeds to send the final ACK via &98EE.

9877 .data_rx_complete←3← 9848 BPL← 985D BNE← 986D BEQ
LDA #&84 ; CR2=&84: disable PSE for bit testing
9879 STA econet_control23_or_status2 ; Write CR2
987C LDA #0 ; CR1=&00: disable all interrupts
987E STA econet_control1_or_status1 ; Write CR1
9881 STY port_buf_len ; Save Y (byte count from data RX loop)
9883 LDA #2 ; A=&02: FV mask
9885 BIT econet_control23_or_status2 ; BIT SR2: test FV (Z) and RDA (N)
9888 BEQ nmi_error_dispatch ; No FV -- error
988A BPL send_ack ; FV set, no RDA -- proceed to ACK
988C LDA port_buf_len_hi ; Check if buffer space remains
988E .read_last_rx_byte←3← 98AB BEQ← 98D2 BEQ← 98DE BEQ
BEQ nmi_error_dispatch ; No buffer space: error/discard frame
9890 LDA econet_data_continue_frame ; FV+RDA: read and store last data byte
9893 LDY port_buf_len ; Y = current buffer write offset
9895 STA (open_port_buf),y ; Store last byte in port receive buffer
9897 INC port_buf_len ; Advance buffer write offset
9899 BNE send_ack ; No page wrap: proceed to send ACK
989B INC open_port_buf_hi ; Page boundary: advance buffer page
989D .send_ack←2← 988A BPL← 9899 BNE
JMP ack_tx ; Send ACK frame to complete handshake
98A0 .nmi_data_rx_tube
LDA econet_control23_or_status2 ; Read SR2 for Tube data receive path
98A3 .rx_tube_data←1← 98BE BNE
BPL data_rx_tube_complete ; RDA clear: no more data, frame complete
98A5 LDA econet_data_continue_frame ; Read data byte from ADLC RX FIFO
98A8 JSR inc_buf_counter_32 ; Check buffer limits and transfer size
98AB BEQ read_last_rx_byte ; Zero: buffer full, handle as error
98AD STA tube_data_register_3 ; Send byte to Tube data register 3
98B0 LDA econet_data_continue_frame ; Read second data byte (paired transfer)
98B3 STA tube_data_register_3 ; Send second byte to Tube
98B6 JSR inc_buf_counter_32 ; Check limits after byte pair
98B9 BEQ data_rx_tube_complete ; Zero: Tube transfer complete
98BB LDA econet_control23_or_status2 ; Re-read SR2 for next byte pair
98BE BNE rx_tube_data ; More data available: continue loop
98C0 .data_rx_tube_error
JMP nmi_rti ; Unexpected end: return from NMI
98C3 .data_rx_tube_complete←2← 98A3 BPL← 98B9 BEQ
LDA #0 ; CR1=&00: disable all interrupts
98C5 STA econet_control1_or_status1 ; Write CR1 for individual bit testing
98C8 LDA #&84 ; CR2=&84: disable PSE
98CA STA econet_control23_or_status2 ; Write CR2: same pattern as main path
98CD LDA #2 ; A=&02: FV mask for Tube completion
98CF BIT econet_control23_or_status2 ; BIT SR2: test FV (Z) and RDA (N)
98D2 BEQ read_last_rx_byte ; No FV: incomplete frame, error
98D4 BPL ack_tx ; FV set, no RDA: proceed to ACK
98D6 LDA port_buf_len ; Check if any buffer was allocated
98D8 ORA port_buf_len_hi ; OR all 4 buffer pointer bytes together
98DA ORA open_port_buf ; Check buffer low byte
98DC ORA open_port_buf_hi ; Check buffer high byte
98DE BEQ read_last_rx_byte ; All zero (null buffer): error
98E0 LDA econet_data_continue_frame ; Read extra trailing byte from FIFO
98E3 STA rx_extra_byte ; Save extra byte at &0D5D for later use
98E6 LDA #&20 ; Bit5 = extra data byte available flag
98E8 ORA tx_flags ; Set extra byte flag in tx_flags
98EB STA tx_flags ; Store updated flags
fall through ↓

ACK transmission

Sends a scout ACK or final ACK frame as part of the four-way handshake. If bit7 of &0D4A is set, this is a final ACK -> completion (&9EA8). Otherwise, configures for TX (CR1=&44, CR2=&A7) and sends the ACK frame (dst_stn, dst_net from &0D3D, src_stn from &FE18, src_net=0). The ACK frame has no data payload -- just address bytes.

After writing the address bytes to the TX FIFO, installs the next NMI handler from &0D4B/&0D4C (saved by the scout/data RX handler) and sends TX_LAST_DATA (CR2=&3F) to close the frame.

98EE .ack_tx←2← 989D JMP← 98D4 BPL
LDA tx_flags ; Load TX flags to check ACK type
98F1 BPL ack_tx_configure ; Bit7 clear: normal scout ACK
98F3 JSR advance_rx_buffer_ptr ; Final ACK: call completion handler
98F6 JMP tx_result_ok ; Jump to TX success result
98F9 .ack_tx_configure←1← 98F1 BPL
LDA #&44 ; CR1=&44: RX_RESET | TIE (switch to TX mode)
98FB STA econet_control1_or_status1 ; Write CR1: switch to TX mode
98FE LDA #&a7 ; CR2=&A7: RTS|CLR_TX_ST|FC_TDRA|2_1_BYTE|PSE
9900 STA econet_control23_or_status2 ; Write CR2: enable TX with status clear
9903 LDA #&95 ; Install saved next handler (&99BB for scout ACK)
9905 LDY #&99 ; High byte of post-ACK handler
9907 .ack_tx_write_dest←2← 97D9 JMP← 9AE4 JMP
STA nmi_next_lo ; Store next handler low byte
990A STY nmi_next_hi ; Store next handler high byte
990D LDA rx_src_stn ; Load dest station from RX scout buffer
9910 BIT econet_control1_or_status1 ; BIT SR1: test TDRA (V=bit6)
9913 BVC dispatch_nmi_error ; TDRA not ready -- error
9915 STA econet_data_continue_frame ; Write dest station to TX FIFO
9918 LDA rx_src_net ; Write dest network to TX FIFO
991B STA econet_data_continue_frame ; Write dest net byte to FIFO
991E LDA #&25 ; Install nmi_ack_tx_src at &9925
9920 LDY #&99 ; High byte of nmi_ack_tx_src
9922 JMP set_nmi_vector ; Set NMI vector to ack_tx_src handler

ACK TX continuation

Writes source station and network to TX FIFO, completing the 4-byte ACK frame. Then sends TX_LAST_DATA (CR2=&3F) to close the frame.

9925 .nmi_ack_tx_src
LDA station_id_disable_net_nmis ; Load our station ID (also INTOFF)
9928 BIT econet_control1_or_status1 ; BIT SR1: test TDRA
992B BVC dispatch_nmi_error ; TDRA not ready -- error
992D STA econet_data_continue_frame ; Write our station to TX FIFO
9930 LDA #0 ; Write network=0 to TX FIFO
9932 STA econet_data_continue_frame ; Write network=0 (local) to TX FIFO
9935 LDA tx_flags ; Check tx_flags for data phase
9938 BMI start_data_tx ; bit7 set: start data TX phase
993A LDA #&3f ; CR2=&3F: TX_LAST_DATA | CLR_RX_ST | FLAG_IDLE | FC_TDRA | 2_1_BYTE | PSE
fall through ↓

Post-ACK scout processing

Called after the scout ACK has been transmitted. Processes the received scout data stored in the buffer at &0D3D-&0D48. Checks the port byte (&0D40) against open receive blocks to find a matching listener. If a match is found, sets up the data RX handler chain for the four-way handshake data phase. If no match, discards the frame.

993C .post_ack_scout
STA econet_control23_or_status2 ; Write CR2 to clear status after ACK TX
993F LDA nmi_next_lo ; Install saved handler from &0D4B/&0D4C
9942 LDY nmi_next_hi ; Load saved next handler high byte
9945 JMP set_nmi_vector ; Install next NMI handler
9948 .start_data_tx←1← 9938 BMI
JMP data_tx_begin ; Jump to start data TX phase
994B .dispatch_nmi_error←2← 9913 BVC← 992B BVC
JMP nmi_error_dispatch ; Jump to error handler

Advance RX buffer pointer after transfer

Adds the transfer count to the RXCB buffer pointer (4-byte addition). If a Tube transfer is active, re-claims the Tube address and sends the extra RX byte via R3, incrementing the Tube pointer by 1.

994E .advance_rx_buffer_ptr←2← 98F3 JSR← 99A4 JSR
LDA #2 ; A=2: test bit1 of tx_flags
9950 BIT tx_flags ; BIT tx_flags: check data transfer bit
9953 BEQ return_10 ; Bit1 clear: no transfer -- return
9955 CLC ; CLC: init carry for 4-byte add
9956 PHP ; Save carry on stack for loop
9957 LDY #8 ; Y=8: RXCB high pointer offset
9959 .add_rxcb_ptr←1← 9965 BCC
LDA (port_ws_offset),y ; Load RXCB[Y] (buffer pointer byte)
995B PLP ; Restore carry from stack
995C ADC net_tx_ptr,y ; Add transfer count byte
995F STA (port_ws_offset),y ; Store updated pointer back to RXCB
9961 INY ; Next byte
9962 PHP ; Save carry for next iteration
9963 CPY #&0c ; Done 4 bytes? (Y reaches &0C)
9965 BCC add_rxcb_ptr ; No: continue adding
9967 PLP ; Discard final carry
9968 LDA #&20 ; A=&20: test bit5 of tx_flags
996A BIT tx_flags ; BIT tx_flags: check Tube bit
996D BEQ skip_tube_update ; No Tube: skip Tube update
996F TXA ; Save X on stack
9970 PHA ; Push X
9971 LDA #8 ; A=8: offset for Tube address
9973 CLC ; CLC for address calculation
9974 ADC port_ws_offset ; Add workspace base offset
9976 TAX ; X = address low for Tube claim
9977 LDY rx_buf_offset ; Y = address high for Tube claim
9979 LDA #1 ; A=1: Tube claim type (read)
997B JSR tube_addr_claim ; Claim Tube address for transfer
997E LDA rx_extra_byte ; Load extra RX data byte
9981 STA tube_data_register_3 ; Send to Tube via R3
9984 SEC ; SEC: init carry for increment
9985 LDY #8 ; Y=8: start at high pointer
9987 .inc_rxcb_ptr←1← 998E BCS
LDA #0 ; A=0: add carry only (increment)
9989 ADC (port_ws_offset),y ; Add carry to pointer byte
998B STA (port_ws_offset),y ; Store back to RXCB
998D INY ; Next byte
998E BCS inc_rxcb_ptr ; Keep going while carry propagates
9990 PLA ; Restore X from stack
9991 TAX ; Transfer to X register
9992 .skip_tube_update←1← 996D BEQ
LDA #&ff ; A=&FF: return value (transfer done)
9994 .return_10←1← 9953 BEQ
RTS ; Return
9995 LDA rx_port ; Load received port byte
9998 BNE rx_complete_update_rxcb ; Port != 0: data transfer frame
999A LDY rx_ctrl ; Port=0: load control byte
999D CPY #&82 ; Ctrl = &82 (POKE)?
999F BEQ rx_complete_update_rxcb ; Yes: POKE also needs data transfer
99A1 JMP imm_op_build_reply ; Other port-0 ops: immediate dispatch

Complete RX and update RXCB

Post-scout completion for data transfer frames (port != 0) and POKE (ctrl=&82). Calls advance_rx_buffer_ptr, updates the open port buffer address, then writes source station/ network, port, and control byte into the RXCB.

99A4 .rx_complete_update_rxcb←3← 9998 BNE← 999F BEQ← 9A16 JMP
JSR advance_rx_buffer_ptr ; Update buffer pointer and check for Tube
99A7 BNE skip_buf_ptr_update ; Transfer not done: skip buffer update
99A9 .add_buf_to_base
LDA port_buf_len ; Load buffer bytes remaining
99AB CLC ; CLC for address add
99AC ADC open_port_buf ; Add to buffer base address
99AE BCC store_buf_ptr_lo ; No carry: skip high byte increment
99B0 .inc_rxcb_buf_hi
INC open_port_buf_hi ; Carry: increment buffer high byte
99B2 .store_buf_ptr_lo←1← 99AE BCC
LDY #8 ; Y=8: store updated buffer position
99B4 .store_rxcb_buf_ptr
STA (port_ws_offset),y ; Store updated low byte to RXCB
99B6 INY ; Y=9: buffer high byte offset
99B7 LDA open_port_buf_hi ; Load updated buffer high byte
99B9 .store_rxcb_buf_hi
STA (port_ws_offset),y ; Store high byte to RXCB
99BB .skip_buf_ptr_update←1← 99A7 BNE
LDA rx_port ; Check port byte again
99BE BEQ discard_reset_listen ; Port=0: immediate op, discard+listen
99C0 LDA rx_src_net ; Load source network from scout buffer
99C3 LDY #3 ; Y=3: RXCB source network offset
99C5 STA (port_ws_offset),y ; Store source network to RXCB
99C7 DEY ; Y=2: source station offset Y=&02
99C8 LDA rx_src_stn ; Load source station from scout buffer
99CB STA (port_ws_offset),y ; Store source station to RXCB
99CD DEY ; Y=1: port byte offset Y=&01
99CE LDA rx_port ; Load port byte
99D1 STA (port_ws_offset),y ; Store port to RXCB
99D3 DEY ; Y=0: control/flag byte offset Y=&00
99D4 LDA rx_ctrl ; Load control byte from scout
99D7 ORA #&80 ; Set bit7 = reception complete flag
99D9 STA (port_ws_offset),y ; Store to RXCB (marks CB as complete)
fall through ↓

Discard with Tube release

Conditionally releases the Tube co-processor before discarding. If tx_flags bit 1 is set (Tube transfer was active), calls sub_c9a2b to release the Tube claim, then falls through to discard_listen. The main teardown path for RX operations that used the Tube.

99DB .discard_reset_listen←4← 9840 JMP← 99BE BEQ← 9E4D JMP← 9EB7 JMP
LDA #2 ; Tube flag bit 1 AND tx_flags bit 1
99DD AND tube_flag ; Check if Tube transfer active
99E0 BIT tx_flags ; Test tx_flags for Tube transfer
99E3 BEQ discard_listen ; No Tube transfer active -- skip release
99E5 JSR release_tube ; Release Tube claim before discarding
fall through ↓

Discard frame and return to idle listen

Calls adlc_rx_listen to re-enter idle RX mode (CR1=&82, CR2=&67), then installs nmi_rx_scout (&96BF) as the NMI handler via set_nmi_vector. Returns to the caller's NMI context. Used as the common discard tail for both gentle rejection (wrong station/network) and error recovery paths.

99E8 .discard_listen←4← 970B JMP← 99E3 BEQ← 9A61 BCS← 9B1D JMP
JSR adlc_rx_listen ; Re-enter idle RX listen mode
fall through ↓

Install RX scout NMI handler

Installs nmi_rx_scout (&96BF) as the NMI handler via set_nmi_vector, without first calling adlc_rx_listen. Used when the ADLC is already in the correct RX mode.

99EB .install_rx_scout_handler←2← 96EF JMP← 9708 JMP
LDA #&bf ; Install nmi_rx_scout (&96BF) as NMI handler
99ED LDY #&96 ; High byte of nmi_rx_scout
99EF JMP set_nmi_vector ; Set NMI vector and return

Copy scout data to port buffer

Copies scout data bytes (offsets 4-11) from the RX scout buffer into the open port buffer, handling both direct memory and Tube R3 write paths.

99F2 .copy_scout_to_buffer←1← 97C8 JMP
TXA ; Save X on stack
99F3 PHA ; Push X
99F4 LDX #4 ; X=4: start at scout byte offset 4
99F6 LDA #2 ; A=2: Tube transfer check mask
99F8 BIT tx_flags ; BIT tx_flags: check Tube bit
99FB BNE copy_scout_via_tube ; Tube active: use R3 write path
99FD LDY port_buf_len ; Y = current buffer position
99FF .copy_scout_bytes←1← 9A12 BNE
LDA rx_src_stn,x ; Load scout data byte
9A02 STA (open_port_buf),y ; Store to port buffer
9A04 INY ; Advance buffer pointer
9A05 BNE next_scout_byte ; No page crossing
9A07 INC open_port_buf_hi ; Page crossing: inc buffer high byte
9A09 DEC port_buf_len_hi ; Decrement remaining page count
9A0B BEQ scout_page_overflow ; No pages left: overflow
9A0D .next_scout_byte←1← 9A05 BNE
INX ; Next scout data byte
9A0E STY port_buf_len ; Save updated buffer position
9A10 CPX #&0c ; Done all scout data? (X reaches &0C)
9A12 BNE copy_scout_bytes ; No: continue copying
9A14 .scout_copy_done←2← 9A29 BEQ← 9A72 BEQ
PLA ; Restore X from stack
9A15 TAX ; Transfer to X register
9A16 JMP rx_complete_update_rxcb ; Jump to completion handler
9A19 .copy_scout_via_tube←2← 99FB BNE← 9A27 BNE
LDA rx_src_stn,x ; Tube path: load scout data byte
9A1C STA tube_data_register_3 ; Send byte to Tube via R3
9A1F JSR inc_buf_counter_32 ; Increment buffer position counters
9A22 BEQ check_scout_done ; Counter overflow: handle end of buffer
9A24 INX ; Next scout data byte
9A25 CPX #&0c ; Done all scout data?
9A27 BNE copy_scout_via_tube ; No: continue Tube writes
9A29 BEQ scout_copy_done ; ALWAYS branch

Release Tube co-processor claim

If need_release_tube bit 7 is clear (Tube is claimed), calls tube_addr_claim with A=&82 to release it, then clears the release flag via LSR.

9A2B .release_tube←2← 99E5 JSR← 9F12 JSR
BIT need_release_tube ; Check if Tube needs releasing
9A2D BMI clear_release_flag ; Bit7 set: already released
9A2F LDA #&82 ; A=&82: Tube release claim type
9A31 JSR tube_addr_claim ; Release Tube address claim
9A34 .clear_release_flag←1← 9A2D BMI
LSR need_release_tube ; Clear release flag (LSR clears bit7)
9A36 RTS ; Return

Increment 32-bit buffer counter

Increments a 4-byte counter across port_buf_len / port_buf_len_hi / open_port_buf / open_port_buf_hi with carry propagation. Returns Z=1 if the counter wraps to zero.

9A37 .inc_buf_counter_32←3← 98A8 JSR← 98B6 JSR← 9A1F JSR
INC port_buf_len ; Increment buffer position (4-byte)
9A39 BNE return_inc_port_buf ; Low byte didn't wrap: done
9A3B INC port_buf_len_hi ; Carry into second byte
9A3D BNE return_inc_port_buf ; No further carry: done
9A3F INC open_port_buf ; Carry into third byte
9A41 BNE return_inc_port_buf ; No further carry: done
9A43 INC open_port_buf_hi ; Carry into fourth byte
9A45 .return_inc_port_buf←3← 9A39 BNE← 9A3D BNE← 9A41 BNE
RTS ; Return

Immediate operation handler (port = 0)

Handles immediate (non-data-transfer) operations received via scout frames with port byte = 0. The control byte (&0D3F) determines the operation type: &81 = PEEK (read memory) &82 = POKE (write memory) &83 = JSR (remote procedure call) &84 = user procedure &85 = OS procedure &86 = HALT &87 = CONTINUE The protection mask (LSTAT at &D63) controls which operations are permitted — each bit enables or disables an operation type. If the operation is not permitted by the mask, it is silently ignored. LSTAT can be read/set via OSWORD &12 sub-functions 4/5.

9A46 .immediate_op←1← 975E JMP
LDY rx_ctrl ; Control byte &81-&88 range check
9A49 CPY #&81 ; Below &81: not an immediate op
9A4B BCC imm_op_out_of_range ; Out of range low: jump to discard
9A4D CPY #&89 ; Above &88: not an immediate op
9A4F BCS imm_op_out_of_range ; Out of range high: jump to discard
9A51 CPY #&87 ; HALT(&87)/CONTINUE(&88) skip protection
9A53 BCS dispatch_imm_op ; Ctrl >= &87: dispatch without mask check
9A55 TYA ; Convert ctrl byte to 0-based index for mask
9A56 SEC ; SEC for subtract
9A57 SBC #&81 ; A = ctrl - &81 (0-based operation index)
9A59 TAY ; Y = index for mask rotation count
9A5A LDA prot_status ; Load protection mask from LSTAT
9A5D .rotate_prot_mask←1← 9A5F BPL
ROR ; Rotate mask right by control byte index
9A5E DEY ; Decrement rotation counter
9A5F BPL rotate_prot_mask ; Loop until bit aligned
9A61 BCS discard_listen ; Bit set = operation disabled, discard
9A63 .dispatch_imm_op←1← 9A53 BCS
LDY rx_ctrl ; Reload ctrl byte for dispatch table
9A66 LDA #&9a ; Hi byte: all handlers are in page &9A
9A68 PHA ; Push hi byte for PHA/PHA/RTS dispatch
9A69 LDA imm_op_dispatch_lo-&81,y ; Load handler low byte from jump table
9A6C PHA ; Push handler low byte
9A6D RTS ; RTS dispatches to handler
9A6E .scout_page_overflow←1← 9A0B BEQ
INC port_buf_len ; Increment port buffer length
9A70 .check_scout_done←1← 9A22 BEQ
CPX #&0b ; Check if scout data index reached 11
9A72 BEQ scout_copy_done ; Yes: loop back to continue reading
9A74 PLA ; Restore A from stack
9A75 TAX ; Transfer to X
9A76 .imm_op_out_of_range←2← 9A4B BCC← 9A4F BCS
JMP nmi_error_dispatch ; Jump to discard handler
9A79 .imm_op_dispatch_lo
EQUB <(rx_imm_peek-1) ; Ctrl &81: PEEK
9A7A EQUB <(rx_imm_poke-1) ; Ctrl &82: POKE
9A7B EQUB <(rx_imm_exec-1) ; Ctrl &83: JSR
9A7C EQUB <(rx_imm_exec-1) ; Ctrl &84: UserProc
9A7D EQUB <(rx_imm_exec-1) ; Ctrl &85: OSProc
9A7E EQUB <(rx_imm_halt_cont-1) ; Ctrl &86: HALT
9A7F EQUB <(rx_imm_halt_cont-1) ; Ctrl &87: CONTINUE
9A80 EQUB <(rx_imm_machine_type-1) ; Ctrl &88: machine type query

RX immediate: JSR/UserProc/OSProc setup

Sets up the port buffer to receive remote procedure data. Copies the 4-byte remote address from rx_remote_addr into the execution address workspace at &0D58, then jumps to the common receive path at c9826. Used for operation types &83 (JSR), &84 (UserProc), and &85 (OSProc).

9A81 .rx_imm_exec
LDA #0 ; A=0: port buffer lo at page boundary
9A83 STA open_port_buf ; Set port buffer lo
9A85 LDA #&82 ; Buffer length lo = &82
9A87 STA port_buf_len ; Set buffer length lo
9A89 LDA #1 ; Buffer length hi = 1
9A8B STA port_buf_len_hi ; Set buffer length hi
9A8D LDA net_rx_ptr_hi ; Load RX page hi for buffer
9A8F STA open_port_buf_hi ; Set port buffer hi
9A91 LDY #3 ; Y=3: copy 4 bytes (3 down to 0)
9A93 .copy_addr_loop←1← 9A9A BPL
LDA rx_remote_addr,y ; Load remote address byte
9A96 STA l0d58,y ; Store to exec address workspace
9A99 DEY ; Next byte (descending)
9A9A BPL copy_addr_loop ; Loop until all 4 bytes copied
9A9C .sub_c9a9c
JMP send_data_rx_ack ; Enter common data-receive path Immediate op dispatch lo bytes

RX immediate: POKE setup

Sets up workspace offsets for receiving POKE data. port_ws_offset=&3D, rx_buf_offset=&0D, then jumps to the common data-receive path at port_match_found.

9A9F .rx_imm_poke
LDA #&3d ; Port workspace offset = &3D
9AA1 STA port_ws_offset ; Store workspace offset lo
9AA3 LDA #&0d ; RX buffer page = &0D
9AA5 STA rx_buf_offset ; Store workspace offset hi
9AA7 JMP port_match_found ; Enter POKE data-receive path

RX immediate: machine type query

Sets up a response buffer (start &25, page &7F, length #&01FC) for the machine type query, then jumps to the query handler at set_tx_reply_flag. Returns system identification data to the remote station.

9AAA .rx_imm_machine_type
LDA #1 ; Buffer length hi = 1
9AAC STA port_buf_len_hi ; Set buffer length hi
9AAE LDA #&fc ; Buffer length lo = &FC
9AB0 STA port_buf_len ; Set buffer length lo
9AB2 LDA #&25 ; Buffer start lo = &25
9AB4 STA open_port_buf ; Set port buffer lo
9AB6 LDA #&7f ; Buffer hi = &7F (below screen)
9AB8 STA open_port_buf_hi ; Set port buffer hi
9ABA BNE set_tx_reply_flag ; ALWAYS branch

RX immediate: PEEK setup

Writes &0D3D to port_ws_offset/rx_buf_offset, sets scout_status=2, then calls tx_calc_transfer to send the PEEK response data back to the requesting station. Uses workspace offsets (&A6/&A7) for nmi_tx_block.

9ABC .rx_imm_peek
LDA #&3d ; Port workspace offset = &3D
9ABE STA port_ws_offset ; Store workspace offset lo
9AC0 LDA #&0d ; RX buffer page = &0D
9AC2 STA rx_buf_offset ; Store workspace offset hi
9AC4 LDA #2 ; Scout status = 2 (PEEK response)
9AC6 STA scout_status ; Store scout status
9AC9 JSR tx_calc_transfer ; Calculate transfer size for response
9ACC BCC imm_op_discard ; C=0: transfer not set up, discard
9ACE .set_tx_reply_flag←1← 9ABA BNE
LDA tx_flags ; Mark TX flags bit 7 (reply pending)
9AD1 ORA #&80 ; Set reply pending flag
9AD3 STA tx_flags ; Store updated TX flags
9AD6 .rx_imm_halt_cont
LDA #&44 ; CR1=&44: TIE | TX_LAST_DATA
9AD8 STA econet_control1_or_status1 ; Write CR1: enable TX interrupts
9ADB .tx_cr2_setup
LDA #&a7 ; CR2=&A7: RTS|CLR_RX_ST|FC_TDRA|PSE
9ADD STA econet_control23_or_status2 ; Write CR2 for TX setup
9AE0 .tx_nmi_setup
LDA #&fd ; NMI handler lo byte (self-modifying)
9AE2 LDY #&9a ; Y=&9A: dispatch table page
9AE4 JMP ack_tx_write_dest ; Acknowledge and write TX dest

Build immediate operation reply header

Stores data length, source station/network, and control byte into the RX buffer header area for port-0 immediate operations. Then disables SR interrupts and configures the VIA shift register for shift-in mode before returning to idle listen.

9AE7 .imm_op_build_reply←1← 99A1 JMP
LDA port_buf_len ; Get buffer position for reply header
9AE9 CLC ; Clear carry for offset addition
9AEA ADC #&80 ; Data offset = buf_len + &80 (past header)
9AEC LDY #&7f ; Y=&7F: reply data length slot
9AEE STA (net_rx_ptr),y ; Store reply data length in RX buffer
9AF0 LDY #&80 ; Y=&80: source station slot
9AF2 LDA rx_src_stn ; Load requesting station number
9AF5 STA (net_rx_ptr),y ; Store source station in reply header
9AF7 INY ; Y=&81
9AF8 LDA rx_src_net ; Load requesting network number
9AFB STA (net_rx_ptr),y ; Store source network in reply header
9AFD LDA rx_ctrl ; Load control byte from received frame
9B00 STA tx_work_57 ; Save ctrl byte for TX response
9B03 LDA #&84 ; IER bit 2: disable SR interrupt
9B05 STA system_via_ier ; Write IER to disable SR
9B08 LDA system_via_acr ; Read ACR for shift register config
9B0B AND #&1c ; Isolate shift register mode bits (2-4)
9B0D STA tx_work_51 ; Save original SR mode for later restore
9B10 LDA system_via_acr ; Reload ACR for modification
9B13 AND #&e3 ; Clear SR mode bits (keep other bits)
9B15 ORA #8 ; SR mode 2: shift in under φ2
9B17 STA system_via_acr ; Apply new shift register mode
9B1A BIT system_via_sr ; Read SR to clear pending interrupt
9B1D .imm_op_discard←1← 9ACC BCC
JMP discard_listen ; Return to idle listen mode
9B20 EQUS "$-;G^" ; Immediate op port number table

TX done: remote JSR execution

Pushes tx_done_exit-1 on the stack (so RTS returns to tx_done_exit), then does JMP (l0d58) to call the remote JSR target routine. When that routine returns via RTS, control resumes at tx_done_exit.

9B25 .tx_done_jsr
LDA #&9b ; Hi byte of tx_done_exit-1
9B27 PHA ; Push hi byte on stack
9B28 LDA #&66 ; Push lo of (tx_done_exit-1)
9B2A PHA ; Push lo byte on stack
9B2B JMP (l0d58) ; Call remote JSR; RTS to tx_done_exit

TX done: UserProc event

Generates a network event (event 8) via OSEVEN with X=l0d58, A=l0d59 (the remote address). This notifies the user program that a UserProc operation has completed.

9B2E .tx_done_user_proc
LDY #event_network_error ; Y=8: network event type
9B30 LDX l0d58 ; X = remote address lo
9B33 LDA l0d59 ; A = remote address hi
9B36 JSR oseven ; Generate event Y='Network error'
9B39 JMP tx_done_exit ; Exit TX done handler

TX done: OSProc call

Calls the ROM entry point at &8000 (rom_header) with X=l0d58, Y=l0d59. This invokes an OS-level procedure on behalf of the remote station.

9B3C .tx_done_os_proc
LDX l0d58 ; X = remote address lo
9B3F LDY l0d59 ; Y = remote address hi
9B42 JSR rom_header ; Call ROM entry point at &8000
9B45 JMP tx_done_exit ; Exit TX done handler

TX done: HALT

Sets bit 2 of rx_flags (&0D64), enables interrupts, and spin-waits until bit 2 is cleared (by a CONTINUE from the remote station). If bit 2 is already set, skips to exit.

9B48 .tx_done_halt
LDA #4 ; A=&04: bit 2 mask for rx_flags
9B4A BIT rx_flags ; Test if already halted
9B4D BNE tx_done_exit ; Already halted: skip to exit
9B4F ORA rx_flags ; Set bit 2 in rx_flags
9B52 STA rx_flags ; Store halt flag
9B55 LDA #4 ; A=4: re-load halt bit mask
9B57 CLI ; Enable interrupts during halt wait
9B58 .halt_spin_loop←1← 9B5B BNE
BIT rx_flags ; Test halt flag
9B5B BNE halt_spin_loop ; Still halted: keep spinning
9B5D BEQ tx_done_exit ; ALWAYS branch

TX done: CONTINUE

Clears bit 2 of rx_flags (&0D64), releasing any station that is halted and spinning in tx_done_halt.

9B5F .tx_done_continue
LDA rx_flags ; Load current RX flags
9B62 AND #&fb ; Clear bit 2: release halted station
9B64 STA rx_flags ; Store updated flags
9B67 .tx_done_exit←4← 9B39 JMP← 9B45 JMP← 9B4D BNE← 9B5D BEQ
PLA ; Restore Y from stack
9B68 TAY ; Transfer to Y register
9B69 PLA ; Restore X from stack
9B6A TAX ; Transfer to X register
9B6B LDA #0 ; A=0: success status
9B6D RTS ; Return with A=0 (success)

Begin TX operation

Main TX initiation entry point (called via trampoline at &06CE). Copies dest station/network from the TXCB to the scout buffer, dispatches to immediate op setup (ctrl >= &81) or normal data transfer, calculates transfer sizes, copies extra parameters, then enters the INACTIVE polling loop.

9B6E .tx_begin←1← 06CE JMP
TXA ; Save X on stack
9B6F PHA ; Push X
9B70 LDY #2 ; Y=2: TXCB offset for dest station
9B72 LDA (nmi_tx_block),y ; Load dest station from TX control block
9B74 STA tx_dst_stn ; Store to TX scout buffer
9B77 INY ; Y=&03
9B78 LDA (nmi_tx_block),y ; Load dest network from TX control block
9B7A STA tx_dst_net ; Store to TX scout buffer
9B7D LDY #0 ; Y=0: first byte of TX control block
9B7F LDA (nmi_tx_block),y ; Load control/flag byte
9B81 BMI tx_imm_op_setup ; Bit7 set: immediate operation ctrl byte
9B83 JMP tx_active_start ; Bit7 clear: normal data transfer
9B86 .tx_imm_op_setup←1← 9B81 BMI
STA tx_ctrl_byte ; Store control byte to TX scout buffer
9B89 TAX ; X = control byte for range checks
9B8A INY ; Y=1: port byte offset
9B8B LDA (nmi_tx_block),y ; Load port byte from TX control block
9B8D STA tx_port ; Store port byte to TX scout buffer
9B90 BNE tx_line_idle_check ; Port != 0: skip immediate op setup
9B92 CPX #&83 ; Ctrl < &83: PEEK/POKE need address calc
9B94 BCS tx_ctrl_range_check ; Ctrl >= &83: skip to range check
9B96 SEC ; SEC: init borrow for 4-byte subtract
9B97 PHP ; Save carry on stack for loop
9B98 LDY #8 ; Y=8: high pointer offset in TXCB
9B9A .calc_peek_poke_size←1← 9BAE BCC
LDA (nmi_tx_block),y ; Load TXCB[Y] (end addr byte)
9B9C DEY ; Y -= 4: back to start addr offset
9B9D DEY ; (continued)
9B9E DEY ; (continued)
9B9F DEY ; (continued)
9BA0 PLP ; Restore borrow from stack
9BA1 SBC (nmi_tx_block),y ; end - start = transfer size byte
9BA3 STA tx_data_start,y ; Store result to tx_data_start
9BA6 INY ; Y += 5: advance to next end byte
9BA7 INY ; (continued)
9BA8 INY ; (continued)
9BA9 INY ; (continued)
9BAA INY ; (continued)
9BAB PHP ; Save borrow for next byte
9BAC CPY #&0c ; Done all 4 bytes? (Y reaches &0C)
9BAE BCC calc_peek_poke_size ; No: next byte pair
9BB0 PLP ; Discard final borrow
9BB1 .tx_ctrl_range_check←1← 9B94 BCS
CPX #&81 ; Ctrl < &81: not an immediate op
9BB3 BCC tx_active_start ; Below range: normal data transfer
9BB5 .check_imm_range
CPX #&89 ; Ctrl >= &89: out of immediate range
9BB7 BCS tx_active_start ; Above range: normal data transfer
9BB9 LDY #&0c ; Y=&0C: start of extra data in TXCB
9BBB .copy_imm_params←1← 9BC3 BCC
LDA (nmi_tx_block),y ; Load extra parameter byte from TXCB
9BBD STA nmi_shim_1a,y ; Copy to NMI shim workspace at &0D1A+Y
9BC0 INY ; Next byte
9BC1 CPY #&10 ; Done 4 bytes? (Y reaches &10)
9BC3 BCC copy_imm_params ; No: continue copying
9BC5 .tx_line_idle_check←1← 9B90 BNE
LDA #&20 ; A=&20: mask for SR2 INACTIVE bit
9BC7 BIT econet_control23_or_status2 ; BIT SR2: test if line is idle
9BCA BNE tx_no_clock_error ; Line not idle: handle as line jammed
9BCC LDA #&fd ; A=&FD: high byte of timeout counter
9BCE PHA ; Push timeout high byte to stack
9BCF LDA #6 ; Scout frame = 6 address+ctrl bytes
9BD1 STA tx_length ; Store scout frame length
9BD4 LDA #0 ; A=0: init low byte of timeout counter
fall through ↓

INACTIVE polling loop

Polls SR2 for INACTIVE (bit2) to confirm the network line is idle before attempting transmission. Uses a 3-byte timeout counter on the stack. The timeout (~256^3 iterations) generates "Line Jammed" if INACTIVE never appears. The CTS check at &9BF4-&9BF9 works because CR2=&67 has RTS=0, so cts_input_ is always true, and SR1_CTS reflects presence of clock hardware.

9BD6 .inactive_poll
STA tx_index ; Save TX index
9BD9 PHA ; Push timeout byte 1 on stack
9BDA PHA ; Push timeout byte 2 on stack
9BDB LDY #&e7 ; Y=&E7: CR2 value for TX prep (RTS|CLR_TX_ST|CLR_RX_ST|FC_TDRA|2_1_BYTE| PSE)
9BDD .test_inactive_retry←3← 9C03 BNE← 9C08 BNE← 9C0D BNE
LDA #4 ; A=&04: INACTIVE mask for SR2 bit2
9BDF PHP ; Save interrupt state
9BE0 SEI ; Disable interrupts for ADLC access
fall through ↓

Disable NMIs and test INACTIVE

Mid-instruction label within the INACTIVE polling loop. The intoff_operand address (intoff_test_inactive+1) is referenced as a constant for self-modifying code. Disables NMIs twice (belt-and-braces) then tests SR2 for INACTIVE before proceeding with TX.

9BE1 .intoff_test_inactive
BIT station_id_disable_net_nmis ; INTOFF -- disable NMIs
9BE4 BIT station_id_disable_net_nmis ; INTOFF again (belt-and-braces)
9BE7 .test_line_idle
BIT econet_control23_or_status2 ; BIT SR2: Z = &04 AND SR2 -- tests INACTIVE
9BEA BEQ inactive_retry ; INACTIVE not set -- re-enable NMIs and loop
9BEC LDA econet_control1_or_status1 ; Read SR1 (acknowledge pending interrupt)
9BEF LDA #&67 ; CR2=&67: CLR_TX_ST|CLR_RX_ST|FC_ TDRA|2_1_BYTE|PSE
9BF1 STA econet_control23_or_status2 ; Write CR2: clear status, prepare TX
9BF4 LDA #&10 ; A=&10: CTS mask for SR1 bit4
9BF6 BIT econet_control1_or_status1 ; BIT SR1: tests CTS present
9BF9 BNE tx_prepare ; CTS set -- clock hardware detected, start TX
9BFB .inactive_retry←1← 9BEA BEQ
BIT video_ula_control ; INTON -- re-enable NMIs (&FE20 read)
9BFE PLP ; Restore interrupt state
9BFF TSX ; 3-byte timeout counter on stack
9C00 INC error_text,x ; Increment timeout counter byte 1
9C03 BNE test_inactive_retry ; Not overflowed: retry INACTIVE test
9C05 INC stk_timeout_mid,x ; Increment timeout counter byte 2
9C08 BNE test_inactive_retry ; Not overflowed: retry INACTIVE test
9C0A INC stk_frame_3,x ; Increment timeout counter byte 3
9C0D BNE test_inactive_retry ; Not overflowed: retry INACTIVE test
9C0F BEQ tx_line_jammed ; ALWAYS branch
; TX_ACTIVE branch (A=&44 = CR1 value for TX active)
9C11 .tx_active_start←3← 9B83 JMP← 9BB3 BCC← 9BB7 BCS
LDA #&44 ; CR1=&44: TIE | TX_LAST_DATA
9C13 BNE store_tx_error ; ALWAYS branch

TX timeout error handler (Line Jammed)

Writes CR2=&07 to abort TX, cleans 3 bytes from stack (the timeout loop's state), then stores error code &40 ("Line Jammed") into the TX control block and signals completion.

9C15 .tx_line_jammed←1← 9C0F BEQ
LDA #7 ; CR2=&07: FC_TDRA | 2_1_BYTE | PSE (abort TX)
9C17 STA econet_control23_or_status2 ; Write CR2 to abort TX
9C1A PLA ; Clean 3 bytes of timeout loop state
9C1B PLA ; Pop saved register
9C1C PLA ; Pop saved register
9C1D LDA #&40 ; Error &40 = 'Line Jammed'
9C1F BNE store_tx_error ; ALWAYS branch to shared error handler ALWAYS branch
9C21 .tx_no_clock_error←1← 9BCA BNE
LDA #&43 ; Error &43 = 'No Clock'
9C23 .store_tx_error←2← 9C13 BNE← 9C1F BNE
LDY #0 ; Offset 0 = error byte in TX control block
9C25 STA (nmi_tx_block),y ; Store error code in TX CB byte 0
9C27 LDA #&80 ; &80 = TX complete flag
9C29 STA tx_clear_flag ; Signal TX operation complete
9C2C PLA ; Restore X saved by caller
9C2D TAX ; Move to X register
9C2E RTS ; Return to TX caller

TX preparation

Configures ADLC for transmission: asserts RTS via CR2, enables TIE via CR1, installs NMI TX handler at &9CCC (nmi_tx_data), and re-enables NMIs. For port-0 (immediate) operations, dispatches via a lookup table indexed by control byte to set tx_flags, tx_length, and a per-operation handler. For port non-zero, branches to c9c8e for standard data transfer setup.

9C2F .tx_prepare←1← 9BF9 BNE
STY econet_control23_or_status2 ; Write CR2 = Y (&E7: RTS|CLR_TX_ST|CLR_RX_ST| FC_TDRA|2_1_BYTE|PSE)
9C32 LDX #&44 ; CR1=&44: RX_RESET | TIE (TX active, TX interrupts enabled)
9C34 STX econet_control1_or_status1 ; Write to ADLC CR1
9C37 LDX #&cc ; Install NMI handler at &9CCC (nmi_tx_data)
9C39 LDY #&9c ; High byte of NMI handler address
9C3B STX nmi_jmp_lo ; Write NMI vector low byte directly
9C3E STY nmi_jmp_hi ; Write NMI vector high byte directly
9C41 SEC ; Set need_release_tube flag (SEC/ROR = bit7)
9C42 ROR need_release_tube ; Rotate carry into bit 7 of flag
9C44 BIT video_ula_control ; INTON -- NMIs now fire for TDRA (&FE20 read)
9C47 LDA tx_port ; Load destination port number
9C4A BNE setup_data_xfer ; Port != 0: standard data transfer
9C4C LDY tx_ctrl_byte ; Port 0: load control byte for table lookup
9C4F LDA tube_tx_sr1_operand,y ; Look up tx_flags from table
9C52 STA tx_flags ; Store operation flags
9C55 LDA tube_tx_inc_operand,y ; Look up tx_length from table
9C58 STA tx_length ; Store expected transfer length
9C5B LDA #&9c ; Push high byte of return address (&9C)
9C5D PHA ; Push high byte for PHA/PHA/RTS dispatch
9C5E LDA intoff_operand,y ; Look up handler address low from table
9C61 PHA ; Push low byte for PHA/PHA/RTS dispatch
9C62 RTS ; RTS dispatches to control-byte handler
9C63 EQUB &6E, &72, &B4, &B4, &B4, ; TX timeout/retry parameter &C4, &C4, &6A ; table
9C6B .imm_op_status3
EQUB &A9, &03, &D0, &48 ; A=3: scout_status for PEEK

TX ctrl: PEEK transfer setup

Sets scout_status=3, then performs a 4-byte addition of bytes from the TX block into the transfer parameter workspace at &0D1E-&0D21 (with carry propagation). Calls tx_calc_transfer to finalise, then exits via tx_ctrl_exit.

9C6F .tx_ctrl_peek
LDA #3 ; A=3: scout_status for PEEK op
9C71 BNE store_status_add4 ; ALWAYS branch

TX ctrl: POKE transfer setup

Sets scout_status=2 and shares the 4-byte addition and transfer calculation path with tx_ctrl_peek.

9C73 .tx_ctrl_poke
LDA #2 ; Scout status = 2 (POKE transfer)
9C75 .store_status_add4←1← 9C71 BNE
STA scout_status ; Store scout status
9C78 CLC ; Clear carry for 4-byte addition
9C79 PHP ; Save carry on stack
9C7A LDY #&0c ; Y=&0C: start at offset 12
9C7C .add_bytes_loop←1← 9C89 BCC
LDA l0d1e,y ; Load workspace address byte
9C7F PLP ; Restore carry from previous byte
9C80 ADC (nmi_tx_block),y ; Add TXCB address byte
9C82 STA l0d1e,y ; Store updated address byte
9C85 INY ; Next byte
9C86 PHP ; Save carry for next addition
fall through ↓

TX ctrl: JSR/UserProc/OSProc setup

Sets scout_status=2 and calls tx_calc_transfer directly (no 4-byte address addition needed for procedure calls). Shared by operation types &83-&85.

9C87 .tx_ctrl_proc
CPY #&10 ; Compare Y with 16-byte boundary
9C89 BCC add_bytes_loop ; Below boundary: continue addition
9C8B PLP ; Restore processor flags
9C8C BNE skip_buf_setup ; Skip buffer setup if transfer size is zero
9C8E .setup_data_xfer←1← 9C4A BNE
LDA tx_dst_stn ; Load dest station for broadcast check
9C91 AND tx_dst_net ; AND with dest network
9C94 CMP #&ff ; Both &FF = broadcast address?
9C96 BNE setup_unicast_xfer ; Not broadcast: unicast path
9C98 LDA #&0e ; Broadcast scout: 14 bytes total
9C9A STA tx_length ; Store broadcast scout length
9C9D LDA #&40 ; A=&40: broadcast flag
9C9F STA tx_flags ; Set broadcast flag in tx_flags
9CA2 LDY #4 ; Y=4: start of address data in TXCB
9CA4 .copy_bcast_addr←1← 9CAC BCC
LDA (nmi_tx_block),y ; Copy TXCB address bytes to scout buffer
9CA6 STA tx_src_stn,y ; Store to TX source/data area
9CA9 INY ; Next byte
9CAA CPY #&0c ; Done 8 bytes? (Y reaches &0C)
9CAC BCC copy_bcast_addr ; No: continue copying
9CAE BCS tx_ctrl_exit ; ALWAYS branch
9CB0 .setup_unicast_xfer←1← 9C96 BNE
LDA #0 ; A=0: clear flags for unicast
9CB2 STA tx_flags ; Clear tx_flags
9CB5 .proc_op_status2
LDA #2 ; scout_status=2: data transfer pending
9CB7 .store_status_copy_ptr
STA scout_status ; Store scout status
9CBA .skip_buf_setup←1← 9C8C BNE
LDA nmi_tx_block ; Copy TX block pointer to workspace ptr
9CBC STA port_ws_offset ; Store low byte
9CBE LDA nmi_tx_block_hi ; Copy TX block pointer high byte
9CC0 STA rx_buf_offset ; Store high byte
9CC2 JSR tx_calc_transfer ; Calculate transfer size from RXCB
9CC5 .tx_ctrl_exit←1← 9CAE BCS
PLP ; Restore processor status from stack
9CC6 PLA ; Restore stacked registers (4 PLAs)
9CC7 PLA ; Second PLA
9CC8 PLA ; Third PLA
9CC9 PLA ; Fourth PLA
9CCA TAX ; Restore X from A
9CCB RTS ; Return to caller

NMI TX data handler

Writes 2 bytes per NMI invocation to the TX FIFO at &FEA2. Uses the BIT instruction on SR1 to test TDRA (V flag = bit6) and IRQ (N flag = bit7). After writing 2 bytes, checks if the frame is complete. If more data, tests SR1 bit7 (IRQ) via BMI -- if IRQ still asserted, writes 2 more bytes without returning from NMI (tight loop). Otherwise returns via RTI.

9CCC .nmi_tx_data
LDY tx_index ; Load TX buffer index
9CCF BIT econet_control1_or_status1 ; BIT SR1: V=bit6(TDRA), N=bit7(IRQ)
9CD2 .tx_fifo_write←1← 9CED BMI
BVC tx_fifo_not_ready ; TDRA not set -- TX error
9CD4 LDA tx_dst_stn,y ; Load byte from TX buffer
9CD7 STA econet_data_continue_frame ; Write to TX_DATA (continue frame)
9CDA INY ; Next TX buffer byte
9CDB LDA tx_dst_stn,y ; Load second byte from TX buffer
9CDE INY ; Advance TX index past second byte
9CDF STY tx_index ; Save updated TX buffer index
9CE2 STA econet_data_continue_frame ; Write second byte to TX_DATA
9CE5 CPY tx_length ; Compare index to TX length
9CE8 BCS tx_last_data ; Frame complete -- go to TX_LAST_DATA
9CEA BIT econet_control1_or_status1 ; Check if we can send another pair
9CED BMI tx_fifo_write ; IRQ set -- send 2 more bytes (tight loop)
9CEF JMP nmi_rti ; RTI -- wait for next NMI
; TX error path
9CF2 .tx_error←1← 9D35 BEQ
LDA #&42 ; Error &42
9CF4 BNE tx_store_error ; ALWAYS branch
9CF6 .tx_fifo_not_ready←1← 9CD2 BVC
LDA #&67 ; CR2=&67: clear status, return to listen
9CF8 STA econet_control23_or_status2 ; Write CR2: clear status, idle listen
9CFB LDA #&41 ; Error &41 (TDRA not ready)
9CFD .tx_store_error←1← 9CF4 BNE
LDY station_id_disable_net_nmis ; INTOFF (also loads station ID)
9D00 .delay_nmi_disable←1← 9D03 BNE
PHA ; PHA/PLA delay loop (256 iterations for NMI disable)
9D01 PLA ; PHA/PLA delay (~7 cycles each)
9D02 INY ; Increment delay counter
9D03 BNE delay_nmi_disable ; Loop 256 times for NMI disable
9D05 JMP tx_store_result ; Store error and return to idle

TX_LAST_DATA and frame completion

Signals end of TX frame by writing CR2=&3F (TX_LAST_DATA). Then installs the TX completion NMI handler at &9D14 (nmi_tx_complete). CR2=&3F = 0011_1111: bit5: CLR_RX_ST -- clears fv_stored_ (prepares for RX of reply) bit4: TX_LAST_DATA -- tells ADLC this is the final data byte bit3: FLAG_IDLE -- send flags/idle after frame bit2: FC_TDRA -- force clear TDRA bit1: 2_1_BYTE -- two-byte transfer mode bit0: PSE -- prioritised status enable Note: NO CLR_TX_ST (bit6=0), NO RTS (bit7=0 -- drops RTS after frame)

9D08 .tx_last_data←1← 9CE8 BCS
LDA #&3f ; CR2=&3F: TX_LAST_DATA | CLR_RX_ST | FLAG_IDLE | FC_TDRA | 2_1_BYTE | PSE
9D0A STA econet_control23_or_status2 ; Write to ADLC CR2
9D0D LDA #&14 ; Install NMI handler at &9D14 (nmi_tx_complete)
9D0F LDY #&9d ; High byte of handler address
9D11 JMP set_nmi_vector ; Install and return via set_nmi_vector

TX completion: switch to RX mode

Called via NMI after the frame (including CRC and closing flag) has been fully transmitted. Switches from TX mode to RX mode by writing CR1=&82. CR1=&82 = 1000_0010: TX_RESET | RIE (listen for reply). Checks workspace flags to decide next action: - bit6 set at &0D4A -> tx_result_ok at &9EA8 - bit0 set at &0D4A -> handshake_await_ack at &9E50 - Otherwise -> install nmi_reply_scout at &9D30

9D14 .nmi_tx_complete
LDA #&82 ; CR1=&82: TX_RESET | RIE (now in RX mode)
9D16 STA econet_control1_or_status1 ; Write CR1 to switch from TX to RX
9D19 BIT tx_flags ; Test workspace flags
9D1C BVC check_handshake_bit ; bit6 not set -- check bit0
9D1E JMP tx_result_ok ; bit6 set -- TX completion
9D21 .check_handshake_bit←1← 9D1C BVC
LDA #1 ; A=1: mask for bit0 test
9D23 BIT tx_flags ; Test tx_flags bit0 (handshake)
9D26 BEQ install_reply_scout ; bit0 clear: install reply handler
9D28 JMP handshake_await_ack ; bit0 set -- four-way handshake data phase
9D2B .install_reply_scout←1← 9D26 BEQ
LDA #&30 ; Install nmi_reply_scout at &9D30
9D2D JMP install_nmi_handler ; Install handler and RTI

RX reply scout handler

Handles reception of the reply scout frame after transmission. Checks SR2 bit0 (AP) for incoming data, reads the first byte (destination station) and compares to our station ID via &FE18 (which also disables NMIs as a side effect).

9D30 .nmi_reply_scout
LDA #1 ; A=&01: AP mask for SR2
9D32 BIT econet_control23_or_status2 ; BIT SR2: test AP (Address Present)
9D35 BEQ tx_error ; No AP -- error
9D37 LDA econet_data_continue_frame ; Read first RX byte (destination station)
9D3A CMP station_id_disable_net_nmis ; Compare to our station ID (INTOFF side effect)
9D3D BNE reject_reply ; Not our station -- error/reject
9D3F LDA #&44 ; Install nmi_reply_cont at &9D44
9D41 JMP install_nmi_handler ; Install continuation handler

RX reply continuation handler

Reads the second byte of the reply scout (destination network) and validates it is zero (local network). Installs nmi_reply_validate (&9D5B) for the remaining two bytes (source station and network). Optimisation: checks SR1 bit7 (IRQ still asserted) via BMI at &9D53. If IRQ is still set, falls through directly to &9D5B without an RTI, avoiding NMI re-entry overhead for short frames where all bytes arrive in quick succession.

9D44 .nmi_reply_cont
BIT econet_control23_or_status2 ; BIT SR2: test for RDA (bit7 = data available)
9D47 BPL reject_reply ; No RDA -- error
9D49 LDA econet_data_continue_frame ; Read destination network byte
9D4C BNE reject_reply ; Non-zero -- network mismatch, error
9D4E LDA #&5b ; Install nmi_reply_validate at &9D5B
9D50 BIT econet_control1_or_status1 ; BIT SR1: test IRQ (N=bit7) -- more data ready?
9D53 BMI nmi_reply_validate ; IRQ set: validate reply immediately
9D55 JMP install_nmi_handler ; IRQ not set -- install handler and RTI
9D58 .reject_reply←7← 9D3D BNE← 9D47 BPL← 9D4C BNE← 9D5E BPL← 9D66 BNE← 9D6E BNE← 9D75 BEQ
JMP tx_result_fail ; Store error and return to idle

RX reply validation (Path 2 for FV/PSE interaction)

Reads the source station and source network from the reply scout and validates them against the original TX destination (&0D20/&0D21). Sequence: 1. Check SR2 bit7 (RDA) at &9D5B -- must see data available 2. Read source station at &9D60, compare to &0D20 (tx_dst_stn) 3. Read source network at &9D68, compare to &0D21 (tx_dst_net) 4. Check SR2 bit1 (FV) at &9D72 -- must see frame complete If all checks pass, the reply scout is valid and the ROM proceeds to send the scout ACK (CR2=&A7 for RTS, CR1=&44 for TX mode).

9D5B .nmi_reply_validate←1← 9D53 BMI
BIT econet_control23_or_status2 ; BIT SR2: test RDA (bit7). Must be set for valid reply.
9D5E BPL reject_reply ; No RDA -- error (FV masking RDA via PSE would cause this)
9D60 LDA econet_data_continue_frame ; Read source station
9D63 CMP tx_dst_stn ; Compare to original TX destination station (&0D20)
9D66 BNE reject_reply ; Mismatch -- not the expected reply, error
9D68 LDA econet_data_continue_frame ; Read source network
9D6B CMP tx_dst_net ; Compare to original TX destination network (&0D21)
9D6E BNE reject_reply ; Mismatch -- error
9D70 LDA #2 ; A=&02: FV mask for SR2 bit1
9D72 BIT econet_control23_or_status2 ; BIT SR2: test FV -- frame must be complete
9D75 BEQ reject_reply ; No FV -- incomplete frame, error
9D77 LDA #&a7 ; CR2=&A7: RTS|CLR_TX_ST|FC_TDRA|2_ 1_BYTE|PSE (TX in handshake)
9D79 STA econet_control23_or_status2 ; Write CR2: enable RTS for TX handshake
9D7C LDA #&44 ; CR1=&44: RX_RESET | TIE (TX active for scout ACK)
9D7E STA econet_control1_or_status1 ; Write CR1: reset RX, enable TX interrupt
9D81 LDA #&50 ; Install next handler at &9E50 into &0D4B/&0D4C
9D83 LDY #&9e ; High byte &9E of next handler address
9D85 STA nmi_next_lo ; Store low byte to nmi_next_lo
9D88 STY nmi_next_hi ; Store high byte to nmi_next_hi
9D8B LDA tx_dst_stn ; Load dest station for scout ACK TX
9D8E BIT econet_control1_or_status1 ; BIT SR1: test TDRA (V=bit6)
9D91 BVC data_tx_check_fifo ; TDRA not ready -- error
9D93 STA econet_data_continue_frame ; Write dest station to TX FIFO
9D96 LDA tx_dst_net ; Load dest network for scout ACK TX
9D99 STA econet_data_continue_frame ; Write dest network to TX FIFO
9D9C LDA #&a3 ; Install nmi_scout_ack_src at &9DA3
9D9E LDY #&9d ; High byte &9D of handler address
9DA0 JMP set_nmi_vector ; Set NMI vector and return

TX scout ACK: write source address

Writes our station ID and network=0 to TX FIFO, completing the 4-byte scout ACK frame. Then proceeds to send the data frame.

9DA3 .nmi_scout_ack_src
LDA station_id_disable_net_nmis ; Load our station ID (also INTOFF)
9DA6 BIT econet_control1_or_status1 ; BIT SR1: check TDRA before writing
9DA9 BVC data_tx_check_fifo ; TDRA not ready: TX error
9DAB STA econet_data_continue_frame ; Write our station to TX FIFO
9DAE LDA #0 ; Network = 0 (local network)
9DB0 STA econet_data_continue_frame ; Write network byte to TX FIFO
9DB3 .data_tx_begin←1← 9948 JMP
LDA #2 ; Test bit 1 of tx_flags
9DB5 BIT tx_flags ; Check if immediate-op or data-transfer
9DB8 BNE install_imm_data_nmi ; Bit 1 set: immediate op, use alt handler
9DBA LDA #&c8 ; Install nmi_data_tx at &9DC8
9DBC LDY #&9d ; High byte of handler address
9DBE JMP set_nmi_vector ; Install and return via set_nmi_vector
9DC1 .install_imm_data_nmi←1← 9DB8 BNE
LDA #&0f ; Install nmi_imm_data at &9E0F
9DC3 LDY #&9e ; High byte of handler address
9DC5 JMP set_nmi_vector ; Install and return via set_nmi_vector

TX data phase: send payload

Sends the data frame payload from (open_port_buf),Y in pairs per NMI. Same pattern as the NMI TX handler at &9CCC but reads from the port buffer instead of the TX workspace. Writes two bytes per iteration, checking SR1 IRQ between pairs for tight looping.

9DC8 .nmi_data_tx
LDY port_buf_len ; Y = buffer offset, resume from last position
9DCA BIT econet_control1_or_status1 ; BIT SR1: test TDRA (V=bit6)
9DCD .data_tx_check_fifo←3← 9D91 BVC← 9DA9 BVC← 9DF0 BMI
BVC tx_tdra_error ; TDRA not ready -- error
9DCF LDA (open_port_buf),y ; Write data byte to TX FIFO
9DD1 STA econet_data_continue_frame ; Write first byte of pair to FIFO
9DD4 INY ; Advance buffer offset
9DD5 BNE write_second_tx_byte ; No page crossing
9DD7 DEC port_buf_len_hi ; Page crossing: decrement page count
9DD9 BEQ data_tx_last ; No pages left: send last data
9DDB INC open_port_buf_hi ; Increment buffer high byte
9DDD .write_second_tx_byte←1← 9DD5 BNE
LDA (open_port_buf),y ; Load second byte of pair
9DDF STA econet_data_continue_frame ; Write second byte to FIFO
9DE2 INY ; Advance buffer offset
9DE3 STY port_buf_len ; Save updated buffer position
9DE5 BNE check_irq_loop ; No page crossing
9DE7 DEC port_buf_len_hi ; Page crossing: decrement page count
9DE9 BEQ data_tx_last ; No pages left: send last data
9DEB INC open_port_buf_hi ; Increment buffer high byte
9DED .check_irq_loop←1← 9DE5 BNE
BIT econet_control1_or_status1 ; BIT SR1: test IRQ (N=bit7) for tight loop
9DF0 BMI data_tx_check_fifo ; IRQ still set: write 2 more bytes
9DF2 JMP nmi_rti ; No IRQ: return, wait for next NMI
9DF5 .data_tx_last←4← 9DD9 BEQ← 9DE9 BEQ← 9E28 BEQ← 9E3E BEQ
LDA #&3f ; CR2=&3F: TX_LAST_DATA (close data frame)
9DF7 STA econet_control23_or_status2 ; Write CR2 to close frame
9DFA LDA tx_flags ; Check tx_flags for next action
9DFD BPL data_tx_error ; Bit7 clear: error, install saved handler
9DFF LDA #&db ; Install discard_reset_listen at &99DB
9E01 LDY #&99 ; High byte of &99DB handler
9E03 JMP set_nmi_vector ; Set NMI vector and return
9E06 .data_tx_error←1← 9DFD BPL
.install_saved_handler←1← 9DFD BPL
LDA nmi_next_lo ; Load saved next handler low byte
9E09 LDY nmi_next_hi ; Load saved next handler high byte
9E0C JMP set_nmi_vector ; Install saved handler and return
9E0F .nmi_data_tx_tube
BIT econet_control1_or_status1 ; Tube TX: BIT SR1 test TDRA
9E12 .tube_tx_fifo_write←1← 9E43 BMI
BVC tx_tdra_error ; TDRA not ready -- error
9E14 LDA tube_data_register_3 ; Read byte from Tube R3
9E17 STA econet_data_continue_frame ; Write to TX FIFO
9E1A INC port_buf_len ; Increment 4-byte buffer counter
9E1C BNE write_second_tube_byte ; Low byte didn't wrap
9E1E INC port_buf_len_hi ; Carry into second byte
9E20 BNE write_second_tube_byte ; No further carry
9E22 INC open_port_buf ; Carry into third byte
9E24 BNE write_second_tube_byte ; No further carry
9E26 INC open_port_buf_hi ; Carry into fourth byte
9E28 BEQ data_tx_last ; Counter wrapped to zero: last data
9E2A .write_second_tube_byte←3← 9E1C BNE← 9E20 BNE← 9E24 BNE
LDA tube_data_register_3 ; Read second Tube byte from R3
9E2D STA econet_data_continue_frame ; Write second byte to TX FIFO
9E30 INC port_buf_len ; Increment 4-byte counter (second byte)
9E32 BNE check_tube_irq_loop ; Low byte didn't wrap
9E34 .tube_tx_inc_byte2
INC port_buf_len_hi ; Carry into second byte
9E36 BNE check_tube_irq_loop ; No further carry
9E38 .tube_tx_inc_byte3
INC open_port_buf ; Carry into third byte
9E3A BNE check_tube_irq_loop ; No further carry
9E3C .tube_tx_inc_byte4
INC open_port_buf_hi ; Carry into fourth byte
9E3E BEQ data_tx_last ; Counter wrapped to zero: last data
9E40 .check_tube_irq_loop←3← 9E32 BNE← 9E36 BNE← 9E3A BNE
BIT econet_control1_or_status1 ; BIT SR1: test IRQ for tight loop
9E43 BMI tube_tx_fifo_write ; IRQ still set: write 2 more bytes
9E45 JMP nmi_rti ; No IRQ: return, wait for next NMI
9E48 .tx_tdra_error←2← 9DCD BVC← 9E12 BVC
LDA tx_flags ; TX error: check flags for path
9E4B BPL tx_result_fail ; Bit7 clear: TX result = not listening
9E4D JMP discard_reset_listen ; Bit7 set: discard and return to listen

Four-way handshake: switch to RX for final ACK

After the data frame TX completes, switches to RX mode (CR1=&82) and installs &9EF8 to receive the final ACK from the remote station.

9E50 .handshake_await_ack←1← 9D28 JMP
LDA #&82 ; CR1=&82: TX_RESET | RIE (switch to RX for final ACK)
9E52 STA econet_control1_or_status1 ; Write to ADLC CR1
9E55 LDA #&5c ; Install nmi_final_ack at &9E5C
9E57 LDY #&9e ; High byte of handler address
9E59 JMP set_nmi_vector ; Install and return via set_nmi_vector

RX final ACK handler

Receives the final ACK in a four-way handshake. Same validation pattern as the reply scout handler (&9D30-&9D5B): &9E5C: Check AP, read dest_stn, compare to our station &9E70: Check RDA, read dest_net, validate = 0 &9E84: Check RDA, read src_stn/net, compare to TX dest &9EA3: Check FV for frame completion On success, stores result=0 at tx_result_ok. On failure, error &41.

9E5C .nmi_final_ack
LDA #1 ; A=&01: AP mask
9E5E BIT econet_control23_or_status2 ; BIT SR2: test AP
9E61 BEQ tx_result_fail ; No AP -- error
9E63 LDA econet_data_continue_frame ; Read dest station
9E66 CMP station_id_disable_net_nmis ; Compare to our station (INTOFF side effect)
9E69 BNE tx_result_fail ; Not our station -- error
9E6B LDA #&70 ; Install nmi_final_ack_net at &9E70
9E6D JMP install_nmi_handler ; Install continuation handler
9E70 .nmi_final_ack_net
BIT econet_control23_or_status2 ; BIT SR2: test RDA
9E73 BPL tx_result_fail ; No RDA -- error
9E75 LDA econet_data_continue_frame ; Read dest network
9E78 BNE tx_result_fail ; Non-zero -- network mismatch, error
9E7A LDA #&84 ; Install nmi_final_ack_validate at &9E84
9E7C BIT econet_control1_or_status1 ; BIT SR1: test IRQ -- more data ready?
9E7F BMI nmi_final_ack_validate ; IRQ set: validate final ACK immediately
9E81 JMP install_nmi_handler ; Install handler and RTI

Final ACK validation

Reads and validates src_stn and src_net against original TX dest. Then checks FV for frame completion.

9E84 .nmi_final_ack_validate←1← 9E7F BMI
BIT econet_control23_or_status2 ; BIT SR2: test RDA
9E87 BPL tx_result_fail ; No RDA -- error
9E89 LDA econet_data_continue_frame ; Read source station
9E8C CMP tx_dst_stn ; Compare to TX dest station (&0D20)
9E8F BNE tx_result_fail ; Mismatch -- error
9E91 LDA econet_data_continue_frame ; Read source network
9E94 CMP tx_dst_net ; Compare to TX dest network (&0D21)
9E97 BNE tx_result_fail ; Mismatch -- error
9E99 LDA tx_flags ; Load TX flags for next action
9E9C BPL check_fv_final_ack ; bit7 clear: no data phase
9E9E JMP install_data_rx_handler ; Install data RX handler
9EA1 .check_fv_final_ack←1← 9E9C BPL
LDA #2 ; A=&02: FV mask for SR2 bit1
9EA3 BIT econet_control23_or_status2 ; BIT SR2: test FV -- frame must be complete
9EA6 BEQ tx_result_fail ; No FV -- error
fall through ↓

TX completion handler

Stores result code 0 (success) into the first byte of the TX control block (nmi_tx_block),Y=0. Then sets &0D3A bit7 to signal completion and calls discard_reset_listen to return to idle.

9EA8 .tx_result_ok←2← 98F6 JMP← 9D1E JMP
LDA #0 ; A=0: success result code
9EAA BEQ tx_store_result ; BEQ: always taken (A=0) ALWAYS branch

TX failure: not listening

Loads error code &41 (not listening) and falls through to tx_store_result. The most common TX error path — reached from 11 sites across the final-ACK validation chain when the remote station doesn't respond or the frame is malformed.

9EAC .tx_result_fail←11← 983A JMP← 9D58 JMP← 9E4B BPL← 9E61 BEQ← 9E69 BNE← 9E73 BPL← 9E78 BNE← 9E87 BPL← 9E8F BNE← 9E97 BNE← 9EA6 BEQ
LDA #&41 ; A=&41: not listening error code
fall through ↓

TX result store and completion

Stores result code (A) into the TX control block at (nmi_tx_block),0 and sets bit 7 of &0D3A to signal completion. Returns to idle via discard_reset_listen. Reached from tx_result_ok (A=0, success), tx_result_fail (A=&41, not listening), and directly with other codes (A=&40 line jammed, A=&42 net error).

9EAE .tx_store_result←2← 9D05 JMP← 9EAA BEQ
LDY #0 ; Y=0: index into TX control block
9EB0 STA (nmi_tx_block),y ; Store result/error code at (nmi_tx_block),0
9EB2 LDA #&80 ; &80: completion flag for &0D3A
9EB4 STA tx_clear_flag ; Signal TX complete
9EB7 JMP discard_reset_listen ; Full ADLC reset and return to idle listen
; Unreferenced data block (purpose unknown)
9EBA EQUB &0E, &0E, &0A, &0A, &0A, &06, &06, &0A, &81, &00, &00, &00, &00, &01, &01, &81 ; Unreferenced data

Calculate transfer size

Computes the number of bytes actually transferred during a data frame reception by subtracting RXCB[8..11] (start address) from RXCB[4..7] (current pointer), giving the byte count. Two paths: the main path performs a 4-byte subtraction for Tube transfers, storing results to port_buf_len..open_port_buf_hi (&A2-&A5). The fallback path (no Tube or buffer addr = &FFFF) does a 2-byte subtraction using open_port_buf/open_port_buf_hi (&A4/&A5) as scratch. Both paths clobber &A4/&A5 as a side effect of the result area overlapping open_port_buf.

On ExitC1 if transfer set up, 0 if not
Xpreserved
9ECA .tx_calc_transfer←3← 97BE JSR← 9AC9 JSR← 9CC2 JSR
LDY #6 ; Load RXCB[6] (buffer addr byte 2)
9ECC LDA (port_ws_offset),y ; Load workspace byte at offset Y
9ECE INY ; Y=&07
9ECF AND (port_ws_offset),y ; AND with TX block[7] (byte 3)
9ED1 CMP #&ff ; Both &FF = no buffer?
9ED3 BEQ fallback_calc_transfer ; Yes: fallback path
9ED5 LDA tube_flag ; Tube transfer in progress?
9ED8 BEQ fallback_calc_transfer ; No: fallback path
9EDA LDA tx_flags ; Load TX flags for transfer setup
9EDD ORA #2 ; Set bit 1 (transfer complete)
9EDF STA tx_flags ; Store with bit 1 set (Tube xfer)
9EE2 SEC ; Init borrow for 4-byte subtract
9EE3 PHP ; Save carry on stack
9EE4 LDY #4 ; Y=4: start at RXCB offset 4
9EE6 .calc_transfer_size←1← 9EF8 BCC
LDA (port_ws_offset),y ; Load RXCB[Y] (current ptr byte)
9EE8 INY ; Y += 4: advance to RXCB[Y+4]
9EE9 INY ; (continued)
9EEA INY ; (continued)
9EEB INY ; (continued)
9EEC PLP ; Restore borrow from previous byte
9EED SBC (port_ws_offset),y ; Subtract RXCB[Y+4] (start ptr byte)
9EEF STA net_tx_ptr,y ; Store result byte
9EF2 DEY ; Y -= 3: next source byte
9EF3 DEY ; (continued)
9EF4 DEY ; (continued)
9EF5 PHP ; Save borrow for next byte
9EF6 CPY #8 ; Done all 4 bytes?
9EF8 BCC calc_transfer_size ; No: next byte pair
9EFA PLP ; Discard final borrow
9EFB TXA ; A = saved X
9EFC PHA ; Save X
9EFD LDA #4 ; Compute address of RXCB+4
9EFF CLC ; CLC for base pointer addition
9F00 ADC port_ws_offset ; Add RXCB base to get RXCB+4 addr
9F02 TAX ; X = low byte of RXCB+4
9F03 LDY rx_buf_offset ; Y = high byte of RXCB ptr
9F05 LDA #&c2 ; Tube claim type &C2
9F07 JSR tube_addr_claim ; Claim Tube transfer address
9F0A BCC restore_x_and_return ; No Tube: skip reclaim
9F0C LDA scout_status ; Tube: reclaim with scout status
9F0F JSR tube_addr_claim ; Reclaim with scout status type
9F12 JSR release_tube ; Release Tube claim after reclaim
9F15 SEC ; C=1: Tube address claimed
9F16 .restore_x_and_return←1← 9F0A BCC
PLA ; Restore X
9F17 TAX ; Restore X from stack
9F18 RTS ; Return with C = transfer status
9F19 .fallback_calc_transfer←2← 9ED3 BEQ← 9ED8 BEQ
LDY #4 ; Y=4: RXCB current pointer offset
9F1B LDA (port_ws_offset),y ; Load RXCB[4] (current ptr lo)
9F1D LDY #8 ; Y=8: RXCB start address offset
9F1F SEC ; Set carry for subtraction
9F20 SBC (port_ws_offset),y ; Subtract RXCB[8] (start ptr lo)
9F22 STA port_buf_len ; Store transfer size lo
9F24 LDY #5 ; Y=5: current ptr hi offset
9F26 LDA (port_ws_offset),y ; Load RXCB[5] (current ptr hi)
9F28 SBC #0 ; Propagate borrow from lo subtraction
9F2A STA open_port_buf_hi ; Temp store adjusted current ptr hi
9F2C LDY #8 ; Y=8: start address lo offset
9F2E LDA (port_ws_offset),y ; Copy RXCB[8] to open port buffer lo
9F30 STA open_port_buf ; Store to scratch (side effect)
9F32 LDY #9 ; Y=9: start address hi offset
9F34 LDA (port_ws_offset),y ; Load RXCB[9] (start ptr hi)
9F36 SEC ; Set carry for subtraction
9F37 SBC open_port_buf_hi ; start_hi - adjusted current_hi
9F39 STA port_buf_len_hi ; Store transfer size hi
9F3B SEC ; Return with C=1
9F3C .nmi_shim_rom_src
RTS ; Return with C=1 (success)

ADLC full reset

Aborts all activity and returns to idle RX listen mode.

On ExitA0
9F3D .adlc_full_reset←3← 967D JSR← 9705 JSR← 983D JSR
LDA #&c1 ; CR1=&C1: TX_RESET | RX_RESET | AC (both sections in reset, address control set)
9F3F STA econet_control1_or_status1 ; Write CR1 to ADLC register 0
9F42 LDA #&1e ; CR4=&1E (via AC=1): 8-bit RX word length, abort extend enabled, NRZ encoding
9F44 STA econet_data_terminate_frame ; Write CR4 to ADLC register 3
9F47 LDA #0 ; CR3=&00 (via AC=1): no loop-back, no AEX, NRZ, no DTR
9F49 STA econet_control23_or_status2 ; Write CR3 to ADLC register 1
fall through ↓

Enter RX listen mode

TX held in reset, RX active with interrupts. Clears all status.

On ExitA&67
9F4C .adlc_rx_listen←2← 99E8 JSR← 9F7A JMP
LDA #&82 ; CR1=&82: TX_RESET | RIE (TX in reset, RX interrupts enabled)
9F4E STA econet_control1_or_status1 ; Write to ADLC CR1
9F51 LDA #&67 ; CR2=&67: CLR_TX_ST | CLR_RX_ST | FC_TDRA | 2_1_BYTE | PSE
9F53 STA econet_control23_or_status2 ; Write to ADLC CR2
9F56 RTS ; Return; ADLC now in RX listen mode

Wait for idle NMI state and reset Econet

Called via svc_12_nmi_release (&06D4). Checks if Econet has been initialised; if not, skips to adlc_rx_listen. If initialised, spins until the NMI handler is idle (pointing at nmi_rx_scout), then falls through to save_econet_state to clear flags and re-enter RX listen mode.

9F57 .wait_idle_and_reset←1← 06D4 JMP
BIT econet_init_flag ; Econet not initialised -- skip to adlc_rx_listen
9F5A BPL reset_enter_listen ; Not initialised: skip to RX listen
9F5C .poll_nmi_idle←2← 9F61 BNE← 9F68 BNE
LDA nmi_jmp_lo ; Spin until NMI handler = &96BF (nmi_rx_scout)
9F5F CMP #&bf ; Expected: &BF (nmi_rx_scout low)
9F61 BNE poll_nmi_idle ; Not idle: spin and wait
9F63 LDA nmi_jmp_hi ; Read current NMI handler high byte
9F66 CMP #&96 ; Expected: &96 (nmi_rx_scout high)
9F68 BNE poll_nmi_idle ; Not idle: spin and wait
9F6A BIT station_id_disable_net_nmis ; INTOFF before clearing state
fall through ↓

Reset Econet flags and enter RX listen

Disables NMIs via INTOFF (BIT &FE18), clears tx_clear_flag and econet_init_flag to zero, then falls through to adlc_rx_listen with Y=5.

9F6D .save_econet_state
BIT station_id_disable_net_nmis ; INTOFF: disable NMIs
9F70 LDA #0 ; Clear both flags
9F72 STA tx_clear_flag ; TX not in progress
9F75 STA econet_init_flag ; Econet not initialised
9F78 LDY #5 ; Y=5: service call workspace page
9F7A .reset_enter_listen←1← 9F5A BPL
JMP adlc_rx_listen ; Set ADLC to RX listen mode

Bootstrap NMI entry point (in ROM)

An alternate NMI handler that lives in the ROM itself rather than in the RAM workspace at &0D00. Unlike the RAM shim (which uses a self-modifying JMP to dispatch to different handlers), this one hardcodes JMP nmi_rx_scout (&96BF). Used as the initial NMI handler before the workspace has been properly set up during initialisation. Same sequence as the RAM shim: BIT &FE18 (INTOFF), PHA, TYA, PHA, LDA romsel, STA &FE30, JMP &96BF.

9F7D .nmi_bootstrap_entry
BIT station_id_disable_net_nmis ; INTOFF: disable NMIs while switching ROM
9F80 PHA ; Save A
9F81 TYA ; Transfer Y to A
9F82 PHA ; Save Y (via A)
9F83 LDA #0 ; ROM bank 0 (patched during init for actual bank)
9F85 STA romsel ; Select Econet ROM bank via ROMSEL
9F88 JMP nmi_rx_scout ; Jump to scout handler in ROM

ROM copy of set_nmi_vector + nmi_rti

A version of the NMI vector-setting subroutine and RTI sequence that lives in ROM. The RAM workspace copy at &0D0E/&0D14 is the one normally used at runtime; this ROM copy is used during early initialisation before the RAM workspace has been set up, and as the source for the initial copy to RAM.

9F8B .rom_set_nmi_vector
STY nmi_jmp_hi ; Store handler high byte at &0D0D
9F8E STA nmi_jmp_lo ; Store handler low byte at &0D0C
9F91 LDA romsel_copy ; Restore NFS ROM bank
9F93 STA romsel ; Page in via hardware latch
9F96 PLA ; Restore Y from stack
9F97 TAY ; Transfer ROM bank to Y
9F98 PLA ; Restore A from stack
9F99 BIT video_ula_control ; INTON: re-enable NMIs
9F9C RTI ; Return from interrupt

Print byte as two hex digits

Prints the high nibble first (via 4x LSR), then the low nibble. Each nibble is converted to ASCII '0'-'9' or 'A'-'F' and output via OSASCI. Returns with carry set.

On EntryAbyte to print as two hex digits
On ExitApreserved (original byte)
Xcorrupted (by OSASCI)
Cset
9F9D .print_hex←2← 8CD7 JSR← 8D74 JSR
PHA ; Save original byte for low nibble
9F9E LSR ; Shift high nibble right (4x LSR)
9F9F LSR ; Shift high nibble to low
9FA0 LSR ; Shift high nibble to low
9FA1 LSR ; Shift high nibble to low
9FA2 JSR print_hex_nibble ; Print high nibble as hex
9FA5 PLA ; Restore byte; fall through for low nibble
fall through ↓

Print single hex nibble

Converts the low nibble of A to ASCII hex ('0'-'9' or 'A'-'F') and prints via OSASCI. Returns with carry set.

On EntryAlow nibble (bits 0-3) to print
On ExitAASCII character printed
Xcorrupted (by OSASCI)
Cset
9FA6 .print_hex_nibble←1← 9FA2 JSR
AND #&0f ; Mask to low nibble (0-F)
9FA8 CMP #&0a ; Digit A-F?
9FAA BCC add_ascii_base ; No: skip letter offset
9FAC ADC #6 ; A-F: ADC #6 + ADC #&30 + C = &41-&46
9FAE .add_ascii_base←1← 9FAA BCC
ADC #&30 ; Add ASCII '0' base (with carry)
9FB0 JSR osasci ; Write character
9FB3 SEC ; C=1: callers use SEC as sentinel
9FB4 RTS ; Return

*EX vs *EXEC disambiguation trampoline

Added in 3.62 to fix *EX matching *EXEC. The "EX" command table entry dispatches here instead of directly to ex_handler. Checks the next character in the command line: if < &21 (space/CR/ctrl) the command is *EX so jumps to ex_handler; if >= &21 (printable) the command continues (e.g. *EXEC) so forwards to the FS.

9FB5 .ex_trampoline
.ex_exec_trampoline
LDA (fs_crc_lo),y ; Load next char from command line
9FB7 CMP #&21 ; Printable non-space character?
9FB9 BCS forward_ex_to_fs ; Yes: not *EX, forward to FS
9FBB JMP ex_handler ; No: handle *EX locally
9FBE .forward_ex_to_fs←1← 9FB9 BCS
JMP forward_star_cmd ; Forward *EXEC etc. to fileserver
9FC1 EQUB &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, ; & &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, ; FF &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, ; pa &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, ; dd &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, ; in &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, ; g &FF, &FF, &FF ; ( ; en ; d ; of ; RO ; M)