Acorn ANFS 4.26

Updated 25 Jul 2026

← All Acorn NFS and Acorn ANFS versions

ANFS ROM 4.26 disassembly (Acorn Advanced Network Filing System)

Sideways ROM header — language-entry slot (3 bytes)

MOS dispatches JMP &8000 on language startup.

Service-only ROM (rom_type bit 6 clear). Per-byte detail is inline.

Reason code in A on entry:

A Meaning
0 No language available — MOS calling Tube ROM
1 Normal startup
2 Request next byte of softkey expansion (Electron)
3 Request length of softkey expansion (Electron)
8000 .language_entry
EQUB &00 ; no-language sentinel (rom_type bit 6 clear)
8001 .rom_header_byte1
EQUB &42, &43 ; unused padding

Service-entry slot (3 bytes)

MOS calls JMP &8003 for service-call dispatch — unrecognised * commands, OSWORDs, OSBYTEs, *HELP, filing-system init / select, paged-ROM scans, and many other events. The reason code arrives in A.

8003 .service_entry
JMP service_handler ; Service-call entry: jump to service_handler
8006 .rom_type
; ROM type byte
; ┌─────┬───────┬─────────────────────────────┐
; │ Bit │ Value │ Meaning │
; ├─────┼───────┼─────────────────────────────┤
; │ 7 │ 1 │ Service entry present │
; │ 6 │ 0 │ No language entry │
; │ 5 │ 0 │ No Tube relocation │
; │ 4 │ 0 │ No Electron firmkey │
; │ 3-0 │ 0010 │ Processor: 6502 (non-BASIC) │
; └─────┴───────┴─────────────────────────────┘
EQUB %10000010 ; ROM type
8007 .copyright_offset
EQUB copyright - language_entry ; Offset of NUL preceding copyright (= &19 → copyright at &8019)
8008 .binary_version
EQUB &04 ; Binary version: &04 (informational, not used by MOS)
8009 .title
EQUS "Acorn ANFS 4.26"
8018 EQUB &00 ; NUL terminator
8019 .copyright
EQUB &00 ; NUL preceding copyright string
801A .copyright_string
EQUS "(C)1986 Acorn"
8027 .machine_id_bytes←1Used as index base by← 84FC LDA
EQUB &00 ; NUL terminator
8028 EQUB &26, &04

Service 5: unrecognised interrupt (dispatch)

Delivers work deferred out of the Econet NMI receive handler. An execute-class immediate operation -- a remote JSR, a user/OS procedure call, halt or continue -- cannot safely JSR into user code or call an OS routine from NMI context, so the NMI handler records it instead: setup_sr_tx stores the operation type in tx_op_type and sets the Master 128 ACCCON IRR latch (bit 7 at &FE34) via TSB. The latch raises an ordinary IRQ once the NMI handler has returned, which reaches the ROM here as service call &05 (unrecognised interrupt) -- the normal IRQ path, where it is safe to run the deferred operation.

Reads the deferred-work flag at &0D65; if zero, returns early via PLX/PLY/RTS. Otherwise clears bit 7 of the Master 128 ACCCON register at &FE34 (TRB), zeros &0D65, then dispatches one of two ways depending on bit 7 of the saved Y:

Caller Y bit 7 Action
Set Dispatch via the PHA/PHA/RTS table at dispatch_svc5
Clear Fire Econet RX event &FE via generate_event, then JMP to tx_done_exit
On EntryA5 (service call number)
XROM slot
Yparameter (high bit selects dispatch path)
802A .svc5_irq_check
PHX ; Save X (the ROM slot we're being called on behalf of)
802B PHY ; Save Y (the dispatch-path selector via its high bit)
802C LDY tx_op_type ; Read deferred-work flag at &0D65 (set by NMI when work queued)
802F BNE irq_check_dispatch ; Non-zero: there's work to dispatch
8031 PLY ; Zero: no work; restore Y
8032 PLX ; Restore X
8033 RTS ; Return to MOS (service unclaimed)
8034 .irq_check_dispatch←1← 802F BNE
LDA #&80 ; A=&80: bit 7 -- the bit to clear in ACCCON
8036 TRB acccon ; Clear ACCCON bit 7 (drop the software IRQ)
8039 STZ tx_op_type ; Zero the deferred-work flag (we're handling it now)
803C TYA ; Copy to A for sign test
803D BMI dispatch_svc5 ; Bit 7 set: dispatch via table
803F LDA #&fe ; A=&FE: Econet receive event
8041 JSR generate_event ; Call event vector handler
8044 JMP tx_done_exit ; Fire event (enable: *FX52,150)

Generate event via EVNTV

Single-instruction JMP (evntv) that hands control to whatever handler is hooked into the MOS event vector. Called via service call &05 (svc5_irq_check) on a 'transmit complete' or 'receive complete' edge so user/MOS code can react to network events.

On EntryAevent number
On ExitApreserved
Xpreserved
Ypreserved
8047 .generate_event←1← 8041 JSR
JMP (evntv) ; Dispatch through event vector

Service-5 PHA/PHA/RTS dispatch tail

Builds an RTS-target on the stack from the svc5_dispatch_lo low-byte table and a hard- coded high byte of &85, then falls through into the shared svc_5_unknown_irq RTS to land on the matching svc5_dispatch_lo+Y page-&85 handler.

On EntryYsvc5_dispatch_lo offset (post-&83 base bias)
804A .dispatch_svc5←1← 803D BMI
LDA #&85 ; Push return addr high (&85)
804C PHA ; High byte on stack for RTS
804D LDA imm_reply_flag,y ; Load dispatch target low byte
8050 PHA ; Low byte on stack for RTS
fall through ↓

Service-5 unknown-IRQ tail (PHA/PHA/RTS landing)

Bare RTS reused as the final step of every dispatch_svc5 entry. With the target's high/low bytes already pushed by the caller, RTS jumps to the selected handler. Also reached as the unclaimed-IRQ tail of the service-5 prologue when no ANFS handler matches.

8051 .svc_5_unknown_irq
RTS ; RTS = dispatch to PHA'd address

ADLC initialisation

Initialise ADLC hardware and Econet workspace. Disables NMIs via BIT disable_net_nmis (the Master 128 INTOFF register at &FE38). Performs a full ADLC reset via adlc_full_reset, then probes for a Tube co-processor via OSBYTE &EA and stores the result in tube_present. Issues an NMI-claim service request (OSBYTE &8F, X=&0C). Falls through to init_nmi_workspace to copy the NMI shim to RAM.

8052 .adlc_init←1← 9045 JSR
BIT disable_net_nmis ; INTOFF: disable NMIs (Master &FE38)
8055 JSR adlc_full_reset ; Full ADLC hardware reset
8058 LDA #&ea ; OSBYTE &EA: check Tube co-processor
805A LDX #0 ; X=0 for OSBYTE
805C STX econet_init_flag ; Clear Econet init flag before setup
805F JSR osbyte_x0 ; Check Tube presence via OSBYTE &EA
8062 STX tube_present ; Store Tube presence flag from OSBYTE &EA
8065 LDA #&8f ; OSBYTE &8F: issue service request
8067 LDX #&0c ; X=&0C: NMI claim service
8069 JSR osbyte_yff ; Issue NMI claim service request
806C LDY #5 ; Y=5: NMI claim service number
806E .econet_restore
CPY #5 ; Check if NMI service was claimed (Y changed)
8070 BNE adlc_init_done ; Service claimed by other ROM: skip init
fall through ↓

Initialise NMI workspace (skip service request)

Copies 32 bytes of NMI shim code from ROM (listen_jmp_hi) to the start of the NFS workspace RAM block, then patches the current ROM bank number into the self-modifying code at nmi_romsel (&0D07).

The shim includes the INTOFF/INTON pair (BIT disable_net_nmis at entry, BIT enable_net_nmis before RTI) that toggles the Econet NMI-enable flip-flop, guaranteeing edge re-triggering on /NMI.

Workspace fields written:

Address / label Value Role
tx_src_net 0 clear
need_release_tube 0 clear
tx_op_type 0 clear
tx_src_stn (&0D22) station ID from (net_rx_ptr)+1
tx_complete_flag &80 mark idle
econet_init_flag &80 mark initialised

Finally re-enables NMIs via INTON (enable_net_nmis read).

8072 .init_nmi_workspace
LDY #&20 ; Copy NMI shim from ROM to &0D0C area
8074 .copy_nmi_shim←1← 807B BNE
LDA nmi_shim_source,y ; Read byte from NMI shim ROM source
8077 STA nmi_code_base,y ; Write to NMI shim RAM (start of NFS workspace)
807A DEY ; Next byte (descending)
807B BNE copy_nmi_shim ; Loop until all 32 bytes copied
807D LDA romsel_copy ; Patch current ROM bank into NMI shim
807F STA nmi_romsel ; Self-modifying code: ROM bank at &0D07
8082 STY tx_src_net ; Clear source network (Y=0 from copy loop)
8085 STY prot_flags ; Clear Tube release flag
8087 STY tx_op_type ; Clear TX operation type
808A LDY #1 ; Y=1: tx_src_stn offset in NMI block
808C LDA (net_rx_ptr),y ; Read TX source station from (net_rx_ptr)+1
808E STA tx_src_stn ; Store as tx_src_stn
8091 LDA #&80 ; &80 = Econet initialised
8093 STA tx_complete_flag ; Mark TX as complete (ready)
8096 STA econet_init_flag ; Mark Econet as initialised
8099 BIT enable_net_nmis ; INTON: re-enable NMIs (Master &FE3C)
809C .adlc_init_done←1← 8070 BNE
RTS ; Return

NMI RX scout handler (initial byte)

Default NMI handler for incoming scout frames. Checks whether the frame is addressed to us or is a broadcast. Installed as the NMI target during idle RX listen mode.

Tests SR2 bit 0 (AP = Address Present) to detect incoming data. Reads the first byte (destination station) from the RX FIFO and compares it against our station ID (the workspace copy in tx_src_stn).

809D .nmi_rx_scout←1← 8A0E JMP
LDA #1 ; A=&01: mask for SR2 bit0 (AP = Address Present)
809F BIT econet_control23_or_status2 ; Z = A AND SR2 -- tests if AP is set
80A2 BEQ scout_error ; AP not set, no incoming data -- check for errors
80A4 LDA econet_data_continue_frame ; Read first RX byte (destination station address)
80A7 CMP tx_src_stn ; Compare to our station ID (tx_src_stn copy)
80AA BEQ accept_frame ; Match -- accept frame
80AC CMP #&ff ; Check for broadcast address (&FF)
80AE BNE scout_reject ; Neither our address nor broadcast -- reject frame
80B0 LDA #&40 ; Flag &40 = broadcast frame
80B2 STA net_frame_flags ; Store broadcast flag in net_frame_flags
80B5 .accept_frame←1← 80AA BEQ
LDA #<(nmi_rx_scout_net) ; Next NMI handler address (low)
80B7 JMP install_nmi_handler ; Install next handler

RX scout second byte handler

Reads the second byte of an incoming scout (destination network).

Value Meaning Action
0 local network accept
&FF broadcast accept and flag
other foreign network reject

Installs copy_scout_to_buffer as the scout-data reading loop handler.

80BA .nmi_rx_scout_net
BIT econet_control23_or_status2 ; Test SR2 for RDA (bit7 = data available)
80BD BPL scout_error ; No RDA -- check errors
80BF LDA econet_data_continue_frame ; Read destination network byte
80C2 BEQ accept_local_net ; Network = 0 -- local network, accept
80C4 EOR #&ff ; Test if network = &FF (broadcast)
80C6 BEQ accept_scout_net ; Broadcast network -- accept
80C8 .scout_reject←1← 80AE BNE
LDA #&a2 ; Reject: wrong network. CR1=&A2: RIE|RX_DISCONTINUE
80CA STA econet_control1_or_status1 ; Write CR1 to discontinue RX
80CD JMP set_nmi_rx_scout ; Return to idle scout listening
80D0 .accept_local_net←1← 80C2 BEQ
STA net_frame_flags ; Network = 0 (local): clear tx_flags
80D3 .accept_scout_net←1← 80C6 BEQ
STA port_buf_len ; Store Y offset for scout data buffer
80D5 LDA #<(nmi_scout_data) ; Install scout-data reader (low)
80D7 JMP install_nmi_handler ; Install scout data loop

Scout error/discard handler

Handles scout reception errors and end-of-frame conditions. Reads SR2 and tests AP|RDA (bits 0 and 7):

  • Neither set – the frame ended cleanly; simply discard.
  • Either set – unexpected data is present; perform a full ADLC reset.

Also serves as the common discard path for address/network mismatches from nmi_rx_scout and scout_complete – reached by 5 branch sites across the scout reception chain.

80DA .scout_error←5← 80A2 BEQ← 80BD BPL← 80EF BPL← 8123 BEQ← 8125 BPL
LDA econet_control23_or_status2 ; Read SR2
80DD AND #&81 ; Test AP (b0) | RDA (b7)
80DF BEQ scout_discard ; Neither set -- clean end, discard frame
80E1 JSR adlc_full_reset ; Unexpected data/status: full ADLC reset
80E4 JMP set_nmi_rx_scout ; Discard and return to idle
80E7 .scout_discard←1← 80DF BEQ
JMP reset_adlc_rx_listen ; Gentle discard: RX_DISCONTINUE
80EA .nmi_scout_data
LDY port_buf_len ; Y = buffer offset
80EC LDA econet_control23_or_status2 ; Read SR2
80EF .scout_loop_rda←1← 810F BNE
BPL scout_error ; No RDA -- error handler
80F1 LDA econet_data_continue_frame ; Read data byte from RX FIFO
80F4 STA scout_buf,y ; Store at &0D3D+Y (scout buffer)
80F7 INY ; Advance buffer index
80F8 LDA econet_control23_or_status2 ; Read SR2 again (FV detection point)
80FB BMI scout_loop_second ; RDA set -- more data, read second byte
80FD BNE scout_complete ; SR2 non-zero (FV or other) -- scout completion
80FF .scout_loop_second←1← 80FB BMI
LDA econet_data_continue_frame ; Read second byte of pair
8102 STA scout_buf,y ; Store at &0D3D+Y
8105 INY ; Advance and check buffer limit
8106 CPY #&0c ; Copied all 12 scout bytes?
8108 BEQ scout_complete ; Buffer full (Y=12) -- force completion
810A STY port_buf_len ; Save final buffer offset
810C LDA econet_control23_or_status2 ; Read SR2 for next pair
810F BNE scout_loop_rda ; SR2 non-zero -- loop back for more bytes
8111 JMP nmi_rti ; SR2 = 0 -- wait for next NMI

Scout completion handler

Processes a completed scout frame. Writes CR1=&00 and CR2=&84 to disable PSE and suppress FV, then tests SR2 for FV (frame valid). If FV is set with RDA, reads the remaining scout data bytes in pairs into the buffer at &0D3D.

Matches the port byte (&0D40) against open receive control blocks to find a listener:

  • On match – calculates the transfer size via tx_calc_transfer, sets up the data RX handler chain, and sends a scout ACK.
  • On no match or error – discards the frame via scout_error.
8114 .scout_complete←2← 80FD BNE← 8108 BEQ
LDA #0 ; Save Y for next iteration
8116 STA econet_control1_or_status1 ; Write CR1
8119 LDA #&84 ; CR2=&84: disable PSE, enable RDA_SUPPRESS_FV
811B STA econet_control23_or_status2 ; Write CR2
811E LDA #2 ; A=&02: FV mask for SR2 bit1
8120 BIT econet_control23_or_status2 ; Test SR2 FV (Z) and RDA (N)
8123 BEQ scout_error ; No FV -- not a valid frame end, error
8125 BPL scout_error ; FV set but no RDA -- missing last byte, error
8127 LDA econet_data_continue_frame ; Read last byte from RX FIFO
812A STA scout_buf,y ; Store last byte at &0D3D+Y
812D LDA #&44 ; CR1=&44: RX_RESET | TIE (switch to TX for ACK)
812F STA econet_control1_or_status1 ; Write CR1: switch to TX mode
8132 SEC ; Set bit7 of need_release_tube flag
8133 ROR prot_flags ; Rotate C=1 into bit7: mark Tube release needed
8135 LDA scout_port ; Check port byte: 0 = immediate op, non-zero = data transfer
8138 BNE scout_match_port ; Port non-zero -- look for matching receive block
813A .scout_no_match
JMP immediate_op ; Port = 0 -- immediate operation handler
813D .scout_match_port←1← 8138 BNE
BIT net_frame_flags ; Check if broadcast (bit6 of tx_flags)
8140 BVC scan_port_list ; Not broadcast -- skip CR2 setup
8142 LDA #7 ; CR2=&07: broadcast prep
8144 STA econet_control23_or_status2 ; Write CR2: broadcast frame prep
8147 .scan_port_list←1← 8140 BVC
BIT econet_flags ; Check if RX port list active (bit7)
814A BPL try_nfs_port_list ; No active ports -- try NFS workspace
814C LDA #&c0 ; Start scanning port list at page &C0
814E LDY #0 ; Y=0: start offset within each port slot
8150 CLD ; Clear decimal mode before scanning the port slots
8151 .scan_nfs_port_list←1← 8196 BNE
STA port_ws_offset ; Store page to workspace pointer low
8153 STY rx_buf_offset ; Store page high byte for slot scanning
8155 .check_port_slot←1← 8188 BCC
LDY #0 ; Y=0: read control byte from start of slot
8157 .scout_ctrl_check
LDA (port_ws_offset),y ; Read port control byte from slot
8159 BEQ discard_no_match ; Zero = end of port list, no match
815B CMP #&7f ; &7F = any-port wildcard
815D BNE next_port_slot ; Not wildcard -- check specific port match
815F INY ; Y=1: advance to port byte in slot
8160 LDA (port_ws_offset),y ; Read port number from slot (offset 1)
8162 BEQ check_station_filter ; Zero port in slot = match any port
8164 CMP scout_port ; Check if port matches this slot
8167 BNE next_port_slot ; Port mismatch -- try next slot
8169 .check_station_filter←1← 8162 BEQ
INY ; Y=2: advance to station byte
816A LDA (port_ws_offset),y ; Read station filter from slot (offset 2)
816C BEQ port_match_found ; Zero station = match any station, accept
816E CMP scout_buf ; Check if source station matches
8171 BNE next_port_slot ; Station mismatch -- try next slot
8173 .scout_port_match
INY ; Y=3: advance to network byte
8174 LDA (port_ws_offset),y ; Read network filter from slot (offset 3)
8176 BEQ port_match_found ; Zero = accept any network
8178 CMP scout_src_net ; Check if source network matches
817B BEQ port_match_found ; Network matches or zero = accept
817D .next_port_slot←3← 815D BNE← 8167 BNE← 8171 BNE
LDA rx_buf_offset ; Check if NFS workspace search pending
817F BEQ try_nfs_port_list ; No NFS workspace -- try fallback path
8181 LDA port_ws_offset ; Load current slot base address
8183 CLC ; For 12-byte slot advance
8184 ADC #&0c ; Advance to next 12-byte port slot
8186 STA port_ws_offset ; Update workspace pointer to next slot
8188 BCC check_port_slot ; Always branches (page &C0 won't overflow)
818A .discard_no_match←2← 8159 BEQ← 8190 BVC
JMP nmi_error_dispatch ; No match found -- discard frame
818D .try_nfs_port_list←2← 814A BPL← 817F BEQ
BIT econet_flags ; Try NFS workspace if paged list exhausted
8190 BVC discard_no_match ; No NFS workspace RX (bit6 clear) -- discard
8192 LDA #0 ; NFS workspace starts at offset 0 in page
8194 LDY nfs_workspace_hi ; NFS workspace high byte for port list
8196 BNE scan_nfs_port_list ; Scan NFS workspace port list
fall through ↓

Scout matched: arm data RX, ACK or discard

Sets scout_status=3 (match found) at rx_port, calls tx_calc_transfer to compute the transfer parameters from the RXCB, then triages:

Carry net_frame_flags (V) Action
C=0 no Tube claimed → nmi_error_dispatch (discard)
C=1 broadcast discard (broadcasts get no ACK)
C=1 unicast send_data_rx_ack

Four inbound refs (one JSR from &84B9 and three branches from the scout_complete dispatch).

On ExitA3 (scout_status)
8198 .port_match_found←4← 816C BEQ← 8176 BEQ← 817B BEQ← 84C4 JMP
LDA #3 ; Match found: set scout_status = 3
819A STA rx_port ; Record match for completion handler
819D JSR tx_calc_transfer ; Calculate transfer parameters
81A0 BCC nmi_error_dispatch ; C=0: no Tube claimed -- discard
81A2 BIT net_frame_flags ; Check broadcast flag for ACK path
81A5 BVC send_data_rx_ack ; Not broadcast -- normal ACK path
81A7 JMP copy_scout_to_buffer ; Broadcast: different completion path

Send scout ACK and arm data-RX continuation

Switches the ADLC to TX mode for the scout ACK frame: writes CR1=&44 (RX_RESET | TIE), CR2=&A7 (RTS | CLR_TX_ST | FC_TDRA | PSE), then loads (A,Y) = (&B8, &81) – the address of data_rx_setup – and JMPs to ack_tx_write_dest which saves the pair into saved_nmi_lo/saved_nmi_hi (so the NMI handler will install it later) and writes the ACK destination address bytes to the TX FIFO.

Two callers: the dispatch in scout_complete at &81A2 and the immediate-op POKE path at &84AE (jmp_send_data_rx_ack).

On ExitA&B8 (low byte of data_rx_setup)
Y&81 (high byte of data_rx_setup)
81AA .send_data_rx_ack←2← 81A5 BVC← 84B9 JMP
LDA #&44 ; CR1=&44: RX_RESET | TIE
81AC STA econet_control1_or_status1 ; Write CR1: TX mode for ACK
81AF LDA #&a7 ; CR2=&A7: RTS | CLR_TX_ST | FC_TDRA | PSE
81B1 STA econet_control23_or_status2 ; Write CR2: enable TX with PSE
81B4 LDA #&bb ; Install data_rx_setup at &81B8
81B6 LDY #&81 ; High byte of data_rx_setup handler
81B8 JMP ack_tx_write_dest ; Send ACK with data_rx_setup as next NMI

NMI handler: switch ADLC to RX for the data frame

NMI continuation entry installed by send_data_rx_ack (which pushes (&81B8 - 1) on the stack and routes it through ack_tx_write_dest). When the next NMI fires, this body writes CR1 = &82 (TX_RESET | RIE) to switch the ADLC from scout-ACK TX mode to data-frame RX mode, then JMPs to install_nmi_handler to install nmi_data_rx as the next NMI handler.

81BB .data_rx_setup
LDA #&82 ; CR1=&82: TX_RESET | RIE (switch to RX for data frame)
81BD STA econet_control1_or_status1 ; Write CR1: switch to RX for data frame
81C0 LDA #<(nmi_data_rx) ; Next NMI handler address (low)
81C2 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: this routine (AP + dest-stn check) → nmi_data_rx_net (dest-net check) → nmi_data_rx_skip (skip ctrl + port) → nmi_data_rx_bulk (bulk data read) → data_rx_complete (completion).

81C5 .nmi_data_rx
LDA #1 ; A=1: AP mask for SR2 bit test
81C7 BIT econet_control23_or_status2 ; Test SR2 AP bit
81CA BEQ nmi_error_dispatch ; No AP: wrong frame or error
81CC LDA econet_data_continue_frame ; Read first byte (dest station)
81CF CMP tx_src_stn ; Compare to our station ID (tx_src_stn copy)
81D2 BNE nmi_error_dispatch ; Not for us: error path
81D4 LDA #<(nmi_data_rx_net) ; Next NMI handler address (low)
81D6 JMP install_nmi_handler ; Set NMI vector via RAM shim

NMI handler: validate dest-net byte of data frame

NMI continuation entry installed by nmi_data_rx once the AP and dest-station bytes have validated. Polls SR2 (BIT econet_control23_or_status2); on no RDA, branches to nmi_error_dispatch. Otherwise reads the dest- network byte from the ADLC FIFO and falls through to the control/port skip step.

On ExitAdest-network byte (validated against local)
81D9 .nmi_data_rx_net
BIT econet_control23_or_status2 ; Validate source network = 0
81DC BPL nmi_error_dispatch ; SR2 bit7 clear: no data ready -- error
81DE LDA econet_data_continue_frame ; Read dest network byte
81E1 BNE nmi_error_dispatch ; Network != 0: wrong network -- error
81E3 LDA #&ef ; Install skip handler at &8211
81E5 LDY #&81 ; High byte of &8211 handler
81E7 BIT econet_control1_or_status1 ; SR1 bit7: IRQ, data already waiting
81EA BMI nmi_data_rx_skip ; Data ready: skip directly, no return
81EC JMP set_nmi_vector ; Install handler and return

NMI handler: skip control + port bytes

NMI continuation entry that consumes the control and port bytes of the data frame (already known from the scout) and proceeds to the bulk-data-read continuation. Polls SR2 for RDA on entry; on no RDA, branches to nmi_error_dispatch.

81EF .nmi_data_rx_skip←1← 81EA BMI
BIT econet_control23_or_status2 ; Test SR2 RDA (RX data byte ready)
81F2 BPL nmi_error_dispatch ; SR2 bit7 clear: error
81F4 LDA econet_data_continue_frame ; Discard control byte
81F7 LDA econet_data_continue_frame ; Discard port byte
fall through ↓

Install data RX bulk or Tube handler

Selects between the normal bulk-RX handler at nmi_data_rx_bulk and the Tube RX handler at nmi_data_rx_tube based on bit 1 of net_frame_flags (tx_flags).

net_frame_flags bit 1 Handler
clear nmi_data_rx_bulk (A=&23, Y=&82)
set nmi_data_rx_tube (A=&91, Y=&82)

In the bulk path, after loading the handler address, checks SR1 bit 7. If IRQ is already asserted (more data waiting), jumps directly to nmi_data_rx_bulk to avoid NMI re-entry overhead. Otherwise installs the handler via set_nmi_vector (the (A,Y) pair becomes the NMI dispatch target) and returns via RTI.

81FA .install_data_rx_handler←1← 89A5 JMP
LDA #2 ; A=2: Tube transfer flag mask
81FC BIT net_frame_flags ; Check if Tube transfer active
81FF BNE install_tube_rx ; Tube active: use Tube RX path
8201 LDA #<(nmi_data_rx_bulk) ; Next NMI handler address (low)
8203 LDY #>(nmi_data_rx_bulk) ; Next NMI handler address (high)
8205 BIT econet_control1_or_status1 ; SR1 bit7: more data already waiting?
8208 BMI nmi_data_rx_bulk ; Yes: enter bulk read directly
820A JMP set_nmi_vector ; No: install handler
820D .install_tube_rx←1← 81FF BNE
LDA #<(nmi_data_rx_tube) ; Next NMI handler address (low)
820F LDY #>(nmi_data_rx_tube) ; Next NMI handler address (high)
8211 JMP set_nmi_vector ; Install Tube handler
; Page-overflow exit from nmi_data_rx_bulk: restores the Master ; 128 ACCCON
; that was saved at &822A before falling through to the ; RXCB-update path.
8214 .page_boundary_restore←1← 8242 BEQ
PLA ; Pull saved ACCCON from stack
8215 STA acccon ; Restore caller's ACCCON on page-overflow exit
fall through ↓

NMI error handler dispatch

Common error/abort entry used by 11 call sites. The dispatch byte at net_frame_flags doubles as a TX-state flag here: bit 7 distinguishes whether the NMI handler reached this point on an RX-error path or a TX not-listening path.

net_frame_flags bit 7 Path
clear RX error – full ADLC reset; return to idle listen
set TX not-listening – JMP tx_result_fail
8218 .nmi_error_dispatch←11← 818A JMP← 81A0 BCC← 81CA BEQ← 81D2 BNE← 81DC BPL← 81E1 BNE← 81F2 BPL← 827C BEQ← 8282 BEQ← 834C JMP← 849B JMP
LDA net_frame_flags ; Check tx_flags for error path
821B BPL rx_error_reset ; Bit7 clear: RX error path
821D JMP tx_result_fail ; Bit7 set: TX result = not listening
8220 .rx_error_reset←1← 821B BPL
JSR adlc_full_reset ; Full ADLC reset on RX error
8223 JMP discard_reset_rx ; 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) → completion via data_rx_complete.
  • SR2 = 0 → RTI, wait for next NMI to continue.
8226 .nmi_data_rx_bulk←1← 8208 BMI
LDY port_buf_len ; Y = buffer offset, resume from last position
8228 LDA econet_control23_or_status2 ; Read SR2 for next pair
822B .data_rx_loop←1← 8262 BNE
BPL data_rx_complete ; SR2 bit7 clear: frame complete (FV)
; Save/restore ACCCON across the (open_port_buf),Y stores
; in this bulk-read loop. Same idiom as in copy_scout_to_buffer; ; workspace
; &97 holds the desired ACCCON value pre-loaded by the caller.
822D LDA acccon ; Save current ACCCON on stack
8230 PHA ; Push ACCCON snapshot
8231 LDA escapable ; Load desired ACCCON from workspace &97
8233 STA acccon ; Set ACCCON for the upcoming buffer stores
8236 LDA econet_data_continue_frame ; Read first byte of pair from RX FIFO
8239 STA (open_port_buf),y ; Store byte to buffer
823B INY ; Advance buffer offset
823C BNE read_sr2_between_pairs ; Y != 0: no page boundary crossing
823E INC open_port_buf_hi ; Crossed page: increment buffer high byte
8240 DEC port_buf_len_hi ; Decrement remaining page count
8242 BEQ page_boundary_restore ; No pages left: handle as complete
8244 .read_sr2_between_pairs←1← 823C BNE
LDA econet_control23_or_status2 ; Read SR2 between byte pairs
8247 BMI read_second_rx_byte ; SR2 bit7 set: more data available
8249 BNE frame_complete_restore ; SR2 non-zero, bit7 clear: frame done
824B .read_second_rx_byte←1← 8247 BMI
LDA econet_data_continue_frame ; Read second byte of pair from RX FIFO
824E STA (open_port_buf),y ; Store byte to buffer
8250 INY ; Advance buffer offset
8251 STY port_buf_len ; Save updated buffer position
8253 BNE byte_pair_restore ; Y != 0: no page boundary crossing
8255 INC open_port_buf_hi ; Crossed page: increment buffer high byte
8257 DEC port_buf_len_hi ; Decrement remaining page count
8259 BEQ frame_complete_restore ; No pages left: frame complete
825B .byte_pair_restore←1← 8253 BNE
PLA ; Pull saved ACCCON from stack
825C STA acccon ; Restore caller's ACCCON between byte pairs
825F .check_sr2_loop_again
LDA econet_control23_or_status2 ; Re-poll ADLC SR2 for next byte pair
8262 BNE data_rx_loop ; More data: loop back to data_rx_loop
8264 JMP nmi_rti ; No more data: return from NMI
8267 .frame_complete_restore←2← 8249 BNE← 8259 BEQ
PLA ; Pull saved ACCCON (frame-complete path)
8268 STA acccon ; Restore caller's ACCCON before completion
fall through ↓

Data frame completion

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

826B .data_rx_complete←1← 822B BPL
LDA #&84 ; A=&84: CR2 value (disable PSE)
826D STA econet_control23_or_status2 ; Write CR2 = &84 to disable PSE for bit testing
8270 LDA #0 ; A=0: CR1 value (disable all interrupts)
8272 STA econet_control1_or_status1 ; Write CR1 = 0 to disable all interrupts
8275 STY port_buf_len ; Save Y (byte count from data RX loop)
8277 LDA #2 ; A=&02: FV mask
8279 BIT econet_control23_or_status2 ; Test SR2 FV (Z) and RDA (N)
827C BEQ nmi_error_dispatch ; No FV -- error
827E BPL send_ack ; FV set, no RDA -- proceed to ACK
8280 LDA port_buf_len_hi ; Check if buffer space remains
8282 .read_last_rx_byte←3← 82AC BEQ← 82D3 BEQ← 82DF BEQ
BEQ nmi_error_dispatch ; No buffer space: error/discard frame
8284 LDA acccon ; FV+RDA: read and store last data byte
8287 PHA ; Save current ACCCON on stack
8288 LDA escapable ; Load desired paging mode from escapable (&97)
828A STA acccon ; Select paging so the buffer store lands correctly
828D LDA econet_data_continue_frame ; Read last data byte from RX FIFO (FV+RDA)
8290 LDY port_buf_len ; Y = current buffer write offset
8292 STA (open_port_buf),y ; Store last byte in port receive buffer
8294 PLA ; Pull saved ACCCON
8295 STA acccon ; Restore caller's ACCCON
8298 INC port_buf_len ; Advance buffer write offset
829A BNE send_ack ; No page wrap: proceed to send ACK
829C INC open_port_buf_hi ; Page boundary: advance buffer page
829E .send_ack←2← 827E BPL← 829A BNE
JMP ack_tx ; Send ACK frame to complete handshake

NMI handler: data-frame RX into Tube buffer

NMI continuation entry for the Tube data-RX path. Polls SR2 for RDA, reads the next data byte from the ADLC RX FIFO, and writes it to the Tube data register, advancing the Tube transfer pointer each iteration. Tests for end-of-frame via FV and either continues the tight inner loop or returns via RTI. Reached only via the NMI vector after install_tube_rx configures the handler.

82A1 .nmi_data_rx_tube
LDA econet_control23_or_status2 ; Read SR2 for Tube data receive path
82A4 .rx_tube_data←1← 82BF BNE
BPL data_rx_tube_complete ; RDA clear: no more data, frame complete
82A6 LDA econet_data_continue_frame ; Read data byte from ADLC RX FIFO
82A9 JSR advance_buffer_ptr ; Check buffer limits and transfer size
82AC BEQ read_last_rx_byte ; Zero: buffer full, handle as error
82AE STA tube_data_register_3 ; Send byte to Tube data register 3
82B1 LDA econet_data_continue_frame ; Read second data byte (paired transfer)
82B4 STA tube_data_register_3 ; Send second byte to Tube
82B7 JSR advance_buffer_ptr ; Check limits after byte pair
82BA BEQ data_rx_tube_complete ; Zero: Tube transfer complete
82BC LDA econet_control23_or_status2 ; Re-read SR2 for next byte pair
82BF BNE rx_tube_data ; More data available: continue loop
82C1 .data_rx_tube_error
JMP nmi_rti ; Unexpected end: return from NMI
82C4 .data_rx_tube_complete←2← 82A4 BPL← 82BA BEQ
LDA #0 ; CR1=&00: disable all interrupts
82C6 STA econet_control1_or_status1 ; Write CR1 for individual bit testing
82C9 LDA #&84 ; CR2=&84: disable PSE
82CB STA econet_control23_or_status2 ; Write CR2: same pattern as main path
82CE LDA #2 ; A=&02: FV mask for Tube completion
82D0 BIT econet_control23_or_status2 ; Test SR2 FV (Z) and RDA (N)
82D3 BEQ read_last_rx_byte ; No FV: incomplete frame, error
82D5 BPL ack_tx ; FV set, no RDA: proceed to ACK
82D7 LDA port_buf_len ; Check if any buffer was allocated
82D9 ORA port_buf_len_hi ; OR all 4 buffer pointer bytes together
82DB ORA open_port_buf ; Check buffer low byte
82DD ORA open_port_buf_hi ; Check buffer high byte
82DF BEQ read_last_rx_byte ; All zero (null buffer): error
82E1 LDA econet_data_continue_frame ; Read extra trailing byte from FIFO
82E4 STA rx_extra_byte ; Save extra byte in workspace for later use
82E7 LDA #&20 ; Bit5 = extra data byte available flag
82E9 ORA net_frame_flags ; Set extra byte flag in tx_flags
82EC STA net_frame_flags ; Store updated flags
fall through ↓

ACK transmission

Sends a scout ACK or final ACK frame as part of the four-way handshake. Tests bit 7 of net_frame_flags (used as TX-flags here): if set this is a final ACK and completion runs through tx_result_ok. Otherwise configures for TX (CR1=&44, CR2=&A7) and writes the ACK address frame: destination station from scout_buf, destination network from scout_src_net, source station from the workspace copy tx_src_stn, and 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 saved_nmi_lo/saved_nmi_hi (saved by the scout/data RX handler via ack_tx_write_dest) and sends TX_LAST_DATA (CR2=&3F) to close the frame.

82EF .ack_tx←2← 829E JMP← 82D5 BPL
LDA net_frame_flags ; Load TX flags to check ACK type
82F2 BPL ack_tx_configure ; Bit7 clear: normal scout ACK
82F4 JSR advance_rx_buffer_ptr ; Final ACK: call completion handler
82F7 JMP tx_result_ok ; Jump to TX success result
82FA .ack_tx_configure←1← 82F2 BPL
LDA #&44 ; CR1=&44: RX_RESET | TIE (switch to TX mode)
82FC STA econet_control1_or_status1 ; Write CR1: switch to TX mode
82FF LDA #&a7 ; CR2=&A7: RTS|CLR_TX_ST|FC_TDRA|2_1_BYTE|PSE
8301 STA econet_control23_or_status2 ; Write CR2: enable TX with status clear
8304 LDA #&97 ; Install saved next handler (scout ACK path)
8306 LDY #&83 ; High byte of post-ACK handler
fall through ↓

Begin ACK transmit: write destination address to ADLC

First step of the four-byte ACK frame transmission. Saves the caller-supplied (A=lo, Y=hi) next-NMI handler address into saved_nmi_lo / saved_nmi_hi, loads the destination station from scout_buf and tests SR1 bit 6 (TDRA, TX Data Register Available) via BIT econet_control1_or_status1. If TDRA is clear the TX FIFO isn't ready and control branches to dispatch_nmi_error to abort.

When TDRA is set, writes the destination station and network bytes (from scout_src_net) into econet_data_continue_frame, then installs nmi_ack_tx_src as the next NMI handler via set_nmi_vector -- that handler will write the source-address pair on the next NMI.

Two callers: send_data_rx_ack's tail JMP and imm_op_build_reply.

On EntryAlow byte of next NMI handler
Yhigh byte of next NMI handler
8308 .ack_tx_write_dest←2← 81B8 JMP← 84EF JMP
STA saved_nmi_lo ; Store next handler low byte
830B STY saved_nmi_hi ; Store next handler high byte
830E LDA scout_buf ; Load dest station from RX scout buffer
8311 BIT econet_control1_or_status1 ; Test SR1 TDRA (V=bit6)
8314 BVC dispatch_nmi_error ; TDRA not ready -- error
8316 STA econet_data_continue_frame ; Write dest station to TX FIFO
8319 LDA scout_src_net ; Load dest network from RX scout buffer
831C STA econet_data_continue_frame ; Write dest net byte to FIFO
831F LDA #<(nmi_ack_tx_src) ; Next NMI handler address (low)
8321 LDY #>(nmi_ack_tx_src) ; Next NMI handler address (high)
8323 JMP set_nmi_vector ; Set NMI vector to ack_tx_src handler

ACK TX continuation

Continuation of ACK frame transmission, reached via NMI after ack_tx_write_dest installed it as the next handler. Reads our station ID from the workspace copy tx_src_stn, tests TDRA via SR1, and writes (station, network=0) to the TX FIFO -- completing the 4-byte ACK address header.

Then dispatches on net_frame_flags bit 7 (which the caller uses as a TX-flags byte):

Bit 7 Action
set branch to start_data_tx to begin the data phase
clear write CR2=&3F (TX_LAST_DATA) and fall through to post_ack_scout
8326 .nmi_ack_tx_src
LDA tx_src_stn ; Load our station ID from workspace copy
8329 BIT econet_control1_or_status1 ; Test SR1 TDRA
832C BVC dispatch_nmi_error ; TDRA not ready -- error
832E STA econet_data_continue_frame ; Write our station to TX FIFO
8331 LDA #0 ; Write network=0 to TX FIFO
8333 STA econet_data_continue_frame ; Write network=0 (local) to TX FIFO
8336 LDA net_frame_flags ; Check tx_flags for data phase
8339 BMI start_data_tx ; bit7 set: start data TX phase
833B 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 starting at rx_src_stn (scout-ACK destination addresses). Checks the port byte at rx_port against open receive blocks to find a matching listener.

  • Match – sets up the data-RX handler chain for the four-way- handshake data phase.
  • No match – discards the frame.
833D .post_ack_scout
STA econet_control23_or_status2 ; Write CR2 to clear status after ACK TX
8340 LDA saved_nmi_lo ; Install saved handler from &0D4B/&0D4C
8343 LDY saved_nmi_hi ; Load saved next handler high byte
8346 JMP set_nmi_vector ; Install next NMI handler
8349 .start_data_tx←1← 8339 BMI
JMP data_tx_begin ; Jump to start data TX phase
834C .dispatch_nmi_error←2← 8314 BVC← 832C 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.

Reads:

  • tx_flags bit 1 – data transfer in progress
  • tx_flags bit 5 – Tube transfer
  • 4-byte transfer count from net_tx_ptr,Y (Y=8..&0B)
  • RXCB pointer at (port_ws_offset),Y

Updates the RXCB in place. Clobbers A and Y; preserves X across the Tube branch (saved/restored via stack).

On ExitA&FF when transfer was active, else preserved entry value
834F .advance_rx_buffer_ptr←2← 82F4 JSR← 83A7 JSR
LDA #2 ; A=2: test bit1 of tx_flags
8351 BIT net_frame_flags ; Check tx_flags data-transfer bit
8354 BEQ return_rx_complete ; Bit1 clear: no transfer -- return
8356 CLC ; Init carry for 4-byte add
8357 PHP ; Save carry on stack for loop
8358 LDY #8 ; Y=8: start at byte 0 of the 4-byte RXCB pointer
835A CLD ; Clear decimal mode: the RXCB-pointer ADC below must be binary
835B .add_rxcb_ptr←1← 8367 BCC
LDA (port_ws_offset),y ; Load RXCB[Y] (buffer pointer byte)
835D PLP ; Restore carry from stack
835E ADC net_tx_ptr,y ; Add transfer count byte
8361 STA (port_ws_offset),y ; Store updated pointer back to RXCB
8363 INY ; Next byte
8364 PHP ; Save carry for next iteration
8365 CPY #&0c ; Done 4 bytes? (Y reaches &0C)
8367 BCC add_rxcb_ptr ; No: continue adding
8369 PLP ; Discard final carry
836A LDA #&20 ; A=&20: test bit5 of tx_flags
836C BIT net_frame_flags ; Check tx_flags Tube bit
836F BEQ skip_tube_update ; No Tube: skip Tube update
8371 TXA ; Save X on stack
8372 PHA ; Push X
8373 LDA #8 ; A=8: offset for Tube address
8375 CLC ; For address calculation
8376 ADC port_ws_offset ; Add workspace base offset
8378 TAX ; X = address low for Tube claim
8379 LDY rx_buf_offset ; Y = address high for Tube claim
837B LDA #1 ; A=1: Tube claim type (read)
837D JSR tube_addr_data_dispatch ; Claim Tube address for transfer
8380 LDA rx_extra_byte ; Load extra RX data byte
8383 STA tube_data_register_3 ; Send to Tube via R3
8386 SEC ; Init carry for increment
8387 LDY #8 ; Y=8: start at byte 0 of the 4-byte RXCB pointer
8389 .inc_rxcb_ptr←1← 8390 BCS
LDA #0 ; A=0: add carry only (increment)
838B ADC (port_ws_offset),y ; Add carry to pointer byte
838D STA (port_ws_offset),y ; Store back to RXCB
838F INY ; Next byte
8390 BCS inc_rxcb_ptr ; Keep going while carry propagates
8392 PLA ; Restore X from stack
8393 TAX ; Transfer to X register
8394 .skip_tube_update←1← 836F BEQ
LDA #&ff ; A=&FF: return value (transfer done)
8396 .return_rx_complete←1← 8354 BEQ
RTS ; Return
8397 EQUB &D8 ; Unreached &D8 (CLD) byte after the RTS

Post-ACK frame-complete NMI handler

Installed by ack_tx_configure via saved_nmi_lo / saved_nmi_hi. Fires as an NMI after the ACK frame (CRC + closing flag) has been fully transmitted by the ADLC. Dispatches on scout_port:

scout_port Control Target
≠ 0 rx_complete_update_rxcb (finalise data transfer, mark RXCB complete)
0 &82 (POKE) rx_complete_update_rxcb (same path)
0 other imm_op_build_reply
8398 .nmi_post_ack_dispatch
LDA scout_port ; Load received port byte
839B BNE rx_complete_update_rxcb ; Port != 0: data transfer frame
839D LDY scout_ctrl ; Port=0: load control byte
83A0 CPY #&82 ; Ctrl = &82 (POKE)?
83A2 BEQ rx_complete_update_rxcb ; Yes: POKE also needs data transfer
83A4 JMP imm_op_build_reply ; Other port-0 ops: immediate dispatch

Complete RX and update RXCB

Called from nmi_post_ack_dispatch after the final ACK has been transmitted. Finalises the received data transfer:

  1. Calls advance_rx_buffer_ptr to update the 4-byte buffer pointer with the transfer count (and handle Tube re-claim if needed).
  2. Stores the source station, network, and port into the RXCB.
  3. ORs &80 into the RXCB control byte (bit 7 = complete).

This is the NMI-to-foreground synchronisation point: wait_net_tx_ack polls bit 7 of the RXCB control byte to detect that the reply has arrived.

Falls through to discard_reset_rx to reset the ADLC to idle RX-listen mode.

83A7 .rx_complete_update_rxcb←3← 839B BNE← 83A2 BEQ← 8443 JMP
JSR advance_rx_buffer_ptr ; Update buffer pointer and check for Tube
83AA BNE skip_buf_ptr_update ; Transfer not done: skip buffer update
83AC .add_buf_to_base
LDA port_buf_len ; Load buffer bytes remaining
83AE CLC ; For address add
83AF ADC open_port_buf ; Add to buffer base address
83B1 BCC store_buf_ptr_lo ; No carry: skip high byte increment
83B3 .inc_rxcb_buf_hi
INC open_port_buf_hi ; Carry: increment buffer high byte
83B5 .store_buf_ptr_lo←1← 83B1 BCC
LDY #8 ; Y=8: store updated buffer position
83B7 .store_rxcb_buf_ptr
STA (port_ws_offset),y ; Store updated low byte to RXCB
83B9 INY ; Y=9: buffer high byte offset
83BA LDA open_port_buf_hi ; Load updated buffer high byte
83BC .store_rxcb_buf_hi
STA (port_ws_offset),y ; Store high byte to RXCB
83BE .skip_buf_ptr_update←1← 83AA BNE
LDA scout_port ; Check port byte again
83C1 BEQ discard_reset_rx ; Port=0: immediate op, discard+listen
83C3 LDA scout_src_net ; Load source network from scout buffer
83C6 LDY #3 ; Y=3: RXCB source network offset
83C8 STA (port_ws_offset),y ; Store source network to RXCB
83CA DEY ; Y=2: source station offset
83CB LDA scout_buf ; Load source station from scout buffer
83CE STA (port_ws_offset),y ; Store source station to RXCB
83D0 DEY ; Y=1: port byte offset
83D1 LDA scout_port ; Load port byte
83D4 STA (port_ws_offset),y ; Store port to RXCB
83D6 DEY ; Y=0: control/flag byte offset
83D7 LDA scout_ctrl ; Load control byte from scout
83DA ORA #&80 ; Set bit7: signals wait_net_tx_ack that reply arrived
83DC STA (port_ws_offset),y ; Store to RXCB byte 0 (bit 7 set = complete)
83DE LDA fs_flags ; Load callback event flags
83E1 ROR ; Shift bit 0 into carry
83E2 BCC discard_reset_rx ; Bit 0 clear: no callback, skip to reset
83E4 LDA port_ws_offset ; Load RXCB workspace pointer low byte (carry set on entry)
83E6 .loop_count_rxcb_slot←1← 83E9 BCS
INY ; Count slots
83E7 SBC #&0c ; Subtract 12 bytes per RXCB slot
83E9 BCS loop_count_rxcb_slot ; Loop until pointer exhausted
83EB DEY ; Adjust for off-by-one
83EC CPY #3 ; Check slot index >= 3
83EE BCC discard_reset_rx ; Slot < 3: no callback, skip to reset
83F0 JSR discard_reset_listen ; Discard scout and reset listen state
83F3 TYA ; Pass slot index as callback parameter
83F4 JMP setup_sr_tx ; Jump to TX completion with slot index

Discard scout, reset ADLC, install RX-scout NMI

Three-stage idle-restore chain:

  1. discard_reset_listen – abandon any in-flight scout and release a held Tube claim.
  2. reset_adlc_rx_listen – call adlc_rx_listen (reset CR1/CR2 and re-arm RX).
  3. set_nmi_rx_scout – install nmi_rx_scout as the active NMI handler and JMP out via set_nmi_vector.

Used as the standard "something went wrong, get back to listening" exit.

83F7 .discard_reset_rx←6← 8223 JMP← 83C1 BEQ← 83E2 BCC← 83EE BCC← 8954 JMP← 89BE JMP
JSR discard_reset_listen ; Discard scout and reset RX listen
fall through ↓

Reset ADLC and install RX-scout NMI

Tail of the discard_reset_rx chain entered directly when no scout needs discarding. Calls adlc_rx_listen to reset CR1/CR2 to RX-only mode, then falls through to set_nmi_rx_scout.

Two inbound JSRs plus one fall-through (from discard_reset_rx).

83FA .reset_adlc_rx_listen←3← 80E7 JMP← 8446 BCS← 854D JMP
JSR adlc_rx_listen ; Reset ADLC and return to RX listen
fall through ↓

Install nmi_rx_scout as NMI handler

Loads (A=&9B, Y=&80) -- the address of nmi_rx_scout -- and JMPs to set_nmi_vector, which writes both bytes into the NMI JMP-target slot at nmi_jmp_lo/nmi_jmp_hi. Tail of the discard_reset_rx / reset_adlc_rx_listen chain, used to put the NMI vector back to scout-handling after a discard or reset.

Two callers: &80CD (after init) and &80E4 (after error).

83FD .set_nmi_rx_scout←2← 80CD JMP← 80E4 JMP
LDA #<(nmi_rx_scout) ; Next NMI handler address (low)
83FF LDY #>(nmi_rx_scout) ; Next NMI handler address (high)
8401 JMP set_nmi_vector ; Install nmi_rx_scout as NMI handler

Discard with Tube release

Checks whether a Tube transfer is active by ANDing bit 1 of tube_present with net_frame_flags (tx_flags). If a Tube claim is held, calls release_tube to free it before returning.

Used as the clean-up path after RXCB completion and after ADLC reset to ensure no stale Tube claims persist.

8404 .discard_reset_listen←2← 83F0 JSR← 83F7 JSR
LDA #2 ; Tube flag bit 1 AND tx_flags bit 1
8406 AND tube_present ; Check if Tube transfer active
8409 BIT net_frame_flags ; Test tx_flags for Tube transfer
840C BEQ rts_discard_reset ; No Tube transfer active -- skip release
840E JSR release_tube ; Release Tube claim before discarding
8411 .rts_discard_reset←1← 840C BEQ
RTS ; Return

Copy scout data to port buffer (entry point)

Five-instruction prologue that prepares to copy scout-payload bytes (offsets 4..&0B) from scout_buf into the open port buffer. Saves X on the stack, loads X=4 (the first scout-data offset) and A=&02 (Tube-flag mask), then BITs net_frame_flags (tx_flags) so the immediately following BNE in save_acccon_for_shadow_ram can dispatch:

Bit 1 Path
clear fall through into save_acccon_for_shadow_ram (direct memory store via (open_port_buf),Y, with ACCCON saved/restored on Master 128)
set branch to copy_scout_via_tube (Tube R3 write)

Both paths walk the four-byte buffer pointer and end via scout_copy_done which restores X and returns. Single caller: port_match_found at &81A7.

8412 .copy_scout_to_buffer←1← 81A7 JMP
TXA ; Save X on stack
8413 PHA ; Push X
8414 LDX #4 ; X=4: start at scout byte offset 4
8416 LDA #2 ; A=2: Tube transfer check mask
8418 .copy_scout_select
BIT net_frame_flags ; Check tx_flags Tube bit
fall through ↓

Save ACCCON across scout-buffer access

Saves the current acccon value, sets ACCCON for the upcoming (open_port_buf),Y stores (so writes go to the right shadow / main RAM bank on the Master 128), performs the copy, then restores the saved ACCCON before returning. Wraps the inner copy loop with shadow-RAM gating so scout-buffer writes land in the caller's address space rather than the FS-private HAZEL window.

841B .save_acccon_for_shadow_ram
BNE copy_scout_via_tube ; Tube active: use R3 write path
; Save/restore ACCCON across the (open_port_buf),Y stores.
; The destination port buffer may live in shadow RAM; bit 0 of ; ACCCON (D)
; controls whether (zp),Y addressing hits shadow vs main RAM. ; Workspace &97
; holds the desired ACCCON value pre-loaded by the caller.
841D LDA acccon ; Save current ACCCON on stack
8420 PHA ; Push ACCCON snapshot
8421 LDA escapable ; Load desired ACCCON from workspace &97
8423 STA acccon ; Set ACCCON for the upcoming (open_port_buf),Y stores
8426 LDY port_buf_len ; Y = current buffer position
8428 .copy_scout_bytes←1← 843B BNE
LDA scout_buf,x ; Load scout data byte
842B STA (open_port_buf),y ; Store to port buffer
842D INY ; Advance buffer pointer
842E BNE next_scout_byte ; No page crossing
8430 INC open_port_buf_hi ; Page crossing: inc buffer high byte
8432 DEC port_buf_len_hi ; Decrement remaining page count
8434 BEQ scout_page_overflow ; No pages left: overflow
8436 .next_scout_byte←1← 842E BNE
INX ; Next scout data byte
8437 STY port_buf_len ; Save updated buffer position
8439 CPX #&0c ; Done all scout data? (X reaches &0C)
843B BNE copy_scout_bytes ; No: continue copying
843D .scout_copy_done
PLA ; Pull saved ACCCON from stack
843E STA acccon ; Restore caller's ACCCON before continuing
8441 .scout_done_restore_x←2← 8458 BEQ← 8497 BEQ
PLA ; Pull saved X from stack
8442 TAX ; Copy saved ACCCON to X
8443 JMP rx_complete_update_rxcb ; Tail-jump to rx_complete_update_rxcb
8446 .dispatch_imm_op_fail←1← 8482 BCS
BCS reset_adlc_rx_listen ; Reset ADLC if carry set
8448 .copy_scout_via_tube←2← 841B BNE← 8456 BNE
LDA scout_buf,x ; Tube path: load scout data byte
844B STA tube_data_register_3 ; Send byte to Tube via R3
844E JSR advance_buffer_ptr ; Increment buffer position counters
8451 BEQ tube_overflow_restore ; Counter overflow: handle end of buffer
8453 INX ; Next scout data byte
8454 CPX #&0c ; Done all scout data?
8456 BNE copy_scout_via_tube ; No: continue Tube writes
8458 BEQ scout_done_restore_x ; Index reached end: restore X and finish
fall through ↓

Release Tube co-processor claim

Tests bit 7 of prot_flags -- the bit ANFS uses to track whether the Tube is currently still claimed:

Bit 7 State Action
set already released branch to clear_release_flag (skips the release call)
clear claim held JSR tube_addr_data_dispatch with A=&82 to release the claim, then fall through

Both paths end at clear_release_flag which LSRs prot_flags (shifting bit 7 to 0) before returning.

Called after completed RX transfers and during discard paths to ensure no stale Tube claims persist.

Idempotent: safe to call when the Tube has already been released. Clobbers A; preserves X and Y.

On ExitAclobbered
845A .release_tube←2← 840E JSR← 8611 JSR
BIT prot_flags ; Check if Tube needs releasing
845C BMI clear_release_flag ; Bit7 set: already released
845E LDA #&82 ; A=&82: Tube release claim type
8460 JSR tube_addr_data_dispatch ; Release Tube address claim
8463 .clear_release_flag←1← 845C BMI
LSR prot_flags ; Clear release flag (LSR clears bit7)
8465 RTS ; Return

Immediate operation handler (port = 0)

Checks the control byte at scout_ctrl for immediate-operation codes:

Range Op Treatment
< &81 or > &88 out of range; discarded
&81..&86 PEEK / POKE / JSR / UserProc / OSProc / HALT gated by the prot_status protection mask
&87..&88 CONTINUE / machine-type bypass the mask check

For &81..&86, converts the code to a 0-based index and tests against the per-station protection mask prot_status to determine whether this station accepts the operation. If accepted, dispatches via the immediate-op dispatch table (PHA/PHA/RTS).

The execute-class operations (&83 JSR, &84 UserProc, &85 OSProc, &86 HALT, &87 CONTINUE) cannot run inside the NMI receive handler -- a JSR into user code or an OS call is unsafe there -- so they are not run inline. They are completed later from normal IRQ context: setup_sr_tx records the operation in tx_op_type and sets the Master 128 ACCCON IRR latch (bit 7 at &FE34), which raises an IRQ that the ROM picks up as service call &05 (svc5_irq_check) and dispatches via the svc5_dispatch_lo table. PEEK, POKE and machine-type (&81 / &82 / &88) only touch memory and reply immediately, so they run here.

Builds the reply by storing data length, station / network, and control byte into the RX buffer header.

8466 .immediate_op←1← 813A JMP
LDY scout_ctrl ; Control byte &81-&88 range check
8469 CPY #&81 ; Below &81: not an immediate op
846B BCC imm_op_out_of_range ; Out of range low: jump to discard
846D CPY #&89 ; Above &88: not an immediate op
846F BCS imm_op_out_of_range ; Out of range high: jump to discard
8471 CPY #&87 ; CONTINUE(&87)/mc-type(&88) skip protection
8473 BCS dispatch_imm_op ; Ctrl >= &87: dispatch without mask check
8475 TYA ; Convert ctrl byte to 0-based index for mask
8476 CLD ; Clear decimal mode: the operation-index SBC below must be binary
8477 SEC ; For subtract
8478 SBC #&81 ; A = ctrl - &81 (0-based operation index)
847A TAY ; Y = index for mask rotation count
847B LDA prot_status ; Load protection mask from LSTAT
847E .rotate_prot_mask←1← 8480 BPL
ROR ; Rotate mask right by control byte index
847F DEY ; Decrement rotation counter
8480 BPL rotate_prot_mask ; Loop until bit aligned
8482 BCS dispatch_imm_op_fail ; Bit set = operation disabled, discard
8484 .dispatch_imm_op←1← 8473 BCS
LDY scout_ctrl ; Reload ctrl byte for dispatch table
8487 LDA #&84 ; Hi byte: all handlers are in page &84
8489 PHA ; Push hi byte for PHA/PHA/RTS dispatch
848A LDA imm_op_handler_lo_table,y ; Load handler low byte from jump table
848D PHA ; Push handler low byte
848E RTS ; RTS dispatches to handler
848F .scout_page_overflow←1← 8434 BEQ
INC port_buf_len ; Increment port buffer length
; Tube-path overflow exit from copy_scout_to_buffer: restores ; the Master 128
; ACCCON that was saved at &840B before re-joining the ; scout-done path.
8491 .tube_overflow_restore←1← 8451 BEQ
PLA ; Pull saved ACCCON from stack
8492 .tube_overflow_restore_acccon
STA acccon ; Restore caller's ACCCON on Tube-overflow exit
8495 .check_scout_done
CPX #&0b ; Check if scout data index reached 11
8497 BEQ scout_done_restore_x ; Yes: loop back to continue reading
8499 PLA ; Restore A from stack
849A TAX ; Transfer to X
849B .imm_op_out_of_range←2← 846B BCC← 846F BCS
JMP nmi_error_dispatch ; Jump to discard handler

RX immediate: JSR / UserProc / OSProc setup

Sets up the port buffer to receive remote-procedure data. Copies the 2-byte remote address from scout_data into the execution-address workspace at exec_addr_lo / exec_addr_hi, then jumps to the common data-receive path via send_data_rx_ack.

Used for operation types &83 (JSR), &84 (UserProc), and &85 (OSProc).

849E .rx_imm_exec
LDA #0 ; A=0: port buffer lo at page boundary
84A0 STA open_port_buf ; Set port buffer lo
84A2 LDA #&82 ; Buffer length lo = &82
84A4 STA port_buf_len ; Set buffer length lo
84A6 LDA #1 ; Buffer length hi = 1
84A8 STA port_buf_len_hi ; Set buffer length hi
84AA LDA net_rx_ptr_hi ; Load RX page hi for buffer
84AC STA open_port_buf_hi ; Set port buffer hi
84AE LDY #1 ; Y=1: copy 2 bytes (1 down to 0)
84B0 .copy_addr_loop←1← 84B7 BPL
LDA scout_data,y ; Load remote address byte
84B3 STA exec_addr_lo,y ; Store to exec address workspace
84B6 DEY ; Next byte (descending)
84B7 BPL copy_addr_loop ; Loop until all 4 bytes copied
84B9 .jmp_send_data_rx_ack
JMP send_data_rx_ack ; Enter common data-receive path

RX immediate: POKE setup

Sets up workspace offsets for receiving POKE data: port_ws_offset = &2E, rx_buf_offset = &0D. Jumps to the POKE data-receive path at port_match_found. POKE (&82) only writes memory and replies, so it is serviced inline in the receive path -- not deferred like the execute-class operations &83-&87.

84BC .svc5_dispatch_lo
.rx_imm_poke
LDA #&2e ; Port workspace offset = &2E
84BE STA port_ws_offset ; Store as port_ws_offset
84C0 LDA #&0d ; RX buffer page = &0D
84C2 STA rx_buf_offset ; Store as rx_buf_offset
84C4 JMP port_match_found ; Enter POKE data-receive path

RX immediate: PEEK setup

Sets up workspace offsets for a PEEK response: port_ws_offset = &2E, rx_buf_offset = &0D, rx_port = 2 (PEEK response status), then calls tx_calc_transfer to send the requested memory back to the requesting station. PEEK (&81) only reads memory and replies, so it is serviced inline in the receive path -- not deferred like the execute-class operations &83-&87.

Reached via the immediate-op dispatch table (imm_op_handler_lo_table) for control byte &81.

84C7 .rx_imm_peek
LDA #&2e ; Port workspace offset = &2E
84C9 STA port_ws_offset ; Store as port_ws_offset
84CB LDA #&0d ; RX buffer page = &0D
84CD STA rx_buf_offset ; Store as rx_buf_offset
84CF LDA #2 ; Scout status = 2 (PEEK response)
84D1 STA rx_port ; Store scout status
84D4 JSR tx_calc_transfer ; Calculate transfer size for response
84D7 BCC imm_op_discard ; C=0: transfer not set up, discard
84D9 .set_tx_reply_flag←1← 8513 BNE
LDA net_frame_flags ; Mark TX flags bit 7 (reply pending)
84DC .imm_reply_flag←1Used as index base by← 804D LDA
ORA #&80 ; Set reply pending flag
84DE STA net_frame_flags ; Store updated TX flags
84E1 .rx_imm_halt_cont
LDA #&44 ; CR1=&44: TIE | TX_LAST_DATA
84E3 STA econet_control1_or_status1 ; Write CR1: enable TX interrupts
84E6 .tx_cr2_setup
LDA #&a7 ; CR2=&A7: RTS|CLR_RX_ST|FC_TDRA|PSE
84E8 STA econet_control23_or_status2 ; Write CR2 for TX setup
84EB .tx_nmi_setup
LDA #&33 ; NMI handler lo byte (self-modifying)
84ED LDY #&85 ; Y=&85: NMI handler high byte
84EF JMP ack_tx_write_dest ; Acknowledge and write TX dest

RX immediate: machine-type (MachinePeek) reply

Builds the fixed machine-identity reply for a MachinePeek immediate operation (control byte &88). Copies the 3 identity bytes at machine_id_bytes into mc_reply_machine_id, copies spool_control_flag to mc_reply_status, points the reply buffer via open_port_buf and sets its length, then branches to set_tx_reply_flag to send it. Serviced inline (like PEEK/POKE), not deferred.

Reached only via the immediate-op dispatch table (imm_op_dispatch_lo) for control byte &88.

84F2 .rx_imm_machine_type
LDA #1 ; Reply length hi = &01
84F4 STA port_buf_len_hi ; Set port_buf_len_hi
84F6 LDA #&fc ; Reply length lo = &FC
84F8 STA port_buf_len ; Set port_buf_len
84FA LDY #2 ; Y=2: copy 3 identity bytes (2 down to 0)
84FC .copy_machine_id_loop←1← 8503 BPL
LDA machine_id_bytes,y ; Read machine-identity byte from ROM
84FF STA mc_reply_machine_id,y ; Store into mc_reply_machine_id
8502 DEY ; Next byte (descending)
8503 BPL copy_machine_id_loop ; Loop until all 3 copied
8505 LDA spool_control_flag ; Load station/config byte from &0D71
8508 STA mc_reply_status ; Store into mc_reply_status
850B LDA #&3a ; Reply buffer lo = &3A
850D STA open_port_buf ; Set open_port_buf
850F LDA #&0c ; Reply buffer hi = &0C
8511 STA open_port_buf_hi ; Set open_port_buf_hi
8513 BNE set_tx_reply_flag ; Always taken: join common reply-send path
fall through ↓

Immediate-op dispatch lo-byte table (8 entries)

Eight low-byte entries at &8515-&851C indexed by the immediate-op control byte (&81-&88) via LDA imm_op_handler_lo_table,Y at the dispatch site (the table's nominal base imm_op_handler_lo_table so the entry for control byte N sits at that base plus N). Each byte is the low byte of (handler-1) so the PHA/PHA/RTS dispatch (constant high byte &84) lands on the handler in page &84xx. Per-entry inline comments identify each control byte's handler.

8515 .imm_op_dispatch_lo
EQUB <(rx_imm_peek - 1) ; ctrl &81: PEEK
8516 EQUB <(rx_imm_poke - 1) ; ctrl &82: POKE
8517 EQUB <(rx_imm_exec - 1) ; ctrl &83: JSR
8518 EQUB <(rx_imm_exec - 1) ; ctrl &84: UserProc
8519 EQUB <(rx_imm_exec - 1) ; ctrl &85: OSProc
851A EQUB <(rx_imm_halt_cont - 1) ; ctrl &86: HALT
851B EQUB <(rx_imm_halt_cont - 1) ; ctrl &87: CONTINUE
851C EQUB <(rx_imm_machine_type - 1) ; ctrl &88: machine-type
851D .imm_op_build_reply←1← 83A4 JMP
LDA port_buf_len ; Get buffer position for reply header
851F CLC ; Clear carry for offset addition
8520 ADC #&80 ; Data offset = buf_len + &80 (past header)
8522 LDY #&7f ; Y=&7F: reply data length slot
8524 STA (net_rx_ptr),y ; Store reply data length in RX buffer
8526 LDY #&80 ; Y=&80: source station slot
8528 LDA scout_buf ; Load requesting station number
852B STA (net_rx_ptr),y ; Store source station in reply header
852D INY ; Advance buffer index
852E LDA scout_src_net ; Load requesting network number
8531 STA (net_rx_ptr),y ; Store source network in reply header
8533 LDA scout_ctrl ; Load control byte from received frame
fall through ↓

Save TX op type and raise JSR re-entrancy protection

Stores the TX operation type in tx_op_type, then (at enable_irq_pending) sets the ACCCON IRR latch to arm the deferred dispatch -- see svc5_irq_check.

Op code Path
≥ &86 (HALT / CONTINUE / machine-type) branch forward to the ACCCON IRR set; the protection mask is left untouched
< &86 (JSR / UserProc / OSProc) load the prot_status protection mask, copy it to prot_status_save, ORA in bits 2-4 to disable further JSR / UserProc / OSProc, then write the raised mask back to prot_status

Raising bits 2-4 of the LSTAT protection mask is re-entrancy protection: while the deferred remote routine runs (it is a JSR into user code, or an OS call), the station will not accept another remote execute operation. The previous mask is preserved in prot_status_save so it can be restored once the call returns. (The bit pattern &1C happens to equal the System VIA ACR shift-register mode-field mask, but here it is applied to the LSTAT mask, not the VIA -- the Model B ROMs raise the same bits in a separate step on the dispatch side.) Single caller (&83E2 in scout_complete).

On EntryATX operation type
8536 .setup_sr_tx←1← 83F4 JMP
STA tx_op_type ; Save TX operation type for SR dispatch
8539 CMP #&86 ; Ops >= &86 run no code: skip JSR protection
853B BCS enable_irq_pending ; Skip ahead to the ACCCON IRR set
853D LDA prot_status ; Load LSTAT protection mask
8540 STA prot_status_save ; Save old mask in prot_status_save for restore
8543 ORA #&1c ; Set bits 2-4: disable JSR/UserProc/OSProc
8545 STA prot_status ; Write raised protection mask back
8548 .enable_irq_pending←1← 853B BCS
LDA #&80 ; A=&80: ACCCON bit 7 (IRR -- raise interrupt)
854A TSB acccon ; Set ACCCON IRR to flag a pending interrupt to MOS
854D .imm_op_discard←1← 84D7 BCC
JMP reset_adlc_rx_listen ; Return to idle listen mode

Increment 4-byte receive-buffer pointer

Adds 1 to the 4-byte counter at &A2..&A5 (port_buf_len lo/hi, open_port_buf lo/hi), cascading overflow through all four bytes. Called after each byte is stored during scout-data copy and data-frame reception to track the current write position in the receive buffer.

Preserves A, X, Y (uses INC zp throughout).

On ExitA, X, Ypreserved (INC zp only)
8550 .advance_buffer_ptr←3← 82A9 JSR← 82B7 JSR← 844E JSR
INC port_buf_len ; Increment buffer length low byte
8552 BNE rts_advance_buf ; No overflow: done
8554 INC port_buf_len_hi ; Increment buffer length high byte
8556 BNE rts_advance_buf ; No overflow: done
8558 INC open_port_buf ; Increment buffer pointer low byte
855A BNE rts_advance_buf ; No overflow: done
855C INC open_port_buf_hi ; Increment buffer pointer high byte
855E .rts_advance_buf←3← 8552 BNE← 8556 BNE← 855A BNE
RTS ; Return
855F EQUS "clz"
8562 EQUB &86, &9D

TX done: remote JSR execution

Executes the Econet Remote Subroutine Jump (immediate operation &83), now running in deferred IRQ context after svc5_irq_check picked up the ACCCON IRR latch -- so the JSR happens safely outside the NMI handler.

Pushes (tx_done_exit - 1) on the stack so RTS returns to tx_done_exit when the remote routine completes, then does JMP indirect through exec_addr_lo to call the remote-supplied JSR target. When that routine returns via RTS, control resumes at tx_done_exit which tidies up TX state.

8564 .tx_done_jsr
LDA #>(tx_done_exit - 1) ; tx_done_exit RTS target (high)
8566 PHA ; Push hi byte on stack
8567 LDA #<(tx_done_exit - 1) ; tx_done_exit RTS target (low)
8569 PHA ; Push lo byte on stack
856A JMP (exec_addr_lo) ; Call remote JSR; RTS to tx_done_exit

TX done: fire Econet event

Handler for TX operation type &84. Loads the remote address from exec_addr_lo / exec_addr_hi into X / A and sets Y=8 (Econet event number), then falls through to tx_done_fire_event to call OSEVEN.

Reached only via PHA/PHA/RTS dispatch from tx_done_dispatch_lo / hi. The dispatcher pushed the caller's X and Y onto the stack before transferring control, and the shared tx_done_exit restores them via PLA/TAY/PLA/TAX before returning A=0.

On ExitA0 (success status)
X, Yrestored from stack via tx_done_exit
856D .tx_done_econet_event
LDX exec_addr_lo ; X = remote address lo from exec_addr_lo
8570 LDA exec_addr_hi ; A = remote address hi from exec_addr_hi
8573 LDY #event_network_error ; Y = 8: Econet event number
8575 .tx_done_fire_event
JSR oseven ; oseven: network error
8578 JMP tx_done_exit ; Exit TX done handler

TX done: OSProc call

Calls the ROM service entry point with X = exec_addr_lo, Y = exec_addr_hi. This invokes an OS-level procedure on behalf of the remote station, then exits via tx_done_exit.

Reached only via PHA/PHA/RTS dispatch from tx_done_dispatch_lo / hi.

On ExitA0 (success status)
X, Yrestored from stack via tx_done_exit
857B .tx_done_os_proc
LDX exec_addr_lo ; X = remote address lo
857E LDY exec_addr_hi ; Y = remote address hi
8581 JSR dir_op_dispatch ; Call ROM entry point at &8000
8584 JMP tx_done_exit ; Exit TX done handler

TX done: HALT

Sets bit 2 of econet_flags, 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.

Reached only via PHA/PHA/RTS dispatch from tx_done_dispatch_lo / hi. Falls through to tx_done_continue after the spin completes; on the already-halted path it branches directly to tx_done_exit.

On ExitA0 (success status, set by tx_done_exit)
I FLAGinterrupts enabled (CLI inside the spin)
X, Yrestored from stack via tx_done_exit
8587 .tx_done_halt
LDA #4 ; A=&04: bit 2 mask (halt flag in econet_flags)
8589 BIT econet_flags ; Test if already halted
858C BNE tx_done_exit ; Already halted: skip to exit
858E ORA econet_flags ; Set bit 2 in econet_flags (halt)
8591 STA econet_flags ; Store halt flag
8594 LDA #4 ; A=4: re-load halt bit mask
8596 CLI ; Enable interrupts during halt wait
8597 .halt_spin_loop←1← 859A BNE
BIT econet_flags ; Test halt flag
859A BNE halt_spin_loop ; Still halted: keep spinning
859C BEQ tx_done_exit ; Zero: transfer done, exit
fall through ↓

TX done: CONTINUE

Clears bit 2 of econet_flags, releasing any station that is halted and spinning in tx_done_halt.

Reached either as a fall-through from tx_done_halt or directly via PHA/PHA/RTS dispatch from tx_done_dispatch_lo / hi. Falls through to tx_done_exit which restores X and Y from the stack and returns A=0.

On ExitA0 (success status)
X, Yrestored from stack via tx_done_exit
859E .tx_done_continue
LDA econet_flags ; Load current econet_flags
85A1 AND #&fb ; Clear bit 2: release halted station
85A3 STA econet_flags ; Store updated flags
fall through ↓

Shared TX-done exit: restore X/Y, return A=0

Common cleanup tail used by every entry in the tx_done_dispatch_lo table. Pulls the saved Y and X off the stack (the dispatcher pushed them before the PHA/PHA/RTS jump), loads A=0 (success status), and RTS to the caller.

Five inbound refs: a tail-jump from &8044 (the SVC 5 IRQ-check path in svc5_irq_check), plus the JMPs at &8554, &8584, &858C, and the fall-through at &8578.

On ExitA0 (success status)
X, Yrestored from stack
85A6 .tx_done_exit←5← 8044 JMP← 8578 JMP← 8584 JMP← 858C BNE← 859C BEQ
PLA ; Restore Y from stack
85A7 TAY ; Transfer to Y register
85A8 PLA ; Restore X from stack
85A9 TAX ; Transfer to X register
85AA LDA #0 ; A=0: success status
85AC STA svc_state ; Store success status (A=0) to svc_state
85AE RTS ; Return with A=0 (success)

Calculate transfer size; handle Tube and shadow buffers

Prepares the buffer-transfer for a completed receive. Clears decimal mode, then seeds escapable from ACCCON with the transfer-mode bit set. Inspects RXCB[6..7] (buffer end address byte 2 and high) to classify the buffer:

Buffer type Action
Tube (RXCB[7]=&FF, RXCB[6] in [&FE, &FF]) if shadow RAM is enabled (ACCCON.E), also set the shadow bit in escapable; then compute the 4-byte transfer size by subtracting RXCB[8..&B] (start) from RXCB[4..7] (end)
Non-Tube branch to fallback_calc_transfer for the 1-byte size subtraction

Three callers: scout_complete (&819D), rx_imm_peek (&84D4), tx_ctrl_proc (&87A4).

On EntryY0 -- caller convention
On ExitAtransfer status
Cset if Tube/shadow address handled, clear otherwise
85AF .tx_calc_transfer←3← 819D JSR← 84D4 JSR← 87A6 JSR
CLD ; Clear decimal mode before the binary transfer-size arithmetic
85B0 LDA acccon ; Read ACCCON (access-control register)
85B3 ORA #8 ; Set bit 3 of A (transfer-mode flag)
85B5 STA escapable ; Store as escapable mode
85B7 LDY #7 ; Y=7: scout-bytes counter
85B9 LDA (port_ws_offset),y ; Read RXCB[7] (buffer addr high byte)
85BB .tx_length_table←1Used as index base by← 8741 LDA
CMP #&ff ; Compare to &FF
85BD BNE check_tx_in_progress ; Not &FF: normal buffer, skip Tube check
85BF DEY ; Next byte (descending)
85C0 LDA (port_ws_offset),y ; Read RXCB[6] (buffer addr byte 2)
85C2 .tx_calc_tube_check
CMP #&fe ; Check if addr byte 2 >= &FE (Tube range)
85C4 BCC check_tx_in_progress ; C clear: no Tube, plain transfer path
85C6 BNE shadow_enable_flag ; Z clear (other state set): use fallback path
85C8 LDA acccon ; Z set: re-read ACCCON for second decision
85CB ROR ; Rotate bit 0 (E flag) into C
85CC BCC shadow_enable_flag ; C clear: shadow not enabled, fallback path
85CE LDA #4 ; Shadow enabled: set bit 2 of escapable
85D0 TSB escapable ; Atomic bit-set on escapable
85D2 .shadow_enable_flag←2← 85C6 BNE← 85CC BCC
BRA fallback_calc_transfer ; Branch to fallback_calc_transfer (always)
85D4 .check_tx_in_progress←2← 85BD BNE← 85C4 BCC
LDA tube_present ; Transmit in progress?
85D7 BEQ fallback_calc_transfer ; No: fallback path
85D9 LDA net_frame_flags ; Load TX flags for transfer setup
85DC ORA #2 ; Set bit 1 (transfer complete)
85DE STA net_frame_flags ; Store with bit 1 set (Tube xfer)
85E1 SEC ; Init borrow for 4-byte subtract
85E2 PHP ; Save carry on stack
85E3 LDY #4 ; Y=4: start at RXCB offset 4
85E5 .calc_transfer_size←1← 85F7 BCC
LDA (port_ws_offset),y ; Load RXCB[Y] (current ptr byte)
85E7 INY ; Y += 4: advance to RXCB[Y+4]
85E8 INY ; (continued)
85E9 INY ; (continued)
85EA INY ; (continued)
85EB PLP ; Restore borrow from previous byte
85EC SBC (port_ws_offset),y ; Subtract RXCB[Y+4] (start ptr byte)
85EE STA net_tx_ptr,y ; Store result byte
85F1 DEY ; Y -= 3: next source byte
85F2 DEY ; (continued)
85F3 DEY ; (continued)
85F4 PHP ; Save borrow for next byte
85F5 CPY #8 ; Done all 4 bytes?
85F7 BCC calc_transfer_size ; No: next byte pair
85F9 PLP ; Discard final borrow
85FA TXA ; Save X
85FB PHA ; Save X
85FC LDA #4 ; Compute address of RXCB+4
85FE CLC ; For base pointer addition
85FF ADC port_ws_offset ; Add RXCB base to get RXCB+4 addr
8601 TAX ; X = low byte of RXCB+4
8602 LDY rx_buf_offset ; Y = high byte of RXCB ptr
8604 LDA #&c2 ; Tube claim type &C2
8606 JSR tube_addr_data_dispatch ; Claim Tube transfer address
8609 BCC restore_x_and_return ; No Tube: skip reclaim
860B LDA rx_port ; Tube: reclaim with scout status
860E JSR tube_addr_data_dispatch ; Reclaim with scout status type
8611 JSR release_tube ; Release Tube claim after reclaim
8614 SEC ; C=1: Tube address claimed
8615 .restore_x_and_return←1← 8609 BCC
PLA ; Restore X
8616 TAX ; Restore X from stack
8617 RTS ; Return with C = transfer status
8618 .fallback_calc_transfer←2← 85D2 BRA← 85D7 BEQ
LDY #4 ; Y=4: RXCB current pointer offset
861A LDA (port_ws_offset),y ; Load RXCB[4] (current ptr lo)
861C LDY #8 ; Y=8: RXCB start address offset
861E SEC ; Set carry for subtraction
861F SBC (port_ws_offset),y ; Subtract RXCB[8] (start ptr lo)
8621 STA port_buf_len ; Store transfer size lo
8623 LDY #5 ; Y=5: current ptr hi offset
8625 LDA (port_ws_offset),y ; Load RXCB[5] (current ptr hi)
8627 SBC #0 ; Propagate borrow only
8629 STA open_port_buf_hi ; Temp store of adjusted hi byte
862B LDY #8 ; Y=8: start address lo offset
862D LDA (port_ws_offset),y ; Copy RXCB[8] to open port buffer lo
862F STA open_port_buf ; Store to scratch (side effect)
8631 LDY #9 ; Y=9: start address hi offset
8633 LDA (port_ws_offset),y ; Load RXCB[9]
8635 SEC ; Set carry for subtraction
8636 SBC open_port_buf_hi ; Subtract adjusted hi byte
8638 STA port_buf_len_hi ; Store transfer size hi
863A SEC ; Return with C=1
863B .nmi_shim_rom_src
RTS ; Return with C=1 (success)

Purpose unknown. Unreferenced, unreachable.

863C .rom_gap_88f0
EQUB &0E, &0E, &0A, &0A, &0A, &06, &06, &0A, &81, &00, &00, &00, &00, &01, &01, &81

Seed TX scout from the TX control block and dispatch on type

Copies the destination station and network from the TX control block ((nmi_tx_block)) into the TX scout buffer (tx_dst_stn / tx_dst_net), then reads the TXCB control byte: bit 7 set selects tx_imm_op_setup (an immediate operation), bit 7 clear falls to tx_bad_ctrl_error. Three callers in the TX-start paths.

864C .tx_setup_from_txcb←3← 9BC3 JSR← A94C JMP← AC40 JSR
TXA ; Save X on stack
864D PHA ; Push X
864E LDY #2 ; Y=2: TXCB offset for dest station
8650 LDA (nmi_tx_block),y ; Load dest station from TX control block
8652 STA tx_dst_stn ; Store to TX scout buffer
8655 INY ; Advance to destination network byte
8656 LDA (nmi_tx_block),y ; Load dest network from TX control block
8658 STA tx_dst_net ; Store to TX scout buffer
865B LDY #0 ; Y=0: first byte of TX control block
865D LDA (nmi_tx_block),y ; Load control/flag byte
865F BMI tx_imm_op_setup ; Bit7 set: immediate operation ctrl byte
8661 JMP tx_bad_ctrl_error ; Bit7 clear: normal data transfer
8664 .tx_imm_op_setup←1← 865F BMI
STA tx_ctrl_byte ; Store control byte to TX scout buffer
8667 TAX ; X = control byte for range checks
8668 INY ; Y=1: port byte offset
8669 LDA (nmi_tx_block),y ; Load port byte from TX control block
866B STA tx_port ; Store port byte to TX scout buffer
866E BNE tx_line_idle_check ; Port != 0: skip immediate op setup
8670 CPX #&83 ; Ctrl < &83: PEEK/POKE need address calc
8672 BCS tx_ctrl_range_check ; Ctrl >= &83: skip to range check
8674 SEC ; Init borrow for 4-byte subtract
8675 PHP ; Save carry on stack for loop
8676 LDY #8 ; Y=8: high pointer offset in TXCB
8678 .calc_peek_poke_size←1← 868C BCC
LDA (nmi_tx_block),y ; Load TXCB[Y] (end addr byte)
867A DEY ; Y -= 4: back to start addr offset
867B DEY ; (continued)
867C DEY ; (continued)
867D DEY ; (continued)
867E PLP ; Restore borrow from stack
867F SBC (nmi_tx_block),y ; end - start = transfer size byte
8681 STA tx_data_start,y ; Store result to tx_data_start
8684 INY ; Y += 5: advance to next end byte
8685 INY ; (continued)
8686 INY ; (continued)
8687 INY ; (continued)
8688 INY ; (continued)
8689 PHP ; Save borrow for next byte
868A CPY #&0c ; Done all 4 bytes? (Y reaches &0C)
868C BCC calc_peek_poke_size ; No: next byte pair
868E PLP ; Discard final borrow
868F .tx_ctrl_range_check←1← 8672 BCS
CPX #&81 ; Ctrl < &81: not an immediate op
8691 BCC tx_bad_ctrl_error ; Below range: normal data transfer
8693 .check_imm_range
CPX #&89 ; Ctrl >= &89: out of immediate range
8695 BCS tx_bad_ctrl_error ; Above range: normal data transfer
8697 LDY #&0c ; Y=&0C: start of extra data in TXCB
8699 .copy_imm_params←1← 86A1 BCC
LDA (nmi_tx_block),y ; Load extra parameter byte from TXCB
869B STA imm_param_base,y ; Copy to NMI shim workspace at &0D1A+Y
869E INY ; Next byte
869F CPY #&10 ; Done 4 bytes? (Y reaches &10)
86A1 BCC copy_imm_params ; No: continue copying
86A3 .tx_line_idle_check←1← 866E BNE
LDA #&20 ; A=&20: mask for SR2 INACTIVE bit
86A5 BIT econet_control23_or_status2 ; Test SR2 if line is idle
86A8 BNE tx_no_clock_error ; Line not idle: handle as line jammed
86AA LDA #&fd ; A=&FD: high byte of timeout counter
86AC PHA ; Push timeout high byte to stack
86AD LDA #6 ; Scout frame = 6 address+ctrl bytes
86AF STA rx_ctrl ; Store scout frame length
86B2 LDA #0 ; A=0: init low byte of timeout counter
fall through ↓

INACTIVE polling loop

Entry point for the Econet line-idle detection loop.

  1. Saves the TX index in rx_remote_addr.
  2. Pushes two timeout-counter bytes onto the stack.
  3. Loads Y = &E7 (CR2 value for TX preparation).
  4. Loads the INACTIVE bit mask (&04) into A.
  5. Falls through to intoff_test_inactive to begin polling SR2 with interrupts disabled.
On ExitY&E7 (CR2 value for tx_prepare)
86B4 .inactive_poll
STA rx_remote_addr ; Save TX index
86B7 PHA ; Push timeout byte 1 on stack
86B8 PHA ; Push timeout byte 2 on stack
86B9 LDY #&e7 ; Y=&E7: CR2 value for TX prep (RTS|CLR_TX_ST|CLR_RX_ST|FC_TDRA|2_1_ BYTE|PSE)
86BB .tx_irq_off←3← 86EA BNE← 86EF BNE← 86F4 BNE
PHP ; Save interrupt state
86BC SEI ; Disable interrupts for ADLC access
86BD LDA #&40 ; A=&40: 'line inactive' status code
86BF STA net_poll_status ; Store net poll-status byte
86C2 BIT disable_net_nmis ; INTOFF -- disable NMIs
86C5 LDA #4 ; A=&04: INACTIVE bit mask for SR2 test
86C7 BIT econet_control23_or_status2 ; Z = &04 AND SR2 -- tests INACTIVE
86CA BEQ set_line_jammed ; INACTIVE not set -- re-enable NMIs and loop
86CC LDA econet_control1_or_status1 ; Read SR1 (acknowledge pending interrupt)
86CF BMI set_line_jammed ; N set: line still driven, report jammed
86D1 LDA #&67 ; CR2=&67: CLR_TX_ST|CLR_RX_ST|FC_ TDRA|2_1_BYTE|PSE
86D3 STA econet_control23_or_status2 ; Write CR2: clear status, prepare TX
86D6 LDA #&10 ; A=&10: CTS mask for SR1 bit4
86D8 BIT econet_control1_or_status1 ; Test SR1 CTS present
86DB BNE tx_prepare ; CTS set -- clock hardware detected, start TX
86DD .set_line_jammed←2← 86CA BEQ← 86CF BMI
LDA #&2c ; A=&2C: 'line jammed' status code
86DF STA net_poll_status ; Store net poll-status byte
86E2 .inactive_retry
BIT enable_net_nmis ; INTON -- re-enable NMIs (Master &FE3C)
86E5 PLP ; Restore interrupt state
86E6 TSX ; 3-byte timeout counter on stack
86E7 INC error_text,x ; Increment timeout counter byte 1
86EA BNE tx_irq_off ; Not overflowed: retry INACTIVE test
86EC INC stack_page_2,x ; Increment timeout counter byte 2
86EF BNE tx_irq_off ; Not overflowed: retry INACTIVE test
86F1 INC stack_page_3,x ; Increment timeout counter byte 3
86F4 BNE tx_irq_off ; Not overflowed: retry INACTIVE test
86F6 BEQ tx_line_jammed ; Zero: report line jammed
fall through ↓

Raise TX 'Bad control byte' (&44) error

Loads error code &44 ("Bad control") and ALWAYS-branches to store_tx_error, which records it in the TX control block and finishes the TX attempt.

Reached from three early-validation sites in tx_begin (&859E, &85CE, &85D2) when the operation type is out of range.

On ExitA&44 (TX 'Bad control' error code)
86F8 .tx_bad_ctrl_error←3← 8661 JMP← 8691 BCC← 8695 BCS
LDA #&44 ; Error &44: control byte out of valid range
86FA BNE store_tx_error ; Branch to store TX error
fall through ↓

TX timeout error handler (Line Jammed)

Reached when the inactive_poll / intoff_test_inactive loop times out without detecting a quiet line.

  1. Writes CR2=&07 (FC_TDRA | 2_1_BYTE | PSE) to abort the TX attempt.
  2. Pulls the 3-byte timeout state from the stack.
  3. Stores error code &40 ("Line Jammed") in the TX control block via store_tx_error.
86FC .tx_line_jammed←1← 86F6 BEQ
LDA #7 ; CR2=&07: FC_TDRA | 2_1_BYTE | PSE (abort TX)
86FE STA econet_control23_or_status2 ; Write CR2 to abort TX
8701 PLA ; Clean 3 bytes of timeout loop state
8702 PLA ; Pop saved register
8703 PLA ; Pop saved register
8704 LDA #&40 ; Error &40 = 'Line Jammed'
8706 BNE store_tx_error ; ALWAYS branch to shared error handler
8708 .tx_no_clock_error←1← 86A8 BNE
LDA #&43 ; Error &43 = 'No Clock'
870A .store_tx_error←2← 86FA BNE← 8706 BNE
LDY #0 ; Offset 0 = error byte in TX control block
870C STA (nmi_tx_block),y ; Store error code in TX CB byte 0
870E LDA #&80 ; &80 = TX complete flag
8710 STA tx_complete_flag ; Signal TX operation complete
8713 PLA ; Restore X saved by caller
8714 TAX ; Move to X register
8715 RTS ; Return to TX caller

TX preparation

Configures the ADLC for frame transmission and dispatches to the control-byte handler.

  1. Writes CR2 = Y (&E7) and CR1 = &44 to enable TX with interrupts (RX_RESET + transmit-IRQ enable).
  2. Installs nmi_tx_data as the next NMI handler by writing &E7,&86 directly into nmi_jmp_lo / nmi_jmp_hi.
  3. Sets bit 7 of prot_flags (Tube-claimed marker, paired with release_tube) via SEC / ROR prot_flags.
  4. BIT enable_net_nmis re-enables NMIs so TDRA can fire.

Then dispatches on tx_port:

tx_port Path
non-zero branch to setup_data_xfer (standard data transfer)
zero (immediate op) look up tx_flags / tx_length from tx_flags_table / tx_length_table indexed by tx_ctrl_byte, push &86 (high byte) and tx_ctrl_dispatch_lo[Y-&81] (low byte) and RTS to the control-byte handler

The 4-byte destination-address write to the TX FIFO happens in the dispatched-to handler (e.g. setup_data_xfer, tx_ctrl_machine_type, etc.), not here.

On EntryY&E7 (CR2 prep value)
8716 .tx_prepare←1← 86DB BNE
STY econet_control23_or_status2 ; Write CR2 = Y (&E7: RTS|CLR_TX_ST|CLR_RX_ST| FC_TDRA|2_1_BYTE|PSE)
8719 LDX #&44 ; CR1=&44: RX_RESET | TIE (TX active, TX interrupts enabled)
871B STX econet_control1_or_status1 ; Write to ADLC CR1
871E LDX #<(nmi_tx_data) ; Next NMI handler address (low)
8720 LDY #&87 ; High byte of NMI handler address
8722 STX nmi_jmp_lo ; Write NMI vector low byte directly
8725 STY nmi_jmp_hi ; Write NMI vector high byte directly
8728 SEC ; SEC: prepare carry for ROR into bit 7
8729 ROR prot_flags ; Rotate carry into bit 7 of prot_flags (Tube-claimed)
872B LDA #&2c ; A=&2C: 'line jammed' status code
872D .tx_enable_nmis
STA net_poll_status ; Store net poll-status byte
8730 BIT enable_net_nmis ; INTON -- NMIs now fire for TDRA (Master &FE3C)
8733 LDA tx_port ; Load destination port number
8736 BNE setup_data_xfer ; Port != 0: standard data transfer
8738 LDY tx_ctrl_byte ; Port 0: load control byte for table lookup
873B LDA tx_flags_table,y ; Look up tx_flags from table
873E STA net_frame_flags ; Store operation flags
8741 LDA tx_length_table,y ; Look up tx_length from table
8744 STA rx_ctrl ; Store expected transfer length
8747 LDA #&87 ; A=&87: high byte of tx_ctrl_* dispatch target
8749 PHA ; Push high byte for PHA/PHA/RTS dispatch
874A LDA tx_ctrl_dispatch_lo,y ; Look up handler address low from table
874D PHA ; Push low byte for PHA/PHA/RTS dispatch
874E RTS ; RTS dispatches to control-byte handler
874F EQUB &A9, &03
8751 EQUB &D0, &48 ; Skip address addition, store status

TX ctrl: PEEK transfer setup

Sets A=3 (scout_status for PEEK) and branches to tx_ctrl_store_and_add to store the status and perform the 4-byte transfer-address addition.

On ExitA3 (scout_status for PEEK)
8753 .tx_ctrl_peek
LDA #3 ; A=3: scout_status for PEEK op
8755 BNE tx_ctrl_store_and_add ; Non-zero: store control byte and add
fall through ↓

TX ctrl: POKE transfer setup

Sets A=2 (scout_status for POKE) and falls through to tx_ctrl_store_and_add to store the status and perform the 4-byte transfer-address addition.

On ExitA2 (scout_status for POKE)
8757 .tx_ctrl_poke
LDA #2 ; Scout status = 2 (POKE transfer)
fall through ↓

TX ctrl: store status and add transfer address

Shared path for PEEK (A=3) and POKE (A=2):

  1. Stores A as the scout status byte at rx_port.
  2. Performs a 4-byte addition with carry propagation. For Y=&0C..&0F it adds (nmi_tx_block),Y (i.e. TXCB bytes 12..15 from the block pointed to by nmi_tx_block) into tx_addr_base,Y -- tx_addr_base+&0C..&0F is the 4-byte transfer-length workspace at tx_data_len (4 bytes).
  3. Falls through to tx_ctrl_proc which checks the loop boundary, then continues to tx_calc_transfer and tx_ctrl_exit.
On EntryAscout status (3=PEEK, 2=POKE)
8759 .tx_ctrl_store_and_add←1← 8755 BNE
STA rx_port ; Store scout status
875C CLC ; Clear carry for 4-byte addition
875D PHP ; Save carry on stack
875E LDY #&0c ; Y=&0C: start at offset 12
8760 .add_bytes_loop←1← 876D BCC
LDA tx_addr_base,y ; Load workspace address byte
8763 PLP ; Restore carry from previous byte
8764 ADC (nmi_tx_block),y ; Add TXCB address byte
8766 STA tx_addr_base,y ; Store updated address byte
8769 INY ; Next byte
876A PHP ; Save carry for next addition
fall through ↓

TX ctrl: tail of address-add loop + setup_data_xfer entry

Tail of the 4-byte transfer-address addition loop that started in tx_ctrl_store_and_add: CPY #&10 ends the loop when Y reaches &10, PLP restores the saved carry, and BNE skips the buffer-setup code if the transfer size is zero.

Falls through (or is reached via the dispatch from tx_prepare when port != 0) to setup_data_xfer at &8770, which dispatches between broadcast and unicast based on whether tx_dst_stn and tx_dst_net are both &FF.

876B .tx_ctrl_proc
CPY #&10 ; Compare Y with 16-byte boundary
876D BCC add_bytes_loop ; Below boundary: continue addition
876F PLP ; Restore processor flags
8770 BNE skip_buf_setup ; Skip buffer setup if transfer size is zero
8772 .setup_data_xfer←1← 8736 BNE
LDA tx_dst_stn ; Load dest station for broadcast check
8775 AND tx_dst_net ; AND with dest network
8778 CMP #&ff ; Both &FF = broadcast address?
877A BNE setup_unicast_xfer ; Not broadcast: unicast path
877C LDA #&0e ; Broadcast scout: 14 bytes total
877E STA rx_ctrl ; Store broadcast scout length
8781 LDA #&40 ; A=&40: broadcast flag
8783 STA net_frame_flags ; Set broadcast flag in tx_flags
8786 LDY #4 ; Y=4: start of address data in TXCB
8788 .copy_bcast_addr←1← 8790 BCC
LDA (nmi_tx_block),y ; Copy TXCB address bytes to scout buffer
878A STA tx_src_stn,y ; Store to TX source/data area
878D INY ; Next byte
878E CPY #&0c ; Done 8 bytes? (Y reaches &0C)
8790 BCC copy_bcast_addr ; No: continue copying
8792 BCS tx_ctrl_exit ; C set: control byte out of range, exit
8794 .setup_unicast_xfer←1← 877A BNE
LDA #0 ; A=0: clear flags for unicast
8796 STA net_frame_flags ; Clear tx_flags
8799 .proc_op_status2
LDA #2 ; scout_status=2: data transfer pending
879B .store_status_copy_ptr
STA rx_port ; Store scout status
879E .skip_buf_setup←1← 8770 BNE
LDA nmi_tx_block ; Copy TX block pointer to workspace ptr
87A0 STA port_ws_offset ; Store low byte
87A2 LDA nmi_tx_block_hi ; Copy TX block pointer high byte
87A4 STA rx_buf_offset ; Store high byte
87A6 JSR tx_calc_transfer ; Calculate transfer size from RXCB
87A9 .tx_ctrl_exit←1← 8792 BCS
PLP ; Restore processor status from stack
87AA PLA ; Restore stacked registers (4 PLAs)
87AB PLA ; Unwind first stacked byte
87AC PLA ; Unwind second stacked byte
87AD PLA ; Unwind third stacked byte
87AE TAX ; Restore X from A
87AF RTS ; Return to caller
87B0 EQUB &52, &56, &98, &98, &98, &A8, &A8, &4E

NMI TX data handler

Writes 2 bytes per NMI invocation to the TX FIFO at econet_data_continue_frame. Uses BIT econet_control1_or_status1 on SR1 to test TDRA (V flag = bit 6) and IRQ (N flag = bit 7).

After writing 2 bytes, checks if the frame is complete:

SR1 bit 7 (IRQ) Action
set tight loop: write 2 more bytes without returning from NMI
clear return via RTI and wait for the next NMI
87B8 .nmi_tx_data
LDY rx_remote_addr ; Load TX buffer index
87BB BIT econet_control1_or_status1 ; SR1: V=bit6(TDRA), N=bit7(IRQ)
87BE .tx_fifo_write←1← 87D9 BMI
BVC tx_fifo_not_ready ; TDRA not set -- TX error
87C0 LDA tx_dst_stn,y ; Load byte from TX buffer
87C3 STA econet_data_continue_frame ; Write to TX_DATA (continue frame)
87C6 INY ; Next TX buffer byte
87C7 LDA tx_dst_stn,y ; Load second byte from TX buffer
87CA INY ; Advance TX index past second byte
87CB STY rx_remote_addr ; Save updated TX buffer index
87CE STA econet_data_continue_frame ; Write second byte to TX_DATA
87D1 CPY rx_ctrl ; Compare index to TX length
87D4 BCS tx_last_data ; Frame complete -- go to TX_LAST_DATA
87D6 BIT econet_control1_or_status1 ; Check if we can send another pair
87D9 BMI tx_fifo_write ; IRQ set -- send 2 more bytes (tight loop)
87DB JMP nmi_rti ; Wait for next NMI
87DE .tx_error←1← 8821 BEQ
LDA #&42 ; Error &42
87E0 BNE tx_store_error ; Branch to store TX error
87E2 .tx_fifo_not_ready←1← 87BE BVC
LDA #&67 ; CR2=&67: clear status, return to listen
87E4 STA econet_control23_or_status2 ; Write CR2: clear status, idle listen
87E7 LDA #&41 ; Error &41 (TDRA not ready)
87E9 .tx_store_error←1← 87E0 BNE
LDY tx_src_stn ; INTOFF: disable NMIs (Master &FE38)
87EC .delay_nmi_disable←1← 87EF BNE
PHA ; PHA/PLA delay loop (256 iterations for NMI disable)
87ED PLA ; PHA/PLA delay (~7 cycles each)
87EE INY ; Increment delay counter
87EF BNE delay_nmi_disable ; Loop 256 times for NMI disable
87F1 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 nmi_tx_complete as the next NMI handler.

CR2=&3F = %0011_1111, with each bit selecting an ADLC control function:

Bit Mnemonic Effect
7 (RTS) 0 – drops RTS after frame
6 (CLR_TX_ST) 0 – do not clear TX status
5 CLR_RX_ST clears fv_stored_ (prepares for RX of reply)
4 TX_LAST_DATA tells the ADLC this is the final data byte
3 FLAG_IDLE send flags / idle after the frame
2 FC_TDRA force clear TDRA
1 2_1_BYTE two-byte transfer mode
0 PSE prioritised status enable

The routine exits via JMP to set_nmi_vector, which installs nmi_tx_complete and falls through to nmi_rti. The BIT of enable_net_nmis (INTON) inside nmi_rti creates the /NMI edge for the frame-complete interrupt – essential because the ADLC IRQ may transition atomically from TDRA to frame-complete without de-asserting in between.

87F4 .tx_last_data←1← 87D4 BCS
LDA #&3f ; CR2=&3F: TX_LAST_DATA | CLR_RX_ST | FLAG_IDLE | FC_TDRA | 2_1_BYTE | PSE
87F6 STA econet_control23_or_status2 ; Write to ADLC CR2
87F9 LDA #<(nmi_tx_switch_rx) ; Install TX->RX switch handler (low)
87FB JMP install_nmi_handler ; 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. Writes CR1=&82 (TX_RESET | RIE) to clear RX_RESET and enable RX interrupts – the TX-to-RX pivot in the four-way handshake. The scout ACK can only be received after this point.

Full CR1 sequence through a handshake:

Step CR1 Meaning
1 &44 scout TX
2 &82 await scout ACK
3 &44 data TX
4 &82 await data ACK

Dispatches on net_frame_flags flags:

Flag Action
bit 6 set (broadcast) jump to tx_result_ok
bit 0 set (handshake data pending) jump to handshake_await_ack
both clear install nmi_reply_scout for scout ACK reception
87FE .nmi_tx_complete
.nmi_tx_switch_rx
LDA #&82 ; Jump to error handler
8800 STA econet_control1_or_status1 ; Write CR1 to switch from TX to RX
8803 BIT net_frame_flags ; Test workspace flags
8806 BVC check_handshake_bit ; bit6 not set -- check bit0
8808 JMP tx_result_ok ; bit6 set -- TX completion
880B .check_handshake_bit←1← 8806 BVC
LDA #1 ; A=1: mask for bit0 test
880D BIT net_frame_flags ; Test tx_flags bit0 (handshake)
8810 BEQ install_reply_scout ; bit0 clear: install reply handler
8812 JMP handshake_await_ack ; bit0 set -- four-way handshake data phase
8815 .install_reply_scout←1← 8810 BEQ
LDA #<(nmi_reply_scout) ; Install nmi_reply_scout (low)
8817 LDY #>(nmi_reply_scout) ; Install nmi_reply_scout (high)
8819 JMP set_nmi_vector ; Install handler

RX reply-scout handler

NMI handler installed before the reply-scout reception phase. Tests SR2 bit 0 (AP) for an incoming address; on AP clear falls through to tx_error. Otherwise reads the first RX byte (destination station) and compares it against the workspace copy tx_src_stn. On mismatch branches to reject_reply; on match installs nmi_reply_cont as the next NMI handler via install_nmi_handler (low-byte only -- the high byte stays at &87).

881C .nmi_reply_scout
LDA #1 ; A=&01: AP mask for SR2
881E BIT econet_control23_or_status2 ; Test SR2 AP (Address Present)
8821 BEQ tx_error ; No AP -- error
8823 LDA econet_data_continue_frame ; Read first RX byte (destination station)
8826 CMP tx_src_stn ; Compare to our station ID (workspace copy)
8829 BNE reject_reply ; Not our station -- error/reject
882B LDA #<(nmi_reply_cont) ; Install reply-continuation handler (low)
882D 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). Loads A=&76, the low byte of nmi_reply_validate, to install it as the next NMI handler.

Optimisation: before installing, checks SR1 bit 7 (IRQ still asserted) via BIT econet_control1_or_status1 / BMI. When IRQ is still set the next byte is already in the FIFO, so the routine falls through directly to nmi_reply_validate without an intermediate RTI, avoiding NMI re-entry overhead for short frames where all bytes arrive in quick succession.

8830 .nmi_reply_cont
BIT econet_control23_or_status2 ; Read RX byte (destination station)
8833 BPL reject_reply ; No RDA -- error
8835 LDA econet_data_continue_frame ; Read destination network byte
8838 BNE reject_reply ; Non-zero -- network mismatch, error
883A LDA #<(nmi_reply_validate) ; Next NMI handler address (low)
883C BIT econet_control1_or_status1 ; Test SR1 IRQ (N=bit7) -- more data ready?
883F BMI nmi_reply_validate ; IRQ set -- fall through to &884A
8841 JMP install_nmi_handler ; IRQ not set -- install handler

Abandon reply scout (1-instruction trampoline)

Single JMP to tx_result_fail. Acts as a near-target for the BPL/BNE exits scattered through nmi_reply_scout, nmi_reply_validate, and nmi_scout_ack_src that need to abort the reply path – the unconditional JMP at &8775 takes them to tx_result_fail (which stores the error and returns to idle).

Seven inbound refs in total (one JSR plus six branches).

8844 .reject_reply←7← 8829 BNE← 8833 BPL← 8838 BNE← 884A BPL← 8852 BNE← 885A BNE← 8861 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 (tx_dst_stn / tx_dst_net).

  1. Check SR2 bit 7 (RDA) -- must see data available.
  2. Read source station, compare to tx_dst_stn.
  3. Read source network, compare to tx_dst_net.
  4. Check SR2 bit 1 (FV) -- 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).

8847 .nmi_reply_validate←1← 883F BMI
BIT econet_control23_or_status2 ; Test SR2 RDA (bit7). Must be set for valid reply.
884A BPL reject_reply ; No RDA -- error (FV masking RDA via PSE would cause this)
884C LDA econet_data_continue_frame ; Read source station
884F CMP tx_dst_stn ; Compare to original TX destination station (&0D20)
8852 BNE reject_reply ; Mismatch -- not the expected reply, error
8854 LDA econet_data_continue_frame ; Read source network
8857 CMP tx_dst_net ; Compare to original TX destination network (&0D21)
885A BNE reject_reply ; Mismatch -- error
885C LDA #2 ; A=&02: FV mask for SR2 bit1
885E BIT econet_control23_or_status2 ; Test SR2 FV -- frame must be complete
8861 BEQ reject_reply ; No FV -- incomplete frame, error
8863 LDA #&a7 ; CR2=&A7: RTS|CLR_TX_ST|FC_TDRA|2_ 1_BYTE|PSE (TX in handshake)
8865 STA econet_control23_or_status2 ; Write CR2: enable RTS for TX handshake
8868 LDA #&44 ; CR1=&44: RX_RESET | TIE (TX active for scout ACK)
886A STA econet_control1_or_status1 ; Write CR1: reset RX, enable TX interrupt
886D LDA #&57 ; Install handshake_await_ack into &0D43/&0D44 (four-way data phase)
886F LDY #&89 ; High byte &88 of next handler address
8871 STA saved_nmi_lo ; Store low byte to nmi_next_lo
8874 STY saved_nmi_hi ; Store high byte to nmi_next_hi
8877 LDA tx_dst_stn ; Load dest station for scout ACK TX
887A BIT econet_control1_or_status1 ; Test SR1 TDRA (V=bit6)
887D BVC tx_check_tdra_ready ; TDRA not ready -- error
887F STA econet_data_continue_frame ; Write dest station to TX FIFO
8882 LDA tx_dst_net ; Write dest network to TX FIFO
8885 STA econet_data_continue_frame ; Write dest network to TX FIFO
8888 LDA #<(nmi_scout_ack_src) ; Next NMI handler address (low)
888A LDY #>(nmi_scout_ack_src) ; Next NMI handler address (high)
888C JMP set_nmi_vector ; Set NMI vector and return

TX scout ACK: write source address

Continuation of the TX-side scout ACK. Reads our station ID from the workspace copy tx_src_stn, tests TDRA via SR1, and writes (station, network=0) to the TX FIFO.

Then dispatches on bit 1 of net_frame_flags to select the next NMI handler:

Bit 1 Handler
set immediate-op data NMI handler
clear normal nmi_tx_data

Installs the chosen handler via set_nmi_vector. Shares the tx_check_tdra_ready entry with ack_tx.

888F .nmi_scout_ack_src
LDA tx_src_stn ; Load our station ID from workspace copy
8892 BIT econet_control1_or_status1 ; Test SR1 TDRA
8895 .tx_check_tdra_ready←1← 887D BVC
BVC data_tx_check_fifo ; TDRA not ready -- error
8897 STA econet_data_continue_frame ; Write our station to TX FIFO
889A LDA #0 ; Write network=0 to TX FIFO
889C STA econet_data_continue_frame ; Write network byte to TX FIFO
fall through ↓

Begin data-frame TX: install nmi_data_tx or alt

Tests bit 1 of net_frame_flags (tx_flags):

Bit 1 Path
set (immediate-op) branch to install_imm_data_nmi to use the alternative handler
clear install the nmi_data_tx alt-entry at &87ED (lo=&EB, hi=&87) into the NMI vector. The alt-entry skips the page-counter check and goes straight to the byte-count load

Single caller (&8339 inside ack_tx).

889F .data_tx_begin←1← 8349 JMP
LDA #2 ; Test bit 1 of tx_flags
88A1 BIT net_frame_flags ; Check if immediate-op or data-transfer
88A4 BNE install_imm_data_nmi ; Bit 1 set: immediate op, use alt handler
88A6 LDA #<(nmi_data_tx_alt) ; Install nmi_data_tx alt-entry (low)
88A8 LDY #&88 ; Y=&88: high byte of nmi_data_tx
88AA JMP set_nmi_vector ; Install and return via set_nmi_vector
88AD .install_imm_data_nmi←1← 88A4 BNE
LDA #<(nmi_data_tx_tube) ; Next NMI handler address (low)
88AF LDY #>(nmi_data_tx_tube) ; Next NMI handler address (high)
88B1 JMP set_nmi_vector ; Install and return via set_nmi_vector

TX data phase: send payload

NMI handler that transmits the data payload of a four-way handshake. Loads bytes from (open_port_buf),Y (or from Tube R3 in the immediate-op variant), writing pairs to the TX FIFO. After each pair, decrements the byte counters (port_buf_len/port_buf_len_hi):

Condition Action
port_buf_len_hi = 0 (final partial page) branch to data_tx_last (internal label) to send the remaining bytes and tail-call tx_last_data
count > 0, SR1 IRQ still set tight loop: write another pair without returning from NMI
count > 0, SR1 IRQ clear return via RTI and wait for next NMI

The alt-entry at &87ED (used by data_tx_begin) skips the page-counter check and starts at the byte-count load.

88B4 .nmi_data_tx←1← 88BE BEQ
LDY port_buf_len_hi ; Y = buffer offset, resume from last position
88B6 BEQ data_tx_last ; No pages left: send final partial page
88B8 LDY port_buf_len ; Load remaining byte count
88BA BEQ check_tdra_status ; Zero bytes left: skip to TDRA check
88BC .nmi_data_tx_alt
LDY port_buf_len ; Load remaining byte count (alt entry)
88BE BEQ nmi_data_tx ; Zero: loop back to top of handler
88C0 .check_tdra_status←1← 88BA BEQ
BIT econet_control1_or_status1 ; Test SR1 TDRA (V=bit6)
88C3 .data_tx_check_fifo←2← 8895 BVC← 88F3 BMI
BVC tube_tx_fifo_write ; TDRA not ready -- error
; Save/restore ACCCON across the (open_port_buf),Y reads
; in this TX FIFO loop. Same idiom as copy_scout_to_buffer / ; nmi_data_rx_bulk;
; workspace &97 holds the desired ACCCON value pre-loaded by the ; caller.
88C5 LDA acccon ; Save current ACCCON on stack
88C8 PHA ; Push ACCCON snapshot
88C9 LDA escapable ; Load desired ACCCON from workspace &97
88CB STA acccon ; Set ACCCON for the upcoming buffer reads
88CE LDA (open_port_buf),y ; Write data byte to TX FIFO
88D0 STA econet_data_continue_frame ; Write first byte of pair to FIFO
88D3 INY ; Advance buffer offset
88D4 BNE write_second_tx_byte ; No page crossing
88D6 DEC port_buf_len_hi ; Page crossing: decrement page count
88D8 BEQ frame_end_restore ; No pages left: send last data
88DA INC open_port_buf_hi ; Increment buffer high byte
88DC .write_second_tx_byte←1← 88D4 BNE
LDA (open_port_buf),y ; Load second byte of pair
88DE STA econet_data_continue_frame ; Write second byte to FIFO
88E1 INY ; Advance buffer offset
88E2 STY port_buf_len ; Save updated buffer position
88E4 BNE check_fifo_loop ; No page crossing
88E6 DEC port_buf_len_hi ; Page crossing: decrement page count
88E8 BEQ frame_end_restore ; No pages left: send last data
88EA INC open_port_buf_hi ; Increment buffer high byte
88EC .check_fifo_loop←1← 88E4 BNE
PLA ; Pull saved ACCCON from stack
88ED STA acccon ; Restore caller's ACCCON between byte pairs
88F0 .check_irq_loop
BIT econet_control1_or_status1 ; Test ADLC SR1 IRQ flag for next byte pair
88F3 BMI data_tx_check_fifo ; IRQ still set: more bytes to send
88F5 JMP nmi_rti ; IRQ cleared: return from NMI
88F8 .frame_end_restore←2← 88D8 BEQ← 88E8 BEQ
PLA ; Pull saved ACCCON (frame-end path)
88F9 STA acccon ; Restore caller's ACCCON before TX_LAST_DATA
88FC .data_tx_last←3← 88B6 BEQ← 892F BEQ← 8945 BEQ
LDA #&3f ; CR2=&3F: TX_LAST_DATA (close data frame)
88FE STA econet_control23_or_status2 ; Write CR2 to close frame
8901 LDA net_frame_flags ; Check tx_flags for next action
8904 BPL install_saved_handler ; Bit7 clear: error, install saved handler
8906 LDA #<(discard_reset_rx) ; Next NMI handler address (low)
8908 LDY #>(discard_reset_rx) ; Next NMI handler address (high)
890A JMP set_nmi_vector ; Set NMI vector and return
890D .install_saved_handler←1← 8904 BPL
LDA saved_nmi_lo ; Load saved next handler low byte
8910 LDY saved_nmi_hi ; Load saved next handler high byte
8913 JMP set_nmi_vector ; Install saved handler and return

NMI handler: TX FIFO write from Tube buffer

NMI continuation handler used during TX of a Tube-sourced data frame. Tests SR1 TDRA via BIT econet_control1_or_status1, writes the next pair of bytes from the Tube buffer to the ADLC TX FIFO (the tube_tx_fifo_write shared body at &884A), and either continues the tight inner loop on a continuing IRQ or returns via RTI. Reached only via the NMI vector after tx_prepare installs it.

8916 .nmi_data_tx_tube
BIT econet_control1_or_status1 ; Tube TX: test SR1 TDRA
8919 .tube_tx_fifo_write←2← 88C3 BVC← 894A BMI
BVC tx_tdra_error ; TDRA not ready -- error
891B LDA tube_data_register_3 ; Read byte from Tube R3
891E STA econet_data_continue_frame ; Write to TX FIFO
8921 INC port_buf_len ; Increment 4-byte buffer counter
8923 BNE write_second_tube_byte ; Low byte didn't wrap
8925 INC port_buf_len_hi ; Carry into second byte
8927 BNE write_second_tube_byte ; No further carry
8929 INC open_port_buf ; Carry into third byte
892B BNE write_second_tube_byte ; No further carry
892D INC open_port_buf_hi ; Carry into fourth byte
892F BEQ data_tx_last ; Counter wrapped to zero: last data
8931 .write_second_tube_byte←3← 8923 BNE← 8927 BNE← 892B BNE
LDA tube_data_register_3 ; Read second Tube byte from R3
8934 STA econet_data_continue_frame ; Write second byte to TX FIFO
8937 INC port_buf_len ; Increment 4-byte counter (second byte)
8939 BNE check_tube_irq_loop ; Low byte didn't wrap
893B .tube_tx_inc_byte2
INC port_buf_len_hi ; Carry into second byte
893D BNE check_tube_irq_loop ; No further carry
893F .tube_tx_inc_byte3
INC open_port_buf ; Carry into third byte
8941 BNE check_tube_irq_loop ; No further carry
8943 .tube_tx_inc_byte4
INC open_port_buf_hi ; Carry into fourth byte
8945 BEQ data_tx_last ; Counter wrapped to zero: last data
8947 .check_tube_irq_loop←3← 8939 BNE← 893D BNE← 8941 BNE
BIT econet_control1_or_status1 ; Test SR1 IRQ for tight loop
894A BMI tube_tx_fifo_write ; IRQ still set: write 2 more bytes
894C JMP nmi_rti ; No IRQ: return, wait for next NMI
894F .tx_tdra_error←1← 8919 BVC
LDA net_frame_flags ; TX error: check flags for path
8952 BPL tx_result_fail ; Bit7 clear: TX result = not listening
8954 JMP discard_reset_rx ; Bit7 set: discard and return to listen

Four-way handshake: switch to RX for final ACK

Called via JMP from nmi_tx_complete when bit 0 of tx_flags is set (four-way handshake in progress). Writes CR1=&82 (TX_RESET|RIE) to switch the ADLC from TX mode to RX mode, listening for the final ACK from the remote station. Installs nmi_final_ack as the next NMI handler via set_nmi_vector.

8957 .handshake_await_ack←1← 8812 JMP
LDA #&82 ; CR1=&82: TX_RESET | RIE (switch to RX for final ACK)
8959 STA econet_control1_or_status1 ; Write to ADLC CR1
895C LDA #<(nmi_final_ack) ; Next NMI handler address (low)
895E LDY #>(nmi_final_ack) ; Next NMI handler address (high)
8960 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 nmi_reply_validate:

  1. Check AP, read dest_stn, compare to our station.
  2. Check RDA, read dest_net, validate = 0.
  3. Check RDA, read src_stn / src_net, compare to TX dest.
  4. Check FV for frame completion.

On success, stores result=0 via tx_result_ok. On failure, error &41.

8963 .nmi_final_ack
LDA #1 ; A=&01: AP mask
8965 BIT econet_control23_or_status2 ; Test SR2 AP
8968 BEQ tx_result_fail ; No AP -- error
896A LDA econet_data_continue_frame ; Read dest station
896D CMP tx_src_stn ; Compare to our station (workspace copy)
8970 BNE tx_result_fail ; Not our station -- error
8972 LDA #<(nmi_final_ack_net) ; Next NMI handler address (low)
8974 JMP install_nmi_handler ; Install continuation handler

NMI handler: final-ACK source-net validation

NMI continuation entry installed by nmi_final_ack. Polls SR2 for RDA, reads the source-network byte from the ADLC RX FIFO, and compares with the original TX destination network (tx_dst_net, &0D21). On mismatch, branches to tx_result_fail. On match, falls through into nmi_final_ack_validate for the source-station check. Reached only via the NMI vector (no static caller).

On ExitAsource-network byte read from FIFO
8977 .nmi_final_ack_net
BIT econet_control23_or_status2 ; Test SR2 RDA
897A BPL tx_result_fail ; No RDA -- error
897C LDA econet_data_continue_frame ; Read dest network
897F BNE tx_result_fail ; Non-zero -- network mismatch, error
8981 LDA #&8b ; Install nmi_final_ack_validate handler
8983 BIT econet_control1_or_status1 ; Test SR1 IRQ -- more data ready?
8986 BMI nmi_final_ack_validate ; IRQ set -- fall through to validate
8988 JMP install_nmi_handler ; Install handler

Final ACK validation

Continuation of nmi_final_ack. Tests SR2 for RDA, then reads the source station and source network bytes from the RX FIFO, comparing each against the original TX destination at tx_dst_stn and tx_dst_net. Finally tests SR2 bit 1 (FV) for frame completion.

Any mismatch or missing FV branches to tx_result_fail. On success, falls through to tx_result_ok.

898B .nmi_final_ack_validate←1← 8986 BMI
BIT econet_control23_or_status2 ; Test SR2 RDA
898E BPL tx_result_fail ; No RDA -- error
8990 LDA econet_data_continue_frame ; Read source station
8993 CMP tx_dst_stn ; Compare to TX dest station (&0D20)
8996 BNE tx_result_fail ; Mismatch -- error
8998 LDA econet_data_continue_frame ; Read source network
899B CMP tx_dst_net ; Compare to TX dest network (&0D21)
899E BNE tx_result_fail ; Mismatch -- error
89A0 LDA net_frame_flags ; Load TX flags for next action
89A3 BPL check_fv_final_ack ; bit7 clear: no data phase
89A5 JMP install_data_rx_handler ; Install data RX handler
89A8 .check_fv_final_ack←1← 89A3 BPL
LDA #2 ; A=&02: FV mask for SR2 bit1
89AA BIT econet_control23_or_status2 ; Test SR2 FV -- frame must be complete
89AD BEQ tx_result_fail ; No FV -- error
fall through ↓

TX completion handler

Loads A=0 (success) and branches unconditionally to tx_store_result (BEQ is always taken since A=0, skipping the tx_result_fail body at &88E2). This two- instruction entry point exists so that JMP sites can target the success path without needing to set A. Called from ack_tx for final-ACK completion and from nmi_tx_complete for immediate-op completion where no ACK is expected.

On ExitA0 (TX success)
89AF .tx_result_ok←2← 82F7 JMP← 8808 JMP
LDA #0 ; A=0: success result code
89B1 BEQ tx_store_result ; Always taken (A=0)
fall through ↓

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.

On ExitA&41 ('not listening' TX error)
89B3 .tx_result_fail←11← 821D JMP← 8844 JMP← 8952 BPL← 8968 BEQ← 8970 BNE← 897A BPL← 897F BNE← 898E BPL← 8996 BNE← 899E BNE← 89AD BEQ
LDA #&41 ; A=&41: not listening error code
fall through ↓

TX result store and completion

Stores the TX result code (in A) at offset 0 of the TX control block via (nmi_tx_block),Y=0. Sets tx_complete_flag to &80 to signal TX completion to the foreground polling loop. Then jumps to discard_reset_rx for a full ADLC reset and return to idle RX-listen mode.

On EntryAresult code (0=success, &40=jammed, &41=not listening)
89B5 .tx_store_result←2← 87F1 JMP← 89B1 BEQ
LDY #0 ; Y=0: index into TX control block
89B7 STA (nmi_tx_block),y ; Store result/error code at (nmi_tx_block),0
89B9 LDA #&80 ; A=&80: TX-complete signal for tx_complete_flag
89BB STA tx_complete_flag ; Signal TX complete
89BE JMP discard_reset_rx ; Full ADLC reset and return to idle listen

ADLC full reset

Performs a full ADLC hardware reset:

  1. Writes CR1=&C1 (TX_RESET | RX_RESET | AC) to put both TX and RX sections in reset with address-control enabled.
  2. Configures CR4=&1E (8-bit RX word, abort extend, NRZ encoding).
  3. Configures CR3=&00 (no loopback, no AEX, NRZ, no DTR).
  4. Falls through to adlc_rx_listen to re-enter RX-listen mode.
On ExitA, X, Yclobbered
89C1 .adlc_full_reset←3← 8055 JSR← 80E1 JSR← 8220 JSR
LDA #&c1 ; CR1=&C1: TX_RESET | RX_RESET | AC (both sections in reset, address control set)
89C3 STA econet_control1_or_status1 ; Write CR1 to ADLC register 0
89C6 LDA #&1e ; CR4=&1E (via AC=1): 8-bit RX word length, abort extend enabled, NRZ encoding
89C8 STA econet_data_terminate_frame ; Write CR4 to ADLC register 3
89CB LDA #0 ; CR3=&00 (via AC=1): no loop-back, no AEX, NRZ, no DTR
89CD STA econet_control23_or_status2 ; Write CR3 to ADLC register 1
fall through ↓

Enter RX-listen mode

Configures the ADLC for passive RX-listen mode:

Register Value Meaning
CR1 &82 TX_RESET | RIE – TX section held in reset, RX interrupts enabled
CR2 &67 CLR_TX_ST | CLR_RX_ST | FC_TDRA | 2_1_BYTE | PSE – clear all pending status, enable prioritised status

This is the idle state where the ADLC listens for incoming scout frames via NMI.

On ExitA, Xclobbered (control byte writes)
Ypreserved
89D0 .adlc_rx_listen←2← 83FA JSR← 8A00 JMP
LDA #&82 ; CR1=&82: TX_RESET | RIE (TX in reset, RX interrupts enabled)
89D2 STA econet_control1_or_status1 ; Write to ADLC CR1
89D5 LDA #&67 ; CR2=&67: CLR_TX_ST | CLR_RX_ST | FC_TDRA | 2_1_BYTE | PSE
89D7 STA econet_control23_or_status2 ; Write to ADLC CR2
89DA RTS ; Return; ADLC now in RX listen mode

Wait for idle NMI state and reset Econet

Service-13 (&0D) handler -- the post-hard-reset Econet shutdown path. Reached via svc_dispatch slot &0D. Checks econet_init_flag to see if Econet has been initialised; if not, skips straight to adlc_rx_listen. Otherwise spins in a tight loop comparing the NMI handler vector at nmi_jmp_lo / nmi_jmp_hi against the address of nmi_rx_scout to wait until the in-flight NMI handler chain has unwound back to scout-listening.

When the NMI vector matches nmi_rx_scout again, falls through to save_econet_state to clear the initialised flags and re-enter RX-listen mode. (Service &0B 'NMI release' is handled by the separate econet_restore.)

On EntryA13 (service call number, &0D)
On ExitA, X, Yclobbered
89DB .wait_idle_and_reset
BIT econet_init_flag ; Check if Econet has been initialised
89DE BPL reset_enter_listen ; Not initialised: skip to RX listen
89E0 .poll_nmi_idle←2← 89E5 BNE← 89EC BNE
LDA nmi_jmp_lo ; Read current NMI handler low byte
89E3 CMP #&9d ; Expected: &B3 (nmi_rx_scout low)
89E5 BNE poll_nmi_idle ; Not idle: spin and wait
89E7 LDA nmi_jmp_hi ; Read current NMI handler high byte
89EA CMP #&80 ; Compare to &80 (line-idle threshold)
89EC BNE poll_nmi_idle ; Not idle: spin and wait
89EE LDA #&40 ; A=&40: 'line inactive' status code
89F0 STA net_poll_status ; Store net poll-status byte
89F3 BIT disable_net_nmis ; INTOFF: disable NMIs
89F6 LDA #0 ; A=0
89F8 STA tx_complete_flag ; TX not in progress
89FB STA econet_init_flag ; Econet not initialised
89FE LDY #5 ; Y=5: service call workspace page
8A00 .reset_enter_listen←1← 89DE 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 shim at the start of the NFS workspace block. Unlike the RAM shim (which uses a self-modifying JMP to dispatch to different handlers), this one hardcodes JMP nmi_rx_scout. Used as the initial NMI handler before the workspace has been properly set up during initialisation.

Same sequence as the RAM shim:

BIT disable_net_nmis      ; INTOFF (Master 128 dedicated register)
PHA
TYA
PHA
LDA #romsel-bank
STA romsel
JMP nmi_rx_scout

The BIT of disable_net_nmis (INTOFF) at entry and BIT of enable_net_nmis (INTON) before RTI in nmi_rti are essential for edge-triggered NMI re-delivery.

The 6502 /NMI is falling-edge triggered; the Econet NMI-enable flip-flop gates the ADLC IRQ onto /NMI. INTOFF clears the flip-flop, forcing /NMI high; INTON sets it, allowing the ADLC IRQ through. This creates a guaranteed high-to-low edge on /NMI even when the ADLC IRQ is continuously asserted (e.g. when it transitions atomically from TDRA to frame-complete without de-asserting). Without this mechanism, nmi_tx_complete would never fire after tx_last_data.

8A03 .nmi_bootstrap_entry
BIT disable_net_nmis ; INTOFF: force /NMI high (clear NMI flip-flop)
8A06 PHA ; Save A
8A07 TYA ; Transfer Y to A
8A08 PHA ; Save Y (via A)
8A09 LDA #0 ; ROM bank 0 (patched during init for actual bank)
8A0B STA romsel ; Select Econet ROM bank via ROMSEL
8A0E JMP nmi_rx_scout ; Jump to scout handler in ROM

ROM copy of set_nmi_vector + nmi_rti

ROM-resident version of the NMI-exit sequence; also the source for the initial copy to RAM at set_nmi_vector.

RAM target Function
set_nmi_vector writes both hi and lo bytes of the JMP target at nmi_jmp_lo / nmi_jmp_hi
nmi_rti restores the original ROM bank, pulls Y and A from the stack, then BIT of enable_net_nmis (INTON) to re-enable the NMI flip-flop before RTI

The INTON creates a guaranteed falling edge on /NMI if the ADLC IRQ is already asserted, ensuring the next handler fires immediately.

8A11 .rom_set_nmi_vector
STY nmi_jmp_hi ; Store handler high byte at &0D0D
8A14 STA nmi_jmp_lo ; Store handler low byte at &0D0C
8A17 LDA romsel_copy ; Restore NFS ROM bank
8A19 STA romsel ; Page in via hardware latch
8A1C PLA ; Restore Y from stack
8A1D TAY ; Transfer ROM bank to Y
8A1E PLA ; Restore A from stack
fall through ↓

NMI exit: re-enable NMIs and return

Two-instruction NMI tail: BIT enable_net_nmis (INTON, guaranteeing a fresh /NMI edge if the ADLC IRQ is still asserted) then RTI.

8A1F .nmi_return_inton
BIT enable_net_nmis ; INTON: guaranteed /NMI edge if ADLC IRQ asserted
8A22 RTI ; Return from interrupt
8A23 EQUB &05, ; Padding before the service-dispatch low-byte &0A ; table

svc_dispatch low-byte table (51 entries)

Low-byte half of the PHA/PHA/RTS dispatch table read by svc_dispatch as LDA &8A23,X. Paired with the high-byte half at svc_dispatch_hi. Index 0 is an unused placeholder; indices 1..50 cover service handlers, language replies, FSCV reasons, FS replies and net-handle / OSWORD &13 trampolines. Per-entry inline comments give each slot's dispatch.

8A25 .svc_dispatch_lo←1Used as index base by← 8E84 LDA
EQUB &0C ; &00: placeholder (never reached)
8A26 EQUB <(dispatch_rts - 1) ; &01: no-op (RTS only)
8A27 EQUB <(svc_dispatch_idx_2 - 1) ; &02: workspace claim helper (CMOS bit 0)
8A28 EQUB <(svc_2_priv_ws - 1) ; &03: svc &02: private workspace pages
8A29 EQUB <(svc_3_autoboot - 1) ; &04: svc &03: auto-boot
8A2A EQUB <(svc_4_star_command - 1) ; &05: svc &04: unrecognised *command
8A2B EQUB <(svc5_irq_check - 1) ; &06: svc &05: IRQ check
8A2C EQUB <(dispatch_rts - 1) ; &07: no-op (RTS only)
8A2D EQUB <(svc_7_osbyte - 1) ; &08: svc &07: unrecognised OSBYTE
8A2E EQUB <(svc_8_osword_disp - 1) ; &09: svc &08: OSWORD dispatch
8A2F EQUB <(svc_9_help - 1) ; &0A: svc &09: *HELP
8A30 EQUB <(dispatch_rts - 1) ; &0B: no-op (RTS only)
8A31 EQUB <(econet_restore - 1) ; &0C: svc &0B: NMI release
8A32 EQUB <(wait_idle_and_reset - 1) ; &0D: svc &0D: wait idle and reset
8A33 EQUB <(svc_18_fs_select - 1) ; &0E: svc &12: FS select
8A34 EQUB <(match_on_suffix - 1) ; &0F: svc &18: interactive HELP 'ON ' matcher
8A35 EQUB <(raise_y_to_c8 - 1) ; &10: svc &21: static workspace claim
8A36 EQUB <(set_rom_ws_page - 1) ; &11: svc &22: dynamic workspace offer
8A37 EQUB <(store_ws_page_count - 1) ; &12: svc &23: top-of-static- workspace
8A38 EQUB <(noop_dey_rts - 1) ; &13: svc &24: dynamic workspace claim
8A39 EQUB <(copy_template_to_zp - 1) ; &14: svc &25: FS name + info reply
8A3A EQUB <(svc_26_close_all_files - 1) ; &15: svc &26: close all files
8A3B EQUB <(nfs_init_body - 1) ; &16: svc &27: post-hard-reset re-init
8A3C EQUB <(print_fs_ps_help - 1) ; &17: svc &28: print *FS/*PS no-arg syntax help
8A3D EQUB <(svc_29_status - 1) ; &18: svc &29: *STATUS handler
8A3E EQUB <(lang_0_insert_key - 1) ; &19: language reply 0: insert remote key
8A3F EQUB <(lang_1_remote_boot - 1) ; &1A: language reply 1: remote boot
8A40 EQUB <(lang_2_save_palette_vdu - 1) ; &1B: language reply 2: save palette/VDU
8A41 EQUB <(lang_3_exec_0100 - 1) ; &1C: language reply 3: execute at &0100
8A42 EQUB <(lang_4_validated - 1) ; &1D: language reply 4: remote validated
8A43 EQUB <(fscv_0_opt_entry - 1) ; &1E: FSCV 0: *OPT
8A44 EQUB <(fscv_1_eof - 1) ; &1F: FSCV 1: EOF
8A45 EQUB <(cmd_run_via_urd - 1) ; &20: FSCV 2: *RUN
8A46 EQUB <(fscv_3_star_cmd - 1) ; &21: FSCV 3: *command
8A47 EQUB <(cmd_run_via_urd - 1) ; &22: FSCV 4: *RUN (alias)
8A48 EQUB <(fscv_5_cat - 1) ; &23: FSCV 5: *CAT
8A49 EQUB <(fscv_6_shutdown - 1) ; &24: FSCV 6: shutdown
8A4A EQUB <(fscv_7_read_handles - 1) ; &25: FSCV 7: read handles
8A4B EQUB <(dispatch_rts - 1) ; &26: no-op (RTS only)
8A4C EQUB <(ps_scan_resume - 1) ; &27: PS scan tail (after pop_requeue)
8A4D EQUB <(cmd_info_dispatch - 1) ; &28: *Info dispatch
8A4E EQUB <(check_urd_present - 1) ; &29: URD-present check
8A4F EQUB <(ex_init_scan_x0 - 1) ; &2A: *Ex scan init
8A50 EQUB <(fsreply_1_boot - 1) ; &2B: FS reply 1: copy handles + boot
8A51 EQUB <(fsreply_2_copy_handles - 1) ; &2C: FS reply 2: copy handles
8A52 EQUB <(fsreply_3_set_csd - 1) ; &2D: FS reply 3: set CSD
8A53 EQUB <(cmd_run_via_urd - 1) ; &2E: FS reply 4: *RUN (alias)
8A54 EQUB <(fsreply_5_set_lib - 1) ; &2F: FS reply 5: set library
8A55 EQUB <(net_1_read_handle - 1) ; &30: net handle 1: read handle
8A56 EQUB <(net_2_read_entry - 1) ; &31: net handle 2: read handle entry
8A57 EQUB <(net_3_close_handle - 1) ; &32: net handle 3: close handle
8A58 .svc_dispatch_hi←1Used as index base by← 8E80 LDA
EQUB &11 ; &00: placeholder (never reached)
8A59 EQUB >(dispatch_rts - 1) ; &01: no-op (RTS only)
8A5A EQUB >(svc_dispatch_idx_2 - 1) ; &02: workspace claim helper (CMOS bit 0)
8A5B EQUB >(svc_2_priv_ws - 1) ; &03: svc &02: private workspace pages
8A5C EQUB >(svc_3_autoboot - 1) ; &04: svc &03: auto-boot
8A5D EQUB >(svc_4_star_command - 1) ; &05: svc &04: unrecognised *command
8A5E EQUB >(svc5_irq_check - 1) ; &06: svc &05: IRQ check
8A5F EQUB >(dispatch_rts - 1) ; &07: no-op (RTS only)
8A60 EQUB >(svc_7_osbyte - 1) ; &08: svc &07: unrecognised OSBYTE
8A61 EQUB >(svc_8_osword_disp - 1) ; &09: svc &08: OSWORD dispatch
8A62 EQUB >(svc_9_help - 1) ; &0A: svc &09: *HELP
8A63 EQUB >(dispatch_rts - 1) ; &0B: no-op (RTS only)
8A64 EQUB >(econet_restore - 1) ; &0C: svc &0B: NMI release
8A65 EQUB >(wait_idle_and_reset - 1) ; &0D: svc &0D: wait idle and reset
8A66 EQUB >(svc_18_fs_select - 1) ; &0E: svc &12: FS select
8A67 EQUB >(match_on_suffix - 1) ; &0F: svc &18: interactive HELP 'ON ' matcher
8A68 EQUB >(raise_y_to_c8 - 1) ; &10: svc &21: static workspace claim
8A69 EQUB >(set_rom_ws_page - 1) ; &11: svc &22: dynamic workspace offer
8A6A EQUB >(store_ws_page_count - 1) ; &12: svc &23: top-of-static- workspace
8A6B EQUB >(noop_dey_rts - 1) ; &13: svc &24: dynamic workspace claim
8A6C EQUB >(copy_template_to_zp - 1) ; &14: svc &25: FS name + info reply
8A6D EQUB >(svc_26_close_all_files - 1) ; &15: svc &26: close all files
8A6E EQUB >(nfs_init_body - 1) ; &16: svc &27: post-hard-reset re-init
8A6F EQUB >(print_fs_ps_help - 1) ; &17: svc &28: print *FS/*PS no-arg syntax help
8A70 EQUB >(svc_29_status - 1) ; &18: svc &29: *STATUS handler
8A71 EQUB >(lang_0_insert_key - 1) ; &19: language reply 0: insert remote key
8A72 EQUB >(lang_1_remote_boot - 1) ; &1A: language reply 1: remote boot
8A73 EQUB >(lang_2_save_palette_vdu - 1) ; &1B: language reply 2: save palette/VDU
8A74 EQUB >(lang_3_exec_0100 - 1) ; &1C: language reply 3: execute at &0100
8A75 EQUB >(lang_4_validated - 1) ; &1D: language reply 4: remote validated
8A76 EQUB >(fscv_0_opt_entry - 1) ; &1E: FSCV 0: *OPT
8A77 EQUB >(fscv_1_eof - 1) ; &1F: FSCV 1: EOF
8A78 EQUB >(cmd_run_via_urd - 1) ; &20: FSCV 2: *RUN
8A79 EQUB >(fscv_3_star_cmd - 1) ; &21: FSCV 3: *command
8A7A EQUB >(cmd_run_via_urd - 1) ; &22: FSCV 4: *RUN (alias)
8A7B EQUB >(fscv_5_cat - 1) ; &23: FSCV 5: *CAT
8A7C EQUB >(fscv_6_shutdown - 1) ; &24: FSCV 6: shutdown
8A7D EQUB >(fscv_7_read_handles - 1) ; &25: FSCV 7: read handles
8A7E EQUB >(dispatch_rts - 1) ; &26: no-op (RTS only)
8A7F EQUB >(ps_scan_resume - 1) ; &27: PS scan tail (after pop_requeue)
8A80 EQUB >(cmd_info_dispatch - 1) ; &28: *Info dispatch
8A81 EQUB >(check_urd_present - 1) ; &29: URD-present check
8A82 EQUB >(ex_init_scan_x0 - 1) ; &2A: *Ex scan init
8A83 EQUB >(fsreply_1_boot - 1) ; &2B: FS reply 1: copy handles + boot
8A84 EQUB >(fsreply_2_copy_handles - 1) ; &2C: FS reply 2: copy handles
8A85 EQUB >(fsreply_3_set_csd - 1) ; &2D: FS reply 3: set CSD
8A86 EQUB >(cmd_run_via_urd - 1) ; &2E: FS reply 4: *RUN (alias)
8A87 EQUB >(fsreply_5_set_lib - 1) ; &2F: FS reply 5: set library
8A88 EQUB >(net_1_read_handle - 1) ; &30: net handle 1: read handle
8A89 EQUB >(net_2_read_entry - 1) ; &31: net handle 2: read handle entry
8A8A EQUB >(net_3_close_handle - 1), &8B ; &32: net handle 3: close handle

Service call dispatch

Handles service calls 1, 4, 8, 9, 13, 14, and 15.

Call Meaning
1 Absolute workspace claim
4 Unrecognised * command
8 Unrecognised OSWORD
9 *HELP
13 ROM initialisation
14 ROM initialisation complete
15 Vectors claimed

On service 15 the ROM verifies the host OS via OSBYTE 0 with the input X=1, which returns the OS version code:

OSBYTE 1 value Host
0 OS 1.00 (early BBC B or Electron)
1 OS 1.20 or American OS
2 OS 2.00 (BBC B+)
3 OS 3.2 / 3.5 (Master 128)
4 OS 4.0 (Master Econet Terminal)
5 OS 5.0 (Master Compact)

Only Master 128 and Master Econet Terminal are supported. Any other version gets a Bad ROM <slot> message printed and its workspace byte cleared at &02A0 + adjusted-slot, effectively rejecting the ROM.

On EntryAservice call number
XROM slot
Yparameter
8A8C .service_handler←1← 8003 JMP
PHA ; Save service call number
8A8D CMP #&0f ; Service call &0F (vectors claimed)?
8A8F BNE restore_rom_slot_entry ; No: skip vectors-claimed handling
8A91 PHY ; Save Y on stack across the version-check
8A92 LDA #osbyte_read_os_version ; OSBYTE 0: read OS version
8A94 LDX #1 ; X=1 to request version number
; On return, X is the OS version number:
; ┌───┬───────────────────────────────────────────┐
; │ X │ Meaning │
; ├───┼───────────────────────────────────────────┤
; │ 0 │ OS 1.00 (Early BBC B or Electron OS 1.00) │
; │ 1 │ OS 1.20 or American OS │
; │ 2 │ OS 2.00 (BBC B+) │
; │ 3 │ OS 3.2/3.5 (Master 128) │
; │ 4 │ OS 4.0 (Master Econet Terminal) │
; │ 5 │ OS 5.0 (Master Compact) │
; └───┴───────────────────────────────────────────┘
8A96 JSR osbyte ; X=1, OS 1.20 or American OS
8A99 LDA os_spool_flag_table,x ; X is the host OS version (0=1.00, 1=1.20, 3=Master, 4=Master Terminal, 5=Master Compact)
8A9C STA spool_control_flag ; Store the spool-control flag
8A9F CPX #3 ; OS 3.2/3.5 (Master 128)?
8AA1 BCS restore_rom_slot ; C set: not ours, restore ROM slot and return
8AA3 TXA ; Transfer OS version to A
8AA4 PHP ; Save flags (Z set if OS 1.00) across print
8AA5 JSR print_inline ; Print '<CR>Bad ROM ' to mark non-Master OS
8AA8 EQUS &0D, "Bad ROM " ; svc 13 fail path
8AB1 LDA romsel_copy ; Load this ROM's slot number
8AB3 JSR print_num_no_leading ; Print slot number as decimal
8AB6 JSR print_newline_no_spool ; Print trailing newline, bypassing *SPOOL
8AB9 LDX romsel_copy ; Reload ROM slot for workspace clearing
8ABB PLP ; Restore flags
8ABC BEQ clear_workspace_byte ; OS 1.00: skip INX (table starts at slot 0)
8ABE INX ; Adjust index for OS 1.20/2.00/5.00 layout
8ABF .clear_workspace_byte←1← 8ABC BEQ
LDA #0 ; A=0
8AC1 STA rom_type_table,x ; Clear workspace byte for this ROM
8AC4 .restore_rom_slot←1← 8AA1 BCS
LDX romsel_copy ; Restore ROM slot to X
8AC6 PLY ; Restore Y from stack
8AC7 .restore_rom_slot_entry←1← 8A8F BNE
PLA ; Pop service call number into A
8AC8 PHA ; Re-save service call number
8AC9 .check_adlc_flag
LDA rom_ws_pages,x ; Load ROM slot flag byte
8ACC ASL ; Shift bit 7 (ADLC absent) into carry
8ACD PLA ; Restore service call number
8ACE BCC dispatch_svc_with_state ; ADLC present: continue dispatch
8AD0 RTS ; ADLC absent: decline service, return
8AD1 .dispatch_svc_with_state←1← 8ACE BCC
TAX ; Transfer service number to X
8AD2 LDA svc_state ; Save current service state
8AD4 PHA ; Push old state
8AD5 TXA ; Restore service number to A
8AD6 STA svc_state ; Store as current service state
8AD8 CMP #&0d ; Service < 13?
8ADA BCC dispatch_svc_index ; Yes: use as dispatch index directly
8ADC SBC #5 ; Subtract 5 (map 13-17 to 8-12)
8ADE CMP #&0d ; Mapped value = 13? (original was 18)
8AE0 BEQ dispatch_svc_index ; Yes: valid service 18 (FS select)
8AE2 BCC dispatch_svc_state_check ; C clear: service number was below the prior CMP threshold, take dispatch fall-through
8AE4 SBC #5 ; Subtract 5 to remap service range
8AE6 CMP #&0e ; Compare with &0E
8AE8 BEQ dispatch_svc_index ; Equal: dispatch directly
8AEA BCC dispatch_svc_state_check ; Below: take dispatch fall-through
8AEC SBC #8 ; Subtract 8 to remap further
8AEE CMP #&0f ; Compare with &0F
8AF0 BCC dispatch_svc_state_check ; Below: dispatch fall-through
8AF2 CMP #&18 ; Compare with &18
8AF4 BCC dispatch_svc_index ; Below: dispatch index now in A
8AF6 .dispatch_svc_state_check←3← 8AE2 BCC← 8AEA BCC← 8AF0 BCC
LDA #0 ; Unknown service: set index to 0
8AF8 .dispatch_svc_index←4← 8ADA BCC← 8AE0 BEQ← 8AE8 BEQ← 8AF4 BCC
TAX ; Transfer dispatch index to X
8AF9 BEQ restore_svc_state ; Index 0: unhandled service, skip
8AFB LDA ws_page ; Save current workspace page
8AFD PHA ; Push old page
8AFE STY ws_page ; Set workspace page from Y parameter
8B00 TYA ; Transfer Y to A
8B01 LDY #0 ; Y=0 for dispatch offset
8B03 JSR svc_dispatch ; Dispatch to service handler via table
8B06 PLA ; Restore old workspace page
8B07 STA ws_page ; Store it back
8B09 .restore_svc_state←1← 8AF9 BEQ
LDX svc_state ; Get service state (return code)
8B0B PLA ; Restore old service state
8B0C STA svc_state ; Store it back
8B0E TXA ; Transfer return code to A
8B0F .restore_romsel_rts
LDX romsel_copy ; Restore ROM slot to X
8B11 RTS ; Return to MOS

*ROFF command handler

Disables remote operation by clearing the flag at offset 0 in the receive block. If remote operation was active, re-enables the keyboard via OSBYTE &C9 (with X=0, Y=0) and calls tx_econet_abort with A=&0A to reinitialise the workspace area. Falls through to scan_remote_keys which clears svc_state and nfs_workspace.

On EntryYcommand line offset (unused -- *ROFF takes no args)
On ExitA, X, Yclobbered
8B12 .cmd_roff
LDY #0 ; Offset 0 in receive block
8B14 LDA (net_rx_ptr),y ; Load remote operation flag
8B16 BEQ clear_svc_and_ws ; Zero: already off, skip to cleanup
8B18 LDA #0 ; A=0
8B1A TAX ; Copy A to X
8B1B STA (net_rx_ptr),y ; Clear remote operation flag
8B1D TAY ; Copy A to Y
8B1E LDA #osbyte_econet_kbd_disable ; OSBYTE &C9: keyboard disable
8B20 JSR osbyte ; osbyte: econet kbd disable
8B23 LDA #&0a ; A=&0A: workspace init parameter
8B25 JSR tx_econet_abort ; Initialise workspace area
fall through ↓

Scan keyboard for remote-operation keys

Uses OSBYTE &7A with Y=&7F to check whether remote-operation keys (&CE..&CF) are currently pressed. If neither key is detected, clears svc_state and nfs_workspace to zero via the clear_svc_and_ws entry point (also used directly by cmd_roff). Called by check_escape.

X is saved into nfs_workspace across the OSBYTE call and restored each iteration – the loop reuses A as the key-code counter without needing X. clear_svc_and_ws is also entered directly (label) by cmd_roff with no register pre-conditions.

On EntryXpreserved by being saved to nfs_workspace and reloaded each iteration (no other preconditions)
On ExitA0 (when no key pressed -- the cleared path)
Xmay be modified by OSBYTE
Y&7F (left over from OSBYTE call setup)
8B28 .scan_remote_keys←1← 986F JSR
STX nfs_workspace ; Save X in workspace
8B2A LDA #osbyte_econet_oscall_intercept ; A=&CE: start of key range
8B2C .loop_scan_key_range←1← 8B37 BEQ
LDX nfs_workspace ; Restore X from workspace
8B2E LDY #&7f ; Y=&7F: OSBYTE scan parameter
8B30 JSR osbyte ; OSBYTE: scan keyboard osbyte: econet oscall intercept
8B33 ADC #1 ; Advance to next key code
8B35 CMP #&d0 ; Reached &D0?
8B37 BEQ loop_scan_key_range ; No: loop back (scan &CE and &CF)
8B39 .clear_svc_and_ws←1← 8B16 BEQ
LDA #0 ; A=0
8B3B STA svc_state ; Clear service state
8B3D STA nfs_workspace ; Clear workspace byte
8B3F RTS ; Return

Save OS text pointer for later retrieval

Copies &F2/&F3 (os_text_ptr) into fs_crc_lo / fs_crc_hi. Called by svc_4_star_command and svc_9_help before attempting command matches, and by match_fs_cmd during iterative help-topic matching. Preserves A via PHA/PLA.

On ExitApreserved
8B40 .save_text_ptr←3← 8C6D JSR← 8C98 JSR← A619 JSR
PHA ; Save A
8B41 LDA os_text_ptr ; Copy OS text pointer low
8B43 STA fs_crc_lo ; to fs_crc_lo
8B45 LDA os_text_ptr_hi ; Copy OS text pointer high
8B47 STA fs_crc_hi ; to fs_crc_hi
8B49 PLA ; Restore A
8B4A .rts_save_text_ptr←2← 8B6F BNE← 8B78 BMI
RTS ; Return

Select Econet network filing system

Computes a checksum over the first &77 bytes of the workspace page and verifies against the stored value; raises an error on mismatch. On success:

  1. Notifies the OS via FSCV reason 6 (notify_new_fs).
  2. Copies the FS context block from the receive block to the HAZEL FS state at hazel_fs_station (offsets 0..9), via the hazel_minus_2,Y indexing-base trick.
  3. Installs 7 filing-system vectors (FILEV etc.) from fs_vector_table.
  4. Initialises the ADLC and extended vectors.
  5. Sets up the channel table.
  6. Sets bit 7 of fs_flags to mark the FS as selected.
  7. Issues service call 15 (vectors claimed) via issue_svc_15.
On EntryYcommand line offset in text pointer (unused for *NET FS but supplied by star-cmd dispatch)
On ExitA, X, Yclobbered
8B4B .cmd_net_fs←2← 8B7A JSR← 9016 JSR
JSR get_ws_page ; Get workspace page for this ROM slot
8B4E STA fs_load_addr_hi ; Store as high byte of load address
8B50 LDA #0 ; A=0
8B52 STA fs_load_addr ; Clear low byte of load address
8B54 CLC ; Clear carry for addition
8B55 LDY #&76 ; Y=&76: checksum range end
8B57 .loop_sum_rom_bytes←1← 8B5A BPL
ADC (fs_load_addr),y ; Add byte to running checksum
8B59 DEY ; Decrement index
8B5A BPL loop_sum_rom_bytes ; Loop until all bytes summed
8B5C LDY #&77 ; Y=&77: checksum storage offset
8B5E EOR (fs_load_addr),y ; Compare with stored checksum
8B60 RTS ; Return -- last instruction of cmd_net_fs body
8B61 .cmd_net_check_hw
LDA #&20 ; A=&20: ADLC IRQ-status mask (CR2 bit 5)
8B63 BIT econet_control23_or_status2 ; Read ADLC CR2/SR2 (&FEA1)
8B66 BEQ select_fs_via_cmd_net_fs ; Z set (no carrier): proceed to FS-select
8B68 LDA #3 ; A=3: 'ROM has no NFS' error code
8B6A JMP build_simple_error ; Raise via build_simple_error (never returns)

Service 18: filing-system selection request

Service-18 entry point.

Condition Action
Y ≠ 5 return unclaimed (not the Econet FS)
Bit 7 of fs_flags set return (FS already selected)
else fall through to cmd_net_fs for the full network-FS selection sequence
On EntryYfiling system number requested
8B6D .svc_18_fs_select
CPY #5 ; Service 18 carries FS number in Y; Econet is FS 5
8B6F BNE rts_save_text_ptr ; Not us: pass the call on (RTS via shared return)
8B71 LDA #0 ; A=0 to claim the service
8B73 STA svc_state ; Clear svc_state and fall into ensure_fs_selected
fall through ↓

Ensure ANFS is the active filing system

If bit 7 of fs_flags is set (ANFS already active), RTS via rts_save_text_ptr. Otherwise calls cmd_net_fs to select ANFS now; on failure, JMPs to error_net_checksum to raise the net checksum error. After successful selection, falls through to the body at &8B5A which sets up the OSWORD parameter block pointer and continues the caller's work.

On EntryX, YOSWORD parameter block pointer (preserved across the cmd_net_fs call when selection happens)
8B75 .ensure_fs_selected←7← 8EA9 JSR← 96B4 JSR← A9EE JSR← A9FC JSR← AAE4 JSR← AAF2 JSR← AC6E JSR
BIT fs_flags ; Test fs_flags bit 7 (ANFS active)
8B78 BMI rts_save_text_ptr ; Already active: tail-RTS via shared exit
fall through ↓

Force ANFS selection (raise net checksum on failure)

Tail-fragment of ensure_fs_selected used directly by svc_3_autoboot when an autoboot needs to force-select ANFS as the active filing system. Calls cmd_net_fs to perform the actual selection; on failure (BEQ not taken), JMPs to error_net_checksum to raise the net checksum error. Used when there is no clean BIT fs_flags / BMI shortcut for early-return.

On EntryX, Ypreserved across cmd_net_fs (as per the ensure_fs_selected calling contract)
On ExitAcurrent FS state byte if selection succeeded
8B7A .select_fs_via_cmd_net_fs←2← 8B66 BEQ← 8D04 JSR
JSR cmd_net_fs ; Auto-select ANFS via the *NFS handler
8B7D BEQ select_fs_cmd_net_fs ; Z=1 (A=0): selection succeeded
8B7F JMP error_net_checksum ; Otherwise raise 'net checksum' error
8B82 .select_fs_cmd_net_fs←1← 8B7D BEQ
LDA osword_pb_ptr_hi ; Read osword_pb_ptr_hi
8B84 PHA ; Push it
8B85 LDA osword_pb_ptr ; Read osword_pb_ptr lo
8B87 PHA ; Push it
8B88 .done_rom_checksum
JSR notify_new_fs ; Call FSCV with A=6 (new FS)
8B8B LDY #9 ; Y=9: end of FS context block
8B8D .loop_copy_fs_ctx←1← 8B95 BNE
LDA (net_rx_ptr),y ; Load byte from receive block
8B8F STA hazel_minus_2,y ; Store into FS workspace
8B92 DEY ; Decrement index
8B93 CPY #1 ; Reached offset 1?
8B95 BNE loop_copy_fs_ctx ; No: continue copying
8B97 ROL fs_flags ; Shift bit 7 of FS flags into carry
8B9A CLC ; Clear carry
8B9B ROR fs_flags ; Clear bit 7 of FS flags
8B9E LDY #&0d ; Y=&0D: vector table size - 1
8BA0 .loop_set_vectors←1← 8BA7 BPL
LDA fs_vector_table,y ; Load FS vector address
8BA3 STA filev,y ; Store into FILEV vector table
8BA6 DEY ; Decrement index
8BA7 BPL loop_set_vectors ; Loop until all vectors installed
8BA9 JSR init_adlc_and_vectors ; Initialise ADLC and NMI workspace
8BAC LDY #&1b ; Y=&1B: extended vector offset
8BAE LDX #7 ; X=7: two more vectors to set up
8BB0 JSR write_vector_entry ; Set up extended vectors
8BB3 LDA #0 ; A=0
8BB5 STA hazel_fs_pending_state ; Clear FS state byte
8BB8 STA hazel_chan_attr ; Clear workspace byte
8BBB STA hazel_fs_lib_flags ; Clear workspace byte
8BBE JSR store_rx_attribute ; Clear receive attribute byte
8BC1 STA hazel_fs_error_code ; Clear workspace byte
8BC4 JSR setup_ws_ptr ; Set up workspace pointers
8BC7 JSR init_channel_table ; Initialise FS state
8BCA LDY #&77 ; Y=&77: workspace block size - 1
8BCC .loop_copy_ws_page←1← 8BD2 BPL
LDA (fs_ws_ptr),y ; Load byte from source workspace
8BCE STA hazel_fcb_addr_lo,y ; Store to the HAZEL &C2 FCB shadow copy
8BD1 DEY ; Decrement index
8BD2 BPL loop_copy_ws_page ; Loop until all bytes copied
8BD4 LDA #&80 ; A=&80: FS selected flag
8BD6 TSB fs_flags ; Set bit 0 of fs_flags (= NFS active)
8BD9 JSR issue_svc_15 ; Issue Master service call &0F (vector update)
8BDC PLA ; Pop saved osword_pb_ptr lo
8BDD STA osword_pb_ptr ; Restore osword_pb_ptr lo
8BDF PLA ; Pop saved osword_pb_ptr hi
8BE0 STA osword_pb_ptr_hi ; Restore osword_pb_ptr hi
8BE2 RTS ; Return

*HELP NFS topic: print NFS-specific commands

Loads X=&35 (the offset of the first NFS-specific command in cmd_table_fs) and tail-falls into print_cmd_table to emit the listing. Single caller (the *HELP topic dispatch at &8C70).

On ExitX&35 + advance through the table
8BE3 .help_print_nfs_cmds←1← 8C95 JMP
LDX #&35 ; X=&35: NFS command table offset
8BE5 JSR print_cmd_table ; Print help for NFS commands
fall through ↓

*HELP UTILS topic handler

Sets X = 0 to select the utility command sub-table and branches to print_cmd_table to display the command list. Prints the version header followed by all utility commands.

On EntryYcommand-line offset (PHA/PHA/RTS dispatch contract)
On ExitA, X, Yclobbered
8BE8 .help_utils
LDX #0 ; X=0: utility command table offset
8BEA BEQ print_cmd_table ; Zero args: print the command table
fall through ↓

*HELP NET topic handler

Sets X = &35 (the NFS command sub-table offset) and falls through to print_cmd_table to display the NFS command list with version header.

On EntryYcommand-line offset (PHA/PHA/RTS dispatch contract)
On ExitA, X, Yclobbered (print_cmd_table)
8BEC .help_net
LDX #&35 ; X=&35: NFS command table offset
fall through ↓

Print *HELP command listing with optional header

V flag Action
set save X/Y, call print_version_header to show the ROM version string and station number, restore X/Y
clear output a newline only

Either path then falls through to print_cmd_table to enumerate commands.

On EntryXoffset into cmd_table_fs
Vset=print version header, clear=newline only
8BEE .print_cmd_table←2← 8BE5 JSR← 8BEA BEQ
BVC print_table_newline ; V clear: take newline-only path (skip version header)
8BF0 PHX ; Save X (cmd-table offset)
8BF1 PHY ; Save Y (text-pointer offset)
8BF2 JSR print_version_header ; Print the version-banner header
8BF5 PLY ; Restore Y
8BF6 PLX ; Restore X
8BF7 CLV ; Clear overflow flag
8BF8 BVC help_table_walk_entry ; V clear: walk the next table entry
8BFA .print_table_newline←1← 8BEE BVC
JSR osnewl ; Print newline
fall through ↓

*HELP / command table walker per-entry body

Per-entry body of the command / *HELP table walker: saves Y and flags, then classifies cmd_table_fs,X (bit 7 marks a sub-table terminator vs. a name byte). Called from the walker loop and from the *HELP command lister.

8BFD .help_table_walk_entry←2← 8BF8 BVC← 8C88 JSR
PHY ; Save Y
8BFE PHP ; Save processor status
fall through ↓

*HELP table walker per-entry body

Loads cmd_table_fs,X (entry byte at offset X):

Bit 7 Target
clear print_indent (continue with this entry)
set JMP done_print_table (end of table reached)

Single caller (the BNE retry at &8C47 in print_cmd_table's outer loop).

On EntryXcurrent cmd_table_fs offset
8BFF .loop_next_entry←1← 8C49 JMP
LDA cmd_table_fs,x ; Load byte from command table
8C02 BPL print_indent ; Bit 7 clear: valid entry, continue
8C04 JMP done_print_table ; End of table: finish up
8C07 .print_indent←1← 8C02 BPL
JSR print_inline ; Print two-space indent
8C0A EQUS " "
8C0C LDY #9 ; Y=9: cmd_table_fs sub-table 1 offset
8C0E LDA cmd_table_fs,x ; Read cmd_table_fs+X (entry name byte)
8C11 .loop_print_cmd_name←1← 8C19 BPL
JSR osasci ; Print character
8C14 INX ; Advance table pointer
8C15 DEY ; Decrement padding counter
8C16 LDA cmd_table_fs,x ; Load next character
8C19 BPL loop_print_cmd_name ; Bit 7 clear: more chars, continue
8C1B .loop_pad_spaces←1← 8C21 BPL
LDA #' ' ; Pad with spaces
8C1D JSR osasci ; Print character
8C20 DEY ; Decrement remaining pad count
8C21 BPL loop_pad_spaces ; More padding needed: loop
8C23 LDA cmd_table_fs,x ; Load syntax descriptor byte
8C26 AND #&1f ; Mask to get syntax string index
8C28 TAY ; Use index as Y
8C29 LDA cmd_syntax_table,y ; Look up syntax string offset
8C2C TAY ; Transfer offset to Y
fall through ↓

Per-character body of *HELP syntax string emit

INY / load syn_opt_dir,Y / detect terminator or line-break:

Byte Action
0 terminator – stop
CR (&0D) line-break – wrap
other print the character

Two callers: the BNE at &8C15 (continue with current char) and the BEQ at &8C19 (fall-through from the line-wrap path).

On EntryYcurrent index into syn_opt_dir
8C2D .loop_print_syntax←2← 8C3A JMP← 8C40 JMP
INY ; Advance to next character
8C2E LDA cmd_syntax_strings,y ; Load syntax string character
8C31 BEQ done_entry_newline ; Zero terminator: end of syntax
8C33 CMP #&0d ; Carriage return: line continuation
8C35 BNE print_syntax_char ; No: print the character
8C37 JSR help_wrap_if_serial ; Handle line wrap in syntax output
8C3A JMP loop_print_syntax ; Continue with next character
8C3D .print_syntax_char←1← 8C35 BNE
JSR osasci ; Print character
8C40 JMP loop_print_syntax ; Continue with next character
8C43 .done_entry_newline←1← 8C31 BEQ
JSR osnewl ; Print newline
8C46 INX ; X += 3: skip syntax descriptor and address
8C47 INX ; (continued)
8C48 INX ; (continued)
8C49 JMP loop_next_entry ; Loop for next command

Cleanup epilogue for print_cmd_table

Pops the saved P and Y registers off the stack and RTS. Used as the shared exit for print_cmd_table after it has emitted a help listing or detected end-of-table. Single caller (the BEQ at &8BDD in print_cmd_table when V was set on entry, indicating the saved state needs restoring).

On ExitYrestored from stack
P (FLAGS)restored from stack
8C4C .done_print_table←1← 8C04 JMP
PLP ; Restore processor status
8C4D PLA ; Restore Y
8C4E TAY ; Transfer to Y
8C4F RTS ; Return

Wrap *HELP syntax lines for serial output

Checks the output destination via vdu_mode:

Stream Action
0 (VDU) return immediately
3 (printer) return immediately
serial output newline + 12 spaces of indentation to align continuation lines with the syntax-description column
On ExitYpreserved (saved/restored via PHY/PLY)
Aclobbered (last char written via OSWRCH)
8C50 .help_wrap_if_serial←1← 8C37 JSR
LDA vdu_mode ; Read output stream type
8C53 BEQ rts_help_wrap ; Stream 0 (VDU): no wrapping
8C55 CMP #3 ; Stream 3 (printer)?
8C57 BEQ rts_help_wrap ; Yes: no wrapping
8C59 PHY ; Save Y across OS call
8C5A JSR osnewl ; Print newline
8C5D LDY #&0b ; Y=&0B: indent width - 1
8C5F LDA #' ' ; Space character
8C61 .loop_indent_spaces←1← 8C65 BPL
JSR osasci ; Print character
8C64 DEY ; Decrement indent counter
8C65 BPL loop_indent_spaces ; More spaces needed: loop
8C67 PLY ; Restore Y
8C68 .rts_help_wrap←2← 8C53 BEQ← 8C57 BEQ
RTS ; Return

Service 4: unrecognised star command

Saves the OS text pointer, then calls match_fs_cmd to search the command table starting at offset 0 (all command sub-tables). If no match is found (carry set), returns with the service call unclaimed. On a match, JMPs to cmd_fs_reentry to execute the matched command handler via the PHA/PHA/RTS dispatch mechanism.

On EntryYcommand line offset in text pointer
8C69 .svc_4_star_command
LDX #0 ; X=0: start of utility command table
8C6B LDY ws_page ; Get command line offset
8C6D .svc4_dispatch_lookup←2← 95EC JMP← 9687 JMP
JSR save_text_ptr ; Save text pointer to fs_crc
8C70 JSR match_fs_cmd ; Try to match command in table
8C73 BCS svc_return_unclaimed ; No match: return to caller
8C75 JMP cmd_fs_reentry ; Match found: execute command

Service 9: *HELP

Handles MOS service call 9 (*HELP). First checks for the credits Easter egg. For bare *HELP (CR at text pointer), prints the version header and full command list starting at table offset &91. For *HELP with an argument, handles '.' as a shortcut to list all NFS commands, otherwise iterates through help topics using PHA/PHA/RTS dispatch to print matching command groups. Returns with Y = ws_page (unclaimed).

On EntryA9 (service call number)
Ycommand-line offset of *HELP argument
On ExitYws_page (workspace page) -- the service call is left UNCLAIMED so MOS continues to the next ROM
8C78 .svc_9_help
JSR check_credits_easter_egg ; Check for credits Easter egg
8C7B LDY ws_page ; Get command line offset
8C7D LDA (os_text_ptr),y ; Load character at offset
8C7F CMP #&0d ; Is it CR (bare *HELP)?
8C81 BNE check_help_topic ; No: check for specific topic
8C83 JSR print_version_header ; Print version string
8C86 LDX #&91 ; X=&91: start of help command list
8C88 JSR help_table_walk_entry ; Print command list from table
fall through ↓

Restore Y and return service-call unclaimed

Reloads Y from ws_page (the saved command-line offset) and RTS to the caller without clearing A – preserving the original service number so the next ROM in the chain sees the unclaimed call.

Reached from the four service-handler escape paths at &8C4C, &8C91, &8CFA, and &95BF that hand a request back to MOS without acting on it.

On ExitYws_page (restored command-line offset)
8C8B .svc_return_unclaimed←5← 8C73 BCS← 8CB8 BEQ← 8CFC BNE← 95BF JMP← 9682 JMP
LDY ws_page ; Restore Y (command line offset)
8C8D RTS ; Return unclaimed
8C8E .check_help_topic←1← 8C81 BNE
BIT always_set_v_byte ; Test for topic match (sets flags)
8C91 CMP #'.' ; Is first char '.' (abbreviation)?
8C93 BNE match_help_topic ; No: try topic-specific help
8C95 JMP help_print_nfs_cmds ; '.' found: show full command list
8C98 .match_help_topic←1← 8C93 BNE
JSR save_text_ptr ; Save text pointer to fs_crc
8C9B .loop_dispatch_help←1← 8CB6 BNE
PHP ; Save flags
8C9C LDX #&96 ; X=&96: help command table start
8C9E JSR match_fs_cmd ; Try to match help topic in table
8CA1 BCS skip_if_no_match ; No match: try next topic
8CA3 PLP ; Restore flags
8CA4 LDA #&8c ; Push return address high (&8C)
8CA6 PHA ; Push it for RTS dispatch
8CA7 LDA #&9a ; Push return address low (&74)
8CA9 PHA ; Push it for RTS dispatch
8CAA LDA cmd_dispatch_hi_table,x ; Load dispatch address high
8CAD PHA ; Push dispatch high for RTS
8CAE LDA cmd_dispatch_lo_table,x ; Load dispatch address low
8CB1 PHA ; Push dispatch low for RTS
8CB2 RTS ; Dispatch via RTS (returns to &8CA7)
8CB3 .skip_if_no_match←1← 8CA1 BCS
PLP ; Restore flags from before match
8CB4 CMP #&0d ; End of command line?
8CB6 BNE loop_dispatch_help ; No: try matching next topic
8CB8 BEQ svc_return_unclaimed ; Loop back for the next character
fall through ↓

Print ANFS version string and station number

Uses an inline string after JSR to print_inline: CR + "Advanced NFS 4.26" + CR. After the inline string, JMPs to print_station_id to append the local Econet station number.

On ExitA, X, Yclobbered (print_inline + print_station_id)
8CBA .print_version_header←2← 8BF2 JSR← 8C83 JSR
JSR print_inline ; Print version string via inline
8CBD .version_string_cr
EQUS &0D, "Advanced NFS 4.26", &0D
8CD0 NOP ; NOP -- bit-7 terminator + harmless resume opcode
8CD1 JMP print_station_id ; Tail-call print_station_id to append ' Econet Station <n>' (and ' No Clock' if appropriate)

Read workspace page number for current ROM slot

Indexes into the MOS per-ROM workspace table rom_ws_pages using romsel_copy (&F4) as the ROM slot. Holds a copy of the slot byte in Y, then runs a ROL / PHP / ROR / PLP sequence at &8CDA&8CB6 that restores A to the original byte while leaving the saved-flags register reflecting bit 6 of the original byte (the ADLC-absent flag). Falls through to whichever caller-specific tail follows.

On ExitAworkspace page byte (preserved through ROL/ROR)
Ysame byte (set by TAY before the rotate trick)
Nset to bit 6 of the original byte (ADLC-absent flag)
8CD4 .get_ws_page←4← 8B4B JSR← 8CE4 JSR← 8F49 JSR← B3D5 JSR
LDY romsel_copy ; Y = current ROM slot number from MOS copy at &F4
8CD6 LDA rom_ws_pages,y ; Load workspace page byte for this ROM slot
8CD9 TAY ; Hold a copy of the slot byte in Y while we test bit 6
8CDA ROL ; ROL puts pre-ROL bit 6 into the post-ROL N flag (and pre-ROL bit 7 into C)
8CDB PHP ; Save those flags so the upcoming ROR doesn't lose N
8CDC ROR ; ROR restores A to its original value (using the saved C)
8CDD PLP ; Restore the ROL flags: N is now pre-ROL bit 6
8CDE BPL get_ws_page_loop ; Bit 6 clear: skip the OR (no ADLC-absent flag)
8CE0 ORA #&80 ; Bit 6 set: re-set bit 7 in the returned page byte (the ADLC-absent flag uses bit 7 in callers)
8CE2 .get_ws_page_loop←1← 8CDE BPL
TAY ; Transfer to Y
8CE3 RTS ; Return with page in A and Y

Set up zero-page pointer to workspace page

Calls get_ws_page to read the page number, stores it as the high byte in nfs_temp (&CD), and clears the low byte at &CC to zero. This gives a page-aligned pointer used by FS initialisation and cmd_net_fs to access the private workspace.

On ExitA0
Yworkspace page number
8CE4 .setup_ws_ptr←2← 8BC4 JSR← 8F69 JSR
JSR get_ws_page ; Get workspace page for ROM slot
8CE7 STY nfs_temp ; Store page in nfs_temp
8CE9 LDA #0 ; A=0
8CEB STA fs_ws_ptr ; Clear low byte of pointer
8CED .rts_setup_ws_ptr←1← 8D13 BNE
RTS ; Return

Service 3: auto-boot on reset

Scans the keyboard via OSBYTE &7A for the 'N' key (&19 or &55 EOR'd with &55). If pressed, records the key state via OSBYTE &78. Selects the network filing system by calling cmd_net_fs, prints the station ID, then checks if this is the first boot (ws_page = 0). If so, sets the auto-boot flag in &1071 and JMPs to cmd_fs_entry to execute the boot file.

On EntryA3 (service call number)
XROM slot
Yparameter (service-call dispatch)
8CEE .svc_3_autoboot
LDA #osbyte_scan_keyboard_from_16 ; OSBYTE &7A: scan keyboard from key 16
8CF0 JSR osbyte ; Scan keyboard from key 16
8CF3 TXA ; Copy X to A
8CF4 BMI select_net_fs ; No key pressed: select Net FS
8CF6 CMP #&19 ; Key &19 (N)?
8CF8 BEQ write_key_state ; Yes: write key state and boot
8CFA EOR #&55 ; EOR with &55: maps to zero if 'N'
8CFC BNE svc_return_unclaimed ; Not N key: return unclaimed
8CFE .write_key_state←1← 8CF8 BEQ
TAY ; Copy A to Y
8CFF LDA #osbyte_write_keys_pressed ; OSBYTE &78: write keys pressed
8D01 JSR osbyte ; Write all keys pressed information
8D04 .select_net_fs←1← 8CF4 BMI
JSR select_fs_via_cmd_net_fs ; Select NFS as current filing system
8D07 JSR print_station_id ; Print the station identity line
8D0A JSR osnewl ; Print newline
8D0D LDA #0 ; A=0: clear svc_state marker
8D0F STA svc_state ; Store -> svc_state
8D11 LDX ws_page ; Get workspace page
8D13 BNE rts_setup_ws_ptr ; Non-zero: already initialised, return
8D15 LDA #4 ; A=4: library-selected flag bit
8D17 TSB hazel_fs_lib_flags ; Set the library-selected flag
8D1A LDX #&40 ; X=&40: boot filename address low
8D1C LDY #&8d ; Y=&8D: boot filename address high
8D1E JMP oscli ; Execute boot file

Notify OS of filing-system selection

Loads A=6 (FSCV reason: filing system change) and falls through to call_fscv, which JMP-indirects through vec_fscv to invoke the FSCV vector. The FSCV handler returns to whatever invoked notify_new_fs -- this is a fire-and-forget notification, not a return-to-caller call.

Single caller (&8B88 inside the FS-selection sequence).

On ExitA6 (clobbered by FSCV handler)
8D21 .notify_new_fs←1← 8B88 JSR
LDA #6 ; A=6: notify new filing system
fall through ↓

Dispatch to filing-system control vector (FSCV)

Indirect JMP through FSCV at vec_fscv, providing OS-level filing-system services such as FS-selection notification (A=6) and *RUN handling.

Also contains issue_svc_15 and issue_svc_osbyte entry points that issue paged-ROM service requests via OSBYTE &8F.

On EntryAFSCV reason code
8D23 .call_fscv←1← A5B4 JMP
JMP (fscv) ; Tail-jump via FSCV vector (filing-system change service)

Issue OSBYTE 143 service 15 (vectors-claimed) request

Tail-call wrapper that loads X=&0F (service number 15) and tail-jumps to OSBYTE 143 (issue paged ROM service request), which broadcasts service 15 to all sideways ROMs. ANFS calls this from svc_2_private_workspace after claiming its workspace, to give other ROMs a chance to react.

On EntryAOSBYTE result is irrelevant -- this is fire-and-forget
8D26 .issue_svc_15←1← 8BD9 JSR
LDX #&0f ; X=&0F: service 15 (vectors claimed)
8D28 .issue_svc_osbyte
LDA #osbyte_issue_service_request ; A=&8F: OSBYTE 'Issue paged-ROM service request'
8D2A JMP osbyte ; Issue paged ROM service call, Reason X

svc_dispatch table[2] handler

Reached only via PHA/PHA/RTS dispatch from the svc_dispatch_lo table at index 2. Pushes Y onto the stack via PHY, sets X=&11 (CMOS RAM offset for the Econet station-flags byte), calls osbyte_a1 to read it, then ANDs the result with &01 (bit 0 = "use page &0B fallback") and pulls Y back. Used by the workspace-allocation path to discover whether the user has overridden the default private workspace base.

On ExitA0 or 1 (CMOS bit 0 of station-flags byte)
8D2D .svc_dispatch_idx_2
PHY ; Save Y on stack
8D2E LDX #&11 ; X=&11: CMOS offset for Econet station-flags
8D30 JSR osbyte_a1 ; Read CMOS byte: result in Y
8D33 TYA ; A = CMOS byte
8D34 PLY ; Restore caller's Y
8D35 AND #1 ; Isolate bit 0 (page-&0B fallback flag)
8D37 BEQ return_1 ; Bit clear: keep caller's Y
8D39 CPY #&10 ; Caller's Y already >= &10?
8D3B BCS return_1 ; Yes: keep it
8D3D LDY #&10 ; Y < &10 with bit set: clamp to &10
8D3F .return_1←2← 8D37 BEQ← 8D3B BCS
RTS ; Return
8D40 EQUS "i .Boot"
8D47 EQUB &0D

Easter egg: match *HELP keyword to author credits

Matches the *HELP argument against a keyword embedded in the credits data at credits_keyword_start. Starts matching from offset 5 in the data (X=5) and checks each byte against the command-line text until a mismatch or X reaches &0D.

On a full match, prints the ANFS author credits:

  • B Cockburn
  • J Dunn
  • B Robertson
  • J Wills

Each name is terminated by CR.

8D48 .check_credits_easter_egg←1← 8C78 JSR
LDY ws_page ; Y = ws_page (workspace high page)
8D4A LDX #5 ; X=5: start of credits keyword
8D4C .loop_match_credits←1← 8D55 BNE
LDA (os_text_ptr),y ; Load character from command line
8D4E CMP credits_keyword_start,x ; Compare with credits keyword
8D51 BNE done_credits_check ; Mismatch: check if keyword complete
8D53 INY ; Advance command line pointer
8D54 INX ; Advance keyword pointer
8D55 BNE loop_match_credits ; Continue matching
8D57 .done_credits_check←1← 8D51 BNE
CPX #&0c ; Reached end of keyword (X=&0C)?
8D59 BNE rts_credits_check ; No: keyword not fully matched, return
8D5B JSR print_inline ; Print the credits string inline (high-bit terminated)
8D5E .credits_keyword_start←1Used as index base by← 8D4E CMP
EQUS &0D, "The authors of ANFS are:", &0D, "B Cockburn", &0D, "J Dunn", &0D, "B Robertson", &0D, "J Wills ", &0D
8D9F NOP ; bit-7 terminator + resume opcode
8DA0 .rts_credits_check←1← 8D59 BNE
RTS ; Return
8DA1 .cmd_iam_save_ctx
PHY ; Save caller Y
8DA2 LDA fs_last_byte_flag ; Read fs_last_byte_flag (work_bd)
8DA4 LDX fs_options ; Read fs_options (work_bb)
8DA6 LDY fs_block_offset ; Read fs_block_offset (work_bc)
8DA8 PHA ; Push fs_last_byte_flag for restore on return
8DA9 PHX ; Save X
8DAA PHY ; Save Y
fall through ↓

*I AM command handler (file server logon)

Closes any *SPOOL/*EXEC files via OSBYTE &77, resets all file control blocks via process_all_fcbs, then parses the command line for an optional station number and file server address. If a station number is present, stores it and calls clear_if_station_match to validate. Copies the logon command template from cmd_table_nfs_iam into the transmit buffer and sends via copy_arg_validated. Falls through to cmd_pass for password entry.

On EntryYcommand line offset in text pointer
8DAB .cmd_iam
LDA #osbyte_close_spool_exec ; OSBYTE &77: close SPOOL/EXEC
8DAD STA hazel_fs_pending_state ; Store as pending operation marker
8DB0 JSR osbyte ; Close any SPOOLed or EXECed files
8DB3 LDY #0 ; Y=0
8DB5 STY fs_work_4 ; Clear password entry flag
8DB7 JSR process_all_fcbs ; Reset FS connection state
8DBA STZ hazel_fs_pending_state ; Clear hazel_fs_pending_state (connection-attempt flag)
8DBD PLY ; Restore Y
8DBE PLX ; Restore X
8DBF PLA ; Pop and discard saved fs_last_byte_flag
fall through ↓

Set FS transfer parameters via set_xfer_params

3-byte trampoline that calls set_xfer_params and falls through into cmd_pass's argument-parse prologue. Reached from init_txcb_and_load_xfer at &B3DB to install the FS transfer context (byte count + source pointer in fs_last_byte_flag / fs_crc_lo/hi) before continuing into the *I am / *Pass station-and-credential parser.

8DC0 .load_transfer_params
JSR set_xfer_params ; Set up transfer parameters
8DC3 PLY ; Restore Y
8DC4 LDA (fs_options),y ; Load first option byte
8DC6 JSR is_decimal_digit ; Parse station number if present
8DC9 BCC cmd_pass ; Not a digit: skip to password entry
8DCB JSR parse_addr_arg ; Parse user ID string
8DCE BCS skip_no_fs_addr ; No user ID: go to password
8DD0 STA hazel_fs_network ; Store file server station low
8DD3 JSR clear_if_station_match ; Check and store FS network
8DD6 INY ; Skip separator
8DD7 JSR parse_addr_arg ; Parse next argument
8DDA .skip_no_fs_addr←1← 8DCE BCS
BEQ cmd_pass ; No FS address: skip to password
8DDC STA hazel_fs_station ; Store file server station high
8DDF LDX #&ff ; X=&FF: pre-decrement for loop
8DE1 .loop_copy_logon_cmd←1← 8DE8 BPL
INX ; Advance index
8DE2 LDA cmd_table_nfs_iam,x ; Load logon command template byte
8DE5 STA hazel_txcb_data,x ; Store into transmit buffer
8DE8 BPL loop_copy_logon_cmd ; Bit 7 clear: more bytes, loop
8DEA JSR copy_arg_validated ; Send logon with file server lookup
8DED BEQ scan_pass_prompt ; Success: skip to password entry
fall through ↓

*PASS command handler (change password)

Builds the FS command packet via copy_arg_to_buf_x0, then scans the reply buffer for a ':' separator indicating a password prompt. If found, reads characters from the keyboard without echo, handling Delete (&7F) for backspace and NAK (&15) to restart from the colon position. Sends the completed password to the file server via save_net_tx_cb and branches to send_cmd_and_dispatch for the reply.

On EntryYcommand line offset in text pointer (also the entry point for cmd_iam fall-through)
8DEF .cmd_pass←2← 8DC9 BCC← 8DDA BEQ
JSR copy_arg_to_buf_x0 ; Build FS command packet
8DF2 .scan_pass_prompt←1← 8DED BEQ
LDY #&ff ; Y=&FF: pre-increment for loop
8DF4 .loop_scan_colon←1← 8DFE BNE
INY ; Advance to next byte
8DF5 LDA hazel_txcb_data,y ; Load byte from reply buffer
8DF8 CMP #&0d ; Is it CR (end of prompt)?
8DFA BEQ send_pass_to_fs ; Yes: no colon found, skip to send
8DFC CMP #':' ; Is it ':' (password prompt)?
8DFE BNE loop_scan_colon ; No: keep scanning
8E00 JSR print_byte_no_spool ; Print byte no-spool
8E03 STY fs_work_4 ; Save position of colon
8E05 .read_pw_char←4← 8E15 BNE← 8E19 BEQ← 8E1C BNE← 8E28 BNE
LDA #&ff ; A=&FF: mark as escapable
8E07 STA need_release_tube ; Set escape flag
8E09 JSR check_escape_and_classify ; Check for escape condition
8E0C JSR osrdch ; Read a character (password entry, no echo)
8E0F CMP #&15 ; Ctrl-U (&15): line-delete?
8E11 BNE check_pw_special ; Not NAK (&15): check other chars
8E13 LDY fs_work_4 ; Restore colon position
8E15 BNE read_pw_char ; Non-zero: restart from colon
8E17 .loop_erase_pw←1← 8E20 BEQ
CPY fs_work_4 ; At colon position?
8E19 BEQ read_pw_char ; Yes: restart password input
8E1B DEY ; Backspace: move back one character
8E1C BNE read_pw_char ; If not at start: restart input
8E1E .check_pw_special←1← 8E11 BNE
CMP #&7f ; Delete key (&7F)?
8E20 BEQ loop_erase_pw ; Yes: handle backspace
8E22 STA hazel_txcb_data,y ; Store character in password buffer
8E25 INY ; Advance buffer pointer
8E26 CMP #&0d ; Is it CR (end of password)?
8E28 BNE read_pw_char ; No: read another character
8E2A JSR print_newline_no_spool ; Print newline no-spool
8E2D .send_pass_to_fs←1← 8DFA BEQ
TYA ; Transfer string length to A
8E2E PHA ; Save string length
8E2F JSR init_txcb ; Set up transmit control block
8E32 JSR init_tx_ptr_for_pass ; Send to file server and get reply
8E35 PLX ; Restore X
8E36 INX ; Include terminator
8E37 LDY #0 ; Y=0
8E39 BEQ send_cmd_and_dispatch ; Branch to send the command
fall through ↓

Clear hazel_fs_network if it matches the bridge status byte

Calls init_bridge_poll (returning the spool_control_flag bridge status byte in A, either freshly populated or already cached from a previous invocation) and EORs it with hazel_fs_network. When the two match (EOR result is zero), zeroes hazel_fs_network so subsequent FS operations fall back to the local network.

Called by cmd_iam and osword_13_set_station when reconciling a parsed file-server station address against the bridge state.

On ExitA0 if cleared (match), bridge-XOR-network otherwise
8E3B .clear_if_station_match←2← 8DD3 JSR← AA0E JSR
JSR init_bridge_poll ; Ensure bridge initialised; A=spool_control_flag (bridge status)
8E3E EOR hazel_fs_network ; EOR with hazel_fs_network: zero result if equal
8E41 BNE rts_station_match ; Different: return without clearing
8E43 STA hazel_fs_network ; Same: clear station byte
8E46 .rts_station_match←1← 8E41 BNE
RTS ; Return

Branch to *RUN handler if first arg char is '&'

Reads the first character of the parsed command text via (fs_crc_lo),Y:

First char Path
'&' (URD prefix marker) JMP cmd_run_via_urd
any other fall through to pass_send_cmd (send as normal FS request)

Single caller (the FS command-name post-match path at &959E).

8E47 .check_urd_prefix←1← 959E JMP
LDY #0 ; Y=0: first character offset
8E49 LDA (fs_crc_lo),y ; Load first character of command text
8E4B CMP #'&' ; Is it '&' (URD prefix)?
8E4D BNE pass_send_cmd ; No: send as normal FS command
8E4F JMP cmd_run_via_urd ; Yes: route via *RUN for URD prefix handling
8E52 .pass_send_cmd←1← 8E4D BNE
JSR copy_arg_to_buf_x0 ; Build FS command packet
8E55 TAY ; Transfer result to Y
fall through ↓

Send FS command and dispatch the reply

  1. JSR save_net_tx_cb to set up and transmit the command.
  2. Read the reply function code from hazel_txcb_network.
Reply code Action
0 branch to the no-reply path (dispatch_rts)
non-zero load hazel_txcb_data (first reply byte), Y=&25 (dispatch offset for the standard reply table), continue into the reply-dispatch chain

Two callers: the fall-through from check_urd_prefix (&8E37 via pass_send_cmd) and the JMP from send_fs_request (&9465).

On EntryYextra dispatch offset (0 from send_fs_request, non-zero for some specialised paths)
8E56 .send_cmd_and_dispatch←3← 8E39 BEQ← 9467 JMP← B3A5 JMP
JSR save_net_tx_cb ; Set up command and send to FS
8E59 LDX hazel_txcb_network ; Load reply function code
8E5C BEQ dispatch_rts ; Zero: no reply, return
8E5E LDA hazel_txcb_data ; Load first reply byte
8E61 LDY #&25 ; Y=&25: logon dispatch offset
8E63 BNE svc_dispatch ; Not matched: fall to service dispatch
8E65 .fscv_handler
JSR set_xfer_params ; Parse reply as decimal number
8E68 CMP #&0c ; Result >= 8?
8E6A BCS dispatch_rts ; Yes: out of range, return
8E6C TAX ; Transfer handle to X
8E6D JSR mask_owner_access ; Look up in open files table
8E70 TYA ; Transfer result to A
8E71 LDY #&1d ; Y=&1D: handle dispatch offset
8E73 BNE svc_dispatch ; Not matched: fall to service dispatch
fall through ↓

Dispatch directory operation via PHA/PHA/RTS

Validates X < 5 and sets Y = &18 as the dispatch offset, then falls through into svc_dispatch. The INX/DEY/BPL loop in svc_dispatch then settles X_final = X_caller + Y + 1, landing on indices &19..&1D of the svc_dispatch_lo / svc_dispatch_hi tables. Those slots map to the language-reply handlers lang_0_insert_key (idx &19) through lang_4_validated (idx &1D).

On EntryXdirectory operation code (0-4)
8E75 .dir_op_dispatch←1← 8581 JSR
CPX #5 ; Handle >= 5?
8E77 BCS dispatch_rts ; Yes: out of range, return
8E79 LDY #&18 ; Y=&18: settles X_final to &19..&1D (lang reply 0..4)
fall through ↓

PHA/PHA/RTS table dispatch

Computes a target index by incrementing X and decrementing Y until Y goes negative, effectively calculating X+Y+1. Pushes the target address (high then low byte) from svc_dispatch_lo / svc_dispatch_hi onto the stack, loads fs_options into X, then RTS jumps to the target subroutine. Used for all service dispatch, FS command execution, and OSBYTE handler routing.

Routine extent is &8E61-&8E88 (the RTS is the dispatch). The short Master service handlers at noop_dey_rts (svc &24), copy_template_to_zp (svc &25) and svc_26_close_all_files sit immediately after.

On EntryXbase dispatch index
Yadditional offset
On ExitXfs_options value
8E7B .svc_dispatch←5← 8B03 JSR← 8E63 BNE← 8E73 BNE← 8E7D BPL← 8F00 JMP
INX ; Advance X to target index
8E7C DEY ; Decrement Y offset counter
8E7D BPL svc_dispatch ; Y still positive: continue counting
8E7F TAY ; Y=&FF: will be ignored by caller
8E80 LDA svc_dispatch_hi,x ; Load dispatch address high byte
8E83 PHA ; Push high byte for RTS dispatch
8E84 .push_dispatch_lo
LDA svc_dispatch_lo,x ; Load dispatch address low byte
8E87 PHA ; Push low byte for RTS dispatch
8E88 LDX fs_options ; Load FS options pointer
8E8A .dispatch_rts←3← 8E5C BEQ← 8E6A BCS← 8E77 BCS
RTS ; Dispatch via RTS

Service &24: dynamic workspace claim (1 page)

Two-byte handler reached via svc_dispatch slot &13. DEY decrements the caller's first-available-page count by 1 to claim a single workspace page; RTS returns to the dispatcher.

8E8B .noop_dey_rts
DEY ; Claim 1 page (DEY = decrement Y by 1)
8E8C RTS ; Return

Service &25: FS name + info reply

Reached via svc_dispatch slot &14. Copies the 11-byte template at fs_info_template into the caller's workspace at (os_text_ptr),Y. The loop counts X down from 10 to 0 reading from template[X], while Y increments from the caller's value, so the destination ends up holding the template byte-reversed ('NET /' + length-byte). Returns via the shared RTS at fs_template_done.

8E8D .copy_template_to_zp
LDX #&0a ; X = 10 (top of 11-byte template)
8E8F .loop_copy_return_template←1← 8E96 BPL
LDA fs_info_template,x ; Load template byte X from &8E99+X
8E92 STA (os_text_ptr),y ; Store at (&F2),Y
8E94 INY ; Advance destination cursor
8E95 DEX ; Step to previous template byte
8E96 BPL loop_copy_return_template ; Loop until X has wrapped past 0
8E98 .fs_template_done←1← 8EA7 BVC
RTS ; Return

FS-name reply template (11 bytes, byte-reversed)

Source data for the byte-reverse copy in copy_template_to_zp. When stored at (os_text_ptr),Y in reverse order the destination reads "NET" + 6 spaces + "/" + length-byte 5, which is the FS name the ROM reports for service &25 (FS name + info reply).

8E99 .fs_info_template←1Used as index base by← 8E8F LDA
EQUB &05
8E9A EQUS "/ TEN" ; 11-byte template (length 5 in [0], then ' TEN'); copied to (&F2),Y by copy_template_to_zp

Service &26: close all files (FILEV via Y=0)

Reached via svc_dispatch slot &15. Tests bit 6 of fs_flags (NFS-active flag). If clear, branches back to the shared RTS at fs_template_done without acting. Otherwise calls ensure_fs_selected to make NFS the current filing system, sets A=Y=0 and tail-calls findv_handler — the FILEV Y=0 path closes all open NFS channels.

8EA4 .svc_26_close_all_files
BIT fs_flags ; Test bit 6 of fs_flags (NFS currently selected?)
8EA7 BVC fs_template_done ; Clear: return without acting
8EA9 JSR ensure_fs_selected ; Ensure NFS is the selected FS
8EAC LDA #0 ; A=0
8EAE TAY ; Y=0 -- FILEV 'close all files' sub-call
8EAF JMP findv_handler ; Tail-call findv_handler (= FILEV)

Read CMOS RAM byte 0

Sets X=0 and falls through to osbyte_a1, which issues OSBYTE &A1 to read CMOS RAM byte 0 – the file-system / language byte holding the default boot mode and FS selection.

Single caller (&8FBD, inside nfs_init_body's CMOS-read sequence).

On ExitYCMOS byte 0 (returned by OSBYTE &A1)
8EB2 .read_cmos_byte_0←1← 8FD9 JSR
LDX #0 ; X=0: CMOS RAM index 0 (station ID)
fall through ↓

OSBYTE &A1 (read Master CMOS RAM byte)

Loads A=&A1 and tail-jumps to OSBYTE – reads the Master 128 CMOS RAM byte indexed by X. Two callers: format_filename_field and flip_set_station_boot.

Dual-use trick: the 5 bytes A9 A1 4C F4 FF also serve as the leading slot of the vector-dispatch table that write_vector_entry reads via LDA osbyte_a1,Y – a deliberate overlap so the routine's body doubles as table data.

On EntryXCMOS RAM byte index
On ExitYCMOS byte read
Xpreserved
8EB4 .osbyte_a1←19Referenced by← 8D30 JSR← 8F2D JSR← 8F7E JSR← 8F86 JSR← 8F8E JSR← 8F98 JSR← 8FBA JSR← 95F8 JSR← 95FF JSR← 961C JSR← 9626 JSR← 9645 JSR← 966B JSR← 9678 JSR← A10B JSR← A723 JSR← B709 JSRUsed as index base by← 9058 LDA← 905E LDA
LDA #osbyte_read_cmos_ram ; A=&A1: OSBYTE &A1 = read CMOS RAM
8EB6 JMP osbyte ; Read CMOS RAM/EEPROM byte (Master and B+) or Tube ID (Compact)

Printer-server name template (8 bytes)

Eight bytes ("PRINT " then &01 &00) read by copy_ps_data via the indexed-base trick LDA ps_template_base+X with X=&F8..&FF. The base label ps_template_base resolves to ps_template_data - &F8 so the indexed access lands on the bytes here. Default contents installed into the Printer-Server name slot during ANFS initialisation.

; Printer server template (8 bytes)
; Default printer server configuration data, read
; indirectly by copy_ps_data via LDA ps_template_base,X
; with X=&F8..&FF (reaching ps_template_base+&F8 =
; &8EB9). Contains "PRINT " (6 bytes) as the default
; printer server name, followed by &01 and &00 as
; default status bytes. Absent from NFS versions;
; unique to ANFS.
8EB9 .ps_template_data
EQUS "PRINT " ; PS template: default name "PRINT "
8EBF EQUB &01, &00

FS vector dispatch and handler addresses (34 bytes)

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

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

8EC1 .fs_vector_table←1Used as index base by← 8BA0 LDA
EQUW ev_filev ; FILEV dispatch
8EC3 EQUW ev_argsv ; ARGSV dispatch
8EC5 EQUW ev_bgetv ; BGETV dispatch
8EC7 EQUW ev_bputv ; BPUTV dispatch
8EC9 EQUW ev_gbpbv ; GBPBV dispatch
8ECB EQUW ev_findv ; FINDV dispatch
8ECD EQUW ev_fscv ; FSCV dispatch
8ECF EQUW filev_handler ; FILEV handler
8ED1 EQUB &4A ; (ROM bank — not read)
8ED2 EQUW argsv_handler ; ARGSV handler
8ED4 EQUB &44 ; (ROM bank — not read)
8ED5 EQUW bgetv_handler ; BGETV handler
8ED7 EQUB &57 ; (ROM bank — not read)
8ED8 EQUW bputv_handler ; BPUTV handler
8EDA EQUB &42 ; (ROM bank — not read)
8EDB EQUW gbpbv_handler ; GBPBV handler
8EDD EQUB &41 ; (ROM bank — not read)
8EDE EQUW findv_handler ; FINDV handler
8EE0 EQUB &52 ; (ROM bank — not read)
8EE1 EQUW fscv_handler ; FSCV handler

OSBYTE wrapper with X=0, Y=&FF

Sets X=0 and falls through to osbyte_yff to also set Y=&FF. Provides a single call to execute OSBYTE with A as the function code. Used by adlc_init, init_adlc_and_vectors, and Econet OSBYTE handling.

On EntryAOSBYTE function code
On ExitX0
Y&FF
8EE3 .osbyte_x0←3← 805F JSR← 904A JSR← 99FD JSR
LDX #0 ; X=0 then fall through into osbyte_yff
fall through ↓

OSBYTE wrapper with Y=&FF

Sets Y=&FF and JMPs to the MOS OSBYTE entry point. X must already be set by the caller. The osbyte_x0 entry point falls through to here after setting X=0.

On EntryAOSBYTE function code
XOSBYTE X parameter
On ExitY&FF
8EE5 .osbyte_yff←1← 8069 JSR
LDY #&ff ; Y=&FF: 'read' parameter for OSBYTE
8EE7 .jmp_osbyte←1← 8EF0 BEQ
JMP osbyte ; Tail-call OSBYTE
8EEA EQUB &1E, &AD

OSBYTE wrapper with X=0, Y=0

Sets X=0 and Y=0 then branches to jmp_osbyte. Called from the Econet OSBYTE dispatch chain to handle OSBYTEs that require both X and Y cleared. The unconditional BEQ (after LDY #0 sets Z) reaches the JMP osbyte instruction.

On EntryAOSBYTE number
On ExitX0
Y0
8EEC .osbyte_x0_y0←1← 9A10 JSR
LDX #0 ; X=0: clear OSBYTE X arg
8EEE LDY #0 ; Y=0
8EF0 BEQ jmp_osbyte ; Zero: tail-call OSBYTE
fall through ↓

Service 7: unrecognised OSBYTE

Maps Econet OSBYTE codes &32-&35 to dispatch indices 0-3 by subtracting &31 (with carry from a preceding SBC). Returns unclaimed if the OSBYTE number is outside this range. For valid codes, claims the service (sets svc_state to 0) and JMPs to svc_dispatch with Y=&21 to reach the Econet OSBYTE handler table.

On EntryAOSBYTE number (from osbyte_a_copy at &EF)
8EF2 .svc_7_osbyte
LDA osbyte_a_copy ; Get original OSBYTE A parameter
8EF4 SBC #&31 ; Subtract &31 (map &32-&35 to 1-4)
8EF6 CMP #4 ; In range 0-3?
8EF8 BCS rts_raise_y_to_c8 ; No: not ours, return unclaimed
8EFA TAX ; Transfer to X as dispatch index
8EFB STZ svc_state ; Clear svc_state
8EFD TYA ; Transfer Y to A (OSBYTE Y param)
8EFE LDY #&2f ; Y=&2F: OSBYTE dispatch offset
8F00 JMP svc_dispatch ; Dispatch to OSBYTE handler via table

Service &21 handler: claim static hidden-RAM workspace

Four-instruction stub: CPY #&C8 / BCS return / LDY #&C8 / RTS. Reached when MOS issues service call &21 ("Offer Static Workspace in Hidden RAM") to all sideways ROMs at reset. Per the Advanced Reference Manual for the BBC Master, hidden-RAM static workspace runs from page &C0 up to page &DB; each filing-system ROM that wants a slice raises Y to its required base page. ANFS demands its static workspace base at page &C8, so it raises Y to &C8 if a previous ROM hasn't already.

On EntryYcurrent bottom of static workspace claim (some page in &C0..&DB)
On ExitY>= &C8 (ANFS static workspace base)
8F03 .raise_y_to_c8
CPY #&c8 ; Y already >= &C8?
8F05 BCS rts_raise_y_to_c8 ; Yes: return Y unchanged
8F07 LDY #&c8 ; No: raise Y to &C8
8F09 .rts_raise_y_to_c8←2← 8EF8 BCS← 8F05 BCS
RTS ; Return

Record workspace page count (capped at &D3)

Stores the workspace allocation from service 1 into offset &0B of the receive control block, capping the value at &D3 to prevent overflow into adjacent workspace areas. Called by svc_2_priv_ws after issuing the absolute workspace claim service call.

On EntryYworkspace page count from service 1
8F0A .store_ws_page_count
TYA ; Transfer Y to A
8F0B PHA ; Push for save
8F0C CMP #&d3 ; Y >= &D3?
8F0E BCC done_cap_ws_count ; No: use Y as-is
8F10 LDA #&d3 ; Cap at &D3
8F12 .done_cap_ws_count←1← 8F0E BCC
LDY #&0b ; Offset &0B in receive block
8F14 STA (net_rx_ptr),y ; Store workspace page count
8F16 PLY ; Pop -- save Y temporarily
8F17 RTS ; Return -- ws_page count saved
8F18 .set_rom_ws_page←1← 8F4F BCS
TYA ; Caller's page (in Y) into A
8F19 LDY romsel_copy ; Y = current ROM slot from romsel_copy
8F1B PHA ; Push restored value
8F1C AND #&7f ; Mask bit 7 (workspace flag)
8F1E STA rom_ws_pages,y ; Publish page into rom_ws_pages[slot] (bit 7 cleared = workspace claimed)
8F21 LDY fdc_1770_data ; Discarded read of 1770 data reg (&FE2B)
8F24 LDY fdc_1770_command_or_status ; Discarded read of 1770 status reg (&FE28)
8F27 PLY ; Pop saved Y
8F28 INY ; Increment for next page
8F29 RTS ; Return

Service-2 page-allocation prologue

Reads CMOS byte &11 to test bit 2 of the saved Econet status; either advances the caller's first-available-page (Y) by 2 and uses it, or forces page &0B as a fallback. Sets net_rx_ptr_hi / nfs_workspace_hi to the chosen page pair, clears the corresponding lo bytes, and calls get_ws_page. If the resulting page is >= &DC, branches to the helper at &8EFE which publishes the page into rom_ws_pages[romsel_copy] with bit 7 masked off.

This routine handles only the workspace-page allocation half of service 2. The bring-up remainder (station ID, FS workspace zero, cmd_net_fs, init_adlc_and_vectors) lives at nfs_init_body and is dispatched separately – see the comment block above.

On EntryYfirst available private workspace page
8F2A .svc_2_priv_ws
PHY ; Save Y on stack (caller's claim)
8F2B LDX #&11 ; X=&11: CMOS RAM byte index
8F2D JSR osbyte_a1 ; Read CMOS &11 via osbyte_a1
8F30 TYA ; A = CMOS &11 value
8F31 AND #4 ; Mask bit 2 (workspace-size flag)
8F33 BNE private_ws_set_bit ; Bit 2 set: keep caller's Y, advance by 2
8F35 LDA #&0b ; Bit 2 clear: A=&0B (use 11-page minimum)
8F37 BRA commit_workspace_pages ; BRA to common tail
8F39 .private_ws_set_bit←1← 8F33 BNE
PLY ; Bit-2-set path: restore Y
8F3A TYA ; TYA / INY / INY -- raise Y by 2 pages
8F3B INY ; Y += 1
8F3C INY ; Y += 1 again (total +2)
8F3D PHY ; Push raised Y
8F3E .commit_workspace_pages←1← 8F37 BRA
STA net_rx_ptr_hi ; Store final page count high to net_rx_ptr_hi
8F40 INC ; Increment for nfs_workspace_hi
8F41 STA nfs_workspace_hi ; Store workspace high page
8F43 LDA #0 ; A=0: clear-byte for the lo halves below
8F45 STA net_rx_ptr ; Clear net_rx_ptr_lo (page-aligned)
8F47 STA nfs_workspace ; Clear nfs_workspace_lo (page-aligned)
8F49 JSR get_ws_page ; Compute workspace start page via get_ws_page
8F4C CPY #&dc ; Y >= &DC?
8F4E PLY ; Restore Y from stack
8F4F BCS set_rom_ws_page ; Yes: jump to set_rom_ws_page (error path)
8F51 RTS ; Return

ANFS initialisation body

Reached only via PHA/PHA/RTS dispatch (table index 22 in the svc_dispatch table at &8A23 / &8A22). Carries out the bring-up sequence after page allocation:

  • Clears ws_page / tx_complete_flag and the receive-block remote-op flag.
  • On warm reset (last_break_type non-zero) and fs_flags bit 4 set, calls setup_ws_ptr and zeroes the FS workspace page in a 256-byte loop.
  • Calls copy_ps_data_y1c to install the printer- server template.
  • Reads CMOS bytes &01..&04 via osbyte_a1, storing each into the workspace identity block at nfs_workspace+{0..3}.
  • Reads CMOS byte &11 (Econet station): if zero, prints Station number in CMOS RAM invalid. Using 1 instead! and defaults to station 1.
  • Stores station ID into the receive block.
  • Calls cmd_net_fs to select ANFS as the active filing system, then init_adlc_and_vectors to install NETV / FSCV / etc., handle_spool_ctrl_byte and init_bridge_poll for protection setup.

Returns via RTS at &9042.

Reached via Master 128 service call &27 (= 39 decimal), documented in the Advanced Reference Manual for the BBC Master:

Reset has occurred. Call made after hard reset. Mainly for Econet Filing system so that it can claim NMIs. This call is now required since the MOS no longer offers workspace on a soft BREAK. A Sideways ROM should therefore re-initialise itself.

The full set of Master 128 service calls ANFS handles, dispatched via the CMP/SBC normalisation chain in service_handler:

svc idx handler purpose
&00..&0C 1..13 (svc-1..12 handlers) service-1 .. service-12
&12 14 svc_18_fs_select FS select
&18 15 match_on_suffix Interactive HELP
&21 16 raise_y_to_c8 static ws claim
&22 17 set_rom_ws_page dynamic ws offer
&23 18 store_ws_page_count top-of-static-ws
&24 19 noop_dey_rts dynamic ws claim (1 pg)
&25 20 copy_template_to_zp FS name + info reply
&26 21 svc_26_close_all_files close all files
&27 22 nfs_init_body (this) reset re-init
&28 23 print_fs_ps_help *CONFIGURE option
&29 24 svc_29_status *STATUS option

Everything else (svc &0D..&11, &13..&17, &19..&20, &2A+) falls through to dispatch_svc_state_check with A := 0 and dispatches to idx 1 = dispatch_rts (no-op) – deliberately ignoring svc &15 (100 Hz poll), svc &2A (language ROM startup), etc.

8F52 .nfs_init_body
LDA #0 ; A=0: clear-byte for the next four stores
8F54 STA ws_page ; Clear ws_page (workspace page count)
8F56 STA tx_complete_flag ; Clear tx_complete_flag
8F59 LDY #0 ; Y=0: receive-block offset 0 (remote-op flag)
8F5B STA (net_rx_ptr),y ; Clear remote-op flag at (net_rx_ptr)+0
8F5D LDA last_break_type ; Read l028D (current ROM number)
8F60 BNE nfs_init_check_fs_flags ; Non-zero (re-init): take nfs_init_check_fs_flags path
8F62 LDA #&10 ; A=&10: fs_flags bit 4 mask (checks 'workspace already set up')
8F64 BIT fs_flags ; Test the FS flags
8F67 BEQ alloc_post_restore_check ; Zero: first ROM init, skip FS setup
8F69 .nfs_init_check_fs_flags←1← 8F60 BNE
JSR setup_ws_ptr ; Set up workspace pointers
8F6C STA fs_flags ; Clear FS flags
8F6F LDY #&21 ; Y=&21: workspace flag offset
8F71 STA (nfs_workspace),y ; Clear the workspace flag at &21
8F73 TAY ; A=0, transfer to Y
8F74 .loop_zero_workspace←1← 8F77 BNE
STA (fs_ws_ptr),y ; Clear byte in FS workspace
8F76 INY ; Next workspace byte
8F77 BNE loop_zero_workspace ; Loop until full page (256 bytes) cleared
8F79 JSR copy_ps_data_y1c ; Copy initial PS template (1C bytes) into ws
8F7C LDX #1 ; X=1: CMOS &01 = port number
8F7E JSR osbyte_a1 ; Read CMOS &01
8F81 STY hazel_fs_station ; Store at hazel_fs_station (workspace+0)
8F84 LDX #2 ; X=2: CMOS &02 = network number
8F86 JSR osbyte_a1 ; Read CMOS &02
8F89 STY hazel_fs_network ; Store at hazel_fs_network
8F8C LDX #3 ; X=3: CMOS &03 = FS station
8F8E JSR osbyte_a1 ; Read CMOS &03
8F91 TYA ; A = FS station
8F92 LDY #2 ; Y=2: nfs_workspace offset for FS station
8F94 STA (nfs_workspace),y ; Store FS station at (nfs_workspace)+2
8F96 LDX #4 ; X=4: CMOS &04 = FS network
8F98 JSR osbyte_a1 ; Read CMOS &04 (FS network)
8F9B TYA ; A = FS network
8F9C LDY #3 ; Y=3: nfs_workspace offset for FS network
8F9E STA (nfs_workspace),y ; Store at NFS workspace offset 2
8FA0 LDX #3 ; X=3: init data byte count
8FA2 .loop_copy_init_data←1← 8FA9 BNE
LDA cdir_size_done,x ; Load initialisation data byte
8FA5 STA fs_flags,x ; Store in workspace
8FA8 DEX ; Decrement counter
8FA9 BNE loop_copy_init_data ; More bytes: loop
8FAB STX hazel_fs_messages_flag ; Clear workspace flag
8FAE STX hazel_fs_flags ; Clear workspace byte
8FB1 JSR reset_spool_buf_state ; Initialise ADLC protection table
8FB4 DEX ; X=&FF (underflow from X=0)
8FB5 STX bridge_status ; Initialise workspace flag to &FF
8FB8 LDX #&11 ; X=&11: CMOS &11 (ANFS settings)
8FBA JSR osbyte_a1 ; Read CMOS &11
8FBD TYA ; A = settings byte
8FBE AND #&40 ; Mask bit 6 (CMOS protection-state flag)
8FC0 BEQ init_copy_skip_cmos ; Bit clear: skip the &FF substitution
8FC2 LDA #&ff ; A=&FF -- enable protection
8FC4 .init_copy_skip_cmos←1← 8FC0 BEQ
JSR set_ws_pair_0d68_0d69 ; Set prot_status/prot_status_save pair
8FC7 .loop_alloc_handles←1← 8FD4 BNE
LDA ws_page ; Get current workspace page
8FC9 JSR byte_to_2bit_index ; Allocate FS handle page
8FCC BCS done_alloc_handles ; Allocation failed: finish init
8FCE LDA #&3f ; A=&3F: default handle permissions
8FD0 STA (nfs_workspace),y ; Store handle permissions
8FD2 INC ws_page ; Advance to next page
8FD4 BNE loop_alloc_handles ; Continue allocating: loop
8FD6 .done_alloc_handles←1← 8FCC BCS
JSR restore_fs_context ; Restore FS context from saved state
8FD9 .alloc_post_restore_check←1← 8F67 BEQ
JSR read_cmos_byte_0 ; Read CMOS &00 (= station ID byte)
8FDC TYA ; Y (CMOS value) into A
8FDD BNE alloc_common_entry ; Non-zero: station ID valid -> alloc_common_entry
8FDF .alloc_error_overflow←1← 9009 BEQ
JSR print_inline ; Print 'Station number in CMOS RAM invalid...' warning
8FE2 EQUS "Badly configured station number", &07, &0D, &0D
9004 LDA #1 ; A=1: default station ID
9006 BRA alloc_store_station_id ; BRA to alloc_store_station_id with default
9008 .alloc_common_entry←1← 8FDD BNE
INY ; Check next byte (CMOS station ID hi?)
9009 BEQ alloc_error_overflow ; INY wrapped past 0 (station=&FF then INY=&00): report 'CMOS RAM invalid' and default to 1
900B BRA alloc_store_station_id ; BRA to alloc_store_station_id (always)
900D .alloc_store_station_id←2← 9006 BRA← 900B BRA
LDY #1 ; Y=1: net_rx_ptr offset for station-ID byte
900F STA (net_rx_ptr),y ; Store station ID into (net_rx_ptr)+1
9011 LDX #&40 ; X=&40: econet_flags init value
9013 STX econet_flags ; Initialise econet_flags
9016 JSR cmd_net_fs ; Call cmd_net_fs to select NFS
9019 BEQ complete_nfs_init ; Z: selection succeeded
901B LDA #&10 ; A=&10: bit 4 marker for fs_flags
901D ORA fs_flags ; Merge into the FS flags
9020 STA fs_flags ; Store updated fs_flags
9023 .complete_nfs_init←1← 9019 BEQ
JSR init_adlc_and_vectors ; Initialise ADLC and FILEV/ARGSV/...vectors
9026 LDA #3 ; A=3: spool-ctrl byte 'init'
9028 JSR handle_spool_ctrl_byte ; Initialise *SPOOL handle in workspace
902B JSR init_bridge_poll ; Send a bridge-discovery packet and poll
902E PHA ; Save current bridge byte
902F EOR hazel_fs_network ; With stored hazel_fs_network (network number)
9032 BNE verify_copy_station_id ; Different: take verify_copy_station_id path
9034 STA hazel_fs_network ; Same: store as new hazel_fs_network
9037 LDY #3 ; Y=3: net_rx_ptr offset 3
9039 STA (net_rx_ptr),y ; Store at (net_rx_ptr)+3
903B .verify_copy_station_id←1← 9032 BNE
PLA ; Restore saved byte
903C LDY #3 ; Y=3: workspace offset
903E EOR (nfs_workspace),y ; Toggle the workspace flag at &21
9040 BNE return_2 ; Mismatch: skip store
9042 STA (nfs_workspace),y ; Match: store at (nfs_workspace)+3
9044 .return_2←1← 9040 BNE
RTS ; Return

Initialise ADLC and install extended vectors

Reads the ROM pointer table via OSBYTE &A8, writes vector addresses and ROM ID into the extended vector table for NETV and one additional vector, then restores any previous FS context via restore_fs_context. Falls through into write_vector_entry.

On ExitA, X, Yclobbered (falls through into write_vector_entry)
9045 .init_adlc_and_vectors←2← 8BA9 JSR← 9023 JSR
JSR adlc_init ; Initialise ADLC hardware
9048 LDA #&a8 ; OSBYTE &A8: read ROM pointer table
904A JSR osbyte_x0 ; Read ROM pointer table address
904D STX fs_error_ptr ; Store table pointer low
904F STY fs_crflag ; Store table pointer high
9051 LDY #&36 ; Y=&36: NETV vector offset
9053 STY netv ; Set NETV address
9056 LDX #1 ; X=1: one more vector pair to set
fall through ↓

Install extended-vector table entries

Copies vector addresses from the dispatch table at svc_dispatch_lo_offset+Y into the MOS extended-vector table pointed to by fs_error_ptr. For each entry, writes address low, high, then the current ROM ID from romsel_copy (&F4). Loops X times.

After the loop, stores &FF at bridge_status as an installed flag, calls deselect_fs_if_active and get_ws_page to restore FS state.

On EntryXnumber of vectors to install
Ystarting offset in extended vector table
On ExitYworkspace page number + 1
9058 .write_vector_entry←2← 8BB0 JSR← 906A BNE
LDA osbyte_a1,y ; Load vector address low byte
905B STA (fs_error_ptr),y ; Store into extended vector table
905D INY ; Advance to high byte
905E LDA osbyte_a1,y ; Load vector address high byte
9061 STA (fs_error_ptr),y ; Store into extended vector table
9063 INY ; Advance to ROM ID byte
9064 LDA romsel_copy ; Load current ROM slot number
9066 STA (fs_error_ptr),y ; Store ROM ID in extended vector
9068 INY ; Advance to next vector entry
9069 DEX ; Decrement vector counter
906A BNE write_vector_entry ; More vectors to set: loop
906C RTS ; Return

Restore FS context from HAZEL into RX block

Copies 8 bytes (offsets 2..9) from the HAZEL FS state block into the receive control block at (net_rx_ptr)+Y. The source uses the hazel_minus_2 indexing-base trick: LDA hazel_minus_2,Y with Y running 9 down to 2 lands at &C007..&C000 (the hazel_fs_station block -- station, network, saved station, CSD/lib slots, FS flags, etc.). Restores those bytes into the RX control block when the caller needs to re-publish the FS context (e.g. after a flip-set boot).

Called by svc_2_priv_ws during init, deselect_fs_if_active during FS teardown, and flip_set_station_boot.

On ExitA, Yclobbered (loop counter / data byte)
906D .restore_fs_context←3← 8FD6 JSR← 9084 JSR← A6E8 JMP
LDY #9 ; Y=9: end of FS context block
906F .loop_restore_ctx←1← 9077 BNE
LDA hazel_minus_2,y ; Load FS context byte
9072 STA (net_rx_ptr),y ; Store into receive block
9074 DEY ; Decrement index
9075 CPY #1 ; Reached offset 1?
9077 BNE loop_restore_ctx ; No: continue copying
9079 RTS ; Return

Deselect filing system and save workspace

If the filing system is currently selected (bit 7 of fs_flags set):

  1. Closes all open FCBs.
  2. Closes *SPOOL/*EXEC files via OSBYTE &77.
  3. Saves the FS workspace to page &10 shadow with checksum.
  4. Clears the selected flag.
907A .fscv_6_shutdown
BIT fs_flags ; FS currently selected?
907D BPL rts_fs_shutdown ; No (bit 7 clear): return
907F LDY #0 ; Y=0
9081 JSR process_all_fcbs ; Close all FCBs (process_all_fcbs)
9084 JSR restore_fs_context ; Restore FS context to receive block
9087 LDY #&76 ; Y=&76: checksum range end
9089 LDA #0 ; A=0: checksum accumulator
908B CLC ; Clear carry for addition
908C .loop_checksum_byte←1← 9090 BPL
ADC hazel_fcb_addr_lo,y ; Add byte from the HAZEL &C2 FCB shadow
908F DEY ; Decrement index
9090 BPL loop_checksum_byte ; Loop until all bytes summed
9092 LDY #&77 ; Y=&77: checksum storage offset
9094 BPL store_ws_byte ; Positive: store the workspace byte
9096 .loop_copy_to_ws←1← 909C BPL
LDA hazel_fcb_addr_lo,y ; Load byte from the HAZEL &C2 FCB shadow
9099 .store_ws_byte←1← 9094 BPL
STA (fs_ws_ptr),y ; Copy to FS workspace
909B DEY ; Decrement index
909C BPL loop_copy_to_ws ; Loop until all bytes copied
909E LDA fs_flags ; Load FS flags
90A1 AND #&7f ; Clear bit 7 (FS no longer selected)
90A3 STA fs_flags ; Store updated flags
90A6 .rts_fs_shutdown←1← 907D BPL
RTS ; Return

Verify workspace checksum integrity

Sums bytes 0..&76 of the workspace page via the zero-page pointer at &CC/&CD and compares with the stored value at offset &77. On mismatch, raises a 'net sum' error (&AA) via error_net_checksum.

The checksummed page holds open-file information (preserved when ANFS is not the current filing system) and the current printer type. Can only be reset by a control-BREAK.

Preserves A, Y, and processor flags using PHP/PHA. Called by 5 sites across format_filename_field, adjust_fsopts_4bytes, and start_wipe_pass before workspace access.

On ExitApreserved (PHA/PLA)
Ypreserved
P (FLAGS)preserved (PHP/PLP)
90A7 .verify_ws_checksum←5← 9EAB JSR← A057 JSR← A133 JSR← A174 JSR← B9CC JSR
PHP ; Save processor status
90A8 PHA ; Save A
90A9 PHY ; Save Y
90AA LDY #&76 ; Y=&76: checksum range end
90AC LDA #0 ; A=0: checksum accumulator
90AE CLC ; Clear carry for addition
90AF .loop_sum_ws←1← 90B2 BPL
ADC (fs_ws_ptr),y ; Add byte from FS workspace
90B1 DEY ; Decrement index
90B2 BPL loop_sum_ws ; Loop until all bytes summed
90B4 LDY #&77 ; Y=&77: checksum storage offset
90B6 CMP (fs_ws_ptr),y ; Compare with stored checksum
90B8 BNE error_net_checksum ; Mismatch: raise checksum error
90BA PLY ; Restore Y
90BB PLA ; Restore A
90BC PLP ; Restore processor status
90BD RTS ; Return (checksum valid)

Raise 'net checksum' BRK error

Loads error code &AA and tail-calls error_bad_inline with the inline string 'net checksum'. Reached when ensure_fs_selected (auto-select path) cannot bring ANFS up, or when verify_ws_checksum detects that the saved workspace checksum at offset &77 doesn't match the live sum – only resettable by a control-BREAK. Never returns.

90BE .error_net_checksum←2← 8B7F JMP← 90B8 BNE
LDA #&aa ; Error number &AA
90C0 JSR error_bad_inline ; Raise 'net checksum' error
90C3 EQUS "net checksum", &00
fall through ↓

Print Econet station number and clock status

Uses print_inline to output 'Econet Station ', then reads the station ID from offset 1 of the receive control block and prints it as a decimal number via print_num_no_leading. Tests ADLC status register 2 (econet_control23_or_status2) to detect the Econet clock; if absent, appends ' No Clock' via a second inline string. Finishes with OSNEWL.

Called by print_version_header and svc_3_autoboot.

On ExitA, X, Yclobbered (print_inline + print_num_no_leading + OSNEWL)
90D0 .print_station_id←2← 8CD1 JMP← 8D07 JSR
JSR print_inline ; Print 'Station ' inline string
90D3 EQUS "Econet Station " ; Print 'Econet Station ' via inline
90E2 LDY #1 ; Y=1: PB station-byte offset
90E4 LDA (net_rx_ptr),y ; Read RX[1] = station number
90E6 JSR print_num_no_leading ; Print as decimal (no leading zeros)
90E9 LDA #' ' ; Space character
90EB BIT econet_control23_or_status2 ; Check ADLC status register 2
90EE BEQ done_print_newline ; Clock present: skip warning
90F0 JSR print_inline ; Print ' No Clock' via inline
90F3 EQUS " No Clock"
90FC NOP ; String terminator
90FD .done_print_newline←1← 90EE BEQ
JSR osnewl ; Print newline
9100 RTS ; Return

*HELP / *SYNTAX argument strings (8 messages)

Eight zero-terminated argument-syntax strings used by the *HELP text builder. Each string describes the argument shape of a particular command group; their offsets within this table are stored in cmd_syntax_table, keyed by command index. Read by do_print_no_spool when no command argument was supplied.

; *HELP command syntax strings
; 13 null-terminated syntax help strings displayed
; by *HELP after each command name. Multi-line
; entries use &0D as a line break. Indexed by
; cmd_syntax_table via the low 5 bits of each
; command's syntax descriptor byte.
9101 .cmd_syntax_strings←1Used as index base by← 8C2E LDA
.syn_opt_dir←1Used as index base by← 8C2E LDA
EQUS "(<dir>)" ; Syn 1: *Dir, *LCat, *LEx, *Wipe
9108 EQUB &00
9109 .syn_iam
EQUS "(<stn. id.>) <user id.> " ; Syn 2: *I Am (login)
9121 EQUB &0D ; Line break
9122 EQUS "((:<CR>)<password>)" ; syntax help for *Pass / *I am
9135 EQUB &00
9136 .syn_object
EQUS "<object>" ; Syn 3: *Delete, *FS, *Remove
913E EQUB &00
913F EQUS "<filename> (<off"
914F EQUS "set" ; Store as string pointer low
9152 EQUB &3E, &20, &0D ; Store as string pointer high
9155 EQUS "(<address>))" ; Syn 4 continued: address clause
9161 EQUB &00 ; Null terminator
9162 .syn_dir
EQUS "<dir>" ; Syn 5: *Lib
9167 EQUB &00
9168 EQUS "<dir> (<number>)"
9178 EQUB &00
9179 .syn_password
EQUS "(:<CR>) <password> " ; Syn 7: *Pass
918C EQUB &0D
918D EQUS "<new password>" ; Syn 7 continued: new password
919B EQUB &00
919C EQUS "(<ps type>)|(<stn. id.>)" ; syntax help for *PS / *Pollps
91B4 EQUB &00
91B5 .syn_access
EQUS "<object> (L)(W)(R)(/(W)(R))" ; Syn 9: *Access
91D0 EQUB &00 ; Null terminator
91D1 .syn_rename
EQUS "<filename> <new filename>" ; Syn 10: *Rename
91EA EQUB &00 ; Null terminator
91EB .syn_opt_stn
EQUS "(<ps type>)" ; Syn 11: (station id. argument)
91F6 EQUB &00

Argument-syntax offset table (12 entries)

Twelve byte offsets indexing into syn_opt_dir. Each entry is computed as <syn_X> - syn_opt_dir - 1 so the print loop can INY before LDA and still land on the first byte of the chosen string. The byte at &91F9 immediately after the table is the entry point of print_no_spool.

; Command syntax string offset table
; 13 offsets into syn_opt_dir (&9022).
; Indexed by the low 5 bits of each command table
; syntax descriptor byte. Index &0E is handled
; separately as a shared-commands list. The print
; loop at &8BD5 does INY before LDA, so each offset
; points to the byte before the first character.
91F7 .cmd_syntax_table←1Used as index base by← 8C29 LDA
EQUB syn_iam - syn_opt_dir - 2 ; Idx 0: 'opt_dir' (offset -2 variant for *Dir's INY-twice walker)
91F8 EQUB &FF ; Idx 1: &FF = no syntax string for this index
91F9 EQUB syn_iam - syn_opt_dir - 1 ; Idx 2: "(<stn.id.>) <user id.>..."
91FA EQUB syn_object - syn_opt_dir - 1 ; Idx 3: "<object>"
91FB EQUB &3D ; Idx 4: "<filename> (<offset>...)"
91FC EQUB &60 ; Idx 5: '<dir>' (offset 0x60 = syn_dir)
91FD EQUB &66 ; Idx 6: continued <dir> string region
91FE EQUB &77 ; Idx 7: "(:<CR>) <password>..."
91FF EQUB &9A ; Idx 8: "(<stn.id.>|<ps type>)"
9200 EQUB &B3 ; Syntax-table offset entry (into syn_opt_dir)
9201 EQUB &CF
9202 EQUB &A6
9203 EQUB &E9

Print CR via OSASCI, bypassing any open *SPOOL file

Loads A=&0D and falls into print_char_no_spool. The underlying mechanism temporarily writes 0 to the *SPOOL file handle (OSBYTE &C7 with X=0, Y=0) so the printed CR is not captured by spool, then restores the previous handle on exit.

Called from service_handler (&8AB6) after the 'Bad ROM <slot>' message, and from two other diagnostic sites (&8E2A, &9D3E).

On ExitA, X, Y, Ppreserved (print_char_no_spool brackets the call with full register save/restore via PHA/PHP/PLP/PLA)
9204 .print_newline_no_spool←6← 8AB6 JSR← 8E2A JSR← 9D3E JSR← B215 JSR← B25C JMP← B7E2 JSR
LDA #&0d ; A=&0D (CR) for OSASCI translation; fall through
fall through ↓

Print A via OSASCI, bypassing any open *SPOOL file

Pushes the caller's flags, then forces V=1 via the BIT &9769 / BVS trick (&9769 is a constant &FF byte in ROM). Saves X, Y, A and a copy of the (now V=1) flags.

  1. Calls OSBYTE &C7 with X=0, Y=0 to write 0 to the *SPOOL file handle, returning the previous handle in X.
  2. If the previous handle was in the NFS-issued range &21..&2F, calls OSBYTE &C7 again with X=OLD, Y=0 to restore the spool before the print (so the print is captured); otherwise leaves spool closed for the duration of the print.
  3. PLPs the inner P, then routes to OSASCI (the BIT trick set V=1, so the BVC at &9229 is not taken).
  4. Final OSBYTE &C7 with Y=&FF either no-ops (if spool already restored) or writes OLD back (if it was deferred).
  5. Pulls A, Y, X, P and returns.

Eight inner-ROM callers: &9268, &92AD, &9D30, &9D5A, &B21F, &B2FB, &B321, &B77D.

On EntryAbyte to print as ASCII char (CR is translated by OSASCI)
9206 .print_char_no_spool←11← 926A BRA← 92AF JSR← 9D30 JSR← 9D5C JMP← B1EE JSR← B254 JSR← B32E JSR← B356 JSR← B77D JSR← B7A1 JSR← B7B8 JSR
PHP ; Save caller's flags (V from caller is irrelevant — see &91FC)
9207 BIT always_set_v_byte ; Unconditionally sets V=1 (bit 6 of operand &FF)
920A BVS save_regs_print_no_spool ; V=1 always, branch always taken (skips the CLV path)
fall through ↓

Print A via OSWRCH (raw, no CR translation), bypass *SPOOL

As print_char_no_spool but the inner PHP/CLV at &9201 forces V=0 in the saved flags, so the BVC at &9229 takes the OSWRCH branch instead of OSASCI.

Used when the caller wants to emit a raw byte (e.g. a VDU control code) without CR translation. Sole caller in this ROM is at &8DE8.

On EntryAraw byte to print via OSWRCH
920C .print_byte_no_spool←2← 8E00 JSR← B799 JSR
PHP ; Alt entry: save caller's flags BEFORE forcing V=0
920D CLV ; Force V=0 -> OSWRCH path at &9229 (raw byte)
920E .save_regs_print_no_spool←1← 920A BVS
PHX ; Save X
920F PHY ; Save Y
9210 PHA ; Save A (the byte to print)
9211 PHP ; Save inner P — V here picks OSASCI vs OSWRCH later
9212 LDA #osbyte_spool_file_handle ; OSBYTE 199 (read/write *SPOOL file handle)
9214 LDX #0 ; X=0: handle value to write
9216 LDY #0 ; Y=0: write mode (NEW = (OLD AND 0) EOR X = X = 0)
9218 JSR osbyte ; Closes spool; X returns OLD handle osbyte: spool file handle
921B CPX #' ' ; OLD < ' '? (likely 0 = was already closed)
921D BCC do_print_no_spool ; Yes: leave spool closed for the print
921F CPX #'0' ; OLD >= '0'?
9221 BCS do_print_no_spool ; Yes (>= &30): leave spool closed
9223 JSR osbyte ; OLD in [&20,&2F] (NFS handle range): re-open spool with X=OLD osbyte: spool file handle
9226 LDX #0 ; Clear X for the post-print restore
9228 .do_print_no_spool←2← 921D BCC← 9221 BCS
PLP ; Restore inner P (V=1 OSASCI / V=0 OSWRCH)
9229 PLA ; Pull A (the byte)
922A PHA ; Push it back so the final epilogue PLA still works
922B BVC print_via_oswrch ; V=0 -> OSWRCH (raw); V=1 -> OSASCI (CR translation)
922D JSR osasci ; OSASCI: writes A, translating CR to CR/LF
9230 BRA restore_spool_and_return ; Skip OSWRCH branch
9232 .print_via_oswrch←1← 922B BVC
JSR oswrch ; OSWRCH: writes A as a raw byte
9235 .restore_spool_and_return←1← 9230 BRA
LDA #osbyte_spool_file_handle ; OSBYTE 199 again to restore spool state
9237 LDY #&ff ; Y=&FF (read mode): NEW = OLD EOR X
9239 JSR osbyte ; X=0 -> no change; X=OLD -> writes OLD back osbyte: spool file handle
923C PLA ; Pull A (preserved across the call)
923D PLY ; Pull Y
923E PLX ; Pull X
923F PLP ; Pull caller's original flags
9240 RTS ; Return

Print A as two hexadecimal digits

Saves A on the stack, shifts right four times to isolate the high nybble, calls print_hex_nybble to print it, then restores the full byte and falls through to print_hex_nybble for the low nybble.

Callers: print_5_hex_bytes, cmd_ex, cmd_dump, and print_dump_header.

On EntryAbyte to print
On ExitAoriginal byte value
9241 .print_hex_byte←2← BDC2 JSR← BE6A JSR
PHA ; Save full byte
9242 LSR ; Shift high nybble to low
9243 LSR ; Continue shifting
9244 LSR ; Continue shifting
9245 LSR ; High nybble now in bits 0-3
9246 JSR print_hex_nybble ; Print high nybble as hex digit
9249 PLA ; Restore full byte
fall through ↓

Print low nybble of A as hex digit

Masks A to the low 4 bits, then converts to ASCII:

  1. Adds 7 for letters A..F (via ADC #6 with carry set from the CMP).
  2. ADC #&30 for the final '0'..'F' character.
  3. Outputs via JMP OSASCI.
On EntryAvalue (low nybble used)
924A .print_hex_nybble←1← 9246 JSR
AND #&0f ; Mask to low nybble
924C CMP #&0a ; Digit >= &0A?
924E BCC add_ascii_base ; No: skip letter adjustment
9250 ADC #6 ; Add 7 to get 'A'-'F' (6 + carry)
9252 .add_ascii_base←1← 924E BCC
ADC #'0' ; Add &30 for ASCII '0'-'9' or 'A'-'F'
9254 JMP osasci ; Tail-call OSASCI to print the nybble

Print A as two hex digits, *SPOOL-bypassing

As print_hex_byte but emits each digit via print_char_no_spool (the *SPOOL-bypassing OSASCI wrapper), so the digits don't appear in any active spool capture. Saves A, extracts the high nibble (LSR x4), prints it via print_hex_nybble_no_spool, then restores A and falls through for the low nibble. Sole caller: print_5_hex_bytes at &9D51.

On EntryAbyte to print
On ExitApreserved
9257 .print_hex_byte_no_spool←2← 9D53 JSR← B1DE JSR
PHA ; Save full byte
9258 LSR ; Shift high nybble to low (LSR x4)
9259 LSR ; LSR / LSR / LSR -- shift hi nibble down to lo
925A LSR ; (continued)
925B LSR ; (continued)
925C JSR print_hex_nybble_no_spool ; Print high nybble as hex digit
925F PLA ; Restore full byte; fall through for low nybble
fall through ↓

Print low nybble of A as one hex digit, *SPOOL-bypassing

As print_hex_nybble (&923F) but emits via the print_char_no_spool tail-call instead of OSASCI directly, so the digit is not captured by any active *SPOOL file. Standard AND #&0F / CMP #&0A / +6-or-not / + #&30 mapping for hex digits 0-9 / A-F. Tail-jumps to print_char_no_spool via BRA.

On EntryAvalue (low nybble used)
9260 .print_hex_nybble_no_spool←1← 925C JSR
AND #&0f ; Mask to low nybble
9262 CMP #&0a ; Digit >= &0A?
9264 BCC print_nybble_leading_zero ; No: skip letter adjustment
9266 ADC #6 ; Add 7 to get 'A'-'F' (6 + carry)
9268 .print_nybble_leading_zero←1← 9264 BCC
ADC #'0' ; Add &30 for ASCII '0'-'9' or 'A'-'F'
926A BRA print_char_no_spool ; Tail-jump to *SPOOL-bypassing print
fall through ↓

Print inline string, high-bit terminated

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.

Common terminators:

Byte Opcode Effect
&EA NOP fall-through
&B8 CLV followed by BVC for an unconditional forward branch
On ExitAterminator byte (bit 7 set, also next opcode)
Xcorrupted (by OSASCI)
Y0
926C .print_inline←34← 8AA5 JSR← 8C07 JSR← 8CBA JSR← 8D5B JSR← 8FDF JSR← 90D0 JSR← 90F0 JSR← 95B6 JSR← 95C2 JSR← 95C9 JSR← 95CE JSR← 95DB JSR← 964D JSR← 9659 JSR← 9672 JSR← B499 JSR← B4A5 JSR← B4C0 JSR← B4CA JSR← B4D5 JSR← B5A5 JSR← B613 JSR← B627 JSR← B656 JSR← B663 JSR← B67A JSR← B68C JSR← B6A2 JSR← B6B1 JSR← BDDE JSR← BDF6 JSR← BE02 JSR← BE36 JSR← BE53 JSR
PLA ; Pop return address (low) — points to last byte of JSR
926D STA fs_error_ptr ; Store as fs_error_ptr (return-addr saved)
926F PLA ; Pop return address (high)
9270 STA fs_crflag ; Store as fs_crflag (entry flag)
9272 LDY #0 ; Y=0: start scanning at offset 0
fall through ↓

print_inline pointer-advance step

INC fs_error_ptr (lo); on overflow INC fs_crflag (hi). Single caller (the loop tail at &9284 inside print_inline). Falls through to load_char which reads the next inline-string byte.

9274 .loop_next_char←1← 928F JMP
INC fs_error_ptr ; Advance pointer to next character
9276 BNE load_char ; Z clear: continue with this char
9278 INC fs_crflag ; Z set (CR): increment fs_crflag
927A .load_char←1← 9276 BNE
LDA (fs_error_ptr),y ; Load next byte from inline string
927C BMI resume_caller ; Bit 7 set? Done — this byte is the next opcode
927E LDA fs_error_ptr ; Read fs_error_ptr (saved across OSASCI)
9280 PHA ; Push it
9281 LDA fs_crflag ; Read fs_crflag
9283 PHA ; Push it
9284 LDA (fs_error_ptr),y ; Reload character (pointer may have been clobbered)
9286 JSR osasci ; Print character via OSASCI
9289 PLA ; Pop saved fs_crflag
928A STA fs_crflag ; Restore fs_crflag
928C PLA ; Pop saved fs_error_ptr
928D STA fs_error_ptr ; Restore fs_error_ptr
928F JMP loop_next_char ; Loop back
9292 .resume_caller←1← 927C BMI
JMP (fs_error_ptr) ; Jump to address of high-bit byte (resumes code)

Print inline string, high-bit terminated, *SPOOL-bypassing

As print_inline, but each character is emitted via print_char_no_spool instead of OSASCI directly, so the printed text does not appear in any active *SPOOL capture.

Used by status output that should not be saved to a spool file (e.g. *Wipe '(Y/N) ' prompts, *Ex column separators, the 'Bad ROM' service-handler message via the recv_and_process_reply 'Data Lost' warning, and inline-string arguments inside cmd_ex's directory listing).

Six callers: &9818 (recv_and_process_reply), &B18D/&B197 (cmd_ex), &B323 (ex_print_col_sep), &B787 (cmd_wipe), &B7CB (prompt_yn).

On ExitAterminator byte (bit 7 set, also next opcode)
Xcorrupted (by print_char_no_spool)
Y0
9295 .print_inline_no_spool←13← 981A JSR← B18D JSR← B197 JSR← B1A5 JSR← B1B0 JSR← B1CC JSR← B1E1 JSR← B1F4 JSR← B203 JSR← B325 JSR← B789 JSR← B7A9 JSR← B7F6 JSR
PLA ; Pop return-addr low byte (-> string pointer low)
9296 STA fs_error_ptr ; Save in fs_error_ptr (the loop's pointer low)
9298 PLA ; Pop return-addr high byte
9299 STA fs_crflag ; Save in fs_crflag (the loop's pointer high)
929B LDY #0 ; Y=0: indirect index for (fs_error_ptr),Y
929D .loop_print_inline_string←1← 92B8 BRA
INC fs_error_ptr ; Step pointer low byte to next char
929F BNE print_next_string_char ; No carry: skip high-byte INC
92A1 INC fs_crflag ; Page wrap: bump pointer high
92A3 .print_next_string_char←1← 929F BNE
LDA (fs_error_ptr),y ; Read next character from inline string
92A5 BMI print_char_terminator ; Bit 7 set: terminator -- this byte is the next opcode
92A7 LDA fs_error_ptr ; Save pointer low (print_char_no_spool may clobber)
92A9 PHA ; Push it
92AA LDA fs_crflag ; Save pointer high
92AC PHA ; Push it
92AD LDA (fs_error_ptr),y ; Reload the character we're about to print
92AF JSR print_char_no_spool ; Print it via the *SPOOL-bypassing OSASCI wrapper
92B2 PLA ; Pop pointer high back
92B3 STA fs_crflag ; Restore
92B5 PLA ; Pop pointer low back
92B6 STA fs_error_ptr ; Restore
92B8 BRA loop_print_inline_string ; Always taken (BRA-style; A is non-zero from print)
92BA .print_char_terminator←1← 92A5 BMI
JMP (fs_error_ptr) ; Resume execution at the terminator byte's address (JMP indirect via fs_error_ptr)

Parse decimal or hex station address argument

Reads characters from the command argument at (fs_crc_lo),Y. Supports & prefix for hex, . separator for net.station addresses, and plain decimal. Returns the result in fs_load_addr_2 (and A). Raises Bad hex, Bad number, Bad station number, and overflow errors as appropriate. The body uses the standard 6502 idioms: ASL ASL ASL ASL + ADC for hex-digit accumulation, and result*2 + result*8 for decimal *10. Two named callers: from &A3C9 and &A3F2.

On EntryYindex into command-string buffer at (fs_crc_lo),Y
Aignored
On ExitCset if a number was parsed
92BD .parse_addr_arg←5← 8DCB JSR← 8DD7 JSR← A3DF JSR← A3F4 JSR← B0EA JSR
STZ fs_load_addr_2 ; Zero the accumulator (fs_load_addr_2)
92BF LDA (fs_crc_lo),y ; Read first command-line byte
92C1 CMP #'&' ; Hex prefix '&'?
92C3 BNE next_dec_char ; No: try decimal path
92C5 INY ; Yes: skip the '&'
92C6 LDA (fs_crc_lo),y ; Read first hex digit
92C8 BCS check_digit_range ; Always taken (CMP #'&' set C if A>='&'); jump into the hex digit-range check
92CA .next_hex_char←1← 92F9 BCC
INY ; Step to next character
92CB LDA (fs_crc_lo),y ; Read next hex digit candidate
92CD CMP #'.' ; Dot? Net.station separator
92CF BEQ handle_dot_sep ; Yes: switch to station-parsing mode
92D1 CMP #'!' ; Below '!' (CR/space)? End of argument
92D3 BCC done_parse_num ; Yes: number complete
92D5 .check_digit_range←1← 92C8 BCS
CMP #'0' ; Below '0'?
92D7 BCC skip_if_not_hex ; Yes: not a hex digit
92D9 CMP #':' ; Above '9'? (CMP #':')
92DB BCC extract_digit_value ; No (it's '0'-'9'): straight to digit extraction
92DD AND #&5f ; Force uppercase via AND #&5F
92DF ADC #&b8 ; Map 'A'-'F' to &FA-&FF (ADC #&B8 with C from earlier CMP #':' which set C)
92E1 BCS err_bad_hex ; Carry out of ADC: was below 'A' -- bad hex
92E3 CMP #&fa ; Below &FA? (digit > 'F' overflowed past)
92E5 .skip_if_not_hex←1← 92D7 BCC
BCC err_bad_hex ; Yes: bad hex (out of [&FA,&FF])
92E7 .extract_digit_value←1← 92DB BCC
AND #&0f ; Mask to nibble
92E9 STA fs_load_addr_3 ; Stash digit value in fs_load_addr_3
92EB LDA fs_load_addr_2 ; Load accumulator
92ED CMP #&10 ; Above 16? (would overflow when shifted left 4)
92EF BCS error_overflow ; Yes: overflow
92F1 ASL ; Shift accumulator left 4 (multiply by 16)
92F2 ASL ; (shift 2)
92F3 ASL ; (shift 3)
92F4 ASL ; (shift 4)
92F5 ADC fs_load_addr_3 ; Add new nibble
92F7 STA fs_load_addr_2 ; Save updated accumulator
92F9 BCC next_hex_char ; No carry: continue (always taken since accumulator was checked < 16 above)
92FB .next_dec_char←2← 92C3 BNE← 9325 BNE
LDA (fs_crc_lo),y ; Read next decimal-digit candidate
92FD CMP #'.' ; Dot? Net.station separator
92FF BEQ handle_dot_sep ; Yes: switch to station-parsing mode
9301 CMP #'!' ; Below '!' (CR/space)?
9303 BCC done_parse_num ; Yes: number complete
9305 JSR is_dec_digit_only ; Test for '0'-'9' and reject '&'/'.'
9308 BCC error_bad_number ; Not a decimal digit: bad number
930A AND #&0f ; Mask to nibble
930C STA fs_load_addr_3 ; Stash digit
930E ASL fs_load_addr_2 ; Accumulator * 2
9310 BCS error_overflow ; Overflowed: too big for byte
9312 LDA fs_load_addr_2 ; Reload doubled value
9314 ASL ; * 2 again (now * 4)
9315 BCS error_overflow ; Overflow check
9317 ASL ; * 2 again (now * 8)
9318 BCS error_overflow ; Overflow check
931A ADC fs_load_addr_2 ; + accumulator (now * 8 + * 2 = * 10)
931C BCS error_overflow ; Overflow check
931E ADC fs_load_addr_3 ; + new digit
9320 BCS error_overflow ; Overflow check
9322 STA fs_load_addr_2 ; Save * 10 + digit
9324 INY ; Step input cursor
9325 BNE next_dec_char ; Always taken (Y wraps at 256, never zero in practice)
9327 .done_parse_num←2← 92D3 BCC← 9303 BCC
LDA fs_work_4 ; Read mode flag
9329 BPL validate_station ; Bit 7 clear: in net.station mode -- validate result
932B LDA fs_load_addr_2 ; Decimal-only mode: get result
932D BEQ error_bad_param ; Result is zero: bad parameter
932F RTS ; Return with parsed result in A (decimal-only path)
9330 .validate_station←1← 9329 BPL
LDA fs_load_addr_2 ; Reload result
9332 CMP #&ff ; Station 255 is reserved (broadcast)
9334 BEQ err_bad_station_num ; Yes: bad station number
9336 LDA fs_load_addr_2 ; Reload result for the next test
9338 BNE return_parsed ; Non-zero: valid station, return
933A LDA fs_work_4 ; Zero result: must have followed a dot to be valid
933C BEQ err_bad_station_num ; No dot was seen: bad station number
933E DEY ; Dot seen: peek the byte before current cursor
933F LDA (fs_crc_lo),y ; Read previous byte
9341 INY ; Restore Y
9342 EOR #'.' ; Was previous char '.'?
9344 BNE err_bad_station_num ; No: bad station number
9346 .return_parsed←1← 9338 BNE
SEC ; All checks passed: C=1 marks 'parsed successfully'
9347 RTS ; Return
9348 .handle_dot_sep←2← 92CF BEQ← 92FF BEQ
LDA fs_work_4 ; Dot already seen?
934A BNE error_bad_number ; Yes: 'Bad number' (multiple dots)
934C INC fs_work_4 ; Set dot-seen flag
934E LDA fs_load_addr_2 ; Get parsed network number (before dot)
9350 CMP #&ff ; Network 255 is reserved
9352 BEQ error_bad_net_num ; Yes: 'Bad network number'
9354 RTS ; Return; caller continues parsing the station

Raise 'Bad hex' BRK error

Loads error code &F1 and tail-calls error_bad_inline with the inline string 'hex'error_bad_inline prepends 'Bad ' to produce the final 'Bad hex' message. Called from parse_addr_arg and the *DUMP / *LIST hex parsers when a digit is out of range. Never returns.

9355 .err_bad_hex←3← 92E1 BCS← 92E5 BCC← BED1 JMP
LDA #&f1 ; Error code &F1
9357 JSR error_bad_inline ; Raise 'Bad hex' error
935A EQUS "hex", &00
935E .error_overflow←6← 92EF BCS← 9310 BCS← 9315 BCS← 9318 BCS← 931C BCS← 9320 BCS
BIT fs_work_4 ; Test fs_work_4 bit 7
9360 BMI error_bad_param ; Bit 7 set: redirect to error_bad_param
9362 .err_bad_station_num←3← 9334 BEQ← 933C BEQ← 9344 BNE
LDA #&d0 ; A=&D0: 'Bad station' error code
9364 JSR error_bad_inline ; Raise via error_bad_inline (never returns)
9367 EQUS "station number", &00
9376 .error_bad_number←2← 9308 BCC← 934A BNE
LDA #&f0 ; A=&F0: 'Bad number' error code
9378 JSR error_bad_inline ; Raise via error_bad_inline (never returns)
937B EQUS "number", &00
9382 .error_bad_param←2← 932D BEQ← 9360 BMI
LDA #&94 ; A=&94: 'Bad parameter' error code
9384 JSR error_bad_inline ; Raise via error_bad_inline (never returns)
9387 EQUS "parameter", &00
9391 .error_bad_net_num←1← 9352 BEQ
LDA #&d1 ; A=&D1: 'Bad net number' error code
9393 JSR error_bad_inline ; Raise via error_bad_inline (never returns)
9396 EQUS "net number", &00
fall through ↓

Test for digit, '&', or '.' separator

Compares A against '&' and '.' first; if either matches, returns with carry set via the shared rts_digit_test exit. Otherwise falls through to is_dec_digit_only for the '0'..'9' range test.

Called by cmd_iam, cmd_ps, and cmd_pollps when parsing station addresses.

On EntryAcharacter to test
On ExitCset if digit/&/., clear otherwise
93A1 .is_decimal_digit←2← 8DC6 JSR← B400 JSR
CMP #'&' ; Hex prefix '&'?
93A3 BEQ rts_digit_test ; Yes: treat as digit-like (carry set on exit)
93A5 CMP #'.' ; Network/station separator '.'?
93A7 BEQ rts_digit_test ; Yes: also digit-like; else fall through to decimal test
fall through ↓

Test for decimal digit '0'..'9'

Uses two CMPs to bracket-test A against the range &30..&39:

  1. CMP #&3A sets carry if A >= ':' (above digits).
  2. CMP #&30 sets carry if A >= '0'.

The net effect: carry set only for '0'..'9'. Called by parse_addr_arg.

On EntryAcharacter to test
On ExitCset if '0'-'9', clear otherwise
93A9 .is_dec_digit_only←1← 9305 JSR
CMP #':' ; Above '9'? (CMP #':')
93AB BCS not_a_digit ; Yes: not a digit -- jump to clear-carry exit
93AD CMP #'0' ; Below '0'? (CMP sets carry if A >= '0')
93AF .rts_digit_test←2← 93A3 BEQ← 93A7 BEQ
RTS ; Carry now reflects '0'-'9' membership; return
93B0 .not_a_digit←1← 93AB BCS
CLC ; Out-of-range exit: clear carry to signal not-a-digit
93B1 RTS ; Return

Read and encode directory entry access byte

Loads the access byte from offset &0E of the directory entry via (fs_options),Y, masks to 6 bits (AND #&3F), then sets X=4 and branches to begin_prot_encode to map through prot_bit_encode_table. Called by check_and_setup_txcb for owner and public access.

On ExitAencoded access flags
X&FF + bits-set (left in this state by get_prot_bits fall-through)
93B2 .get_access_bits←2← 9E0D JSR← 9E39 JSR
LDY #&0e ; Y=&0E: directory entry access byte offset
93B4 LDA (fs_options),y ; Read access byte through fs_options pointer
93B6 AND #&3f ; Mask to 6 protection bits (clears the unused top two)
93B8 LDX #4 ; X=4: encode-table column index for owner-access bits
93BA BNE begin_prot_encode ; Always taken: LDX #4 cleared Z, so BNE is unconditional
fall through ↓

Encode protection bits via lookup table

Masks A to 5 bits (AND #&1F), sets X=&FF to start at table index 0, then enters the shared encoding loop at begin_prot_encode. Shifts out each source bit and ORs in the corresponding value from prot_bit_encode_table. Called by send_txcb_swap_addrs and check_and_setup_txcb.

On EntryAraw protection bits (low 5 used)
On ExitAencoded protection flags
93BC .get_prot_bits←2← 9D17 JSR← 9E56 JSR
AND #&1f ; Mask to 5 protection bits (low 5)
93BE LDX #&ff ; X=&FF; INX inside the loop bumps to 0 for column 0
93C0 .begin_prot_encode←1← 93BA BNE
STA fs_error_ptr ; Park source bits in fs_error_ptr -- the LSR target
93C2 LDA #0 ; A=0: accumulator for encoded result
93C4 .loop_encode_prot←1← 93CC BNE
INX ; Advance table column index
93C5 LSR fs_error_ptr ; Shift next source bit into carry
93C7 BCC skip_clear_prot ; Source bit was 0: skip the OR for this column
93C9 ORA prot_bit_encode_table,x ; Source bit was 1: OR in this column's encoded mask
93CC .skip_clear_prot←1← 93C7 BCC
BNE loop_encode_prot ; Continue while either fs_error_ptr or A is non-zero (loop ends when source exhausted and result still 0)
93CE RTS ; Return with encoded value in A

Bit-permutation table for protection / access encoding

11-byte lookup table used by get_prot_bits and get_access_bits to map source bits (the raw 5-bit or 6-bit access mask read from the directory entry) into the FS protocol's 8-bit protection-flag layout. The encoder loop at begin_prot_encode shifts each source bit out via LSR; whenever the bit is 1 it ORs the corresponding entry into the result, then advances X.

Two callers partition the table:

  • get_prot_bits enters at index 0 with 5 source bits (raw protection mask, AND #&1F).
  • get_access_bits enters at index 5 with 6 source bits (directory access byte, AND #&3F).
idx caller src bit mask output bits
0 get_prot_bits 0 &50 6, 4
1 get_prot_bits 1 &20 5
2 get_prot_bits 2 &05 2, 0
3 get_prot_bits 3 &02 1
4 get_prot_bits 4 &88 7, 3
5 get_access_bits 0 &04 2
6 get_access_bits 1 &08 3
7 get_access_bits 2 &80 7
8 get_access_bits 3 &10 4
9 get_access_bits 4 &01 0
10 get_access_bits 5 &02 1
93CF .prot_bit_encode_table←1Used as index base by← 93C9 ORA
EQUB &50 ; prot src bit 0 -> out bits 6,4
93D0 EQUB &20 ; prot src bit 1 -> out bit 5
93D1 EQUB &05 ; prot src bit 2 -> out bits 2,0
93D2 EQUB &02 ; prot src bit 3 -> out bit 1
93D3 EQUB &88 ; prot src bit 4 -> out bits 7,3
93D4 EQUB &04 ; access src bit 0 -> out bit 2
93D5 EQUB &08 ; access src bit 1 -> out bit 3
93D6 EQUB &80 ; access src bit 2 -> out bit 7
93D7 EQUB &10 ; access src bit 3 -> out bit 4
93D8 EQUB &01 ; access src bit 4 -> out bit 0
93D9 EQUB &02 ; access src bit 5 -> out bit 1

Set OS text pointer then transfer parameters

Stores X/Y into the MOS text pointer at os_text_ptr / os_text_ptr_hi (&F2/&F3), then falls through to set_xfer_params and set_options_ptr to configure the full FS transfer context. Two callers: fscv_3_star_cmd (FSCV reason 3) and ps_scan_resume (PS scan tail).

On EntryXtext pointer low byte
Ytext pointer high byte
93DA .set_text_and_xfer_ptr←2← A445 JSR← B133 JSR
STX os_text_ptr ; Save text pointer low byte (where caller wants OS to scan from)
93DC STY os_text_ptr_hi ; Save text pointer high byte; fall through to set_xfer_params
fall through ↓

Set FS transfer byte count and source pointer

Stores A into fs_last_byte_flag (&BD) as the transfer byte count, and X/Y into fs_crc_lo/hi (&BE/&BF) as the source-data pointer. Falls through to set_options_ptr to complete the transfer-context setup.

Called by 5 sites across cmd_ex, format_filename_field, and gsread_to_buf.

On EntryAtransfer byte count
Xsource pointer low
Ysource pointer high
93DE .set_xfer_params←6← 8DC0 JSR← 8E65 JSR← 9C22 JSR← A05A JSR← A177 JSR← B14D JSR
STA fs_last_byte_flag ; Stash transfer byte count (in A)
93E0 STX fs_crc_lo ; Source pointer low byte
93E2 STY fs_crc_hi ; Source pointer high byte; fall through to set_options_ptr
fall through ↓

Set FS options pointer and clear escape flag

Stores X/Y into fs_options/fs_block_offset (&BB/&BC) as the options-block pointer. Then enters clear_escapable which uses PHP/LSR/PLP to clear bit 0 of the escape flag at &97 without disturbing processor flags.

Called by format_filename_field and send_and_receive.

On EntryXoptions pointer low
Yoptions pointer high
93E4 .set_options_ptr←2← 9EB0 JSR← BD47 JSR
STX fs_options ; Options pointer low byte (parameter block base)
93E6 STY fs_block_offset ; Options pointer high byte; fall through to clear_escapable
fall through ↓

Clear bit 0 of need_release_tube preserving flags

PHP / LSR need_release_tube / PLP / RTS. Shifts bit 0 of need_release_tube into carry while clearing it, then restores the caller's flags so the operation is invisible to NZC-sensitive code. Single caller (&9B70 in the recv-and-classify reply path).

93E8 .clear_escapable←1← 9B72 JMP
PHP ; Save flags so the LSR doesn't disturb caller's NZC
93E9 LSR need_release_tube ; Shift bit 0 of need_release_tube into carry, clearing the bit
93EB PLP ; Restore caller's flags
93EC RTS ; Return

Compare 5-byte handle buffers for equality

Loops X from 4 down to 1, comparing each byte of addr_work+X with fs_load_addr_3+X using EOR. Returns on the first mismatch (Z=0) or after all 5 bytes match (Z=1).

Called by send_txcb_swap_addrs and check_and_setup_txcb to verify station/handle identity.

On ExitZset if bytes 1..4 match (byte 0 is not compared)
AEOR of last compared bytes
X0 if all matched, else mismatch index
93ED .cmp_5byte_handle←2← 9C85 JSR← 9D88 JSR
LDX #4 ; X=4: loop from offset 4 down to 1 (skips offset 0)
93EF .loop_cmp_handle←1← 93F6 BNE
LDA addr_work,x ; Load saved-handle byte from addr_work[X]
93F1 EOR fs_load_addr_3,x ; EOR with parsed handle byte; Z set iff bytes match
93F3 BNE rts_cmp_handle ; Mismatch: bail out with Z clear
93F5 DEX ; Decrement to next byte
93F6 BNE loop_cmp_handle ; Loop while X != 0 (offset 0 is intentionally not compared)
93F8 .rts_cmp_handle←1← 93F3 BNE
RTS ; Return; Z reflects last EOR (set = match, clear = mismatch)

FSCV reason 7: report FCB handle range

Returns the FCB handle range to the caller: X=&20 (lowest valid handle) and Y=&2F (highest valid handle), then RTS. Reached via the FSCV vector with reason code 7. Used by the OS to discover which handle values this filing system claims.

On ExitX&20 (first valid FCB handle)
Y&2F (last valid FCB handle)
93F9 .fscv_7_read_handles
LDX #&20 ; X=&20: handle-table base offset
93FB LDY #&2f ; Y=&2F: handle count + flag
93FD RTS ; Return

Set connection-active flag in channel table

Saves registers on the stack, recovers the original A from the stack via TSX/LDA &0102,X, then calls attr_to_chan_index to find the channel slot. ORAs bit 6 (&40) into the channel status byte at hazel_fcb_status+X. Preserves A, X, and processor flags via PHP/PHA/PLA/PLP.

Called by format_filename_field and adjust_fsopts_4bytes.

On EntryAchannel attribute byte
93FE .set_conn_active←2← 9F33 JSR← A1AF JSR
PHP ; Save flags so the rest of the routine is transparent
93FF PHA ; Save A (the attribute byte we need to recover via stack)
9400 PHX ; Save X
9401 TSX ; Capture S into X to address stack from below
9402 LDA stack_page_2,x ; Re-read the original A from stack[X+2] (above PHX/PHA)
9405 JSR attr_to_chan_index ; Convert attribute byte to channel-table index
9408 BMI clear_channel_flag ; No matching channel: skip the flag set, just restore
940A LDA #&40 ; A=&40: bit 6 = connection-active mask
940C ORA hazel_fcb_status,x ; OR with current status byte for this channel
940F STA hazel_fcb_status,x ; Write back the updated status
9412 BNE clear_channel_flag ; Always taken (A is non-zero after the OR with &40); join shared exit
fall through ↓

Clear connection-active flag in channel table

Mirror of set_conn_active but ANDs the channel status byte with &BF (bit-6 clear mask) instead of ORAing. Uses the same register-preservation pattern: PHP/PHA/TSX to recover A, then attr_to_chan_index to find the slot. Shares the done_conn_flag exit with set_conn_active.

On EntryAchannel attribute byte
9414 .clear_conn_active←2← 9FBE JSR← A1AA JSR
PHP ; Save flags
9415 PHA ; Save A
9416 PHX ; Save X
9417 TSX ; Capture S into X for stack-relative reads
9418 LDA stack_page_2,x ; Re-read the attribute byte from stack[X+2]
941B JSR attr_to_chan_index ; Convert attribute to channel index
941E BMI clear_channel_flag ; No matching channel: just restore
9420 LDA #&bf ; A=&BF: bit 6 clear mask
9422 AND hazel_fcb_status,x ; Mask the FCB status flags
9425 STA hazel_fcb_status,x ; Write back the updated status
9428 .clear_channel_flag←3← 9408 BMI← 9412 BNE← 941E BMI
PLX ; Restore X (saved at PHX)
9429 PLA ; Restore A
942A PLP ; Restore flags
942B RTS ; Return; A and X preserved across the call

Shared *Access / *Delete / *Info / *Lib command handler

Copies the command name to the TX buffer, parses a quoted filename argument via parse_quoted_arg, and checks the access prefix. Validates the filename does not start with '&', then falls through to read_filename_char to copy remaining characters and send the request. Raises Bad file name if a bare CR is found where a filename was expected.

On EntryYcommand line offset in text pointer
Xbyte offset within cmd_table_fs identifying which of the four shared commands was matched (Access, Delete, Info, or Lib)
942C .cmd_fs_operation
JSR copy_fs_cmd_name ; Copy command name 'Access'/'Delete'/'Info'/'Lib' to TX buffer
942F PHX ; Save X
9430 JSR parse_quoted_arg ; Parse quoted filename argument from command line
9433 JSR parse_access_prefix ; Parse the access prefix (e.g. L,W,R) into a bitmask
9436 PLX ; Restore X
9437 JSR check_not_ampersand ; Reject '&' character in filename
943A CMP #&0d ; End of line?
943C BNE read_filename_char ; No: copy filename chars to buffer
fall through ↓

Raise 'Bad file name' BRK error

Loads error code &CC and tail-calls error_bad_inline with the inline string 'file name'error_bad_inline prepends 'Bad ' to produce the final 'Bad file name' message. Used by check_not_ampersand and other filename validators. Never returns.

943E .error_bad_filename←3← 9452 BEQ← 9545 JMP← B2AE JMP
LDA #&cc ; Error number &CC
9440 JSR error_bad_inline ; Raise 'Bad file name' error
9443 EQUS "file name", &00
fall through ↓

Reject '&' as filename character

Loads the first character from the parse buffer at &0E30 and compares with '&' (&26). Branches to error_bad_filename if matched, otherwise returns.

Also contains read_filename_char which loops reading characters from the command line into the TX buffer at hazel_txcb_data (&C105), calling strip_token_prefix on each byte and terminating on CR. Used by cmd_fs_operation and cmd_rename.

On ExitAfirst byte of parse buffer (preserved unchanged on the non-error path)
944D .check_not_ampersand←2← 9437 JSR← 9455 JSR
LDA hazel_parse_buf ; Load first parsed character
9450 CMP #'&' ; Is it '&'?
9452 BEQ error_bad_filename ; Yes: invalid filename
9454 RTS ; Return

Loop reading filename chars into TX buffer

Per-character loop body of the filename-copy logic in check_not_ampersand:

  1. JSR to check_not_ampersand to reject '&'.
  2. Store the byte at hazel_txcb_data+X (TX buffer area).
  3. Increment X.
  4. Branch to send_fs_request on CR, or strip a BASIC token prefix via strip_token_prefix and re-enter the loop.

Three callers: the loop's own BRA at &945C, plus &9437 (cmd_rename's first-arg copy) and &950F (cmd_fs_operation's filename pickup).

On EntryAcurrent character to copy
XTX-buffer write index
On ExitXadvanced past the CR terminator
9455 .read_filename_char←3← 943C BNE← 9463 BRA← 9516 JMP
JSR check_not_ampersand ; Reject '&' in current char
9458 STA hazel_txcb_data,x ; Store character in TX buffer
945B INX ; Advance buffer pointer
945C CMP #&0d ; End of line?
945E BEQ send_fs_request ; Yes: send request to file server
9460 JSR strip_token_prefix ; Strip BASIC token prefix byte
9463 BRA read_filename_char ; BRA back to read_filename_char
fall through ↓

Send FS command with no extra dispatch offset

Loads Y=0 (so dispatch lookups don't add an offset) and tail-jumps to send_cmd_and_dispatch. Two callers: read_filename_char's BEQ on CR (&945C) and the *RUN argument-handling tail at &953C.

9465 .send_fs_request←2← 945E BEQ← 953E JMP
LDY #0 ; Y=0: ensure offset starts from beginning of TX command buffer
9467 JMP send_cmd_and_dispatch ; Send the FS command and dispatch the reply

Copy matched command name to TX buffer

Scans backwards in cmd_table_fs from the current position to find the bit-7 flag byte marking the start of the command name. Copies each character forward into the TX buffer at &C105 until the next bit-7 byte (end of name), then appends a space separator.

On EntryXbyte offset within cmd_table_fs (just past the matched command's last name char)
Ycurrent command-line offset (saved/restored)
On ExitXTX buffer offset past name+space
Ycommand line offset (restored)
Aclobbered
946A .copy_fs_cmd_name←2← 942C JSR← 94CC JSR
PHY ; Save Y on entry
946B .loop_scan_flag←1← 946F BPL
DEX ; Scan backwards in command table
946C LDA cmd_table_fs,x ; Load table byte
946F BPL loop_scan_flag ; Bit 7 clear: keep scanning
9471 INX ; Point past flag byte to name start
9472 LDY #0 ; Y=0: TX buffer offset
9474 .loop_copy_name←1← 947E BNE
LDA cmd_table_fs,x ; Load command name character
9477 BMI append_space ; Bit 7 set: end of name
9479 STA hazel_txcb_data,y ; Store character in TX buffer
947C INX ; Advance table pointer
947D INY ; Advance buffer pointer
947E BNE loop_copy_name ; Continue copying name
9480 .append_space←1← 9477 BMI
LDA #' ' ; Space separator
9482 STA hazel_txcb_data,y ; Append space after command name
9485 INY ; Advance buffer pointer
9486 TYA ; Transfer length to A
9487 TAX ; And to X (buffer position)
9488 PLY ; Restore Y
9489 .rts_copy_cmd_name←1← 94BE BEQ
RTS ; Return

Parse possibly-quoted filename argument

Reads from the command line at (fs_crc_lo),Y (&BE). Handles double-quote delimiters and stores the result in the parse buffer at &0E30. Raises 'Bad string' on unbalanced quotes.

On EntryYcurrent offset within the command line
On ExitYadvanced past the parsed argument
Aclobbered (last byte read)
948A .parse_quoted_arg←2← 9430 JSR← 94D3 JSR
LDA #0 ; A=0: no quote mode
948C TAX ; Copy A to X
948D STA hazel_quote_mode ; Clear quote tracking flag
9490 .loop_skip_spaces←1← 9497 BNE
LDA (fs_crc_lo),y ; Load char from command line
9492 CMP #' ' ; Space?
9494 BNE check_open_quote ; No: check for opening quote
9496 INY ; Skip leading space
9497 BNE loop_skip_spaces ; Continue skipping spaces
9499 .check_open_quote←1← 9494 BNE
CMP #'"' ; Double-quote character?
949B BNE loop_copy_arg_char ; No: start reading filename
949D INY ; Skip opening quote
949E EOR hazel_quote_mode ; Toggle quote mode flag
94A1 STA hazel_quote_mode ; Store updated quote mode
94A4 .loop_copy_arg_char←2← 949B BNE← 94B9 BNE
LDA (fs_crc_lo),y ; Load char from command line
94A6 CMP #'"' ; Double-quote?
94A8 BNE store_arg_char ; No: store character as-is
94AA EOR hazel_quote_mode ; Toggle quote mode
94AD STA hazel_quote_mode ; Store updated quote mode
94B0 LDA #' ' ; Replace closing quote with space
94B2 .store_arg_char←1← 94A8 BNE
STA hazel_parse_buf,x ; Store character in parse buffer
94B5 INY ; Advance command line pointer
94B6 INX ; Advance buffer pointer
94B7 CMP #&0d ; End of line?
94B9 BNE loop_copy_arg_char ; No: continue parsing
94BB LDA hazel_quote_mode ; Check quote balance flag
94BE BEQ rts_copy_cmd_name ; Balanced: return OK
94C0 LDA #&fd ; A=&FD: 'Bad string' error number (LDA #&FD)
94C2 JSR error_bad_inline ; Raise 'Bad string' error
94C5 EQUS "string", &00 ; Store to TXCB
fall through ↓

*Rename command handler

Parses two space-separated filenames from the command line, each with its own access prefix. Sets the owner-only access mask before parsing each name. Validates that both names resolve to the same file server by comparing the FS-options word – raises 'Bad rename' if they differ. Falls through to read_filename_char to copy the second filename into the TX buffer and send the request.

On EntryYcommand line offset in text pointer
94CC .cmd_rename
JSR copy_fs_cmd_name ; Copy 'Rename ' to TX buffer
94CF PHX ; Save X
94D0 JSR mask_owner_access ; Clear owner-only access bits before parsing
94D3 JSR parse_quoted_arg ; Parse the quoted source filename
94D6 JSR parse_access_prefix ; Parse access prefix on the source filename
94D9 PLX ; Restore X
94DA .loop_copy_rename←1← 94F8 BRA
LDA hazel_parse_buf ; Load next parsed character
94DD CMP #&0d ; End of line?
94DF BNE store_rename_char ; No: store character
94E1 .error_bad_rename←1← 9514 BNE
LDA #&b0 ; Error number &B0
94E3 JSR error_bad_inline ; Raise 'Bad rename' error
94E6 EQUS "rename", &00 ; Add 5 for header size
94ED .store_rename_char←1← 94DF BNE
STA hazel_txcb_data,x ; Store character in TX buffer
94F0 INX ; Advance buffer pointer
94F1 CMP #' ' ; Space (name separator)?
94F3 BEQ skip_rename_spaces ; Yes: first name complete
94F5 JSR strip_token_prefix ; Strip BASIC token prefix byte
94F8 BRA loop_copy_rename ; BRA back to loop_copy_rename
94FA .skip_rename_spaces←2← 94F3 BEQ← 9502 BEQ
JSR strip_token_prefix ; Strip token from next char
94FD LDA hazel_parse_buf ; Load next parsed character
9500 CMP #' ' ; Still a space?
9502 BEQ skip_rename_spaces ; Yes: skip multiple spaces
9504 LDA hazel_fs_lib_flags ; Save current FS options
9507 PHA ; Push them
9508 JSR mask_owner_access ; Reset access mask for second name
950B PHX ; Save loop index across the access parse
950C JSR parse_access_prefix ; Parse access prefix on the second filename
950F PLX ; Restore loop index
9510 PLA ; Restore original FS options
9511 CMP hazel_fs_lib_flags ; Options changed (cross-FS)?
9514 BNE error_bad_rename ; Yes: error (can't rename across FS)
9516 JMP read_filename_char ; Copy second filename and send

*Dir command handler

Handles three argument syntaxes:

Argument Action
plain path delegates to pass_send_cmd
'&' alone root directory
'&N.dir' cross-filesystem directory change

The cross-FS form sends a file-server selection command (code &12) to locate the target server, raising 'Not found' on failure, then sends the directory change (code 6) and calls find_fs_and_exit to update the active FS context.

On EntryYcommand line offset in text pointer
9519 .cmd_dir
LDA (fs_crc_lo),y ; Get first char of argument
951B CMP #'&' ; Is it '&' (FS selector prefix)?
951D BNE dir_pass_simple ; No: simple dir change
951F INY ; Skip '&'
9520 LDA (fs_crc_lo),y ; Get char after '&'
9522 CMP #&0d ; End of line?
9524 BEQ setup_fs_root ; Yes: '&' alone (root directory)
9526 CMP #' ' ; Space?
9528 BNE check_fs_dot ; No: check for '.' separator
952A .setup_fs_root←1← 9524 BEQ
LDY #&ff ; Y=&FF: pre-increment for loop
952C .loop_copy_fs_num←1← 9534 BNE
INY ; Advance index
952D LDA (fs_crc_lo),y ; Load char from command line
952F STA hazel_txcb_data,y ; Copy to TX buffer
9532 CMP #'&' ; Is it '&' (end of FS path)?
9534 BNE loop_copy_fs_num ; No: keep copying
9536 LDA #&0d ; Replace '&' with CR terminator
9538 STA hazel_txcb_data,y ; Store CR in buffer
953B INY ; Point past CR
953C TYA ; Transfer length to A
953D TAX ; And to X (byte count)
953E JMP send_fs_request ; Send directory request to server
9541 .check_fs_dot←1← 9528 BNE
CMP #'.' ; Is char after '&' a dot?
9543 BEQ parse_fs_dot_dir ; Yes: &FS.dir format
9545 JMP error_bad_filename ; No: invalid syntax
9548 .parse_fs_dot_dir←1← 9543 BEQ
INY ; Skip '.'
9549 STY fs_load_addr ; Save dir path start position
954B LDA #4 ; FS command 4: examine directory
954D STA hazel_txcb_data ; Store in TX buffer
9550 LDA hazel_fs_lib_flags ; Load FS flags
9553 ORA #&40 ; Set bit 6 (FS selection active)
9555 STA hazel_fs_lib_flags ; Store updated flags
9558 LDX #1 ; X=1: buffer offset
955A JSR copy_arg_validated ; Copy FS number to buffer
955D LDY #&12 ; Y=&12: select FS command code
955F JSR save_net_tx_cb ; Send FS selection command
9562 LDA hazel_txcb_data ; Load reply status
9565 CMP #2 ; Status 2 (found)?
9567 BEQ dir_found_send ; Yes: proceed to dir change
9569 LDA #&d6 ; Error number &D6
956B JSR error_inline_log ; Raise 'Not found' error
956E EQUS "Not found", &00 ; Store null terminator (A=0 from EOR) Get message length Go to error dispatch
9578 .dir_found_send←1← 9567 BEQ
LDA hazel_fs_context_copy ; Load current FS station byte
957B STA hazel_txcb_data ; Store in TX buffer
957E LDX #1 ; X=1: buffer offset
9580 LDY #7 ; Y=7: change directory command code
9582 JSR save_net_tx_cb ; Send directory change request
9585 LDX #1 ; X=1
9587 STX hazel_txcb_data ; Store start marker in buffer
958A STX hazel_txcb_flag ; Store start marker in buffer+1
958D INX ; Non-zero: commit state and return
958E LDY fs_load_addr ; Restore dir path start position
9590 JSR copy_arg_validated ; Copy directory path to buffer
9593 LDY #6 ; Y=6: set directory command code
9595 JSR save_net_tx_cb ; Send set directory command
9598 LDY hazel_txcb_data ; Load reply handle
959B JMP fsreply_3_set_csd ; Select FS and return
959E .dir_pass_simple←1← 951D BNE
JMP check_urd_prefix ; Simple: pass command to FS
95A1 .print_fs_ps_help
LDA (os_text_ptr),y ; Read first command-line char at (os_text_ptr),Y
95A3 CMP #&0d ; Is it CR (no argument supplied)?
95A5 BNE dispatch_fs_ps_with_arg ; Non-CR: argument present -- exit via dispatch_fs_ps_with_arg (X=&A0)
95A7 JSR print_fs_station ; CR: print 'FS ' header
95AA JSR print_dir_syntax ; Print '[<D>.]<D>\r'
95AD JSR print_station_low ; Print 'PS ' header
95B0 JSR print_dir_syntax ; Print '[<D>.]<D>\r' again
95B3 JSR print_space_line ; Print final 'Space\rNoSpace\r' lines
95B6 JSR print_inline ; Print inline string
95B9 EQUS "No"
95BB NOP ; NOP -- bit-7 terminator + resume opcode for the preceding inline string
95BC JSR print_space_line ; Print the 'Space' free-space label
95BF .bra_target_svc_return←1← 9618 BRA
JMP svc_return_unclaimed ; JMP to svc_return_unclaimed (long-distance via this 3-byte trampoline)

Print 'PS ' 9-column header

Calls print_inline with 'P' then falls through (via the 1-byte CLV terminator and BVC) into print_field_tail_s, so the combined output is 'PS ' -- the 9-column 'PS' field used in the *FS/*PS no-arg help and *STATUS displays.

95C2 .print_station_low←2← 95AD JSR← 963D JSR
JSR print_inline ; Print 'P' prefix
95C5 EQUS "P"
95C6 CLV ; CLV -- bit-7 terminator + resume (V flag is irrelevant here, used as 1-byte resume opcode)
95C7 BVC print_field_tail_s ; BVC: V was just cleared -> always taken; falls into the shared 'S ' tail at &95CC
fall through ↓

Print 'FS ' 9-column header

Calls print_inline with 'F' then falls through (via the 1-byte NOP terminator) into print_field_tail_s, so the combined output is 'FS ' -- the 9-column 'FS' field used in the *FS/*PS no-arg help and *STATUS displays.

95C9 .print_fs_station←2← 95A7 JSR← 9637 JSR
JSR print_inline ; Print 'F' prefix
95CC EQUS "F"
95CD NOP ; NOP -- bit-7 terminator; falls through into the shared 'S ' tail at &95CC
95CE .print_field_tail_s←1← 95C7 BVC
JSR print_inline ; Print 'S ' (S + 7 spaces) -- the shared 8-char field used by both 'FS' and 'PS' callers
95D1 EQUS "S "
95D9 NOP ; Bit-7 terminator
95DA RTS ; Return

Print '[<D>.]<D>\r' directory-name syntax fragment

3-byte JSR + inline '[<D>.]<D>' + CR + NOP terminator. Used as a shared fragment by both *Dir's syntax help and the *FS/*PS no-argument help via print_fs_ps_help.

95DB .print_dir_syntax←2← 95AA JSR← 95B0 JSR
JSR print_inline ; Print '[<D>.]<D>\r' (file-name syntax fragment, shared between *FS/*PS no-arg help and *Dir)
95DE EQUS "[<D>.]<D>", &0D
95E8 NOP ; Bit-7 terminator
95E9 RTS ; Return
95EA .dispatch_fs_ps_with_arg←1← 95A5 BNE
LDX #&a5 ; X=&A5: index into svc4 dispatch table (no-arg path)
95EC JMP svc4_dispatch_lookup ; Tail-jump to svc4_dispatch_lookup with X=&A0

Write FS/PS station+network to CMOS RAM

Reached via PHA/PHA/RTS dispatch from cmd_table_fs sub-table 4 (*FS at &A82A, *PS at &A82F) when the caller supplies a <net>.<stn> argument or wants to inspect/update the saved address.

The flag byte's low 6 bits (AND #&3F) double as the CMOS byte index for the relevant station:

command flag idx CMOS bytes
*FS &C1 1 1 = FS station, 2 = FS network
*PS &C3 3 3 = PS station, 4 = PS network

Pre-reads existing CMOS[idx] and CMOS[idx+1] into fs_work_5 / fs_work_6 so that the no-argument path leaves the saved values unchanged. Calls parse_fs_ps_args which conditionally overwrites fs_work_5 (station), fs_work_6 (canonical network: 0=local, non-zero=remote) and fs_work_7 (raw parsed network).

Writes the station via osbyte_a2, then falls through into osbyte_a2 itself to write the raw network at CMOS[idx+1]. Final BRA inside osbyte_a2 returns via svc_return_unclaimed.

On EntryXoffset in cmd_table_fs of the matched entry's flag byte
95EF .set_fs_or_ps_cmos_station
LDA cmd_table_fs,x ; Read flag byte for matched cmd entry (syntax idx in bits 0..4)
95F2 AND #&3f ; Mask off end-marker (bit 7) and V-if-no-arg flag (bit 6)
95F4 TAX ; X = CMOS byte index (1=FS stn, 3=PS stn)
95F5 PHX ; Save CMOS index
95F6 PHY ; Save caller's command-line cursor
95F7 PHX ; Save CMOS index again (consumed by first PLX below)
95F8 JSR osbyte_a1 ; Read existing CMOS[idx] (current station)
95FB STY fs_work_5 ; Default station if user gives no args
95FD PLX ; Recover CMOS index from stack
95FE INX ; X+=1: advance to network byte
95FF JSR osbyte_a1 ; Read existing CMOS[idx+1] (current network)
9602 STY fs_work_6 ; Default network if user gives no args
9604 PLY ; Restore command-line cursor
9605 JSR parse_fs_ps_args ; Parse '<net>.<stn>'; updates fs_work_5/6/7 if args present
9608 PLX ; Recover CMOS index from stack
9609 PHX ; Re-save CMOS index for second write
960A LDY fs_work_5 ; Y = station (parsed or pre-read default)
960C JSR osbyte_a2 ; Write CMOS[idx] = station
960F PLX ; Recover CMOS index from stack
9610 INX ; X+=1: advance to network byte
9611 LDY fs_work_7 ; Y = raw parsed network (NOT canonical fs_work_6); fall through into osbyte_a2 to write CMOS[idx+1]
fall through ↓

OSBYTE &A2 (write Master CMOS RAM byte)

Three instructions: LDA #&A2 / JSR OSBYTE / BRA &95BF. Writes the Master 128 CMOS RAM byte indexed by X with the value in Y. The trailing BRA lands on bra_target_svc_return (a 3-byte JMP trampoline to svc_return_unclaimed, reached this way because BRA's 8-bit displacement can't span &9618 → &8C8B).

osbyte_a2 ends at &961A (3 instructions, 7 bytes); the next labelled routine is cmd_space. Counterpart of osbyte_a1 (read).

Callers: set_fs_or_ps_cmos_station (once via JSR, once via fall-through), the BRA shortcut at &962F inside cmd_nospace, and an OSARGS-related read-modify-write of CMOS byte &11 ending at osopt_cmos_writeback_jsr.

On EntryXCMOS RAM byte index
Yvalue to write
9613 .osbyte_a2←3← 960C JSR← 962F BRA← A126 JSR
LDA #osbyte_write_cmos_ram ; A=&A2: write CMOS RAM byte via OSBYTE
9615 JSR osbyte ; Write CMOS RAM byte
9618 BRA bra_target_svc_return ; BRA -91 -> bra_target_svc_return
fall through ↓

*Space command: enable space-remaining display

Reached via the cmd_table_fs dispatch entry for *Space. Reads CMOS byte &11 with osbyte_a1, sets bit 0 of the value, then BRAs to the shared write-back tail at osbyte_a2_value_tya.

961A .cmd_space
LDX #&11 ; X=&11: CMOS RAM byte index
961C JSR osbyte_a1 ; Read CMOS &11 via osbyte_a1
961F TYA ; A = current CMOS &11 value
9620 ORA #1 ; Set bit 0 in A
9622 BRA osbyte_a2_value_tya ; BRA osbyte_a2_value_tya: shared write-back tail
fall through ↓

*NoSpace command: disable space-remaining display

Reached via the cmd_table_fs dispatch entry for *NoSpace. Reads CMOS byte &11 with osbyte_a1, clears bit 0 of the value, falls through to osbyte_a2_value_tya, and BRAs back into osbyte_a2 to write CMOS &11 = Y.

9624 .cmd_nospace
LDX #&11 ; X=&11: CMOS RAM byte index
9626 JSR osbyte_a1 ; Read CMOS &11 via osbyte_a1
9629 TYA ; A = current CMOS &11 value
962A AND #&fe ; Clear bit 0 in A
fall through ↓

Shared CMOS write-back tail

Common tail used by cmd_space (via BRA from &9620 with the new value already in A) and cmd_nospace (fall-through with the new value in A). TAY moves the byte to Y, then LDX #&11 reloads the CMOS index and BRA osbyte_a2 performs the write.

962C .osbyte_a2_value_tya←1← 9622 BRA
TAY ; New CMOS value to Y
962D LDX #&11 ; X=&11: CMOS RAM byte index
962F BRA osbyte_a2 ; BRA osbyte_a2: write CMOS &11 = Y
fall through ↓

Service &29: *STATUS handler

Reached via svc_dispatch slot &18. With no argument on the command line (first byte = CR) prints the FS and PS station addresses from CMOS &01-&04, then a single FS-active flag drawn from bit 0 of CMOS &11 (the same bit that cmd_space / cmd_nospace set and clear). With an argument, branches to help_dispatch_setup to parse it.

9631 .svc_29_status
LDA (os_text_ptr),y ; Read first command-line char
9633 CMP #&0d ; Is it CR (no argument)?
9635 BNE help_dispatch_setup ; Non-CR: parse the argument at help_dispatch_setup
9637 JSR print_fs_station ; Print 'FS ' header
963A JSR print_fs_address ; Print FS network.station from CMOS &02/&01
963D JSR print_station_low ; Print 'PS ' header
9640 JSR print_ps_address ; Print PS network.station from CMOS &04/&03
9643 LDX #&11 ; X=&11: CMOS RAM byte index
9645 JSR osbyte_a1 ; Read CMOS &11 (FS state)
9648 TYA ; A = CMOS &11
9649 AND #1 ; Mask bit 0 (FS-active flag)
964B BNE parse_object_space_print ; Bit set: skip 'No ' prefix
964D JSR print_inline ; Print 'No ' prefix via inline
9650 EQUS "No "
9653 NOP ; Bit-7 terminator + resume
9654 .parse_object_space_print←1← 964B BNE
JSR print_space_line ; Print 'Space ' or similar via inline
9657 BRA print_cmos_done ; Branch to the shared CMOS-print return
fall through ↓

Print the 'Space' status label

Emits the inline string "Space" + CR via print_inline; the &EA (NOP) terminator resumes on the trailing RTS. Called from the *STATUS / free-space report.

9659 .print_space_line←3← 95B3 JSR← 95BC JSR← 9654 JSR
JSR print_inline ; Print inline string
965C EQUS "Space", &0D ; Bit-7 terminator + resume opcode
9662 NOP ; bit-7 terminator + resume opcode
9663 RTS ; Return

Print printer-server address from CMOS

Prints the printer server's saved network.station from CMOS RAM. Sets X=4 (the PS network byte) and branches into the shared tail at print_cmos_pair, which prints CMOS[X] then CMOS[X-1] separated by a ..

In 4.26 the two entry points share one body: print_fs_address (X=2) falls straight in, while print_ps_address (X=4) reaches it by BRA, skipping the LDX #2.

9664 .print_ps_address←1← 9640 JSR
LDX #4 ; X=4: CMOS RAM byte 4 (PS network number)
9666 BRA print_cmos_pair ; Branch into shared tail (X already set)
fall through ↓

Print file-server address from CMOS

Prints the file server's saved network.station from CMOS RAM. Sets X=2 (the FS network byte) and falls into the shared tail print_cmos_pair. See print_ps_address for the shared-body structure.

9668 .print_fs_address←1← 963A JSR
LDX #2 ; X=2: CMOS RAM byte 2 (FS network number)
966A .print_cmos_pair←1← 9666 BRA
PHX ; Save network-byte index across the first print
966B JSR osbyte_a1 ; Read CMOS[X] (network number) via osbyte_a1
966E TYA ; A = CMOS network byte
966F JSR print_num_no_leading ; Print as decimal (no leading zeros)
9672 JSR print_inline ; Print '.' separator via inline
9675 EQUS "."
9676 PLX ; PLX terminator: restore X (network index)
9677 DEX ; X-1: the station byte sits just below the network byte
9678 .print_cmos_decimal_nl
JSR osbyte_a1 ; Read CMOS X via osbyte_a1
967B TYA ; A = CMOS value
967C JSR print_num_no_leading ; Print as decimal
967F JSR osnewl ; Print newline
9682 .print_cmos_done←1← 9657 BRA
JMP svc_return_unclaimed ; JMP svc_return_unclaimed (release service call)
9685 .help_dispatch_setup←1← 9635 BNE
LDX #&c2 ; X=&C2: setup index for the dispatch chain
fall through ↓

Dispatch *HELP-style argument via svc4_dispatch_lookup

3-byte trampoline: JMP svc4_dispatch_lookup with X = &BD from the caller. Used by svc_29_status's non-CR path so an argument after *STATUS (or similar *HELP-like cmd) gets parsed and dispatched through the same shared parser as the regular cmd-table dispatch. Note the '!Help.' bytes immediately following are an unrelated inline string used by the filename walker, not part of this routine's body.

9687 .dispatch_help_command
JMP svc4_dispatch_lookup ; JMP svc4_dispatch_lookup -- shared parser dispatch
968A EQUS "!Help.*" ; '!Help.' filename template copied into the TXCB command buffer to open the help file
9691 EQUB &0D

svc &18: interactive-HELP 'ON ' matcher and help-topic printer

Interactive-HELP service handler (svc &18). Reads the command line at os_text_ptr and tests for the two-letter ON keyword (case-insensitive, EOR #'O' / EOR #'N' with AND #&5F); if it is absent the call returns unclaimed. On a match it copies the '!Help.' template plus the requested topic name into the TXCB command buffer, opens the resulting help file and prints it byte-by-byte via osbget / oswrch, honouring paged mode and Escape.

9692 .match_on_suffix
PHY ; Save caller's command-line index Y
9693 BIT fs_flags ; Test fs_flags: bit 6 = interactive HELP armed
9696 BVC help_return ; Bit 6 clear: not our HELP call -> return
9698 LDA os_text_ptr ; Point work_ae at the command line (lo)
969A STA work_ae ; Store command-line pointer lo
969C LDA os_text_ptr_hi ; Command-line pointer hi
969E STA addr_work ; Store pointer hi (addr_work)
96A0 LDA (work_ae),y ; Read first keyword character
96A2 EOR #'O' ; Compare with 'O' ...
96A4 AND #&5f ; ... case-insensitively (mask bit 5)
96A6 BNE help_return ; Not 'O': return (line is not '...ON ')
96A8 INY ; Advance to second character
96A9 LDA (work_ae),y ; Read second keyword character
96AB EOR #'N' ; Compare with 'N' ...
96AD AND #&5f ; ... case-insensitively
96AF BEQ help_on_matched ; 'ON' matched: handle the topic
96B1 .help_return←3← 9696 BVC← 96A6 BNE← 971F BCC
PLY ; Restore caller's Y
96B2 RTS ; Return to service dispatcher
96B3 .help_on_matched←1← 96AF BEQ
PHY ; Save Y across FS-select
96B4 JSR ensure_fs_selected ; Ensure NFS is the current filing system
96B7 PLY ; Restore Y
96B8 .loop_skip_help_spaces←1← 96C2 BEQ
INY ; Advance to next command-line character
96B9 LDA (work_ae),y ; Read it
96BB BIT always_set_v_byte ; Set V (topic-char marker) from &9767 bit 6
96BE CMP #' ' ; Space?
96C0 BCC help_build_cmd ; Control char (<space): stop scanning
96C2 BEQ loop_skip_help_spaces ; Space: keep skipping leading spaces
96C4 .help_have_topic_char←1← 9723 BRA
CLV ; Real char: clear V (topic present)
96C5 .help_build_cmd←1← 96C0 BCC
STY hazel_txcb_data ; Save command-buffer index
96C8 STY hazel_txcb_flag ; Save it as the command flag too
96CB LDX #1 ; X=1: template-walk index
96CD .loop_copy_command_suffix←2← 96D8 BNE← 96DF BNE
INX ; Advance template index
96CE LDA help_topic_template,x ; Read '!Help.' template byte
96D1 STA hazel_txcb_data,x ; Store into the command buffer
96D4 BVC check_template_dot ; V clear (real topic char): check '.' terminator
96D6 CMP #&0d ; V set (line ended): CR?
96D8 BNE loop_copy_command_suffix ; Not CR: keep copying template
96DA INY ; Skip the CR
96DB BRA start_help_file_load ; Open the help file
96DD .check_template_dot←1← 96D4 BVC
CMP #'.' ; Template terminator '.'?
96DF BNE loop_copy_command_suffix ; No: keep copying template
96E1 .loop_copy_topic_name←1← 96EE BNE
INX ; Advance destination index
96E2 LDA (work_ae),y ; Read topic-name character
96E4 INY ; Advance source index
96E5 .store_topic_char←1← 96F2 BRA
STA hazel_txcb_data,x ; Store topic character
96E8 CMP #&0d ; CR (end of name)?
96EA BEQ start_help_file_load ; Yes: open the help file
96EC CMP #' ' ; Space (terminator)?
96EE BNE loop_copy_topic_name ; No: keep copying the name
96F0 LDA #&0d ; Replace trailing space with CR
96F2 BRA store_topic_char ; Store the CR terminator
96F4 .start_help_file_load←2← 96DB BRA← 96EA BEQ
PHY ; Save command-buffer index
96F5 INX ; Account for the last character
96F6 LDA hazel_fs_lib_flags ; Read fs_lib_flags
96F9 AND #&3f ; Clear the top two bits
96FB ORA #&80 ; Set bit 7 (load pending)
96FD STA hazel_fs_lib_flags ; Store fs_lib_flags back
9700 LDA #&40 ; A=&40: load-mode flag
9702 STA fs_last_byte_flag ; Set last-byte flag
9704 JSR send_open_file_request ; Open the help-topic file
9707 TAY ; File handle -> Y (0 = open failed)
9708 BEQ help_next_topic ; Open failed: skip to next topic
970A .loop_print_help_byte←3← 9731 BNE← 9736 BNE← 973B BRA
JSR osbget ; Read next byte from the help file
970D BCC help_print_start ; C clear: byte read OK -> print it
970F LDA #osfind_close ; A=0: OSFIND close mode
9711 JSR osfind ; osfind: close one or all files
9714 JSR osnewl ; Print a newline after the file
9717 .help_next_topic←1← 9708 BEQ
PLY ; Restore command-line index
9718 DEY ; Back up over the first consumed char
9719 DEY ; Back up over the second consumed char
971A .loop_help_next_topic←1← 9721 BEQ
INY ; Advance to next character
971B LDA (work_ae),y ; Read it
971D CMP #' ' ; Space?
971F BCC help_return ; Control char: no more topics -> return
9721 BEQ loop_help_next_topic ; Space: keep scanning
9723 BRA help_have_topic_char ; Real char: process the next topic
9725 .help_print_start←1← 970D BCC
BIT escape_flag ; Check the Escape flag
9727 BPL help_emit_char ; Bit 7 clear: not escaping -> print
9729 JMP escape_error_close ; Escape pressed: abort with error
972C .help_emit_char←1← 9727 BPL
JSR oswrch ; Print the byte
972F CMP #&0d ; Was it a CR?
9731 BNE loop_print_help_byte ; No: read the next byte
9733 LDX vdu_queue_count ; CR: read paged-mode line count
9736 BNE loop_print_help_byte ; Non-zero: no pause, continue
9738 JSR osnewl ; Emit newline
973B BRA loop_print_help_byte ; Loop for the next byte
fall through ↓

Set up open receive for FS reply on port &90

Loads A=&90 (the FS command/reply port) and falls through to init_txcb_port, which creates an open receive control block: the template sets txcb_ctrl to &80, then DEC makes it &7F (bit 7 clear = awaiting reply). The NMI RX handler sets bit 7 when a reply arrives on this port, which wait_net_tx_ack polls for.

973D .init_txcb_bye←1← 97CE JSR
LDA #&90 ; A=&90: bye command port
fall through ↓

Create open receive control block on specified port

Calls init_txcb to copy the 12-byte template into the TXCB workspace at &00C0, then stores A as the port (txcb_port at &C1) and sets txcb_start to 3. The DEC txcb_ctrl changes the control byte from &80 to &7F (bit 7 clear), creating an open receive: the NMI RX handler will set bit 7 when a reply frame arrives on this port, which wait_net_tx_ack polls for.

On EntryAport number
973F .init_txcb_port←1← 9DCD JSR
JSR init_txcb ; Initialise TXCB from template
9742 STA txcb_port ; Set transmit port
9744 LDA #3 ; A=3: data start offset
9746 STA txcb_start ; Set TXCB start offset
9748 DEC txcb_ctrl ; Open receive: &80->&7F (bit 7 clear = awaiting reply)
974A RTS ; Return

Initialise TX control block from ROM template

Copies 12 bytes from txcb_init_template into the TXCB workspace at &00C0. For the first two bytes (Y=0,1), also copies the destination station/network from &0E00 into txcb_dest (&C2). Preserves A via PHA/PLA.

Called by 4 sites including cmd_pass, init_txcb_port, prep_send_tx_cb, and send_wipe_request.

On ExitApreserved
X, Yclobbered (Y left at &FF on loop exit)
974B .init_txcb←5Referenced by← 8E2F JSR← 973F JSR← 97BD JSR← BCFD JSRUsed as index base by← AC77 LDA
PHA ; Save A
974C LDY #&0b ; Y=&0B: template size - 1
974E .loop_init_txcb←1← 975F BPL
LDA txcb_init_template,y ; Load byte from TXCB template
9751 STA txcb_ctrl,y ; Store to TXCB workspace
9754 CPY #2 ; Index >= 2?
9756 BPL skip_txcb_dest ; Yes: skip dest station copy
9758 LDA hazel_fs_station,y ; Load dest station byte
975B STA txcb_dest,y ; Store to TXCB destination
975E .skip_txcb_dest←1← 9756 BPL
DEY ; Decrement index
975F BPL loop_init_txcb ; More bytes: continue
9761 PLA ; Restore A
9762 RTS ; Return

TXCB initialisation template (12 bytes)

Copied byte-for-byte by init_txcb into the TXCB workspace at &00C0. The Nth template byte (at &9763 + N) ends up at TXCB offset N (&00C0 + N).

Bytes 2 and 3 (placeholders &00 &00 here) are overwritten during the copy: while writing TXCB[0] and TXCB[1] the loop also copies hazel_fs_station[0..1] (HAZEL &C000..&C001) into txcb_dest (&00C2..&00C3), so the runtime destination station and network come from the live FS state rather than this template.

The &FF byte at offset 6 (always_set_v_byte) serves double duty: it is part of this template AND a BIT $abs target used by 22 callers to set V and N flags without clobbering A.

9763 .txcb_init_template←1Used as index base by← 974E LDA
EQUB &80 ; Offset 0: txcb_ctrl = &80 (TX command)
9764 EQUB &99 ; Offset 1: txcb_port = &99 (FS command port)
9765 EQUB &00 ; Offset 2: txcb_dest lo placeholder (overwritten with hazel_fs_station[0])
9766 EQUB &00 ; Offset 3: txcb_dest hi placeholder (overwritten with hazel_fs_station[1])
9767 EQUB &00 ; Offset 4: txcb_start lo = 0
9768 EQUB &C1 ; Offset 5: txcb_start hi = &C1 (data buffer starts at &C100 in HAZEL)
9769 .always_set_v_byte←21← 8C8E BIT← 9207 BIT← 96BB BIT← 993D BIT← 9A6A BIT← 9E2E BIT← A018 BIT← A3D1 BIT← A4CE BIT← A670 BIT← A69B BIT← A6D2 BIT← AE24 BIT← B35C BIT← B41E BIT← B5A0 BIT← B630 BIT← B6C8 BIT← B92B BIT← B969 BIT← BC54 BIT
EQUB &FF ; Offset 6: padding &FF; doubles as the always_set_v_byte BIT $abs target
976A .bit_test_ff
EQUB &FF ; Offset 7: txcb_pos = &FF (also labelled bit_test_ff)
976B EQUB &FF ; Offset 8: txcb_end lo = &FF
976C EQUB &C1 ; Offset 9: txcb_end hi = &C1 (buffer end &C1FF)
976D EQUB &FF ; Offset 10: extended-addr fill (&FF)
976E EQUB &FF ; Offset 11: extended-addr fill (&FF)

Send read-only FS request (carry set)

Pushes A and sets carry to indicate no-write mode, then branches to txcb_copy_carry_set to enter the common TXCB copy, send, and reply-processing path. The carry flag controls whether a disconnect is sent on certain reply codes. Called by setup_transfer_workspace.

On EntryYFS function code (stored as TX[1] = txcb_func by txcb_copy_carry_set)
Asaved on stack at entry (consumed by the txcb send/receive path)
976F .send_request_nowrite←1← A247 JSR
PHA ; Save A
9770 SEC ; Set carry (read-only mode)
9771 BCS txcb_copy_carry_set ; C set: copy TXCB with carry set
fall through ↓

Send read-write FS request (V clear)

Clears V flag and branches unconditionally to txcb_copy_carry_clr (via BVC, always taken after CLV) to enter the common TXCB copy, send, and reply-processing path with carry clear (write mode). Called by do_fs_cmd_iteration and send_txcb_swap_addrs.

On EntryYFS function code (stored as TX[1] = txcb_func by txcb_copy_carry_clr)
Arequest payload byte (used by the txcb send path)
9773 .send_request_write←2← 9C45 JSR← 9CF9 JSR
CLV ; Clear V
9774 BVC txcb_copy_carry_clr ; V clear: copy TXCB with carry clear
fall through ↓

*Bye command handler

Closes all open file control blocks via process_all_fcbs, shuts down any *SPOOL/*EXEC files with OSBYTE &77, and closes all network channels. Falls through to save_net_tx_cb with function code &17 to send the bye request to the file server.

9776 .cmd_bye
LDY #0 ; Y=0: process_all_fcbs filter (0 = all FCBs)
9778 JSR process_all_fcbs ; Walk all 16 FCB slots, calling start_wipe_pass on each
977B LDA #osbyte_close_spool_exec ; OSBYTE &77 = close *SPOOL and *EXEC files
977D JSR osbyte ; Close any open *SPOOL/*EXEC handles Close any SPOOLed or EXECed files
9780 LDA #&40 ; A=&40: bit 6 of fs_flags = 'FS in active session'
9782 TRB fs_flags ; Clear bit 6: mark FS session inactive
9785 JSR close_all_net_chans ; Close every Econet client channel
9788 LDY #&17 ; Y=&17: FS function code 'Bye' (logoff request)
fall through ↓

Save FS state and send command to file server

Copies station address and function code (Y) to the TX buffer, builds the TXCB via init_txcb, sends the packet through prep_send_tx_cb, and waits for the reply via recv_and_process_reply. V is clear for standard mode.

On EntryYFS function code (becomes TX[1] = txcb_func)
XTX buffer payload length (prep_send_tx_cb uses X+5 as txcb_end)
On ExitAFS reply status
978A .save_net_tx_cb←24← 8E56 JSR← 955F JSR← 9582 JSR← 9595 JSR← 9E4A JSR← 9F2D JSR← 9F3D JSR← 9FB5 JSR← A040 JSR← A0B9 JSR← A0ED JSR← A1BF JSR← A2A2 JSR← A35D JSR← A521 JSR← A549 JSR← A8B4 JSR← B107 JMP← B185 JSR← B1C3 JSR← B232 JSR← B746 JSR← B7DD JSR← BCDC JSR
CLV ; Clear V: standard send mode (callers set V via save_net_tx_cb_vset for the lib-flag variant)
fall through ↓

Save and send TXCB with V flag set

Variant of save_net_tx_cb for callers that have already set V. Copies the FS station address from &0E02 to &0F02, then falls through to txcb_copy_carry_clr which clears carry and enters the common TXCB copy, send, and reply path.

Called by check_and_setup_txcb, format_filename_field, and cmd_remove.

On EntryYFS function code
XTX buffer payload length
V FLAGset by caller (selects this variant via the 'no CLV' fall-through from save_net_tx_cb)
On ExitAFS reply status
978B .save_net_tx_cb_vset←2← 9E31 JSR← A01E JSR
LDA hazel_fs_saved_station ; Read FS station from &C002 (saved from selection time)
978E STA hazel_txcb_station ; Copy into TX buffer at &C102 (dest station for header)
9791 .txcb_copy_carry_clr←1← 9774 BVC
CLC ; Clear C: caller wants four-way handshake (not disconnect)
9792 .txcb_copy_carry_set←1← 9771 BCS
PHP ; Save flags so we can keep V across the loop
9793 STY hazel_txcb_func_code ; Save Y -- the entry function code -- into TX[1]
9796 LDY #1 ; Y=1: copy 2 bytes (network/control) starting at index 1
9798 .loop_copy_vset_stn←1← 979F BPL
LDA hazel_fs_context_copy,y ; Read source byte at &C003+Y
979B STA hazel_txcb_network,y ; Write to TX buffer at &C103+Y
979E DEY ; Step backwards
979F BPL loop_copy_vset_stn ; Loop while Y >= 0 (covers indices 1, 0)
97A1 BIT hazel_fs_lib_flags ; Test fs_lib_flags: bit 6 = use library, bit 7 = *-prefix-stripped
97A4 BVS use_lib_station ; V (bit 6) set: use the library station instead
97A6 BPL done_vset_station ; Neither bit set: leave the FS station copy intact
97A8 LDA hazel_fs_prefix_stn ; Bit 7 (FS-prefix) set: substitute the saved-prefix station from &C004
97AB STA hazel_txcb_network ; Override TX[3]'s station byte
97AE BVC done_vset_station ; Always taken: V was clear when we entered (BVS at &97A4 didn't fire)
97B0 .use_lib_station←1← 97A4 BVS
LDA hazel_fs_saved_station ; use_lib_station: substitute the library station from &C002 (the original FS station, but bit 6 of fs_lib_flags redirects via lib path)
97B3 STA hazel_txcb_network ; Override TX[3] with the library station byte
97B6 .done_vset_station←2← 97A6 BPL← 97AE BVC
PLP ; Restore the saved flags (V/C control downstream init_txcb behaviour)
fall through ↓

Build TXCB from scratch, send, and receive reply

Full send/receive cycle comprising two separate Econet transactions:

  1. Save flags, set reply port &90.

  2. Call init_txcb, compute txcb_end = X + 5.

  3. Dispatch on carry:

    C Path
    set handle_disconnect
    clear init_tx_ptr_and_send for a client-initiated four-way handshake (scout, ACK, data, ACK) to deliver the command
  4. After TX completes, the ADLC returns to idle RX-listen.

  5. Falls through to recv_and_process_reply which waits for the server to independently initiate a new four-way handshake with the reply on port &90. There is no reply data in the original ACK payload.

On EntryXTX buffer payload length (txcb_end = X + 5)
YFS function code (already stashed by the txcb-copy entry path)
C FLAGset = disconnect path (handle_disconnect); clear = normal four-way handshake send
On ExitAFS reply status (or doesn't return on error)
97B7 .prep_send_tx_cb←1← A2FB JSR
PHP ; Save flags so C survives the init_txcb call
97B8 LDA #&90 ; Reply port = &90 (FS reply port)
97BA STA hazel_txcb_port ; Stash port in TXCB[0]
97BD JSR init_txcb ; Build the rest of the TXCB (control, dest stn/net, etc.)
97C0 TXA ; Move TX-buffer end pointer (returned in X) into A
97C1 ADC #5 ; Add 5 bytes of slack for trailing reply data
97C3 STA txcb_end ; Stash the resulting end-of-buffer offset
97C5 PLP ; Restore the original C flag from caller
97C6 BCS handle_disconnect ; C set: this is a disconnect; jump to handle_disconnect
97C8 PHP ; Save flags again across the actual TX (TX clobbers them)
97C9 JSR init_tx_ptr_and_send ; Send the four-way-handshake-initiated command packet
97CC PLP ; Restore caller's flags before falling into recv_and_process_reply
fall through ↓

Receive FS reply and dispatch on status codes

Waits for a server-initiated reply transaction. After the command TX completes (a separate client-initiated four-way handshake), calls init_txcb_bye to set up an open receive on port &90 (txcb_ctrl = &7F). The server independently initiates a new four-way handshake to deliver the reply; the NMI RX handler matches the incoming scout against this RXCB and sets bit 7 on completion. wait_net_tx_ack polls for this.

Iterates over reply bytes:

Byte / state Action
0 terminates
V set adjust by +&2B
non-zero, V clear dispatch to store_reply_status

Handles disconnect requests (C set from prep_send_tx_cb) and 'Data Lost' warnings when channel status bits indicate pending writes were interrupted.

On EntryC FLAGset = disconnect mode (caller sent a disconnect scout; handle the server's matching reply)
On ExitAFS reply status byte
97CD .recv_and_process_reply←2← 9D0C JSR← A29B JSR
PHP ; Save flags so caller's V/C survive the receive
97CE JSR init_txcb_bye ; Set up open RX on port &90 for the FS reply (TXCB[0] = &90, ctrl = &7F)
97D1 JSR wait_net_tx_ack ; Wait for the reply via the 3-level stack timer
97D4 PLP ; Restore caller's flags
97D5 .loop_next_reply←1← 97E9 BCC
INY ; Step Y to next reply byte
97D6 LDA (txcb_start),y ; Read reply byte at txcb_start+Y
97D8 TAX ; Stash for the dispatch tests below
97D9 BEQ rts_recv_reply ; Zero terminates: return
97DB BVC process_reply_code ; V clear (caller's V): use code as-is
97DD ADC #&2a ; V set: shift the code by +&2A (extended-error mapping)
97DF .process_reply_code←1← 97DB BVC
BNE store_reply_status ; Non-zero: dispatch as an error
97E1 .rts_recv_reply←1← 97D9 BEQ
RTS ; Return
97E2 .handle_disconnect←1← 97C6 BCS
PLA ; Pull caller's pushed return state
97E3 LDX #&c0 ; X=&C0: 'remote disconnect' status
97E5 INY ; Step Y past the disconnect byte
97E6 JSR send_disconnect_reply ; Send disconnect notification to remote
97E9 BCC loop_next_reply ; C clear (success): continue scanning replies
97EB .store_reply_status←1← 97DF BNE
STX hazel_fs_last_error ; Save the error code into &C009
97EE LDA hazel_fs_pending_state ; Read FS state byte at &C007
97F1 PHP ; Save flags so we can branch later
97F2 BNE check_data_loss ; FS state non-zero: data-loss check needed
97F4 CPX #&bf ; Reply was &BF (special: not a real error)?
97F6 BNE build_error_block ; No: build error block
97F8 .check_data_loss←1← 97F2 BNE
LDA #&40 ; A=&40: 'channel-active' bitmask
97FA PHA ; Push it onto the OR-accumulator
97FB TRB fs_flags ; Clear the FS-active bit (we're losing the connection)
97FE LDX #&f0 ; X=&F0: scan from channel offset &F0 upwards
9800 .loop_scan_channels←1← 980E BMI
PLA ; Pull current OR accumulator
9801 ORA hazel_chan_status,x ; OR with channel status byte at &C1C8+X
9804 PHA ; Push back updated accumulator
9805 LDA hazel_chan_status,x ; Reload channel byte
9808 AND #&c0 ; Mask to top 2 bits (preserve TX/RX state)
980A STA hazel_chan_status,x ; Write back trimmed status
980D INX ; Step channel index
980E BMI loop_scan_channels ; Loop while X bit 7 set (covers &F0..&FF)
9810 STX hazel_fs_pending_state ; Clear the FS state byte (no longer active)
9813 JSR close_all_net_chans ; Force-close all client channels
9816 PLA ; Pull final OR accumulator
9817 ROR ; Bit 0 (was bit 6 of any &40 byte) -> C
9818 BCC scan_channel_store_reply ; Any channel was active: skip the warning
981A JSR print_inline_no_spool ; No active channels were lost: print 'Data Lost' warning via inline string
981D EQUS "Data Lost", &0D
9827 .scan_channel_store_reply←1← 9818 BCC
LDX hazel_fs_last_error ; Reload error code from &C009
982A PLP ; Restore saved flags (was bit 7 of fs_flags)
982B BEQ build_error_block ; Z set (no error): build the error block anyway
982D PLA ; Pull caller's saved return state (3 bytes from PHP earlier)
982E PLA ; Unwind stacked byte
982F PLA ; Unwind stacked byte
9830 RTS ; Return -- caller dispatched on a non-error reply
9831 .build_error_block←2← 97F6 BNE← 982B BEQ
LDY #1 ; Y=1: skip past the leading TXCB control byte
9833 CPX #&a8 ; Error code below &A8 (extended)?
9835 BCS setup_error_copy ; No (>= &A8): proceed to copy
9837 LDA #&a8 ; Yes: clamp to &A8 (truncate range)
9839 STA (txcb_start),y ; Write clamped code back into TXCB
983B .setup_error_copy←1← 9835 BCS
LDY #&ff ; Y=&FF: INY in loop bumps to 0
983D .loop_copy_error←1← 9845 BNE
INY ; Step Y
983E LDA (txcb_start),y ; Read TXCB byte (error block content)
9840 STA error_block,y ; Copy to BRK error block at &0100+Y
9843 EOR #&0d ; EOR with CR; Z set when we just copied the terminator
9845 BNE loop_copy_error ; Not yet at CR: continue copying
9847 STA error_block,y ; Write the CR terminator (Z still set so A=0; ensures cleanly terminated)
984A DEY ; Step Y back so it points at the CR position
984B TYA ; Move Y into A for the BRK
984C TAX ; Move Y into X (caller convention)
984D JMP check_net_error_code ; Tail-jump into the BRK-dispatch error path

Language reply 1: remote-boot init / continue

Reads the reply byte at (net_rx_ptr),0. If zero, branches to init_remote_session to (re)initialise the remote session. Otherwise falls through to done_commit_state which finalises the boot state byte for the active session.

9850 .lang_1_remote_boot
LDY #0 ; Y=0: status byte offset
9852 LDA (net_rx_ptr),y ; Read RX status byte
9854 BEQ init_remote_session ; Zero: re-init the session
9856 .done_commit_state←1← 98AD BNE
JMP commit_state_byte ; Non-zero: commit state and continue
9859 .init_remote_session←2← 9854 BEQ← 98A3 BEQ
ORA #9 ; Mark session as 'remote boot'
985B STA (net_rx_ptr),y ; Store updated status byte back to RX[0]
985D LDX #&80 ; X=&80: caller machine-id byte offset
985F LDY #&80 ; Y=&80: same offset
9861 LDA (net_rx_ptr),y ; Read remote machine ID
9863 PHA ; Push -- save across the workspace store
9864 INY ; Advance index
9865 LDA (net_rx_ptr),y ; Re-read for the second store target
9867 LDY #&0f ; Y=&0F: workspace machine-ID lo offset
9869 STA (nfs_workspace),y ; Store at (nfs_workspace)+&0F
986B DEY ; Y=&0E
986C PLA ; Pop saved machine ID
986D STA (nfs_workspace),y ; Store at (nfs_workspace)+&0F (reuse)
986F JSR scan_remote_keys ; Scan remote-key flags
9872 JSR init_ws_copy_narrow ; Initialise narrow workspace template
9875 LDX #1 ; X=1: enable Econet keyboard
9877 LDY #0 ; Y=0
9879 LDA #osbyte_econet_kbd_disable ; OSBYTE &C9: read/write Econet keyboard disable
987B JSR osbyte ; osbyte: econet kbd disable
fall through ↓

Language reply 3: raise 'Remoted' error at &0100

Calls commit_state_byte to record the new state, loads A=0 and tail-calls error_inline_log with the inline string Remoted followed by &07 (BEL). Used by remote-language replies that need to abort the current operation with a terminal beep + error. Never returns.

987E .lang_3_exec_0100
JSR commit_state_byte ; Commit the language-reply state byte
9881 LDA #0 ; A=0: 'Bad' error code
9883 JSR error_inline_log ; Raise via error_inline_log (never returns)
9886 EQUS "Remoted", &07, &00
fall through ↓

Acknowledge escape (if pressed) and classify reply

If escape_flag bit 7 is clear OR need_release_tube bit 7 is clear (so AND result has bit 7 clear), returns immediately via return_1. Otherwise acknowledges escape via OSBYTE &7E (clears the escape condition and runs escape effects), loads A=6 (a synthesized 'Escape' error class), and tail-jumps to classify_reply_error to build the 'Escape' BRK error block.

Two callers: cmd_pass (&8E07) for password-entry escape, and send_net_packet (&9B46) for in-flight TX escape.

On ExitApreserved (return) or never returns (escape path)
988F .check_escape_and_classify←2← 8E09 JSR← 9B48 JSR
LDA escape_flag ; Read escape_flag
9891 AND need_release_tube ; Mask with need_release_tube (escape-disable)
9893 BPL return_3 ; Bit 7 clear: not escaping, return
fall through ↓

Acknowledge escape and raise classified error

Issues OSBYTE &7E (acknowledge_escape -- clears the escape condition and runs any registered escape effects), loads A=6, and tail-jumps to classify_reply_error which builds the Escape error. Reached from &98EF (after recv_and_process_reply detects escape) and &B808 (cmd_wipe's per-iteration escape check). Never returns -- the classify_reply_error path triggers BRK.

On ExitA6 (Escape error code passed to classify_reply_error)
9895 .raise_escape_error←2← 98EF BMI← B80A JMP
LDA #osbyte_acknowledge_escape ; A=&7E: OSBYTE &7E = acknowledge Escape
9897 JSR osbyte ; Clear escape condition and perform escape effects
989A LDA #6 ; A=6: error class for 'Escape'
989C JMP classify_reply_error ; JMP classify_reply_error (never returns)

Language reply 4: validate remote session and apply

Reads the first reply byte at (net_rx_ptr),0. If zero, branches to init_remote_session to set up a fresh remote session. Otherwise reads the validation byte at offset &80 and the local stored value at workspace offset &0E; on mismatch, the remote session is rejected.

989F .lang_4_validated
LDY #0 ; Y=0: status byte offset
98A1 LDA (net_rx_ptr),y ; Read RX status byte
98A3 BEQ init_remote_session ; Zero status: re-init the session
98A5 LDY #&80 ; Y=&80: session-ID byte offset in RX
98A7 LDA (net_rx_ptr),y ; Read remote session-ID
98A9 LDY #&0e ; Y=&0E: stored session-ID offset in workspace
98AB CMP (nfs_workspace),y ; Compare with stored ID
98AD BNE done_commit_state ; Mismatch: skip the commit (treat as foreign)
fall through ↓

Language reply 0: insert remote keypress

Reads the keycode from the reply at (net_rx_ptr),&82 into Y, sets X=0, calls commit_state_byte to record the state change, and issues OSBYTE &99 (insert into keyboard buffer) to deliver the keypress to the local machine.

On EntryAignored (entry from reply dispatch)
98AF .lang_0_insert_key
LDY #&82 ; Y=&82: keypress byte offset in RX
98B1 LDA (net_rx_ptr),y ; Read remote keypress code
98B3 TAY ; Y = key code
98B4 LDX #0 ; X=0: keyboard buffer ID
98B6 JSR commit_state_byte ; Commit the language-reply state
98B9 LDA #osbyte_insert_input_buffer ; OSBYTE &99: insert byte into input buffer
98BB JMP osbyte ; Insert byte Y into input buffer X

Wait for reply on open receive with timeout

Despite the name, this does not wait for a TX acknowledgment. It polls an open receive control block (bit 7 of txcb_ctrl, set to &7F by init_txcb_port) until the NMI RX handler delivers a reply frame and sets bit 7.

Uses a three-level nested polling loop:

Loop Source Default Iterations
inner wraps from 0 256
middle wraps from 0 256
outer rx_wait_timeout &28 (40) 40

Total: 256 × 256 × 40 = 2,621,440 poll iterations. At ~17 cycles per poll on a 2 MHz 6502, the default gives ~22 seconds.

On timeout, branches to build_no_reply_error to raise 'No reply'. Called by 6 sites across the protocol stack.

98BE .wait_net_tx_ack←6← 97D1 JSR← 9C9F JSR← 9DD7 JSR← ACCC JMP← AF75 JSR← B02C JSR
LDA rx_wait_timeout ; Read the configurable rx-wait timeout (&0D6E, default &28 = ~22s on 2 MHz)
98C1 PHA ; Push it as the outermost counter (read back via stack-X indexing later)
98C2 LDA econet_flags ; Read econet_flags so we can preserve it across the wait
98C5 PHA ; Push it (we'll temporarily set bit 7 to mark waiting)
98C6 LDA net_tx_ptr_hi ; Check whether net_tx_ptr_hi is non-zero (TX in flight?)
98C8 BNE init_poll_counters ; Yes: skip the flag-set; counters initialise either way
98CA ORA #&80 ; TX idle: set bit 7 of econet_flags (signal RX-only wait)
98CC STA econet_flags ; Write the modified flags back
98CF .init_poll_counters←1← 98C8 BNE
LDA #0 ; A=0: initial value for inner+middle counters
98D1 PHA ; Push it -- middle counter at stack[X+2]
98D2 PHA ; Push it again -- inner counter at stack[X+1]
98D3 TAY ; Y=0: indirect index for net_tx_ptr poll
98D4 TSX ; Capture S into X so we can address the stack counters
98D5 .loop_poll_tx←4← 98DC BNE← 98E1 BNE← 98E6 BNE← 98F4 BNE
LDA (net_tx_ptr),y ; Read RX/TX flags through net_tx_ptr -- bit 7 set means complete
98D7 BMI done_poll_tx ; Bit 7 set: reply received, exit poll
98D9 DEC error_text,x ; Decrement inner counter at stack[X+1]
98DC BNE loop_poll_tx ; Inner not zero yet: poll again
98DE DEC stack_page_2,x ; Inner wrapped: decrement middle at stack[X+2]
98E1 BNE loop_poll_tx ; Middle not zero: poll again
98E3 DEC stack_page_4,x ; Middle wrapped: decrement outer at stack[X+4] (the saved timeout value)
98E6 BNE loop_poll_tx ; Outer not zero: poll again
98E8 LDA rx_wait_timeout ; Reload the original timeout to test for timeout=0 mode
98EB BNE done_poll_tx ; Configured timeout was non-zero: declare timeout
98ED LDA escape_flag ; Timeout=0 (poll forever): check escape flag
98EF BMI raise_escape_error ; Escape pressed: jump to escape handler at &9895
98F1 INC stack_page_4,x ; Reset outer counter so we keep polling
98F4 BNE loop_poll_tx ; Always taken (INC's result is always non-zero here): back to inner
98F6 .done_poll_tx←2← 98D7 BMI← 98EB BNE
PLA ; done_poll_tx: discard inner counter
98F7 PLA ; Discard middle counter
98F8 PLA ; Pull saved econet_flags
98F9 STA econet_flags ; Restore them (clearing bit 7 if we set it)
98FC PLA ; Pull saved rx_wait_timeout into A
98FD BEQ build_no_reply_error ; If timeout reached zero, raise 'No reply'
98FF .return_3←1← 9893 BPL
RTS ; Reply received normally: return

Conditionally store error code to workspace

Tests bit 7 of fs_flags (FS-selected flag):

Bit 7 Action
clear return immediately
set store A into hazel_fs_last_error (&0E09)

This guards against writing error state when no filing system is active. Called internally by the error-classification chain and by error_inline_log.

On EntryAerror code to store
9900 .cond_save_error_code←6← 9916 JSR← 994F JSR← 996B JSR← 9995 JSR← 99A7 JSR← 99C0 JSR
BIT fs_flags ; Test bit 7 of fs_flags (FS-active flag)
9903 BPL rts_cond_save_err ; FS not active: skip the save
9905 STA hazel_fs_last_error ; FS active: store error code at &C009 (last-error byte)
9908 .rts_cond_save_err←1← 9903 BPL
RTS ; Return
9909 .build_no_reply_error←1← 98FD BEQ
LDX #8 ; X=8: net_error_lookup_data offset for 'No reply' message
990B LDY net_error_lookup_data,x ; Y = message offset within the string table (&9AA4 base)
990E LDX #0 ; X=0: error-text buffer index
9910 STX error_block ; Zero the &0100 length byte (length will be filled in later)
9913 LDA error_msg_table,y ; Read first message byte (the error code)
9916 JSR cond_save_error_code ; Conditionally save it as last-error
9919 .loop_copy_no_reply_msg←1← 9923 BNE
LDA error_msg_table,y ; Read next message byte
991C STA error_text,x ; Append to error-text buffer at &0101+X
991F BEQ done_no_reply_msg ; Null terminator: message done
9921 INX ; Step buffer index
9922 INY ; Step source offset
9923 BNE loop_copy_no_reply_msg ; Loop while Y != 0 (Y wraps at 256, not expected)
9925 .done_no_reply_msg←1← 991F BEQ
JSR append_drv_dot_num ; Append ' on drive <num>' or similar context
9928 LDA #0 ; A=0: null terminator
992A STA error_text,x ; Store at end of message
992D JMP check_net_error_code ; Tail-jump to dispatch the BRK error

Substitute 'B' for 'A' in reply status byte

Reads the FS reply status byte at (net_tx_ptr,X). If it is 'A' (Acknowledge with no error), substitutes 'B' so downstream code treats it as a soft error. CLV before falling through into mask_error_class to ensure the no-extended-error path is taken.

On EntryXindirect index into net_tx_ptr
On ExitAreply status byte (with A->B substitution)
V0 (clear)
9930 .fixup_reply_status_a←1← 9BB3 JMP
LDA (net_tx_ptr,x) ; Read FS reply status byte at (net_tx_ptr,X)
9932 CMP #'A' ; Status 'A'? (Acknowledge with no error)
9934 BNE skip_if_not_a ; Not 'A': pass through unchanged
9936 LDA #'B' ; Substitute 'B' for 'A' (handle ACK as a soft error)
9938 .skip_if_not_a←1← 9934 BNE
CLV ; Clear V to take the standard mask path
9939 BVC mask_error_class ; Always taken: use the standard masked-error path
fall through ↓

Load reply byte and classify error

Single-byte prologue to classify_reply_error: LDA (net_tx_ptr,X) reads the FS reply status byte, then falls through. Single caller (&9B6C, after a recv-and-classify path that already has X set).

On EntryXindirect index into net_tx_ptr
993B .load_reply_and_classify←1← 9B6C JMP
LDA (net_tx_ptr,x) ; Read FS reply status byte
fall through ↓

Classify FS reply error code

Forces V=1 via BIT always_set_v_byte (signals the extended-error path), masks the error code in A to 3 bits (the error class 0..7), saves the class on the stack, and dispatches:

Class Path
2 (station-related) multi-line build_no_reply_error
other build_simple_error

Two callers: raise_escape_error (with A=6) and the FS reply dispatch at &A0E3.

On EntryAerror code byte
993D .classify_reply_error←2← 989C JMP← A0E5 JMP
BIT always_set_v_byte ; BIT $always_set_v_byte: force V=1 (extended-error path)
9940 .mask_error_class←1← 9939 BVC
AND #7 ; Mask to 3 bits (error class 0..7)
9942 PHA ; Save error class on stack
9943 CMP #2 ; Class 2 = 'station-related' family?
9945 BNE build_simple_error ; No: build a simple one-line error
9947 PHP ; Class 2 yes: save flags so we can branch on V later
9948 TAX ; X = error class (=2)
9949 LDY net_error_lookup_data,x ; Y = lookup-table offset
994C LDA error_msg_table,y ; Read first message byte (error code)
994F JSR cond_save_error_code ; Conditionally save it
9952 LDX #0 ; X=0: text-buffer index
9954 STX error_block ; Zero length byte
9957 .loop_copy_station_msg←1← 9961 BNE
LDA error_msg_table,y ; Read message byte
995A STA error_text,x ; Append to buffer
995D BEQ done_station_msg ; Null terminator -- station message done
995F INY ; Advance Y
9960 INX ; Advance X
9961 BNE loop_copy_station_msg ; Loop until X wraps
9963 .done_station_msg←1← 995D BEQ
JSR append_drv_dot_num ; Append ' on drive <num>' suffix
9966 PLP ; Restore the saved class flags
9967 BVS suffix_not_listening ; V was set: use 'not listening' suffix
9969 LDA #&a4 ; A=&A4: 'station <n> not available' error code
996B JSR cond_save_error_code ; Save the alternative error code
996E STA error_text ; Patch error-text buffer length byte
9971 LDY #&0b ; Y=&0B: lookup index for the listening-station suffix
9973 BNE load_suffix_offset ; Always taken (Y is non-zero); jump to load_suffix_offset
9975 .suffix_not_listening←1← 9967 BVS
LDY #9 ; V was clear: 'not listening' suffix variant
9977 .load_suffix_offset←1← 9973 BNE
LDA net_error_lookup_data,y ; Read suffix offset from lookup
997A TAY ; Y = suffix offset
997B .loop_copy_suffix←1← 9985 BNE
LDA error_msg_table,y ; Read suffix byte
997E STA error_text,x ; Append
9981 BEQ done_suffix ; Null: suffix done
9983 INY ; Step Y
9984 .suffix_copy_loop
INX ; Step X
9985 BNE loop_copy_suffix ; Loop while X != 0 (max 255 chars)
9987 .done_suffix←1← 9981 BEQ
BEQ check_msg_terminator ; Always taken (Z still set from BEQ): final terminator check
9989 .build_simple_error←2← 8B6A JMP← 9945 BNE
TAX ; X = error class
998A LDY net_error_lookup_data,x ; Y = lookup-table offset
998D LDX #0 ; X=0: buffer index
998F STX error_block ; Zero length
9992 LDA error_msg_table,y ; Read first message byte (error code)
9995 JSR cond_save_error_code ; Conditionally save it
9998 .loop_copy_error_msg←1← 99A2 BNE
LDA error_msg_table,y ; Read next message byte
999B STA error_text,x ; Append to buffer
999E .check_msg_terminator←1← 9987 BEQ
BEQ check_net_error_code ; Null terminator -> dispatch
99A0 INY ; Step Y
99A1 INX ; Step X
99A2 .bad_str_anchor
BNE loop_copy_error_msg ; Loop while X != 0
99A4 EQUS "Bad"
fall through ↓

Generate 'Bad ...' BRK error from inline string

Like error_inline, but prepends 'Bad ' to the error message. Copies the prefix from a lookup table, then appends the null-terminated inline string. The error number is passed in A. Never returns.

On EntryAerror number
99A7 .error_bad_inline←11← 90C0 JSR← 9357 JSR← 9364 JSR← 9378 JSR← 9384 JSR← 9393 JSR← 9440 JSR← 94C2 JSR← 94E3 JSR← A5B9 JSR← BF7A JSR
JSR cond_save_error_code ; Conditionally log error code to workspace
99AA TAY ; Save error number in Y
99AB PLA ; Pop return address (low) — points to last byte of JSR
99AC STA fs_load_addr ; Store return address low
99AE PLA ; Pop return address (high)
99AF STA fs_load_addr_hi ; Store return address high
99B1 LDX #0 ; X=0: start of prefix string
99B3 .loop_copy_bad_prefix←1← 99BC BNE
INX ; Copy 'Bad ' prefix from lookup table
99B4 LDA bad_prefix_table,x ; Get next prefix character
99B7 STA error_text,x ; Store in error text buffer
99BA CMP #' ' ; Is it space (end of 'Bad ')?
99BC BNE loop_copy_bad_prefix ; No: copy next prefix character
99BE BEQ write_error_num_and_str ; Branch to write the error number and string
fall through ↓

Generate BRK error from inline string (with logging)

Like error_inline, but first conditionally logs the error code to workspace via cond_save_error_code before building the error block.

On EntryAerror number
99C0 .error_inline_log←12← 956B JSR← 9883 JSR← A5D0 JSR← AF9F JSR← AFB5 JSR← AFCB JSR← B84A JSR← B8C0 JSR← B911 JSR← BBAB JSR← BBE5 JSR← BC2F JSR
JSR cond_save_error_code ; Conditionally log error code to workspace
fall through ↓

Generate BRK error from inline string

Pops the return address from the stack and copies the null-terminated inline string into the error block at &0100. The error number is passed in A. Never returns — triggers the error via JMP error_block.

On EntryAerror number (stored in error block at &0101)
99C3 .error_inline←4← A45A JSR← BD69 JSR← BF0D JSR← BFC3 JSR
TAY ; Save error number in Y
99C4 PLA ; Pop return address (low) — points to last byte of JSR
99C5 STA fs_load_addr ; Store return address low
99C7 PLA ; Pop return address (high)
99C8 STA fs_load_addr_hi ; Store return address high
99CA LDX #0 ; X=0: error text index
99CC .write_error_num_and_str←1← 99BE BEQ
STY error_text ; Store error number in error block
99CF TYA ; Copy error number to A
99D0 PHA ; Push error number on stack
99D1 LDY #0 ; Y=0: inline string index
99D3 STY error_block ; Zero the BRK byte at &0100
99D6 .loop_copy_inline_str←1← 99DD BNE
INX ; Copy inline string into error block
99D7 INY ; Advance string index
99D8 LDA (fs_load_addr),y ; Read next byte from inline string
99DA STA error_text,x ; Store byte in error block
99DD BNE loop_copy_inline_str ; Loop until null terminator
fall through ↓

Translate net error: 'OK' → return, 'FS error' → append

Reads the receive-attribute byte:

Receive attribute Action
non-zero network error – branch to handle_net_error
zero, saved error = &DE (FS error code) branch to append_error_number to add the FS-specific code to the error text
zero, saved error other tail-jump to &0100 (BRK error block) to trigger BRK and let MOS dispatch
99DF .check_net_error_code←4← 984D JMP← 992D JMP← 999E BEQ← BD34 JMP
JSR read_rx_attribute ; Read receive attribute byte
99E2 BNE handle_net_error ; Non-zero: network returned an error
99E4 PLA ; Pop saved error number
99E5 CMP #&de ; Was it &DE (file server error)?
99E7 BEQ append_error_number ; Yes: append error number and trigger BRK
99E9 .trigger_brk←1← 9A38 BEQ
JMP error_block ; Jump to BRK via error block
99EC .handle_net_error←1← 99E2 BNE
STA hazel_fs_error_code ; Store error code in workspace
99EF PHA ; Push error code
99F0 TXA ; Save X (error text index)
99F1 PHA ; Push X
99F2 JSR read_rx_attribute ; Read receive attribute byte
99F5 STA fs_load_addr ; Save to fs_load_addr as spool handle
99F7 LDA #0 ; A=0: clear error code in RX buffer
99F9 STA (net_rx_ptr),y ; Zero the error code byte in buffer
99FB LDA #&c6 ; A=&C6: OSBYTE read spool handle
99FD JSR osbyte_x0 ; Read current spool file handle
9A00 CPY fs_load_addr ; Compare Y result with saved handle
9A02 BEQ net_error_close_spool ; Match: close the spool file
9A04 CPX fs_load_addr ; Compare X result with saved handle
9A06 BNE done_close_files ; No match: skip spool close
9A08 PHA ; Push A (preserved)
9A09 LDA #&c6 ; A=&C6: disable spool with OSBYTE
9A0B BNE close_spool_exec ; ALWAYS branch to close spool
9A0D .net_error_close_spool←1← 9A02 BEQ
PHY ; Save Y
9A0E LDA #&c7 ; A=&C7: OSBYTE 'flush input buffer'
9A10 .close_spool_exec←1← 9A0B BNE
JSR osbyte_x0_y0 ; Tail-call OSBYTE with X=0/Y=0
9A13 PLY ; Restore Y
9A14 LDA #osfind_close ; A=0: close file
9A16 JSR osfind ; Close the spool/exec file osfind: close one or all files
9A19 .done_close_files←1← 9A06 BNE
PLA ; Pull saved X (error text index)
9A1A TAX ; Restore X
9A1B LDY #&0a ; Y=&0A: lookup index for 'on channel'
9A1D LDA net_error_lookup_data,y ; Load message offset from lookup table
9A20 TAY ; Transfer offset to Y
9A21 .loop_copy_channel_msg←1← 9A2B BNE
LDA error_msg_table,y ; Load error message byte
9A24 STA error_text,x ; Append to error text buffer
9A27 BEQ append_error_number ; Null terminator: done copying
9A29 INX ; Advance error text index
9A2A INY ; Advance message index
9A2B BNE loop_copy_channel_msg ; Loop until full message copied
9A2D .append_error_number←2← 99E7 BEQ← 9A27 BEQ
STX fs_load_addr_2 ; Save error text end position
9A2F PLA ; Pull saved error number
9A30 JSR append_space_and_num ; Append ' nnn' error number suffix
9A33 LDA #0 ; A=0: null terminator
9A35 STA stack_page_2,x ; Terminate error text string
9A38 BEQ trigger_brk ; ALWAYS branch to trigger BRK error
fall through ↓

Append 'net.station' decimal string to error text

Reads network and station numbers from the TX control block at offsets 3 and 2. Writes:

  1. A space separator.
  2. The network number as decimal (if non-zero).
  3. A dot ('.').
  4. The station number as decimal digits.

into the error-text buffer at the current position.

On EntryXerror text buffer index
On ExitXupdated buffer index past appended text
9A3A .append_drv_dot_num←2← 9925 JSR← 9963 JSR
LDA #' ' ; A=' ': space separator
9A3C STA error_text,x ; Append space to error text
9A3F INX ; Advance error text index
9A40 STX fs_load_addr_2 ; Save position for number formatting
9A42 LDY #3 ; Y=3: offset to network number in TX CB
9A44 LDA (net_tx_ptr),y ; Load network number
9A46 BEQ append_station_num ; Zero: skip network part (local)
9A48 JSR append_decimal_num ; Append network number as decimal
9A4B LDX fs_load_addr_2 ; Reload error text position
9A4D LDA #'.' ; A='.': dot separator
9A4F STA error_text,x ; Append dot to error text
9A52 INC fs_load_addr_2 ; Advance past dot
9A54 .append_station_num←1← 9A46 BEQ
LDY #2 ; Y=2: offset to station number in TX CB
9A56 LDA (net_tx_ptr),y ; Load station number
9A58 JSR append_decimal_num ; Append station number as decimal
9A5B LDX fs_load_addr_2 ; Reload error text position
9A5D RTS ; Return

Append space and decimal number to error text

Writes a space character to the error text buffer at the current position (fs_load_addr_2), then falls through to append_decimal_num to convert the value in A to decimal digits with leading zero suppression.

On EntryAnumber to append (0-255)
9A5E .append_space_and_num←2← 9A30 JSR← B89F JSR
TAY ; Save number in Y
9A5F LDA #' ' ; A=' ': space prefix
9A61 LDX fs_load_addr_2 ; Load current error text position
9A63 STA error_text,x ; Append space to error text
9A66 INC fs_load_addr_2 ; Advance position past space
9A68 TYA ; Restore number to A
fall through ↓

Convert byte to decimal and append to error text

Extracts hundreds, tens and units digits by three successive calls to append_decimal_digit. Uses the V flag to suppress leading zeros — hundreds and tens are skipped when zero, but the units digit is always emitted.

On EntryAnumber to convert (0-255)
9A69 .append_decimal_num←2← 9A48 JSR← 9A58 JSR
TAY ; Save number in Y for division
9A6A BIT always_set_v_byte ; Set V: suppress leading zeros
9A6D LDA #&64 ; A=100: hundreds digit divisor
9A6F JSR append_decimal_digit ; Extract and append hundreds digit
9A72 LDA #&0a ; A=10: tens digit divisor
9A74 JSR append_decimal_digit ; Extract and append tens digit
9A77 LDA #1 ; A=1: units digit (remainder)
9A79 CLV ; Clear V: always print units digit
fall through ↓

Extract and append one decimal digit

Divides Y by A using repeated subtraction to extract a single decimal digit. Stores the ASCII digit in the error text buffer at fs_load_addr_2 unless V is set and the quotient is zero (leading zero suppression). Returns the remainder in Y for subsequent digit extraction.

On EntryAdivisor (100, 10, or 1)
Ynumber to divide
Vset to suppress leading zero
On ExitYremainder after division
Vclear once a non-zero digit is emitted
9A7A .append_decimal_digit←2← 9A6F JSR← 9A74 JSR
STA fs_load_addr_3 ; Store divisor
9A7C TYA ; Copy number to A for division
9A7D LDX #&2f ; X='0'-1: digit counter (ASCII offset)
9A7F PHP ; Save V flag (leading zero suppression)
9A80 SEC ; Set carry for subtraction
9A81 .loop_count_digit←1← 9A84 BCS
INX ; Increment digit counter
9A82 SBC fs_load_addr_3 ; Subtract divisor
9A84 BCS loop_count_digit ; Not negative yet: continue counting
9A86 ADC fs_load_addr_3 ; Add back divisor (restore remainder)
9A88 PLP ; Restore V flag
9A89 TAY ; Save remainder back to Y
9A8A TXA ; Digit counter to A (ASCII digit)
9A8B CMP #'0' ; Is digit '0'?
9A8D BNE store_digit ; Non-zero: always print
9A8F BVS rts_store_digit ; V set (suppress leading zeros): skip
9A91 .store_digit←1← 9A8D BNE
CLV ; Clear V: first non-zero digit seen
9A92 LDX fs_load_addr_2 ; Load current text position
9A94 STA error_text,x ; Store ASCII digit in error text
9A97 INC fs_load_addr_2 ; Advance text position
9A99 .rts_store_digit←1← 9A8F BVS
RTS ; Return

Net-error class -> error_msg_table offset (12 bytes)

Maps Econet network-error classes to byte offsets into error_msg_table.

  • Indices 0-7 are keyed by error class (the reply byte AND 7).
  • Index 8 is used by build_no_reply_error to locate the 'No reply from station' message head.
  • Indices 9-11 point to the suffix strings appended after the station address in compound errors (' not listening', ' on channel', ' not present').

Each byte is computed as <message-label> - error_msg_table so the table reflows automatically if a message string is edited.

9A9A .net_error_lookup_data←5Used as index base by← 990B LDY← 9949 LDY← 9977 LDA← 998A LDY← 9A1D LDA
EQUB &00
9A9B EQUB msg_net_error - error_msg_table
9A9C EQUB msg_station - error_msg_table
9A9D EQUB msg_no_clock - error_msg_table
9A9E EQUB msg_escape - error_msg_table
9A9F EQUB msg_escape - error_msg_table
9AA0 EQUB msg_escape - error_msg_table
9AA1 EQUB msg_bad_option - error_msg_table
9AA2 EQUB msg_no_reply - error_msg_table
9AA3 EQUB msg_not_listening - error_msg_table
9AA4 EQUB msg_on_channel - error_msg_table
9AA5 EQUB msg_not_present - error_msg_table

Net-error message strings

Body of error-text fragments referenced by net_error_lookup_data. Two layouts coexist:

  1. Error entries (offsets 0..&3F) — one byte holding the BRK error code, immediately followed by the null-terminated message string:

    <err_code> <message-bytes...> &00
    
  2. Suffix entries (offsets &56, &65, &71) — bare null-terminated strings appended to a built-up error message; no leading error-code byte.

Per-byte inline comments below name each error code and message; the bytes from this table are read by build_simple_error and build_no_reply_error when classifying a network reply.

9AA6 .error_msg_table←8Used as index base by← 9913 LDA← 9919 LDA← 994C LDA← 9957 LDA← 997B LDA← 9992 LDA← 9998 LDA← 9A21 LDA
EQUB &A0 ; BRK error code &A0 (first table entry)
9AA7 EQUS "Line jammed" ; err_line_jammed = &A0
9AB2 EQUB &00 ; Null terminator
9AB3 .msg_net_error
EQUB &A1 ; Error &A1: Net error
9AB4 EQUS "Net error" ; err_net_error = &A1
9ABD EQUB &00 ; Null terminator
9ABE .msg_station
EQUB &A2 ; Error &A2: Station
9ABF EQUS "Station"
9AC6 EQUB &00
9AC7 .msg_no_clock
EQUB &A3 ; BRK error code &A3
9AC8 EQUS "No clock"
9AD0 EQUB &00 ; Null terminator
9AD1 .msg_escape
EQUB &11 ; Error &11: Escape
9AD2 EQUS "Escape"
9AD8 EQUB &00
9AD9 .msg_bad_option
EQUB &CB ; Error &CB: Bad option
9ADA EQUS "Bad option"
9AE4 EQUB &00 ; Null terminator + Error &A5: No reply from station
9AE5 .msg_no_reply
EQUB &A5 ; BRK error code &A5
9AE6 EQUS "No reply from station" ; err_no_reply = &A5 message body
9AFB EQUB &00 ; Null terminator
9AFC .msg_not_listening
EQUS " not listening" ; Suffix string (offset &56 in lookup)
9B0A EQUB &00 ; Null terminator
9B0B .msg_on_channel
EQUS " on channel" ; Suffix: " on channel"
9B16 EQUB &00 ; Null terminator
9B17 .msg_not_present
EQUS " not present" ; Suffix: " not present"
9B23 EQUB &00 ; Null terminator

Point TX at zero-page TXCB and send

Sets net_tx_ptr/net_tx_ptr_hi to &00C0 (the standard TXCB location in zero page), then falls through to send_net_packet for transmission with retry logic.

On ExitATX result code (0 = success; &40 jammed; &41 not listening; etc.) -- see send_net_packet
9B24 .init_tx_ptr_and_send←2← 97C9 JSR← 9DC8 JSR
LDX #&c0 ; X=&C0: TX control block base (low)
9B26 STX net_tx_ptr ; Set TX pointer low
9B28 LDX #0 ; X=0: TX control block base (high)
9B2A STX net_tx_ptr_hi ; Set TX pointer high (page 0)
fall through ↓

Transmit Econet packet with retry

Two-phase transmit with retry. Loads retry count from tx_retry_count (default &FF = 255; 0 means retry forever). Each failed attempt waits in a nested delay loop: X = TXCB control byte (typically &80), Y = &60; total ~61 ms at 2 MHz (ROM-only fetches, unaffected by video mode).

Phase Activation Behaviour
1 always runs the full count with escape disabled
2 only when tx_retry_count = 0 sets need_release_tube to enable escape checking, retries indefinitely

With default &FF, phase 2 is never entered. Failures go to load_reply_and_classify ('Line jammed', 'Net error', etc.), distinct from the 'No reply' timeout in wait_net_tx_ack.

On ExitATX result (0 = success; non-zero = error class consumed by the BRK path)
9B2C .send_net_packet←6← AD1B JMP← AD78 JSR← AF6A JSR← B00A JSR← B454 JSR← B604 JSR
LDA tx_retry_count ; Load retry count from workspace
9B2F BNE set_timeout ; Non-zero: use configured retry count
9B31 LDA #&ff ; A=&FF: default retry count (255)
9B33 .set_timeout←1← 9B2F BNE
LDY #&60 ; Y=&60: timeout value
9B35 PHA ; Push retry count
9B36 TYA ; A=&60: copy timeout to A
9B37 PHA ; Push timeout
9B38 LDX #0 ; X=0: TX pointer index
9B3A LDA (net_tx_ptr,x) ; Load first byte of TX control block
9B3C .start_tx_attempt←1← 9B5E BEQ
STA (net_tx_ptr,x) ; Restore control byte (overwritten by result code on retry)
9B3E PHA ; Push control byte
9B3F JSR poll_econet_data_continue_frame_status ; Poll ADLC until line idle
9B42 ASL ; Bit 6 (error flag) into N
9B43 BPL tx_success ; N=0 (bit 6 clear): success
9B45 ASL ; Shift away error flag, keep error type
9B46 BEQ tx_send_error ; Z=1 (no type bits): fatal; Z=0: retryable
9B48 JSR check_escape_and_classify ; Check for escape condition
9B4B PLA ; Pull control byte
9B4C TAX ; Restore to X
9B4D PLA ; Pull timeout
9B4E TAY ; Restore to Y
9B4F PLA ; Pull retry count
9B50 BEQ try_alternate_phase ; Zero retries remaining: try alternate
9B52 .loop_retry_tx←1← 9B69 BNE
SBC #1 ; Decrement retry counter
9B54 PHA ; Push updated retry count
9B55 TYA ; Copy timeout to A
9B56 PHA ; Push timeout for delay loop
9B57 TXA ; Copy control byte to A
9B58 .loop_tx_delay←2← 9B59 BNE← 9B5C BNE
DEX ; Inner delay: decrement X
9B59 BNE loop_tx_delay ; Loop until X=0
9B5B DEY ; Decrement outer counter Y
9B5C BNE loop_tx_delay ; Loop until Y=0
9B5E BEQ start_tx_attempt ; ALWAYS branch: retry transmission
9B60 .try_alternate_phase←1← 9B50 BEQ
CMP tx_retry_count ; Compare retry count with alternate
9B63 BNE tx_send_error ; Different: go to error handling
9B65 LDA #&80 ; A=&80: set escapable flag
9B67 STA need_release_tube ; Mark as escapable for second phase
9B69 BNE loop_retry_tx ; ALWAYS branch: retry with escapable
9B6B .tx_send_error←2← 9B46 BEQ← 9B63 BNE
TAX ; Result code to X
9B6C JMP load_reply_and_classify ; Jump to classify reply and return
9B6F .tx_success←1← 9B43 BPL
PLA ; Pull control byte
9B70 PLA ; Pull timeout
9B71 PLA ; Pull retry count
9B72 JMP clear_escapable ; Clear escapable flag and return

Pass-through TX buffer template (12 bytes)

Overlaid onto the TX control block by setup_pass_txbuf for pass-through operations. The 12 bytes follow the Econet TXCB layout used elsewhere in this ROM (compare bridge_rxcb_init_data):

Offset Field
0 TX control byte (&88 = immediate TX)
1 TX port (&00 = immediate op)
2-3 dest station / network (&FD skip = preserve)
4-5 buffer start address (lo, hi)
6-7 extended-address fill (&FF&FF)
8-9 buffer end address (lo, hi)
10-11 extended-address fill (&FF&FF)

The buffer spans &0D3A..&0D3E -- the bytes immediately preceding rx_src_stn through net_frame_flags -- so the same RX-area bytes are echoed back as the TX payload (hence "pass-through"). The &FF&FF filler bytes at offsets 6-7 and 10-11 are a software convention left over from a 4-byte-address format the BBC Econet driver anticipated; for main-RAM buffers they're left as &FF&FF. Original TX buffer values are pushed on the stack and restored after transmission.

9B75 .pass_txbuf_init_table←2Used as index base by← 9B8B LDX← 9BE5 LDX
EQUB &88 ; Offset 0: ctrl = &88 (immediate TX)
9B76 EQUB &00 ; Offset 1: port = &00 (immediate op)
9B77 EQUB &FD ; Offset 2: &FD skip (preserve dest stn)
9B78 EQUB &FD ; Offset 3: &FD skip (preserve dest net)
9B79 EQUB &3A ; Offset 4: buf start lo (&3A) -> &0D3A
9B7A EQUB &0D ; Offset 5: buf start hi (&0D) -> &0D3A
9B7B EQUB &FF ; Offset 6: extended-addr fill (&FF)
9B7C EQUB &FF ; Offset 7: extended-addr fill (&FF)
9B7D EQUB &3E ; Offset 8: buf end lo (&3E) -> &0D3E
9B7E EQUB &0D ; Offset 9: buf end hi (&0D) -> &0D3E
9B7F EQUB &FF ; Offset 10: extended-addr fill (&FF)
9B80 EQUB &FF ; Offset 11: extended-addr fill (&FF)

Set up TX pointer and send pass-through packet

Copies the template into the TX buffer (skipping &FD markers), saves original values on stack, then polls the ADLC and retries until complete.

On ExitATX result (from poll_econet_data_continue_frame_status)
9B81 .init_tx_ptr_for_pass←1← 8E32 JSR
LDY #&c0 ; Y=&C0: TX control block base (low)
9B83 STY net_tx_ptr ; Set TX pointer low byte
9B85 LDY #0 ; Y=0: TX control block base (high)
9B87 STY net_tx_ptr_hi ; Set TX pointer high byte
fall through ↓

Initialise TX buffer from pass-through template

Copies 12 bytes from pass_txbuf_init_table into the TX control block, pushing the original values on the stack for later restoration. Skips offsets marked &FD in the template. Starts transmission via poll_econet_data_continue_frame_status and retries on failure, restoring the original TX buffer contents when done.

On ExitATX result (from poll_econet_data_continue_frame_status)
9B89 .setup_pass_txbuf←1← AF67 JSR
LDY #&0b ; Y=&0B: 12 bytes to process (0-11)
9B8B .loop_copy_template←1← 9B99 BPL
LDX pass_txbuf_init_table,y ; Load template byte for this offset
9B8E CPX #&fd ; Is it &FD (skip marker)?
9B90 BEQ skip_template_byte ; Yes: skip this offset, don't modify
9B92 LDA (net_tx_ptr),y ; Load existing TX buffer byte
9B94 PHA ; Save original value on stack
9B95 TXA ; Copy template value to A
9B96 STA (net_tx_ptr),y ; Store template value to TX buffer
9B98 .skip_template_byte←1← 9B90 BEQ
DEY ; Next offset (descending)
9B99 BPL loop_copy_template ; Loop until all 12 bytes processed
9B9B LDA peek_retry_count ; Load pass-through control value
9B9E PHA ; Push control value
9B9F TYA ; A=&FF (Y is &FF after loop)
9BA0 PHA ; Push &FF as timeout
9BA1 LDX #0 ; X=0: TX pointer index
9BA3 LDA (net_tx_ptr,x) ; Load control byte from TX CB
9BA5 .start_pass_tx←1← 9BDE BEQ
STA (net_tx_ptr,x) ; Write control byte to start TX
9BA7 PHA ; Save control byte on stack
9BA8 JSR poll_econet_data_continue_frame_status ; Poll ADLC until line idle
9BAB ASL ; Shift result: check bit 6 (success)
9BAC BPL pass_tx_success ; Bit 6 clear: transmission complete
9BAE ASL ; Shift result: check bit 5 (fatal)
9BAF BNE restore_retry_state ; Non-zero (not fatal): retry
9BB1 .done_pass_retries←1← 9BD0 BEQ
LDX #0 ; X=0: clear error status
9BB3 JMP fixup_reply_status_a ; Jump to fix up reply status

Wait for TX ready, then start new transmission

  1. Polls tx_complete_flag via ASL (testing bit 7) until set, indicating any previous TX operation has completed and the ADLC is back in idle RX-listen mode.
  2. Copies the TX control-block pointer from net_tx_ptr to nmi_tx_block.
  3. Calls tx_begin, which performs a complete transmission from scratch (copies destination from TXCB to scout buffer, polls for INACTIVE, configures ADLC CR1=&44 RX_RESET|TIE, CR2=&E7 RTS|CLR, runs the full four-way handshake via NMI).
  4. After tx_begin returns, polls the TXCB first byte until bit 7 clears (NMI handler stores result there).

Result in A:

Code Meaning
&00 success
&40 jammed
&41 not listening
&43 no clock
&44 bad control byte
On ExitATX result (&00 success / &40 jammed / &41 not listening / &43 no clock / &44 bad control byte)
9BB6 .poll_econet_data_continue_frame_status←3← 9B3F JSR← 9BA8 JSR← 9BB9 BCC
ASL tx_complete_flag ; Shift ws_0d60 left to poll ADLC
9BB9 BCC poll_econet_data_continue_frame_status ; Bit not set: keep polling
9BBB LDA net_tx_ptr ; Copy TX pointer low to NMI TX block
9BBD STA nmi_tx_block ; Store in NMI TX block low
9BBF LDA net_tx_ptr_hi ; Copy TX pointer high
9BC1 STA nmi_tx_block_hi ; Store in NMI TX block high
9BC3 JSR tx_setup_from_txcb ; Begin Econet frame transmission
9BC6 .loop_poll_pass_tx←1← 9BC8 BMI
LDA (net_tx_ptr,x) ; Read TX status byte
9BC8 BMI loop_poll_pass_tx ; Bit 7 set: still transmitting
9BCA RTS ; Return with result in A
9BCB .restore_retry_state←1← 9BAF BNE
PLA ; Pull control byte
9BCC TAX ; Restore to X
9BCD PLA ; Pull timeout
9BCE TAY ; Restore to Y
9BCF PLA ; Pull retry count
9BD0 BEQ done_pass_retries ; Zero retries: go to error handling
9BD2 SBC #1 ; Decrement retry counter
9BD4 PHA ; Push updated retry count
9BD5 TYA ; Copy timeout to A
9BD6 PHA ; Push timeout
9BD7 TXA ; Copy control byte to A
9BD8 .loop_pass_tx_delay←2← 9BD9 BNE← 9BDC BNE
DEX ; Inner delay loop: decrement X
9BD9 BNE loop_pass_tx_delay ; Loop until X=0
9BDB DEY ; Decrement outer counter Y
9BDC BNE loop_pass_tx_delay ; Loop until Y=0
9BDE BEQ start_pass_tx ; ALWAYS branch: retry transmission
9BE0 .pass_tx_success←1← 9BAC BPL
PLA ; Pull control byte (discard)
9BE1 PLA ; Pull timeout (discard)
9BE2 PLA ; Pull retry count (discard)
9BE3 LDY #0 ; Y=0: start restoring from offset 0
9BE5 .loop_restore_txbuf←1← 9BF2 BNE
LDX pass_txbuf_init_table,y ; Load template byte for this offset
9BE8 CPX #&fd ; Is it &FD (skip marker)?
9BEA BEQ skip_restore_byte ; Yes: don't restore this offset
9BEC PLA ; Pull original value from stack
9BED STA (net_tx_ptr),y ; Restore original TX buffer byte
9BEF .skip_restore_byte←1← 9BEA BEQ
INY ; Next offset (ascending)
9BF0 CPY #&0c ; Processed all 12 bytes?
9BF2 BNE loop_restore_txbuf ; No: continue restoring
9BF4 RTS ; Return with TX buffer restored

Copy text pointer from FS options and parse string

Reads a 2-byte address from (fs_options)+0/1 into os_text_ptr (&00F2), resets Y to zero, then falls through to gsread_to_buf to parse the string at that address into the &0E30 buffer.

On ExitY0 (reset before GSINIT)
9BF5 .load_text_ptr_and_parse←1← 9C25 JSR
LDY #1 ; Y=1: start at second byte of pointer
9BF7 .loop_copy_text_ptr←1← 9BFD BPL
LDA (fs_options),y ; Load pointer byte from FS options
9BF9 STA os_text_ptr,y ; Store in OS text pointer
9BFC DEY ; Decrement index
9BFD BPL loop_copy_text_ptr ; Loop until both bytes copied
9BFF INY ; Y=0: reset index for string reading
fall through ↓

Parse command line via GSINIT/GSREAD into hazel_parse_buf

Calls GSINIT to initialise string reading, then loops calling GSREAD to copy characters into hazel_parse_buf until end-of-string. Appends a CR terminator and sets fs_crc_lo/hi to point at the buffer for subsequent parsing routines.

On EntryYcurrent command-line offset (consumed by GSINIT)
On ExitYadvanced past the parsed source
9C00 .gsread_to_buf←1← B261 JSR
LDX #&ff ; X=&FF: pre-increment for buffer index
9C02 CLC ; C=0: initialise for string input
9C03 JSR gsinit ; GSINIT: initialise string reading
9C06 BEQ terminate_buf ; Z set (empty string): store terminator
9C08 .loop_gsread_char←1← 9C11 BCC
JSR gsread ; GSREAD: read next character
9C0B BCS terminate_buf ; C set: end of string reached
9C0D INX ; Advance buffer index
9C0E STA hazel_parse_buf,x ; Store character in fs_filename_buf buffer
9C11 BCC loop_gsread_char ; ALWAYS branch: read next character
9C13 .terminate_buf←2← 9C06 BEQ← 9C0B BCS
INX ; Advance past last character
9C14 LDA #&0d ; A=CR: terminate filename
9C16 STA hazel_parse_buf,x ; Store CR terminator in buffer
9C19 LDA #<(hazel_parse_buf) ; Parse-buffer pointer (low)
9C1B STA fs_crc_lo ; Set command text pointer low
9C1D LDA #>(hazel_parse_buf) ; Parse-buffer pointer (high)
9C1F STA fs_crc_hi ; Set command text pointer high
9C21 RTS ; Return with buffer filled

FILEV vector handler: OSFILE

Reached via the FILEV vector at &0212. Sets up transfer parameters via set_xfer_params, loads the OS text pointer and parses the filename via load_text_ptr_and_parse, mask_owner_access clears the FS-selection bits, and parse_access_prefix records any access-byte prefix. Routes by fs_last_byte_flag bit: positive (read / display) goes to check_display_type; negative (write / save) falls into the create-new-file path.

On EntryAOSFILE function code
X, Ycontrol-block pointer (low, high)
9C22 .filev_handler
JSR set_xfer_params ; Set up transfer parameters
9C25 JSR load_text_ptr_and_parse ; Load text pointer and parse filename
9C28 JSR mask_owner_access ; Set owner-only access mask
9C2B JSR parse_access_prefix ; Parse access prefix from filename
9C2E LDA fs_last_byte_flag ; Load last byte flag
9C30 BPL check_display_type ; Positive (not last): display file info
9C32 CMP #&ff ; Is it &FF (last entry)?
9C34 BEQ copy_arg_and_enum ; Yes: copy arg and iterate
9C36 JMP return_with_last_flag ; Other value: return with flag
9C39 .copy_arg_and_enum←1← 9C34 BEQ
JSR copy_arg_to_buf_x0 ; Copy argument to buffer at X=0
9C3C LDY #2 ; Y=2: enumerate directory command
fall through ↓

Execute one iteration of a multi-step FS command

Called by match_fs_cmd for commands that enumerate directory entries. Sets port &92, sends the initial request via send_request_write, then synchronises the FS options and workspace state (order depends on the cycle flag at offset 6). Copies 4 address bytes, formats the filename field, sends via send_txcb_swap_addrs, and receives the reply.

On EntryYFS function code (matches send_request_write contract)
On ExitAFS reply status
9C3E .do_fs_cmd_iteration←1← A62B JSR
LDA #&92 ; A=&92: FS port number
9C40 STA need_release_tube ; Set escapable flag to &92
9C42 STA hazel_txcb_station ; Store port number in TX buffer
9C45 JSR send_request_write ; Send request to file server
9C48 LDY #6 ; Y=6: offset to response cycle flag
9C4A LDA (fs_options),y ; Load cycle flag from FS options
9C4C BNE copy_ws_then_fsopts ; Non-zero: already initialised
9C4E JSR copy_fsopts_to_zp ; Copy FS options to zero page first
9C51 JSR copy_workspace_to_fsopts ; Then copy workspace to FS options
9C54 BCC setup_txcb_addrs ; Branch to continue (C clear from JSR)
9C56 .copy_ws_then_fsopts←1← 9C4C BNE
JSR copy_workspace_to_fsopts ; Copy workspace to FS options first
9C59 JSR copy_fsopts_to_zp ; Then copy FS options to zero page
9C5C .setup_txcb_addrs←1← 9C54 BCC
LDY #4 ; Y=4: loop counter
9C5E .loop_copy_addrs←1← 9C69 BNE
LDA fs_load_addr,x ; Load address byte from zero page
9C60 STA txcb_end,x ; Save to TXCB end pointer
9C62 ADC hazel_txcb_addr_lo,x ; Add offset from buffer
9C65 STA fs_work_4,x ; Store sum in fs_work area
9C67 INX ; Advance to next byte
9C68 DEY ; Decrement counter
9C69 BNE loop_copy_addrs ; Loop for all 4 bytes
9C6B SEC ; Set carry for subtraction
9C6C SBC hazel_txcb_addr_hi ; Subtract high offset
9C6F STA fs_work_7 ; Store result in fs_work_7
9C71 JSR format_filename_field ; Format filename for display
9C74 JSR send_txcb_swap_addrs ; Send TXCB and swap addresses
9C77 LDX #2 ; X=2: copy 3 offset bytes
9C79 .loop_copy_offsets←1← 9C80 BPL
LDA hazel_txcb_addr_hi,x ; Load offset byte from fs_file_len_3
9C7C STA hazel_txcb_data,x ; Store in fs_cmd_data for next iteration
9C7F DEX ; Decrement counter
9C80 BPL loop_copy_offsets ; Loop until all bytes copied
9C82 JMP recv_reply ; Jump to receive and process reply

Send TXCB and swap start/end addresses

If the 5-byte handle matches, returns immediately. Otherwise sets port &92, copies addresses, sends, waits for acknowledgment, and retries on address mismatch.

On ExitAFS reply status (or unchanged if handles matched -- the routine returns early when no work is needed)
9C85 .send_txcb_swap_addrs←2← 9C74 JSR← A285 JSR
JSR cmp_5byte_handle ; Compare 5-byte handle with current
9C88 BEQ rts_txcb_swap ; Match: no need to send, return
9C8A LDA #&92 ; A=&92: FS reply port number
9C8C STA txcb_port ; Set TXCB port
9C8E .loop_swap_and_send←1← 9CAA BNE
LDX #3 ; X=3: copy 4 bytes
9C90 .loop_copy_start_end←1← 9C99 BPL
LDA txcb_end,x ; Load TXCB end pointer byte
9C92 STA txcb_start,x ; Store in TXCB start pointer
9C94 LDA fs_work_4,x ; Load new end address from fs_work
9C96 STA txcb_end,x ; Store in TXCB end pointer
9C98 DEX ; Decrement counter
9C99 BPL loop_copy_start_end ; Loop for all 4 bytes
9C9B LDA #&7f ; A=&7F: control byte for data transfer
9C9D STA txcb_ctrl ; Set TXCB control byte
9C9F JSR wait_net_tx_ack ; Wait for network TX acknowledgement
9CA2 LDY #3 ; Y=3: compare 4 bytes
9CA4 .loop_verify_addrs←1← 9CAD BPL
LDA txcb_end,y ; Load TXCB end byte
9CA7 EOR fs_work_4,y ; Compare with expected end address
9CAA BNE loop_swap_and_send ; Mismatch: resend from start
9CAC DEY ; Decrement counter
9CAD BPL loop_verify_addrs ; Loop until all 4 bytes match
9CAF .rts_txcb_swap←1← 9C88 BEQ
RTS ; Return (all bytes match)
9CB0 .check_display_type←1← 9C30 BPL
BEQ setup_dir_display ; Z set: directory entry display
9CB2 JMP dispatch_osword_op ; Non-zero: jump to OSWORD dispatch

Compute display deltas and prep FS info request

Iterates 4 times over paired (lo, hi) address words in the FS options block at offsets &0E and &0A (loop body advances Y by 5 each pass). For each pair, computes (high - low), saves both originals to workspace at &00A6+Y (port_ws_offset region), and overwrites the options entry with the difference so the caller can render 'load addr', 'exec addr', 'length', etc. without redoing the subtraction. Then copies 9 bytes of FS-options metadata into the TX buffer at &C103, sets need_release_tube as the escapable flag, and stores FS port &91 (info request) at &C102. Final tail-call dispatches the request via send_request_write.

On ExitA&91 (FS port for info request)
X, Yclobbered
9CB5 .setup_dir_display←2← 9CB0 BEQ← 9DE5 JMP
LDX #4 ; X=4: loop counter for 4 iterations
9CB7 LDY #&0e ; Y=&0E: FS options offset for addresses
9CB9 SEC ; Set carry for subtraction
9CBA .loop_compute_diffs←1← 9CD4 BNE
LDA (fs_options),y ; Load address byte from FS options
9CBC STA port_ws_offset,y ; Save to workspace (port_ws_offset)
9CBF JSR retreat_y_by_4 ; Y -= 4 to point to paired offset
9CC2 SBC (fs_options),y ; Subtract paired value
9CC4 STA hazel_txcb_network,y ; Store difference in fs_cmd_csd buffer
9CC7 PHA ; Push difference
9CC8 LDA (fs_options),y ; Load paired value from FS options
9CCA STA port_ws_offset,y ; Save to workspace
9CCD PLA ; Pull difference back
9CCE STA (fs_options),y ; Store in FS options for display
9CD0 JSR skip_one_and_advance5 ; Advance Y by 5 for next field
9CD3 DEX ; Decrement loop counter
9CD4 BNE loop_compute_diffs ; Loop for all 4 address pairs
9CD6 LDY #9 ; Y=9: copy 9 bytes of options data
9CD8 .loop_copy_fs_options←1← 9CDE BNE
LDA (fs_options),y ; Load FS options byte
9CDA STA hazel_txcb_network,y ; Store in fs_cmd_csd buffer
9CDD DEY ; Decrement index
9CDE BNE loop_copy_fs_options ; Loop until all 9 bytes copied
9CE0 LDA #&91 ; A=&91: FS port for info request
9CE2 STA need_release_tube ; Set escapable flag
9CE4 STA hazel_txcb_station ; Store port in TX buffer
9CE7 STA fs_error_ptr ; Store in fs_error_ptr
9CE9 LDX #&0b ; X=&0B: copy argument at offset 11
9CEB JSR copy_arg_to_buf ; Copy argument to TX buffer
9CEE LDY #1 ; Y=1: info sub-command
9CF0 LDA fs_last_byte_flag ; Load last byte flag
9CF2 CMP #7 ; Is it 7 (catalogue info)?
9CF4 PHP ; Save comparison result
9CF5 BNE send_info_request ; Not 7: keep Y=1
9CF7 LDY #&1d ; Y=&1D: extended info command
9CF9 .send_info_request←1← 9CF5 BNE
JSR send_request_write ; Send request to file server
9CFC JSR format_filename_field ; Format filename for display
9CFF PLP ; Restore comparison flags
9D00 BNE setup_txcb_transfer ; Not catalogue info: show short format
9D02 LDX #0 ; X=0: start at first byte
9D04 BEQ store_result ; ALWAYS branch to store and display
9D06 .setup_txcb_transfer←1← 9D00 BNE
LDA hazel_txcb_data ; Load file handle from fs_cmd_data
9D09 JSR check_and_setup_txcb ; Check and set up TXCB for transfer
fall through ↓

Receive FS reply and stash result byte

JSRs recv_and_process_reply, then falls through to store_result (STX hazel_txcb_result; LDY #&0E to point at the protection-bits offset). Single caller (the dispatch at &9C80).

On ExitXFS result byte (also written to hazel_txcb_result)
Y&0E (FS options offset for protection)
9D0C .recv_reply←1← 9C82 JMP
JSR recv_and_process_reply ; Receive and process reply
9D0F .store_result←1← 9D04 BEQ
STX hazel_txcb_result ; Store result byte in fs_reply_cmd
9D12 LDY #&0e ; Y=&0E: protection bits offset
9D14 LDA hazel_txcb_data ; Load access byte from fs_cmd_data
9D17 JSR get_prot_bits ; Extract protection bit flags
9D1A BEQ store_prot_byte ; Zero: use reply buffer data
9D1C .loop_copy_file_info←1← 9D24 BNE
LDA hazel_fs_reply_byte,y ; Load file info byte from fs_reply_data
9D1F .store_prot_byte←1← 9D1A BEQ
STA (fs_options),y ; Store in FS options at offset Y
9D21 INY ; Advance to next byte
9D22 CPY #&12 ; Y=&12: end of protection fields?
9D24 BNE loop_copy_file_info ; No: copy next byte
9D26 LDY hazel_fs_messages_flag ; Load display flag from fs_messages_flag
9D29 BEQ rts_advance_y ; Zero: skip display, return
9D2B LDY #&f4 ; Y=&F4: index into hazel_display_buf for filename
9D2D .loop_print_filename←1← 9D34 BNE
LDA hazel_display_buf_minusF4,y ; Load filename character from filename_buf
9D30 JSR print_char_no_spool ; Print character via OSASCI
9D33 INY ; Advance to next character
9D34 BNE loop_print_filename ; Printed all 12 characters?
9D36 LDY #5 ; Y=5: offset for access string
9D38 JSR print_5_hex_bytes ; Print 5 hex bytes (access info)
9D3B JSR print_load_exec_addrs ; Print load and exec addresses
9D3E JSR print_newline_no_spool ; Print newline
9D41 JMP return_with_last_flag ; Jump to return with last flag

Print exec address and file length in hex

Prints the exec address as 5 hex bytes from (fs_options) offset 9 downwards, then the file length as 3 hex bytes from offset &0C. Each group is followed by a space separator via OSASCI.

On ExitA, X, Yclobbered (print_hex_byte + OSASCI)
9D44 .print_load_exec_addrs←1← 9D3B JSR
LDY #9 ; Y=9: offset for exec address
9D46 JSR print_5_hex_bytes ; Print 5 hex bytes (exec address)
9D49 LDY #&0c ; Y=&0C: offset for length (3 bytes)
9D4B LDX #3 ; X=3: print 3 bytes only
9D4D BNE loop_print_hex_byte ; ALWAYS branch to print routine
fall through ↓

Print hex byte sequence from FS options

Outputs X+1 bytes from (fs_options) starting at offset Y, decrementing Y for each byte (big-endian display order). Each byte is printed as two hex digits via print_hex_byte. Finishes with a trailing space via OSASCI.

The default entry with X=4 prints 5 bytes (a full 32-bit address plus extent).

On EntryXbyte count minus 1 (default 4 for 5 bytes)
Ystarting offset in (fs_options)
9D4F .print_5_hex_bytes←2← 9D38 JSR← 9D46 JSR
LDX #4 ; X=4: print 5 bytes (4 to 0)
9D51 .loop_print_hex_byte←2← 9D4D BNE← 9D58 BNE
LDA (fs_options),y ; Load byte from FS options at offset Y
9D53 JSR print_hex_byte_no_spool ; Print as 2-digit hex
9D56 DEY ; Decrement byte offset
9D57 DEX ; Decrement byte count
9D58 BNE loop_print_hex_byte ; Loop until all bytes printed
9D5A LDA #&20 ; A=' ': space separator
9D5C JMP print_char_no_spool ; Print space via OSASCI and return

Copy FS options address bytes to zero page

Copies 4 bytes from (fs_options) at offsets 2-5 into zero page at &00AE+Y. Used by do_fs_cmd_iteration to preserve the current address state. Falls through to skip_one_and_advance5 to advance Y past the copied region.

On EntryYdestination offset within the &00AE.. zero-page region (also indexes the source via (fs_options),Y)
On ExitYadvanced by 5 (via skip_one_and_advance5 fall-through)
Aclobbered
9D5F .copy_fsopts_to_zp←2← 9C4E JSR← 9C59 JSR
LDY #5 ; Y=5: copy 4 bytes (offsets 2-5)
9D61 .loop_copy_fsopts_byte←1← 9D69 BCS
LDA (fs_options),y ; Load byte from FS options
9D63 STA work_ae,y ; Store in zero page at work_ae+Y
9D66 DEY ; Decrement index
9D67 CPY #2 ; Below offset 2?
9D69 BCS loop_copy_fsopts_byte ; No: copy next byte
fall through ↓

Advance Y by 5

Entry point one INY before advance_y_by_4, giving a total Y increment of 5. Used to skip past a 5-byte address/length structure in the FS options block.

On EntryYcurrent offset
On ExitYoffset + 5
A, Xpreserved
9D6B .skip_one_and_advance5←1← 9CD0 JSR
INY ; Y += 5
fall through ↓

Advance Y by 4

Four consecutive INY instructions. Used as a subroutine to step Y past a 4-byte address field in the FS options or workspace structure.

On EntryYcurrent offset
On ExitYoffset + 4
9D6C .advance_y_by_4←2← A262 JSR← B46D JSR
INY ; Y += 4
9D6D INY ; (continued)
9D6E INY ; (continued)
9D6F INY ; (continued)
9D70 .rts_advance_y←1← 9D29 BEQ
RTS ; Return

Copy workspace reply data to FS options

Copies bytes from the reply buffer at &0F02+Y into (fs_options) at offsets &0D down to 2. Used to update the FS options block with data returned from the file server. Falls through to retreat_y_by_4.

On EntryYcurrent offset (controls how many bytes are copied before the loop terminates)
On ExitYdecremented by 4 (via retreat_y_by_4 fall-through)
Aclobbered
9D71 .copy_workspace_to_fsopts←2← 9C51 JSR← 9C56 JSR
LDY #&0d ; Y=&0D: copy bytes from offset &0D down
9D73 TXA ; Transfer X to A
9D74 .loop_copy_ws_byte←1← 9D7C BCS
STA (fs_options),y ; Store byte in FS options at offset Y
9D76 LDA hazel_txcb_station,y ; Load next workspace byte from fs_cmd_urd+Y
9D79 DEY ; Decrement index
9D7A CPY #2 ; Below offset 2?
9D7C BCS loop_copy_ws_byte ; No: copy next byte
fall through ↓

Retreat Y by 4

Four consecutive DEY instructions. Companion to advance_y_by_4 for reverse traversal of address structures.

On EntryYcurrent offset
On ExitYoffset - 4
9D7E .retreat_y_by_4←1← 9CBF JSR
DEY ; Y -= 4
fall through ↓

Retreat Y by 3

Three consecutive DEY instructions. Used by setup_transfer_workspace to step back through interleaved address pairs in the FS options block.

On EntryYcurrent offset
On ExitYoffset - 3
9D7F .retreat_y_by_3←2← 9DFB JSR← A26A JSR
DEY ; Y -= 3
9D80 DEY ; (continued)
9D81 DEY ; (continued)
9D82 RTS ; Return
9D83 .discard_handle_match←2← 9D8B BEQ← 9DD1 BCS
PLA ; Discard stacked value
9D84 LDY fs_block_offset ; Restore Y from fs_block_offset
9D86 RTS ; Return (handle already matches)

Set up data-transfer TXCB and dispatch reply

Compares the 5-byte handle via cmp_5byte_handle; if unchanged, returns. Otherwise:

  1. Computes start / end addresses with overflow clamping.
  2. Sets the port and control byte.
  3. Sends the packet.
  4. Dispatches on the reply sub-operation code.
On ExitAFS reply sub-operation code (drives downstream dispatch)
9D87 .check_and_setup_txcb←2← 9D09 JSR← A280 JSR
PHA ; Save port/sub-function on stack
9D88 JSR cmp_5byte_handle ; Compare 5-byte handle with current
9D8B BEQ discard_handle_match ; Match: discard port and return
9D8D .init_transfer_addrs←1← 9DDA BNE
LDX #0 ; X=0: loop start
9D8F LDY #4 ; Y=4: copy 4 bytes
9D91 STX hazel_txcb_result ; Clear fs_reply_cmd (transfer size low)
9D94 STX hazel_exec_addr ; Clear fs_load_vector (transfer size high)
9D97 CLC ; Clear carry for addition
9D98 .loop_copy_addr_offset←1← 9DA5 BNE
LDA fs_load_addr,x ; Load address byte from zero page
9D9A STA txcb_start,x ; Store in TXCB start pointer
9D9C ADC hazel_txcb_flag,x ; Add offset from fs_func_code
9D9F STA txcb_end,x ; Store sum in TXCB end pointer
9DA1 STA fs_load_addr,x ; Also update load address
9DA3 INX ; Advance to next byte
9DA4 DEY ; Decrement counter
9DA5 BNE loop_copy_addr_offset ; Loop for all 4 bytes
9DA7 BCS clamp_end_to_limit ; Carry set: overflow, use limit
9DA9 SEC ; Set carry for subtraction
9DAA .loop_check_vs_limit←1← 9DB2 BNE
LDA fs_load_addr,y ; Load computed end address
9DAD SBC fs_work_4,y ; Subtract maximum from fs_work_4
9DB0 INY ; Advance to next byte
9DB1 DEX ; Decrement counter
9DB2 BNE loop_check_vs_limit ; Loop for all bytes
9DB4 BCC set_port_and_ctrl ; Below limit: keep computed end
9DB6 .clamp_end_to_limit←1← 9DA7 BCS
LDX #3 ; X=3: copy 4 bytes of limit
9DB8 .loop_copy_limit←1← 9DBD BPL
LDA fs_work_4,x ; Load limit from fs_work_4
9DBA STA txcb_end,x ; Store as TXCB end
9DBC DEX ; Decrement counter
9DBD BPL loop_copy_limit ; Loop for all 4 bytes
9DBF .set_port_and_ctrl←1← 9DB4 BCC
PLA ; Pull port from stack
9DC0 PHA ; Push back (keep for later)
9DC1 PHP ; Save flags (carry = overflow state)
9DC2 STA txcb_port ; Set TXCB port number
9DC4 LDA #&80 ; A=&80: control byte for data request
9DC6 STA txcb_ctrl ; Set TXCB control byte
9DC8 JSR init_tx_ptr_and_send ; Init TX pointer and send packet
9DCB LDA fs_error_ptr ; Load error pointer
9DCD JSR init_txcb_port ; Init TXCB port from error pointer
9DD0 PLP ; Restore overflow flags
9DD1 BCS discard_handle_match ; Carry set: discard and return
9DD3 LDA #&91 ; A=&91: FS reply port
9DD5 STA txcb_port ; Set TXCB port for reply
9DD7 JSR wait_net_tx_ack ; Wait for TX acknowledgement
9DDA BNE init_transfer_addrs ; Non-zero (not done): retry send
fall through ↓

OSWORD &13 sub-operation triage (1-7)

Stores the sub-operation code in hazel_txcb_data and triages by value:

Value Target
0..6 dispatch_ops_1_to_6
7 setup_dir_display (*INFO expansion)
> 7 skip_if_error (routes through finalise_and_return)

Single caller (&9CB0 in the OSWORD &13 handler entry).

On EntryAOSWORD sub-op code
9DDC .dispatch_osword_op←1← 9CB2 JMP
STA hazel_txcb_data ; Store sub-operation code
9DDF CMP #7 ; Compare with 7
9DE1 BCC dispatch_ops_1_to_6 ; Below 7: handle operations 1-6
9DE3 BNE skip_if_error ; Above 7: jump to handle via finalise
9DE5 JMP setup_dir_display ; Equal to 7: jump to directory display
9DE8 .dispatch_ops_1_to_6←1← 9DE1 BCC
CMP #6 ; Compare with 6
9DEA BEQ send_delete_request ; 6: delete file operation
9DEC CMP #5 ; Compare with 5
9DEE BEQ read_cat_info ; 5: read catalogue info
9DF0 CMP #4 ; Compare with 4
9DF2 BEQ setup_write_access ; 4: write file attributes
9DF4 CMP #1 ; Compare with 1
9DF6 BEQ setup_save_access ; 1: read file info
9DF8 ASL ; Shift left twice: A*4
9DF9 ASL ; A*4
9DFA TAY ; Copy to Y as index
9DFB JSR retreat_y_by_3 ; Y -= 3 to get FS options offset
9DFE LDX #3 ; X=3: copy 4 bytes
9E00 .loop_copy_fsopts_4←1← 9E07 BPL
LDA (fs_options),y ; Load byte from FS options at offset Y
9E02 STA hazel_txcb_flag,x ; Store in fs_func_code buffer
9E05 DEY ; Decrement source offset
9E06 DEX ; Decrement byte count
9E07 BPL loop_copy_fsopts_4 ; Loop for all 4 bytes
9E09 LDX #5 ; X=5: copy arg to buffer at offset 5
9E0B BNE send_save_or_access ; ALWAYS branch to copy and send
9E0D .setup_save_access←1← 9DF6 BEQ
JSR get_access_bits ; Get access bits for file
9E10 STA hazel_txcb_access ; Store access byte in fs_file_attrs
9E13 LDY #9 ; Y=9: source offset in FS options
9E15 LDX #8 ; X=8: copy 8 bytes to buffer
9E17 .loop_copy_fsopts_8←1← 9E1E BNE
LDA (fs_options),y ; Load FS options byte
9E19 STA hazel_txcb_data,x ; Store in fs_cmd_data buffer
9E1C DEY ; Decrement source offset
9E1D DEX ; Decrement byte count
9E1E BNE loop_copy_fsopts_8 ; Loop for all 8 bytes
9E20 LDX #&0a ; X=&0A: buffer offset for argument
9E22 .send_save_or_access←2← 9E0B BNE← 9E41 BNE
JSR copy_arg_to_buf ; Copy argument to buffer
9E25 LDY #&13 ; Y=&13: OSWORD &13 (NFS operation)
9E27 BNE send_request_vset ; ALWAYS branch to send request
9E29 .send_delete_request←1← 9DEA BEQ
JSR copy_arg_to_buf_x0 ; Copy argument to buffer at X=0
9E2C LDY #&14 ; Y=&14: delete file command
9E2E .send_request_vset←1← 9E27 BNE
BIT always_set_v_byte ; Set V flag (no directory check)
9E31 JSR save_net_tx_cb_vset ; Send request with V set
9E34 .skip_if_error←1← 9DE3 BNE
BCS done_osword_op ; Carry set: error, jump to finalise
9E36 JMP return_with_last_flag ; No error: return with last flag
9E39 .setup_write_access←1← 9DF2 BEQ
JSR get_access_bits ; Get access bits for file
9E3C STA hazel_txcb_flag ; Store in fs_func_code
9E3F LDX #2 ; X=2: buffer offset
9E41 BNE send_save_or_access ; ALWAYS branch to copy and send
9E43 .read_cat_info←1← 9DEE BEQ
LDX #1 ; X=1: buffer offset
9E45 JSR copy_arg_to_buf ; Copy argument to buffer
9E48 LDY #&12 ; Y=&12: open file command
9E4A JSR save_net_tx_cb ; Send open file request
9E4D LDA hazel_txcb_len ; Load reply handle from fs_obj_type
9E50 STX hazel_txcb_len ; Clear fs_obj_type
9E53 STX hazel_txcb_cycle ; Clear fs_len_clear
9E56 JSR get_prot_bits ; Get protection bits
9E59 LDX hazel_txcb_data ; Load file handle from fs_cmd_data
9E5C BEQ return_with_handle ; Zero: file not found, return
9E5E LDY #&0e ; Y=&0E: store access bits
9E60 STA (fs_options),y ; Store access byte in FS options
9E62 DEY ; Y=&0D
9E63 LDX #&0c ; X=&0C: copy 12 bytes of file info
9E65 .loop_copy_cat_info←1← 9E6C BNE
LDA hazel_txcb_data,x ; Load reply byte from fs_cmd_data+X
9E68 STA (fs_options),y ; Store in FS options at offset Y
9E6A DEY ; Decrement destination offset
9E6B DEX ; Decrement source counter
9E6C BNE loop_copy_cat_info ; Loop for all 12 bytes
9E6E INX ; X=1 (INX from 0)
9E6F INX ; X=2
9E70 LDY #&11 ; Y=&11: FS options offset
9E72 .loop_copy_ext_info←1← 9E79 BPL
LDA hazel_txcb_type,x ; Load extended info byte from fs_access_level
9E75 STA (fs_options),y ; Store in FS options
9E77 DEY ; Decrement destination offset
9E78 DEX ; Decrement source counter
9E79 BPL loop_copy_ext_info ; Loop until all copied
9E7B LDX hazel_txcb_data ; Reload file handle
9E7E .return_with_handle←1← 9E5C BEQ
TXA ; Transfer to A
9E7F .done_osword_op←1← 9E34 BCS
JMP finalise_and_return ; Jump to finalise and return

Format filename into fixed-width display field

Builds a 12-character space-padded filename at filename_buf for directory listing output. Sources the name from either the command line or the fs_cmd_data reply buffer depending on the value in fs_cmd_csd. Truncates or pads to exactly 12 characters.

On ExitA, X, Yclobbered
9E82 .format_filename_field←2← 9C71 JSR← 9CFC JSR
LDY #0 ; Y=0: start writing at filename_buf[0]
9E84 LDX hazel_txcb_network ; Load source offset from fs_cmd_csd
9E87 BNE copy_from_buf_entry ; Non-zero: copy from fs_cmd_data buffer
9E89 .loop_copy_cmdline_char←1← 9E93 BNE
LDA (fs_crc_lo),y ; Load character from command line
9E8B CMP #'!' ; Below '!' (control/space)?
9E8D BCC pad_with_spaces ; Yes: pad with spaces
9E8F STA hazel_display_buf,y ; Store printable character in filename_buf
9E92 INY ; Advance to next character
9E93 BNE loop_copy_cmdline_char ; Loop for more characters
9E95 .pad_with_spaces←2← 9E8D BCC← 9E9D BCC
LDA #' ' ; A=' ': space for padding
9E97 STA hazel_display_buf,y ; Store space in display buffer
9E9A INY ; Advance index
9E9B CPY #&0c ; Filled all 12 characters?
9E9D BCC pad_with_spaces ; No: pad more spaces
9E9F RTS ; Return with field formatted
9EA0 .loop_copy_buf_char←1← 9EA8 BPL
INX ; Advance source and destination
9EA1 INY ; Advance index
9EA2 .copy_from_buf_entry←1← 9E87 BNE
LDA hazel_txcb_data,x ; Load byte from fs_cmd_data buffer
9EA5 STA hazel_display_buf,y ; Store in filename_buf
9EA8 BPL loop_copy_buf_char ; Bit 7 clear: more characters
9EAA RTS ; Return (bit 7 set = terminator)

ARGSV vector handler: OSARGS

Reached via the ARGSV vector at &0214. Verifies the FS workspace checksum, stores the result as the last-byte flag, and sets the FS options pointer. Routes by A: positive (bit 7 clear) dispatches to a sub-operation table; bit 6 vs bit 5 of A then selects between read-and-write paths via further branching.

On EntryAOSARGS function code
Xcontrol-block low byte
Ychannel handle
9EAB .argsv_handler
JSR verify_ws_checksum ; Verify workspace checksum
9EAE STA fs_last_byte_flag ; Store result as last byte flag
9EB0 JSR set_options_ptr ; Set FS options pointer
9EB3 ORA #0 ; OR with 0 to set flags
9EB5 BPL dispatch_osfind_op ; Positive: handle sub-operations
9EB7 ASL ; Shift left to check bit 6
9EB8 BEQ validate_chan_close ; Zero (was &80): close channel
9EBA JMP close_all_fcbs ; Other: process all FCBs first
9EBD .validate_chan_close←1← 9EB8 BEQ
TYA ; Transfer Y to A
9EBE CMP #' ' ; Compare with &20 (space)
9EC0 BCS check_chan_range ; Above &20: check further
9EC2 .error_invalid_chan←1← 9EC7 BCS
JMP err_net_chan_invalid ; Below &20: invalid channel char
9EC5 .check_chan_range←1← 9EC0 BCS
CMP #'0' ; Compare with '0'
9EC7 BCS error_invalid_chan ; Above '0': invalid channel char
9EC9 JSR process_all_fcbs ; Process all matching FCBs
9ECC TYA ; Transfer Y to A (FCB index)
9ECD PHA ; Push FCB index
9ECE TAX ; Copy to X
9ECF LDY #0 ; Y=0: clear counter
9ED1 STY fs_last_byte_flag ; Clear last byte flag
9ED3 STY fs_block_offset ; Clear block offset
9ED5 .loop_copy_fcb_fields←1← 9EE0 BNE
LDA hazel_fcb_addr_mid,x ; Load channel data from fcb_attr_or_count_mid+X
9ED8 STA (fs_options),y ; Store in FS options at Y
9EDA JSR inx16 ; Advance X by 8 (next FCB field)
9EDD INY ; Advance destination index
9EDE CPY #4 ; Copied all 4 channel fields?
9EE0 BNE loop_copy_fcb_fields ; No: copy next field
9EE2 PLA ; Pull saved FCB index
9EE3 STA fs_block_offset ; Restore to fs_block_offset
9EE5 .dispatch_osfind_op←1← 9EB5 BPL
CMP #5 ; Compare with 5
9EE7 BCS done_return_flag ; 5 or above: return with last flag
9EE9 CPY #0 ; Compare Y with 0
9EEB BNE osfind_with_channel ; Non-zero: handle OSFIND with channel
9EED JMP osfind_close_or_open ; Y=0 (close): jump to OSFIND open
9EF0 .osfind_with_channel←1← 9EEB BNE
PHA ; Push sub-function
9EF1 PHX ; Save X (channel index) across the OSARGS body
9EF2 TYA ; Transfer Y to A
9EF3 PHA ; Push Y (channel char)
9EF4 JSR check_not_dir ; Check file is not a directory
9EF7 PLA ; Pull channel char
9EF8 JSR store_rx_attribute ; Store channel char as receive attribute
9EFB LDA hazel_fcb_slot_attr,x ; Load FCB flag byte from fcb_net_or_port
9EFE STA hazel_txcb_data ; Store in fs_cmd_data
9F01 PLX ; Restore X
9F02 PLA ; Pull sub-function
9F03 LSR ; Shift right: check bit 0
9F04 BEQ osargs_ptr_dispatch ; Zero (OSFIND close): handle close
9F06 PHP ; Save flags (carry from LSR)
9F07 PHA ; Push sub-function
9F08 LDX fs_options ; Load FS options pointer low
9F0A LDY fs_block_offset ; Load block offset
9F0C JSR process_all_fcbs ; Process all matching FCBs
9F0F LDA hazel_fcb_addr_mid,y ; Load updated data from fcb_attr_or_count_mid
9F12 STA hazel_txcb_data ; Store in fs_cmd_data
9F15 PLA ; Pull sub-function
9F16 STA hazel_txcb_flag ; Store in fs_func_code
9F19 PLP ; Restore flags
9F1A TYA ; Transfer Y to A
9F1B PHA ; Push Y (offset)
9F1C BCC osargs_read_op ; Carry clear: read operation
9F1E LDY #3 ; Y=3: copy 4 bytes
9F20 .loop_copy_zp_to_buf←1← 9F27 BPL
LDA zp_work_3,x ; Load zero page data
9F22 STA hazel_txcb_count,y ; Store in fs_data_count buffer
9F25 DEX ; Decrement source
9F26 DEY ; Decrement counter
9F27 BPL loop_copy_zp_to_buf ; Loop for all 4 bytes
9F29 LDY #&0d ; Y=&0D: TX buffer size
9F2B LDX #5 ; X=5: argument offset
9F2D JSR save_net_tx_cb ; Send TX control block to server
9F30 STX fs_last_byte_flag ; Store X in last byte flag
9F32 PLA ; Pull saved offset
9F33 JSR set_conn_active ; Set connection active flag
9F36 .done_return_flag←1← 9EE7 BCS
JMP return_with_last_flag ; Return with last flag
9F39 .osargs_read_op←1← 9F1C BCC
LDY #&0c ; Y=&0C: TX buffer size (smaller)
9F3B LDX #2 ; X=2: argument offset
9F3D JSR save_net_tx_cb ; Send TX control block
9F40 STA fs_last_byte_flag ; Store A in last byte flag
9F42 LDX fs_options ; Load FS options pointer low
9F44 LDY #2 ; Y=2: zero page offset
9F46 STA zp_work_3,x ; Store A in zero page
9F48 .loop_copy_reply_to_zp←1← 9F4F BPL
LDA hazel_txcb_data,y ; Load buffer byte from fs_cmd_data+Y
9F4B STA zp_work_2,x ; Store in zero page at offset
9F4D DEX ; Decrement source X
9F4E DEY ; Decrement counter Y
9F4F BPL loop_copy_reply_to_zp ; Loop until all bytes copied
9F51 PLA ; Pull saved offset
9F52 JMP return_with_last_flag ; Return with last flag
9F55 .osargs_ptr_dispatch←1← 9F04 BEQ
BCS osargs_write_ptr ; Carry set: write file pointer
9F57 LDA fs_block_offset ; Load block offset
9F59 JSR attr_to_chan_index ; Convert attribute to channel index
9F5C LDY fs_options ; Load FS options pointer
9F5E LDA hazel_fcb_addr_lo,x ; Load FCB low byte from fcb_count_lo
9F61 STA zp_ptr_lo,y ; Store in zero page pointer low
9F64 LDA hazel_fcb_addr_mid,x ; Load FCB high byte from fcb_attr_or_count_mid
9F67 STA zp_ptr_hi,y ; Store in zero page pointer high
9F6A LDA hazel_fcb_addr_hi,x ; Load FCB extent from fcb_station_or_count_hi
9F6D STA zp_work_2,y ; Store in zero page work area
9F70 LDA #0 ; A=0: clear high byte
9F72 STA zp_work_3,y ; Store zero in work area high
9F75 BEQ return_with_last_flag ; ALWAYS branch to return with flag
9F77 .osargs_write_ptr←1← 9F55 BCS
STA hazel_txcb_flag ; Store write value in fs_func_code
9F7A TXA ; Transfer X to A
9F7B PHA ; Push X (zero page offset)
9F7C LDY #3 ; Y=3: copy 4 bytes
9F7E .loop_copy_ptr_to_buf←1← 9F85 BPL
LDA zp_work_3,x ; Load zero page data at offset
9F80 STA hazel_txcb_count,y ; Store in fs_data_count buffer
9F83 DEX ; Decrement source
9F84 DEY ; Decrement counter
9F85 BPL loop_copy_ptr_to_buf ; Loop for all 4 bytes
9F87 LDA fs_block_offset ; Load channel handle from fs_block_offset
9F89 JSR attr_to_chan_index ; Convert handle to FCB channel index in X
9F8C LDA hazel_txcb_size_hi ; Test high transfer-size byte first
9F8F BNE argsv_send_request ; Non-zero: pointer below extent, send request
9F91 LDA hazel_fcb_addr_hi,x ; Compare FCB pointer high byte...
9F94 CMP hazel_exec_addr ; ...against extent high byte
9F97 BCC argsv_send_request ; Pointer < extent: send request
9F99 BNE argsv_clamp_zero ; Pointer > extent: clamp path
9F9B LDA hazel_fcb_addr_mid,x ; Compare FCB pointer mid byte...
9F9E CMP hazel_txcb_result ; ...against extent mid byte
9FA1 BCC argsv_send_request ; Pointer < extent: send request
9FA3 BNE argsv_clamp_zero ; Pointer > extent: clamp path
9FA5 LDA hazel_fcb_addr_lo,x ; Compare FCB pointer low byte...
9FA8 CMP hazel_txcb_count ; ...against extent low byte
9FAB BCC argsv_send_request ; Pointer < extent: send request
9FAD .argsv_clamp_zero←2← 9F99 BNE← 9FA3 BNE
LDX #0 ; X=0: pointer at/beyond extent
9FAF BEQ argsv_store_result ; Branch to store result (always)
9FB1 .argsv_send_request←4← 9F8F BNE← 9F97 BCC← 9FA1 BCC← 9FAB BCC
LDY #&0d ; Y=&0D: TX buffer size
9FB3 LDX #5 ; X=5: argument offset
9FB5 JSR save_net_tx_cb ; Send TX control block
9FB8 .argsv_store_result←1← 9FAF BEQ
STX fs_last_byte_flag ; Store X in last byte flag
9FBA PLA ; Pull saved zero page offset
9FBB TAY ; Transfer to Y
9FBC LDA fs_block_offset ; Load block offset (attribute)
9FBE JSR clear_conn_active ; Clear connection active flag
9FC1 JSR attr_to_chan_index ; Convert attribute to channel index
9FC4 LDA zp_ptr_lo,y ; Load zero page pointer low
9FC7 STA hazel_fcb_addr_lo,x ; Store back to FCB fcb_count_lo
9FCA LDA zp_ptr_hi,y ; Load zero page pointer high
9FCD STA hazel_fcb_addr_mid,x ; Store back to FCB fcb_attr_or_count_mid
9FD0 LDA zp_work_2,y ; Load zero page work byte
9FD3 STA hazel_fcb_addr_hi,x ; Store back to FCB fcb_station_or_count_hi
9FD6 JMP return_with_last_flag ; Return with last flag

Close all FCBs (process_all_fcbs + finalise)

Single-instruction wrapper: JSR process_all_fcbs to walk every FCB slot and close each open file in turn, then fall through to return_with_last_flag (which loads fs_last_byte_flag and finalises caller state). Single caller (the OSFIND close-all path at &9EB8).

On ExitAfs_last_byte_flag (loaded by return_with_last_flag)
9FD9 .close_all_fcbs←1← 9EBA JMP
JSR process_all_fcbs ; Process all matching FCBs first
fall through ↓

Load last-byte flag and finalise

Loads fs_last_byte_flag (&BD) into A and falls through to finalise_and_return, which clears the receive-attribute byte and restores caller's X/Y. The 12 inbound refs are mostly fall-through exits from FS reply handlers that need to return the last-byte status to their caller; only one site (&9FD6) reaches it via JSR.

On ExitAfs_last_byte_flag
Xfs_options (restored by finalise_and_return)
Yfs_block_offset (restored by finalise_and_return)
9FDC .return_with_last_flag←12← 9C36 JMP← 9D41 JMP← 9E36 JMP← 9F36 JMP← 9F52 JMP← 9F75 BEQ← 9FD6 JMP← A0C4 JMP← A187 JMP← A651 JMP← A657 JMP← A711 JMP
LDA fs_last_byte_flag ; Load last byte flag
fall through ↓

Clear receive-attribute and restore caller's X/Y

Common 7-byte exit sequence used at the end of format_filename_field, several FS reply handlers, and match_fs_cmd. Saves A across a call to store_rx_attribute(0) (which clears the receive-attribute byte), then restores X from fs_options and Y from fs_block_offset before returning. Effectively: 'finish processing, clear network state, restore caller's pointers'.

One JSR caller (match_fs_cmd at &A599) plus 6 branch entries from format_filename_field's various exit paths.

On EntryAresult code to return
On ExitApreserved
Xfs_options low byte
Yfs_block_offset low byte
9FDE .finalise_and_return←7← 9E7F JMP← 9FF3 BNE← 9FFA BPL← A090 JMP← A1EF JMP← A551 JMP← A5AF JSR
PHA ; Push result on stack
9FDF LDA #0 ; A=0: clear error flag
9FE1 JSR store_rx_attribute ; Clear receive attribute (A=0)
9FE4 PLA ; Pull result back
9FE5 LDX fs_options ; Restore X from FS options pointer
9FE7 LDY fs_block_offset ; Restore Y from block offset
9FE9 RTS ; Return to caller

OSFIND dispatch: close-all, close-one, or open

Triages the OSFIND function code in A:

A Meaning Path
≥ 2 open for input / output / update branch to done_file_open
1 close one channel go to done_file_open
0 close all channels load A=5 (close-all return code) and fall through

Single caller (the OSFIND vector table at &9EEB).

On EntryAOSFIND function code (0=close-all, 1=close-one, >=2 = open variants)
9FEA .osfind_close_or_open←1← 9EED JMP
CMP #2 ; Compare with 2 (open for output)
9FEC BCS done_file_open ; 2 or above: handle file open
9FEE TAY ; Transfer to Y (Y=0 or 1)
9FEF BNE done_file_open ; Non-zero (1 = read pointer): copy data
9FF1 LDA #5 ; A=5: return code for close-all
9FF3 BNE finalise_and_return ; ALWAYS branch to finalise
9FF5 .done_file_open←2← 9FEC BCS← 9FEF BNE
BEQ shift_and_finalise ; Z set: jump to clear A and return
fall through ↓

Set A=0 and finalise

Loads A=0 and falls through to shift_and_finalise (LSR A / BPL finalise_and_return). The LSR-then-BPL is the standard FS-handler 'success exit with carry clear' idiom. Two callers: the post- return path at &9FFC and the catalogue tail at tail_update_ catalogue (&A33F).

On ExitA0
C0 (LSR of 0)
9FF7 .clear_result←2← 9FFE BNE← A33F JMP
LDA #0 ; A=0: clear result
9FF9 .shift_and_finalise←1← 9FF5 BEQ
LSR ; Shift right (always positive)
9FFA BPL finalise_and_return ; Positive: jump to finalise
9FFC .alloc_fcb_for_open←1← A06A BEQ
AND #&3f ; Mask to 6-bit access value
9FFE BNE clear_result ; Non-zero: clear A and finalise
A000 TXA ; Transfer X to A (options pointer)
A001 JSR alloc_fcb_or_error ; Allocate FCB slot or raise error
A004 EOR #&80 ; Toggle bit 7
A006 ASL ; Shift left: build open mode
A007 STA hazel_txcb_data ; Store open mode in fs_cmd_data
A00A ROL ; Rotate to complete mode byte
A00B STA hazel_txcb_flag ; Store in fs_func_code
A00E JSR parse_cmd_arg_y0 ; Parse command argument (Y=0)
A011 LDX #2 ; X=2: buffer offset
A013 JSR copy_arg_to_buf ; Copy argument to TX buffer
fall through ↓
A016 .send_open_file_request←1← 9704 JSR
LDY #6 ; Y=6: open file command
A018 BIT always_set_v_byte ; Set V flag (skip directory check)
A01B SEC ; Set carry
A01C ROR need_release_tube ; Rotate carry into escapable flag bit 7
A01E JSR save_net_tx_cb_vset ; Send open request with V set
A021 BCS done_osfind ; Carry set (error): jump to finalise
A023 LDA #&ff ; A=&FF: mark as newly opened
A025 JSR store_rx_attribute ; Store &FF as receive attribute
A028 LDA hazel_txcb_data ; Load handle from fs_cmd_data
A02B PHA ; Push handle
A02C LDA #4 ; A=4: file info sub-command
A02E STA hazel_txcb_data ; Store sub-command
A031 LDX #1 ; X=1: shift filename
A033 .loop_shift_filename←1← A03C BNE
LDA hazel_txcb_flag,x ; Load filename byte from fs_func_code+X
A036 STA hazel_txcb_data,x ; Shift down to fs_cmd_data+X
A039 INX ; Advance source index
A03A CMP #&0d ; Is it CR (end of filename)?
A03C BNE loop_shift_filename ; No: continue shifting
A03E LDY #&12 ; Y=&12: file info request
A040 JSR save_net_tx_cb ; Send file info request
A043 LDA fs_last_byte_flag ; Load last byte flag
A045 AND #&bf ; Clear bit 6 (read/write bits)
A047 ORA hazel_txcb_data ; OR with reply access byte
A04A ORA #1 ; Set bit 0 (file is open)
A04C TAY ; Transfer to Y (access flags)
A04D AND #2 ; Check bit 1 (write access)
A04F BEQ check_open_mode ; No write access: check read-only
A051 PLA ; Pull handle from stack
A052 JSR alloc_fcb_slot ; Allocate FCB slot for channel
A055 BNE store_fcb_flags ; Non-zero: FCB allocated, store flags
A057 .findv_handler←1← 8EAF JMP
JSR verify_ws_checksum ; Verify workspace checksum
A05A JSR set_xfer_params ; Set up transfer parameters
A05D TAX ; Transfer A to X
A05E JSR mask_owner_access ; Set owner-only access mask
A061 TXA ; Transfer X back to A
A062 BEQ close_all_channels ; Zero: close file, process FCBs
A064 JSR save_ptr_to_os_text ; Save text pointer for OS
A067 LDY hazel_cur_dir_handle ; Load current directory handle
A06A BEQ alloc_fcb_for_open ; Zero: allocate new FCB
A06C TYA ; Transfer Y to A
A06D LDX #0 ; X=0: clear directory handle
A06F STX hazel_cur_dir_handle ; Store zero (clear handle)
A072 BEQ done_osfind ; ALWAYS branch to finalise
A074 .check_open_mode←1← A04F BEQ
LDA hazel_txcb_flag ; Load access/open mode byte
A077 ROR ; Rotate right: check bit 0
A078 BCS alloc_fcb_with_flags ; Carry set (bit 0): check read permission
A07A ROR ; Rotate right: check bit 1
A07B BCC alloc_fcb_with_flags ; Carry clear (no write): skip
A07D BIT hazel_txcb_count ; Test bit 7 of fs_data_count (lock flag)
A080 BPL alloc_fcb_with_flags ; Not locked: skip
A082 TYA ; Transfer Y to A (flags)
A083 ORA #&20 ; Set bit 5 (locked file flag)
A085 TAY ; Transfer back to Y
A086 .alloc_fcb_with_flags←3← A078 BCS← A07B BCC← A080 BPL
PLA ; Pull handle from stack
A087 JSR alloc_fcb_slot ; Allocate FCB slot for channel
A08A .store_fcb_flags←1← A055 BNE
TAX ; Transfer to X
A08B TYA ; Transfer Y to A (flags)
A08C STA hazel_fcb_state_byte,x ; Store flags in FCB table fcb_flags
A08F TXA ; Transfer X back to A (handle)
A090 .done_osfind←2← A021 BCS← A072 BEQ
JMP finalise_and_return ; Jump to finalise and return
A093 .close_all_channels←1← A062 BEQ
JSR process_all_fcbs ; Process all matching FCBs
A096 TYA ; Transfer Y to A
A097 BNE close_specific_chan ; Non-zero channel: close specific
A099 LDA fs_options ; Load FS options pointer low
A09B PHA ; Push (save for restore)
A09C LDA #osbyte_close_spool_exec ; A=&77: OSBYTE close spool/exec files
A09E JSR osbyte ; Close any *SPOOL and *EXEC files Close any SPOOLed or EXECed files
A0A1 PLA ; Pull saved options pointer
A0A2 STA fs_options ; Restore FS options pointer
A0A4 LDA #0 ; A=0: clear flags
A0A6 STA fs_last_byte_flag ; Save to fs_work_5
A0A8 STA fs_block_offset ; Load current FS station low
A0AA BEQ send_close_request ; ALWAYS branch to send close request Save to fs_work_6
A0AC .close_specific_chan←1← A097 BNE
JSR check_chan_char ; Validate channel character
A0AF LDA hazel_fcb_slot_attr,x ; Is it CR (no argument)?
A0B2 .send_close_request←1← A0AA BEQ
STA hazel_txcb_data ; Store as fs_cmd_data (file handle)
A0B5 LDX #1 ; X=1: argument size
A0B7 LDY #7 ; Y=7: close file command
A0B9 JSR save_net_tx_cb ; Send close file request
A0BC LDY fs_block_offset ; Parameter block low
A0BE BNE clear_single_fcb ; Parameter block high
A0C0 CLV ; Clear V flag
A0C1 JSR scan_fcb_flags ; Scan and clear all FCB flags
A0C4 .done_close←4← A0CF BEQ← A0E1 BCC← A0F5 BPL← A129 BRA
JMP return_with_last_flag ; Return with last flag
A0C7 .clear_single_fcb←1← A0BE BNE
LDA #0 ; A=0: clear FCB entry
A0C9 STA hazel_fcb_addr_mid,y ; Clear hazel_fcb_addr_mid for slot Y
A0CC STA hazel_fcb_state_byte,y ; Clear hazel_fcb_state_byte for slot Y
A0CF BEQ done_close ; Z still set from LDA #0: always branch to done_close
fall through ↓

FSCV reason 0: read OSARGS

Handles OSARGS via the FSCV vector. If A=0 (initialise dot-seen flag) clears the flag and proceeds. Compares X against 4 (number of args): out-of-range exits via the OSARGS dispatch chain to a shared error path; otherwise dispatches to the per-argument handler. Reached via the FSCV vector with reason code 0.

On EntryAOSARGS sub-function (0 = initialise)
Xargument index (0-3)
A0D1 .fscv_0_opt_entry
BEQ store_display_flag ; A=0 (init sub-code): jump to store_display_flag
A0D3 CPX #4 ; Non-zero A: X==4? (read OSARGS args)
A0D5 BNE osargs_dispatch ; X != 4: take normal OSARGS dispatch
A0D7 CPY #4 ; X==4 path: Y < 4?
A0D9 BCC send_osargs_request ; Yes: send OSARGS request via TXCB
A0DB .osargs_dispatch←1← A0D5 BNE
DEX ; X-- (osargs_dispatch entry): step sub-code down
A0DC BNE osargs_store_ptr_lo ; X != 1: take store-ptr-lo path
A0DE .store_display_flag←1← A0D1 BEQ
STY hazel_fs_messages_flag ; Store Y as hazel_fs_messages_flag (display control)
A0E1 BCC done_close ; Tail-branch to done_close
A0E3 .error_osargs←2← A0F9 BCS← A105 BCS
LDA #7 ; A=7: error code (out-of-range OSARGS sub-code)
A0E5 JMP classify_reply_error ; Raise BRK error
A0E8 .send_osargs_request←1← A0D9 BCC
STY hazel_txcb_data ; Store Y as TXCB data byte (OSARGS payload)
A0EB LDY #&16 ; Y=&16: TXCB function code (OSARGS request)
A0ED JSR save_net_tx_cb ; Send OSARGS request via TX control block
A0F0 LDY fs_block_offset ; Reload Y from fs_block_offset
A0F2 STY hazel_fs_flags ; Update hazel_fs_flags from OSARGS reply
A0F5 BPL done_close ; No error (positive): tail to done_close
A0F7 .osargs_store_ptr_lo←1← A0DC BNE
CPX #8 ; X >= 8?
A0F9 BCS error_osargs ; Yes: out-of-range OSARGS sub-code
A0FB CPX #4 ; X == 4?
A0FD BEQ osargs_check_length ; Yes: take fast read path (osargs_check_length)
A0FF CPY #4 ; Y < 4?
A101 BCC osopt_check_cmos_protect ; Yes: take CMOS-protect path
A103 .osargs_check_length←1← A0FD BEQ
CPY #2 ; Y >= 2?
A105 BCS error_osargs ; Yes: argument out of range
A107 .osopt_check_cmos_protect←1← A101 BCC
PHY ; Save Y
A108 PHX ; Save sub-code across the CMOS read
A109 LDX #&11 ; X=&11: CMOS RAM byte index
A10B JSR osbyte_a1 ; Read CMOS &11 (Econet status) -> Y
A10E PLX ; Restore sub-code
A10F TYA ; Read CMOS &11 result to A
A110 AND cmos_opt_mask_table,x ; Mask CMOS &11 with cmos_opt_mask_table[X]
A113 PLY ; Restore Y
A114 PHA ; Push CMOS value
A115 LDA cmos_attr_table,x ; Load shift count from cmos_attr_table[X]
A118 TAX ; Value to X
A119 TYA ; Caller's Y back to A as the value to shift
A11A .loop_extract_attr_bits←1← A11C BNE
ASL ; Shift CMOS bits
A11B DEX ; Count down shift iterations
A11C BNE loop_extract_attr_bits ; Loop until X reaches 0
A11E STA fs_load_addr ; Stash shifted value in fs_load_addr scratch
A120 PLA ; Pop saved value
A121 ORA fs_load_addr ; OR with the CMOS-masked value
A123 TAY ; Copy A to Y
A124 LDX #&11 ; X=&11: target CMOS byte for write-back
A126 .osopt_cmos_writeback_jsr
JSR osbyte_a2 ; Write CMOS RAM byte (Y) to byte index (X)
A129 BRA done_close ; Tail-branch into the OSARGS done path
fall through ↓

CMOS &11 bit-field masks for OSARGS / *OPT 4 (8 bytes)

Used by the OSARGS-via-FSCV / *OPT 4 path (osopt_check_cmos_protect) to read or update bit fields inside CMOS RAM byte &11 (the Econet status byte holding the auto-boot type and printer/messages flags).

  • Indices 0-3 are extraction masks: AND CMOS_&11 with &01, &02, &04, &06 returns bit 0, bit 1, bit 2 or bits 1+2 respectively.
  • Indices 4-7 are clear masks: AND CMOS_&11 with &FD, &F3, &CF, &3F zeroes bits 1, 2-3, 4-5 or 6-7 in turn, before OR-ing the new value back in.

A second indexed-base trick reads the same eight bytes through cmos_attr_table (this label - 4): for write sub-codes 4-7 the read-masks at indices 0-3 (1, 2, 4, 6) double as the bit-shift counts that left-align the new value into its target field.

A12B .cmos_opt_mask_table←1Used as index base by← A110 AND
EQUB &01 ; Idx 0: AND mask = &01 (extract CMOS &11 bit 0)
A12C EQUB &02 ; Idx 1: AND mask = &02 (extract CMOS &11 bit 1)
A12D EQUB &04 ; Idx 2: AND mask = &04 (extract CMOS &11 bit 2)
A12E EQUB &06 ; Idx 3: AND mask = &06 (extract CMOS &11 bits 1,2)
A12F EQUB &FD ; Idx 4: AND mask = &FD (clear CMOS &11 bit 1)
A130 EQUB &F3 ; Idx 5: AND mask = &F3 (clear CMOS &11 bits 2,3)
A131 EQUB &CF ; Idx 6: AND mask = &CF (clear CMOS &11 bits 4,5)
A132 EQUB &3F ; Idx 7: AND mask = &3F (clear CMOS &11 bits 6,7)

FSCV reason 1: EOF check

Verifies the FS workspace checksum, then loads the channel's block-offset byte (fs_block_offset, &BC), pushes it on the stack and stores the per-channel attribute reference in hazel_chan_attr. The body proceeds to compare the buffer byte count with the file length to decide whether the channel is at EOF. Reached via the FSCV vector with reason code 1.

On EntryYchannel handle
On ExitA0 = not at EOF, non-zero = EOF
A133 .fscv_1_eof
JSR verify_ws_checksum ; Verify workspace checksum
A136 PHA ; Push checksum-verify result -- preserve it across the FCB lookups below
A137 LDA fs_block_offset ; Load block offset
A139 PHA ; Push block offset
A13A STX hazel_chan_attr ; Store X in cur_chan_attr
A13D JSR find_matching_fcb ; Find matching FCB entry
A140 BEQ mark_not_found ; Zero: no match found
A142 LDA hazel_fcb_addr_lo,y ; Load FCB low byte from fcb_count_lo
A145 CMP hazel_fcb_offset_save,x ; Compare with stored offset fcb_buf_offset
A148 BCC mark_not_found ; FCB lo-byte below stored offset -> not the matching FCB; mark_not_found
A14A LDX #&ff ; X=&FF: mark as found (all bits set)
A14C BMI restore_and_return ; ALWAYS branch (negative)
A14E .mark_not_found←2← A140 BEQ← A148 BCC
LDX #0 ; X=0: mark as not found
A150 .restore_and_return←1← A14C BMI
PLA ; Restore block offset from stack
A151 TAY ; Generate 'Syntax' error
A152 PLA ; Restore result from stack
A153 RTS ; Return

Update both address fields in FS options

Calls add_workspace_to_fsopts for offset 9 (the high address / exec address field), then falls through to update_addr_from_offset1 to process offset 1 (the low address / load address field).

On ExitA, X, Y, C FLAGclobbered (4-byte arithmetic loop)
A154 .update_addr_from_offset9←1← A28D JSR
LDY #9 ; Y=9: FS options offset for high address
A156 JSR add_workspace_to_fsopts ; Add workspace values to FS options
fall through ↓

Update low address field in FS options

Sets Y=1 and falls through to add_workspace_to_fsopts to add the workspace adjustment bytes to the load address field at offset 1 in the FS options block.

On EntryCcarry state passed to add_workspace_to_fsopts
A159 .update_addr_from_offset1←1← A386 JSR
LDY #1 ; Y=1: FS options offset for low address
fall through ↓

Add workspace bytes to FS options with clear carry

Clears carry and falls through to adjust_fsopts_4bytes. Provides a convenient entry point when the caller needs addition without a preset carry.

On EntryYFS options offset for first byte
A15B .add_workspace_to_fsopts←1← A156 JSR
CLC ; Clear carry for the upcoming 4-byte add
fall through ↓

Add or subtract 4 workspace bytes from FS options

Processes 4 consecutive bytes at (fs_options)+Y, adding or subtracting the corresponding 4-byte transfer-address record from ANFS workspace.

The direction is controlled by bit 7 of fs_load_addr_2:

Bit 7 Operation
set subtract
clear add

Carry propagates across all 4 bytes for correct multi-byte arithmetic.

On EntryYFS options offset for first byte
Ccarry input for first byte
A15C .adjust_fsopts_4bytes←2← A293 JSR← A392 JSR
LDX #&fc ; X=&FC: loop counter (-4 to -1)
A15E .loop_adjust_byte←1← A171 BNE
LDA (fs_options),y ; Load FS options byte at offset Y
A160 BIT fs_load_addr_2 ; Test fs_load_addr_2 bit 7 (add/subtract)
A162 BMI subtract_ws_byte ; Push high byte
A164 ADC hazel_fs_opts_addend,x ; Add workspace byte to FS options
A167 JMP store_adjusted_byte ; RTS dispatches to command handler
A16A .subtract_ws_byte←1← A162 BMI
SBC hazel_fs_opts_addend,x ; Subtract workspace byte from FS options
fall through ↓

Store adjusted byte and step the loop

Tail of the address-adjustment 4-byte loop: STA (fs_options),Y / INY / INX / BNE loop_adjust_byte / RTS. The BNE retries until X has cycled through all 4 bytes; once X overflows back to 0 the loop exits and the RTS returns. Single caller (the loop-body fall- through at &A167).

On EntryAbyte to store
Ycurrent FS-options index
Xremaining-byte counter
A16D .store_adjusted_byte←1← A167 JMP
STA (fs_options),y ; Store result back to FS options
A16F INY ; Advance to next byte
A170 INX ; Advance counter
A171 BNE loop_adjust_byte ; Loop until 4 bytes processed
A173 RTS ; Return

GBPBV vector handler: OSGBPB

Reached via the GBPBV vector at vec_gbpbv after the fs_vector_table has copied the entry. Verifies the FS workspace checksum, sets up transfer parameters, masks the access prefix, and dispatches the OSGBPB sub-operation in A:

A Operation
1 PUT bytes with pointer
2 PUT bytes
3 GET bytes with pointer
4 GET bytes
5 read disc title
6 read CSD
7 read library
8 read files in CSD
On EntryAOSGBPB function code (1-8)
X, Ycontrol-block pointer (low, high)
A174 .gbpbv_handler
JSR verify_ws_checksum ; Verify workspace checksum
A177 JSR set_xfer_params ; Set up transfer parameters
A17A PHA ; Push transfer type on stack
A17B JSR mask_owner_access ; Set owner-only access mask
A17E PLA ; Pull transfer type
A17F TAX ; Transfer to X
A180 BEQ skip_if_out_of_range ; Zero: no valid operation, return
A182 DEX ; Decrement (convert 1-based to 0-based)
A183 CPX #8 ; Compare with 8 (max operation)
A185 BCC valid_osgbpb_op ; Below 8: valid operation
A187 .skip_if_out_of_range←1← A180 BEQ
JMP return_with_last_flag ; Out of range: return with flag
A18A .valid_osgbpb_op←1← A185 BCC
TXA ; Transfer operation code to A
A18B LDY #0 ; Y=0: buffer offset
A18D PHA ; Push operation code
A18E CMP #4 ; Compare with 4 (write operations)
A190 BCC load_chan_handle ; Below 4: read operation
A192 JMP write_block_entry ; 4 or above: write data block
A195 .load_chan_handle←1← A190 BCC
LDA (fs_options),y ; Load channel handle from FS options
A197 PHA ; Push handle
A198 JSR check_not_dir ; Check file is not a directory
A19B PLY ; Restore Y
A19C JSR process_all_fcbs ; Send TX control block to server
A19F PLA ; Pull operation code
A1A0 JSR setup_transfer_workspace ; Set up transfer workspace
A1A3 PHP ; Save flags (carry from setup)
A1A4 LDY #0 ; Y=0: index for channel handle
A1A6 LDA (fs_options),y ; Load channel handle from FS options
A1A8 BCS set_write_active ; Carry set (write): set active
A1AA JSR clear_conn_active ; Read: clear connection active
A1AD BPL setup_gbpb_request ; Branch to continue (always positive)
A1AF .set_write_active←1← A1A8 BCS
JSR set_conn_active ; Write: set connection active
A1B2 .setup_gbpb_request←1← A1AD BPL
STY hazel_txcb_flag ; Clear fs_func_code (Y=0)
A1B5 JSR lookup_cat_slot_data ; Look up channel slot data
A1B8 STA hazel_txcb_data ; Store flag byte in fs_cmd_data
A1BB LDY #&0c ; Y=&0C: TX buffer size (short)
A1BD LDX #2 ; X=2: argument count
A1BF JSR save_net_tx_cb ; Send TX control block
A1C2 JSR lookup_cat_entry_0 ; Look up channel entry at Y=0
A1C5 LDY #9 ; Y=9: FS options offset for position
A1C7 LDA hazel_txcb_data ; Load new position low from fs_cmd_data
A1CA STA hazel_fcb_addr_lo,x ; Update FCB low byte in fcb_count_lo
A1CD STA (fs_options),y ; Store in FS options at Y=9
A1CF INY ; Y=&0A
A1D0 LDA hazel_txcb_flag ; Load new position high from fs_func_code
A1D3 STA hazel_fcb_addr_mid,x ; Update FCB high byte in fcb_attr_or_count_mid
A1D6 STA (fs_options),y ; Store in FS options at Y=&0A
A1D8 INY ; Y=&0B
A1D9 LDA hazel_txcb_count ; Load new extent from fs_data_count
A1DC STA hazel_fcb_addr_hi,x ; Update FCB extent in fcb_station_or_count_hi
A1DF STA (fs_options),y ; Store in FS options at Y=&0B
A1E1 LDA #0 ; A=0: clear high byte of extent
A1E3 INY ; Y=&0C
A1E4 STA (fs_options),y ; Store zero in FS options at Y=&0C
A1E6 PLP ; Restore flags
A1E7 BCC return_success ; Carry clear: skip last-byte check
A1E9 LDA fs_last_byte_flag ; Load last-byte-of-transfer flag
A1EB CMP #3 ; Is transfer still pending (flag=3)?
A1ED .return_success←1← A1E7 BCC
LDA #0 ; A=0: success
A1EF JMP finalise_and_return ; Jump to finalise and return

Look up channel from FS options offset 0

Loads the channel handle from (fs_options) at offset 0, then falls through to lookup_cat_slot_data to find the corresponding FCB entry.

On ExitAFCB flag byte from hazel_fcb_slot_attr,X
Xchannel slot index
A1F2 .lookup_cat_entry_0←2← A1C2 JSR← A1FE JSR
LDY #0 ; Y=0: offset for channel handle
A1F4 LDA (fs_options),y ; Load channel handle from FS options
fall through ↓

Look up channel and return FCB flag byte

Calls lookup_chan_by_char to find the channel slot for handle A in the channel table, then loads the FCB slot-attribute byte from hazel_fcb_slot_attr+X.

On EntryAchannel handle
On ExitAFCB slot-attribute byte
Xchannel slot index
A1F6 .lookup_cat_slot_data←1← A1B5 JSR
JSR lookup_chan_by_char ; Look up channel by character
A1F9 LDA hazel_fcb_slot_attr,x ; Load slot-attribute byte from hazel_fcb_slot_attr,X
A1FC RTS ; Return with flag in A

Prepare workspace for OSGBPB data transfer

Orchestrates the setup for OSGBPB (get/put multiple bytes) operations:

  1. Look up the channel.

  2. Copy the 6-byte address structure from FS options (skipping the hole at offset 8).

  3. Determine transfer direction from the operation code:

    Operation code parity Direction FS port
    even read &91
    odd write &92
  4. Send the FS request.

  5. Configure the TXCB address pairs for the actual data-transfer phase.

  6. Dispatch to the appropriate handler.

On ExitAFS reply status from the data-transfer phase
A1FD .setup_transfer_workspace←2← A1A0 JSR← BD4A JMP
PHA ; Push operation code on stack
A1FE JSR lookup_cat_entry_0 ; Look up channel entry at Y=0
A201 STA hazel_txcb_data ; Store flag byte in fs_cmd_data
A204 PHX ; Save X (channel index)
A205 LDY #&0b ; Y=&0B: source offset in FS options
A207 LDX #6 ; X=6: copy 6 bytes
A209 .loop_copy_opts_to_buf←1← A215 BNE
LDA (fs_options),y ; Load FS options byte
A20B STA hazel_txcb_flag,x ; Store in fs_func_code buffer
A20E DEY ; Decrement source index
A20F CPY #8 ; Skip offset 8?
A211 BNE skip_struct_hole ; No: continue copy
A213 DEY ; Skip offset 8 (hole in structure)
A214 .skip_struct_hole←1← A211 BNE
DEX ; Decrement destination counter
A215 BNE loop_copy_opts_to_buf ; Loop until all 6 bytes copied
A217 PLY ; Restore Y (channel index)
A218 PLA ; Pull operation code
A219 LSR ; Shift right: check bit 0 (direction)
A21A PHA ; Push updated code
A21B BCC store_direction_flag ; Carry clear: OSBGET (read)
A21D LDA hazel_fcb_addr_lo,y ; Copy FCB pointer low byte...
A220 STA hazel_txcb_size_hi ; ...into TX control block size low
A223 LDA hazel_fcb_addr_mid,y ; Copy FCB pointer mid byte...
A226 STA hazel_txcb_tx_status ; ...into TX control block size mid
A229 LDA hazel_fcb_addr_hi,y ; Copy FCB pointer high byte...
A22C STA hazel_txcb_osword_flag ; ...into TX control block size high
A22F .store_direction_flag←1← A21B BCC
STX hazel_txcb_flag ; Store direction flag in fs_func_code
A232 LDY #&0b ; Y=&0B: TX buffer size
A234 LDX #&91 ; X=&91: port for OSBGET
A236 PLA ; Pull operation code
A237 PHA ; Push back (keep on stack)
A238 BEQ store_port_and_send ; Zero (OSBGET): keep port &91
A23A LDX #&92 ; X=&92: port for OSBPUT
A23C DEY ; Y=&0A: adjusted buffer size
A23D .store_port_and_send←1← A238 BEQ
STX hazel_txcb_station ; Store port in fs_cmd_urd
A240 STX fs_error_ptr ; Store port in fs_error_ptr
A242 LDX #8 ; X=8: argument count
A244 LDA hazel_txcb_data ; Load file handle from fs_cmd_data
A247 JSR send_request_nowrite ; Send request (no write data)
A24A LDX #0 ; X=0: index
A24C LDA (fs_options,x) ; Load channel handle from FS options
A24E TAX ; Transfer to X as index
A24F LDA hazel_fcb_state_byte,x ; Load FCB flags from fcb_flags
A252 EOR #1 ; Toggle bit 0 (transfer direction)
A254 STA hazel_fcb_state_byte,x ; Store updated flags
A257 CLC ; Clear carry for addition
A258 LDX #4 ; X=4: process 4 address bytes
A25A .loop_setup_addr_bytes←1← A26E BNE
LDA (fs_options),y ; Load FS options address byte
A25C STA addr_work,y ; Store in zero page address area
A25F STA txcb_pos,y ; Store in TXCB position
A262 JSR advance_y_by_4 ; Advance Y by 4
A265 ADC (fs_options),y ; Add offset from FS options
A267 STA addr_work,y ; Store computed end address
A26A JSR retreat_y_by_3 ; Retreat Y by 3 for next pair
A26D DEX ; Decrement byte counter
A26E BNE loop_setup_addr_bytes ; Loop for all 4 address bytes
A270 INX ; X=1 (INX from 0)
A271 .loop_copy_offset←1← A278 BPL
LDA hazel_txcb_network,x ; Load offset from fs_cmd_csd
A274 STA hazel_txcb_flag,x ; Copy to fs_func_code
A277 DEX ; Decrement counter
A278 BPL loop_copy_offset ; Loop until both bytes copied
A27A PLA ; Pull operation code
A27B BNE send_with_swap ; Non-zero (OSBPUT): swap addresses
A27D LDA hazel_txcb_station ; Load port from fs_cmd_urd
A280 JSR check_and_setup_txcb ; Check and set up TXCB
A283 BCS recv_and_update ; Carry set: skip swap
A285 .send_with_swap←1← A27B BNE
JSR send_txcb_swap_addrs ; Send TXCB and swap start/end addresses
A288 .recv_and_update←1← A283 BCS
JSR recv_reply_preserve_flags ; Receive and process reply
A28B STX fs_load_addr_2 ; Store result in fs_load_addr_2
A28D JSR update_addr_from_offset9 ; Update addresses from offset 9
A290 DEC fs_load_addr_2 ; Decrement fs_load_addr_2
A292 SEC ; Set carry for subtraction
A293 JSR adjust_fsopts_4bytes ; Adjust FS options by 4 bytes
A296 ASL hazel_txcb_data ; Shift fs_cmd_data left (update status)
A299 RTS ; Return

Receive and process reply, preserving flags

Wrapper around recv_and_process_reply that saves and restores the processor status register, so the caller's flag state is not affected by the reply processing.

On ExitAFS reply status
P (FLAGS)preserved across the call (PHP/PLP)
A29A .recv_reply_preserve_flags←1← A288 JSR
PHP ; Save flags before reply processing
A29B JSR recv_and_process_reply ; Process server reply
A29E PLP ; Restore flags after reply processing
A29F RTS ; Return

Send OSBPUT data block to file server

Sets Y=&15 (TX buffer size for OSBPUT data) and calls save_net_tx_cb to dispatch the TX. Then copies the display flag from hazel_fs_flags to hazel_txcb_byte_16 (TX header continuation). Single caller in the OSBPUT-buffered-write path.

A2A0 .send_osbput_data←1← A2D0 BEQ
LDY #&15 ; Y=&15: TX buffer size for OSBPUT data
A2A2 JSR save_net_tx_cb ; Send TX control block
A2A5 LDA hazel_fs_flags ; Load display flag from hazel_fs_flags
A2A8 STA hazel_txcb_byte_16 ; Store in hazel_txcb_byte_16
A2AB STX fs_load_addr ; Clear fs_load_addr (X=0)
A2AD STX fs_load_addr_hi ; Clear fs_load_addr_hi
A2AF LDA #&12 ; A=&12: byte count for data block
A2B1 STA fs_load_addr_2 ; Store in fs_load_addr_2
A2B3 BNE write_data_block ; ALWAYS branch to write data block
fall through ↓

Pre-write Tube-station check, fall into write_data_block

Y=4 (FS-options offset for station). If tube_present is zero (no Tube co-pro), branch forward to store_station_result and skip the next compare; otherwise CMP (fs_options),Y to validate the caller's station matches the saved Tube station. Falls through to write_data_block. Single caller (&A190 in the OSWORD write path).

On EntryYignored (forced to 4)
A2B5 .write_block_entry←1← A192 JMP
LDY #4 ; Y=4: offset for station comparison
A2B7 LDA tube_present ; Load stored station from tube_present
A2BA BEQ store_station_result ; Zero: skip station check
A2BC CMP (fs_options),y ; Compare with FS options station
A2BE BNE store_station_result ; Mismatch: skip subtraction
A2C0 DEY ; Y=3
A2C1 SBC (fs_options),y ; Subtract FS options value
A2C3 .store_station_result←2← A2BA BEQ← A2BE BNE
STA svc_state ; Store result in svc_state
A2C5 .loop_copy_opts_to_ws←1← A2CB BNE
LDA (fs_options),y ; Load FS options byte at Y
A2C7 STA fs_last_byte_flag,y ; Store in workspace at fs_last_byte_flag+Y
A2CA DEY ; Decrement index
A2CB BNE loop_copy_opts_to_ws ; Loop until all bytes copied
A2CD PLA ; Pull operation code
A2CE AND #3 ; Mask to 2-bit sub-operation
A2D0 BEQ send_osbput_data ; Zero: send OSBPUT data
A2D2 LSR ; Shift right: check bit 0
A2D3 BEQ handle_cat_update ; Zero (bit 0 clear): handle read
A2D5 BCS update_cat_position ; Carry set: handle catalogue update
A2D7 .handle_cat_update←1← A2D3 BEQ
TAY ; Transfer to Y (Y=0)
A2D8 LDA hazel_fs_context_copy,y ; Load data byte from fs_csd_handle
A2DB STA hazel_txcb_network ; Store in fs_cmd_csd
A2DE LDA hazel_fs_prefix_stn ; Load high data byte from fs_lib_handle
A2E1 STA hazel_txcb_lib ; Store in fs_cmd_lib
A2E4 LDA hazel_fs_saved_station ; Load port from fs_urd_handle
A2E7 STA hazel_txcb_station ; Store in fs_cmd_urd
A2EA LDX #&12 ; X=&12: buffer size marker
A2EC STX hazel_txcb_func_code ; Store in fs_cmd_y_param
A2EF LDA #&0d ; A=&0D: count value
A2F1 STA hazel_txcb_flag ; Store in fs_func_code
A2F4 STA fs_load_addr_2 ; Store in fs_load_addr_2
A2F6 LSR ; Shift right (A=6)
A2F7 STA hazel_txcb_data ; Store in fs_cmd_data
A2FA CLC ; Clear carry for addition
A2FB JSR prep_send_tx_cb ; Prepare and send TX control block
A2FE STX fs_load_addr_hi ; Store X in fs_load_addr_hi (X=0)
A300 INX ; X=1 (after INX)
A301 STX fs_load_addr ; Store X in fs_load_addr
fall through ↓

Write data block to destination or Tube

tube_present Action
zero (no Tube) copy directly from the fs_cmd_data buffer via (fs_crc_lo)
non-zero claim the Tube, set up the transfer address, write via R3
On ExitA, X, Yclobbered
A303 .write_data_block←2← A2B3 BNE← A37B JSR
LDA svc_state ; Load svc_state (tube flag)
A305 BNE tube_write_setup ; Non-zero: write via tube
A307 LDX fs_load_addr ; Load source index from fs_load_addr
A309 LDY fs_load_addr_hi ; Load destination index from fs_load_addr_hi
A30B .loop_copy_to_host←1← A314 BNE
LDA hazel_txcb_data,x ; Load data byte from fs_cmd_data buffer
A30E STA (fs_crc_lo),y ; Store to destination via fs_crc pointer
A310 INX ; Advance source index
A311 INY ; Advance destination index
A312 DEC fs_load_addr_2 ; Decrement byte counter
A314 BNE loop_copy_to_host ; Loop until all bytes transferred
A316 BEQ tail_update_catalogue ; X=&10: scan 16 slots (15 to 0)
A318 .tube_write_setup←1← A305 BNE
JSR tube_claim_c3 ; Clear V
A31B LDA #1 ; A=1: tube transfer type (write)
A31D LDX fs_options ; Load destination low from fs_options
A31F LDY fs_block_offset ; No match: try next
A321 INX ; Load slot status byte
A322 BNE set_tube_addr ; No wrap: skip high increment
A324 INY ; Test bit 2 (PS active flag)?
A325 .set_tube_addr←1← A322 BNE
JSR tube_addr_data_dispatch ; Set up tube transfer address
A328 LDX fs_load_addr ; Transfer Y to A
A32A .loop_write_to_tube←1← A338 BNE
LDA hazel_txcb_data,x ; Load data byte from buffer
A32D STA tube_data_register_3 ; Write to tube data register 3 Store Y to fs_urd_handle
A330 INX ; Advance source index
A331 LDY #6 ; Y=6: tube write delay
A333 .loop_tube_delay←1← A334 BNE
DEY ; Delay loop: decrement Y
A334 BNE loop_tube_delay ; Transfer Y to A
A336 DEC fs_load_addr_2 ; Decrement byte counter
A338 BNE loop_write_to_tube ; Store allocation result
A33A LDA #&83 ; A=&83: release tube claim
A33C JSR tube_addr_data_dispatch ; Release tube
fall through ↓

Catalogue-update exit (JMP clear_result)

Single-instruction tail: JMP clear_result -- shared exit for the catalogue-update paths after they have finished writing the new entry. Two callers: &A314 (the success path) and &A38F (the no-change path). Never returns directly (clear_result loads A=0 and tail-falls into finalise_and_return).

A33F .tail_update_catalogue←2← A316 BEQ← A3A3 JMP
JMP clear_result ; Jump to clear A and finalise return
A342 .update_cat_position←1← A2D5 BCS
LDY #9 ; Y=9: offset for position byte
A344 LDA (fs_options),y ; Try next slot
A346 STA hazel_txcb_flag ; Store in fs_func_code
A349 LDY #5 ; Y=5: offset for extent byte
A34B LDA (fs_options),y ; Load extent byte from FS options
A34D STA hazel_txcb_count ; Store in fs_data_count
A350 LDX #&0d ; X=&0D: byte count
A352 STX hazel_txcb_result ; Store in fs_reply_cmd
A355 LDY #2 ; Y=2: command sub-type
A357 STY fs_load_addr ; Set V (found match)
A359 STY hazel_txcb_data ; Store in fs_cmd_data Store Y to fs_csd_handle
A35C INY ; Y=3: TX buffer command byte
A35D JSR save_net_tx_cb ; V set: found, skip allocation
A360 STX fs_load_addr_hi ; Allocate FCB slot
A362 LDA hazel_txcb_flag ; Load data offset from fs_func_code
A365 STA (fs_options,x) ; Store as first byte of FS options
A367 LDA hazel_txcb_data ; Load data count from fs_cmd_data
A36A LDY #9 ; Y=9: position offset in FS options
A36C ADC (fs_options),y ; Add to current position
A36E STA (fs_options),y ; Store updated position
A370 LDA txcb_end ; Load TXCB end byte
A372 SBC #7 ; Subtract 7 (header overhead)
A374 STA hazel_txcb_flag ; Store remaining data size
A377 STA fs_load_addr_2 ; Store in fs_load_addr_2 (byte count)
A379 BEQ clear_buf_after_write ; Zero bytes: skip write
A37B JSR write_data_block ; Write data block to host/tube
A37E .clear_buf_after_write←1← A379 BEQ
LDX #2 ; X=2: clear 3 bytes (indices 0-2)
A380 .loop_clear_buf←1← A384 BPL
STA hazel_txcb_count,x ; Clear fs_data_count+X
A383 DEX ; Decrement index
A384 BPL loop_clear_buf ; Loop until all cleared
A386 JSR update_addr_from_offset1 ; Update addresses from offset 1
A389 SEC ; Set carry for subtraction
A38A DEC fs_load_addr_2 ; Decrement fs_load_addr_2
A38C LDA hazel_txcb_data ; Load data count from fs_cmd_data
A38F STA hazel_txcb_flag ; Copy to fs_func_code
A392 JSR adjust_fsopts_4bytes ; Adjust FS options by 4 bytes (subtract)
A395 LDX #3 ; X=3: check 4 bytes
A397 LDY #5 ; Y=5: starting offset
A399 SEC ; Set carry for comparison
A39A .loop_check_remaining←1← A3A0 BPL
LDA (fs_options),y ; Load FS options byte
A39C BNE done_write_block ; Non-zero: more data remaining
A39E INY ; Advance to next byte
A39F DEX ; Decrement counter
A3A0 BPL loop_check_remaining ; Loop until all bytes checked
A3A2 CLC ; All zero: clear carry (transfer complete)
A3A3 .done_write_block←1← A39C BNE
JMP tail_update_catalogue ; Jump to update catalogue and return

Claim the Tube via protocol &C3

Loops calling tube_addr_data_dispatch with protocol byte &C3 until the claim succeeds (carry set on return). Used before Tube data transfers to ensure exclusive access to the Tube co-processor interface.

On ExitA&C3 (the claim protocol byte left in A)
C FLAGset (the claim succeeded -- this is the loop termination condition)
A3A6 .tube_claim_c3←3← A318 JSR← A3AB BCC← A63D JSR
LDA #&c3 ; A=&C3: tube claim protocol
A3A8 JSR tube_addr_data_dispatch ; Dispatch tube address/data claim
A3AB BCC tube_claim_c3 ; Carry clear: claim failed, retry
A3AD RTS ; Return (tube claimed)

*FS command handler

Saves the current file server station address, then checks for a command-line argument. With no argument, falls through to print_current_fs to display the active server. With an argument, parses the station number via parse_fs_ps_args and issues OSWORD &13 (sub-function 1) to select the new file server.

On EntryYcommand line offset in text pointer
A3AE .cmd_fs
LDA hazel_fs_station ; Read current FS station from workspace
A3B1 STA fs_work_5 ; Save in fs_work_5 (so 'no-arg' path can print it)
A3B3 LDA hazel_fs_network ; Read current FS network
A3B6 STA fs_work_6 ; Save in fs_work_6
A3B8 LDA (fs_crc_lo),y ; Look at the first command-line byte
A3BA CMP #&0d ; Is it CR (no argument)?
A3BC BEQ print_current_fs ; Yes: print the current FS address
A3BE JSR parse_fs_ps_args ; Parse 'net.station' arg into fs_work_5/6
A3C1 LDA #1 ; A=1: OSWORD &13 sub-function 1 = set file server station
A3C3 STA fs_work_4 ; Store sub-function in PB[0]
A3C5 LDA #osword_nfs_info ; A=&13: OSWORD &13
A3C7 LDX #<(fs_work_4) ; X = lo of PB pointer (fs_work_4 = &B4)
A3C9 LDY #>(fs_work_4) ; Y = hi of PB pointer (=0, since fs_work_4 is in zero page)
A3CB JMP osword ; Tail-jump into OSWORD; the OS routes us back through osword_13_set_station Read/Write NFS information (see https://beebwiki.mdfs.net/OSWORDs)
A3CE .print_current_fs←1← A3BC BEQ
JSR print_file_server_is ; Print 'File server is ' fragment
fall through ↓

Print station address and newline

Sets V (suppressing leading-zero padding on the network number) then prints the station address followed by a newline via OSNEWL. Used by *FS and *PS output formatting.

On ExitA, X, Yclobbered (print_station_addr + OSNEWL)
A3D1 .print_fs_info_newline←1← B4B1 JSR
BIT always_set_v_byte ; Set V so print_station_addr suppresses the leading '0.' when the network number is zero
A3D4 JSR print_station_addr ; Print the station/network address
A3D7 JMP osnewl ; Tail-call OSNEWL for the trailing CR/LF

Parse station address from *FS/*PS arguments

Reads a station address in net.station format from the command line, with the network number optional (defaults to local network). Calls init_bridge_poll to ensure the bridge routing table is populated, then validates the parsed address against known stations. The parsed-station value is stored in fs_work_7 (&B7).

On EntryYcurrent command-line offset
On ExitX, Ypreserved (saved/restored via PHX/PHY)
A3DA .parse_fs_ps_args←3← 9605 JSR← A3BE JSR← B40A JSR
PHX ; Save caller's X (command-line offset cursor)
A3DB LDA #0 ; A=0: clear the dot-seen flag for parse_addr_arg
A3DD STA fs_work_4 ; Store cleared dot-seen flag
A3DF JSR parse_addr_arg ; Parse first number (network or standalone station)
A3E2 BCS skip_if_no_station ; C set: parse_addr_arg saw an empty argument -- skip station storage
A3E4 STA fs_work_7 ; Save the network number in fs_work_7
A3E6 PHY ; Save Y (current command-line cursor) for after the bridge poll
A3E7 JSR init_bridge_poll ; Populate the bridge routing table -- returns local network number in A
A3EA EOR fs_load_addr_2 ; EOR with parsed network: Z set iff parse matched local
A3EC BEQ store_station_lo ; Match: keep A=0 to mark local network
A3EE LDA fs_load_addr_2 ; Mismatch: A = parsed network number
A3F0 .store_station_lo←1← A3EC BEQ
STA fs_work_6 ; Store network number into fs_work_6 (the canonical form: 0=local, non-zero=remote)
A3F2 PLY ; Restore Y
A3F3 INY ; Step Y past the dot separator
A3F4 JSR parse_addr_arg ; Parse station number after the dot
A3F7 .skip_if_no_station←1← A3E2 BCS
BEQ no_station_loop ; C set: no station after dot -- leave fs_work_5 alone
A3F9 STA fs_work_5 ; Store parsed station in fs_work_5
A3FB .no_station_loop←1← A3F7 BEQ
PLX ; Restore caller's X
A3FC RTS ; Return

Convert parameter block pointer to table index

Reads the first byte from the OSWORD parameter block pointer and falls through to byte_to_2bit_index to produce a 12-byte-aligned table index in Y.

On ExitAPB[0] (preserved through byte_to_2bit_index)
Ybyte offset (0, 6, 12, ... up to &42)
A3FD .get_pb_ptr_as_index←2← A41B JSR← A42B JSR
LDA osword_pb_ptr ; Read PB[0] (the OSWORD sub-function code in most calls); fall into byte_to_2bit_index
fall through ↓

Convert byte to 12-byte-aligned table index

Computes Y = A * 6 (via A*12/2) for indexing into the OSWORD handler workspace tables. Clamps Y to zero if the result exceeds &48, preventing out-of-bounds access.

On EntryAtable entry number
On ExitYbyte offset (0, 6, 12, ... up to &42)
A3FF .byte_to_2bit_index←4← 8FC9 JSR← A95F JSR← A978 JSR← B503 JSR
ASL ; Multiply A by 2
A400 ASL ; Multiply A by 2 again -- A is now A_orig * 4
A401 PHA ; Stash A_orig * 4 on the stack
A402 ASL ; Multiply A by 2 -- A is now A_orig * 8 (C = bit 7 of A_orig*4)
A403 TSX ; Capture S so we can read the just-pushed value
A404 PHP ; Save the C flag from the third ASL
A405 ADC error_text,x ; ADC stack[X+1] = A_origem>4 (with C from the ASL): A = A_orig/em>8 + A_origem>4 + C = A_orig/em>12 + C
A408 ROR ; Halve the result, putting the new C as bit 7
A409 PLP ; Restore the saved C (from the third ASL)
A40A ASL ; ASL doubles the halved value (effectively undoes the ROR's divide while reusing C)
A40B TAY ; Y = A_orig * 12 (the 12-byte-aligned index)
A40C PLA ; Recover A_orig * 4 (left on the stack at &A3FF)
A40D CMP #&48 ; Above &48 (i.e. A_orig * 4 >= 72, A_orig >= 18)?
A40F BCC rts_2bit_index ; No: keep computed Y
A411 LDY #0 ; Yes: clamp Y to 0 (out of range)
A413 TYA ; Mirror Y -> A so callers can test Z
A414 .rts_2bit_index←1← A40F BCC
RTS ; Return; Y holds 12-byte-aligned offset, A is non-zero on success

FS reply: read handle byte (no workspace lookup)

Reads the inline handle byte directly from the RX buffer at (net_rx_ptr),Y with Y=&6F, then branches into the shared PB-store path. Used when the caller wants the raw handle byte from the FS reply rather than the workspace-tracked value.

On ExitAhandle byte from RX buffer
A415 .net_1_read_handle
LDY #&6f ; Y=&6F: net_rx_ptr offset for the 'inline' handle byte
A417 LDA (net_rx_ptr),y ; Read handle byte directly from RX buffer
A419 BCC store_pb_result ; C clear: read-handle path -- store directly to PB
fall through ↓

FS reply: read handle byte from workspace table

Calls get_pb_ptr_as_index to convert the OSWORD parameter-block pointer to a workspace-table index. On out-of-range (C=1), returns zero. Otherwise reads the handle byte from nfs_workspace,Y; if the slot is ? (uninitialised marker), falls through to the zero-return path; otherwise stores the real handle into PB[0].

A41B .net_2_read_entry
JSR get_pb_ptr_as_index ; Convert PB pointer to workspace table offset
A41E BCS return_zero_uninit ; Out of range: return zero (uninitialised)
A420 LDA (nfs_workspace),y ; Read workspace handle byte
A422 CMP #'?' ; Slot marked '?' (uninitialised)?
A424 BNE store_pb_result ; Has a real handle: keep it and store
A426 .return_zero_uninit←1← A41E BCS
LDA #0 ; Force result to zero (uninitialised marker)
A428 .store_pb_result←2← A419 BCC← A424 BNE
STA osword_pb_ptr ; Write into PB[0] (handle return slot)
A42A RTS ; Return

FS reply: close handle entry

Calls get_pb_ptr_as_index to look up the workspace slot. On out-of-range, marks the workspace as uninitialised. Otherwise rotates fs_flags bit 0 into carry (state save), reads PB[0] (the handle to close), and proceeds with the close path.

A42B .net_3_close_handle
JSR get_pb_ptr_as_index ; Convert PB pointer to workspace table offset
A42E BCC mark_ws_uninit ; Out of range: mark as uninitialised
A430 ROR fs_flags ; Shift bit 0 of fs_flags into C (save state)
A433 LDA osword_pb_ptr ; Read PB[0] (the handle to close)
A435 ROL ; Shift bit 7 of A into C
A436 ROL fs_flags ; Restore C into bit 0 of fs_flags
A439 RTS ; Return; the close action is dispatched elsewhere based on the saved C state
A43A .mark_ws_uninit←1← A42E BCC
ROR econet_flags ; Save bit 0 of econet_flags
A43D LDA #'?' ; A='?': uninitialised marker
A43F STA (nfs_workspace),y ; Write '?' to workspace[Y] (the slot is now free)
A441 ROL econet_flags ; Restore bit 0 of econet_flags
A444 RTS ; Return

FSCV reason 3: process *<command> via FS

Sets up text and transfer pointers via set_text_and_xfer_ptr, marks spool / Tube state as inactive (fs_spool_handle = need_release_tube = &FF), then calls match_fs_cmd with X=&35, Y=0 to look up the user's text in the FS command table. The match-or-error result feeds into the FS dispatch chain that follows. Single caller (the FSCV vector table at &8CFA).

A445 .fscv_3_star_cmd
JSR set_text_and_xfer_ptr ; Set text/transfer pointers from FS context
A448 LDY #&ff ; Y=&FF -- mark spool/Tube state inactive
A44A STY fs_spool_handle ; Store fs_spool_handle = &FF
A44C STY need_release_tube ; Store need_release_tube = &FF
A44E INY ; Advance index
A44F LDX #&35 ; X=&35: NFS-commands sub-table offset
A451 JSR match_fs_cmd ; Match against the NFS sub-table
A454 BCS dispatch_fs_cmd ; C set: no match -> dispatch via fall-through
fall through ↓

FS-command re-entry guard (BVC dispatch_fs_cmd)

Single-instruction prologue: BVC dispatch_fs_cmd. Reached as the fall-through target after a *RUN failure -- if V is clear (the re-entry path is permitted) it branches into dispatch_fs_cmd to re-attempt the command; otherwise falls through to error_syntax to raise 'Syntax'. Single caller (the FS dispatch table at &8C4E).

A456 .cmd_fs_reentry←1← 8C75 JMP
BVC dispatch_fs_cmd ; V clear: re-enter dispatch_fs_cmd
A458 .error_syntax
LDA #&dc ; Error code &DC
A45A JSR error_inline ; Raise 'Syntax' error
A45D EQUS "Syntax", &00
A464 .dispatch_fs_cmd←2← A454 BCS← A456 BVC
LDA #0 ; A=0: clear svc_state
A466 STA svc_state ; Store -> svc_state
A468 LDA cmd_dispatch_hi_table,x ; Load dispatch hi byte from cmd_dispatch_hi_table+X
A46B PHA ; Push hi for RTS dispatch
A46C LDA cmd_dispatch_lo_table,x ; Load dispatch lo byte from cmd_dispatch_lo_table+X
A46F PHA ; Push lo for RTS dispatch
A470 RTS ; RTS -> dispatched command handler

Match command name against FS command table

Case-insensitive compare of the command line against cmd_table_fs entries with bit-7-terminated names. Returns with the matched entry address on success.

On EntryXstarting offset within cmd_table_fs (selects which sub-table is searched: NFS commands, FS commands, etc.)
On ExitXbyte offset just past the matched command name in cmd_table_fs (or end-of-table if no match)
Ycommand-line offset of the first non-name character (typically the argument start)
Z FLAGset on match, clear on no-match
A471 .match_fs_cmd←3← 8C70 JSR← 8C9E JSR← A451 JSR
TYA ; Save command-line offset Y on stack
A472 PHA ; Push for save/restore
A473 .restart_table_scan←1← A499 BNE
PLA ; Reload saved Y (peek without popping)
A474 PHA ; Push it back to keep on stack
A475 TAY ; Y = saved command-line offset
A476 LDA cmd_table_fs,x ; First char of current entry name
A479 BMI check_char_type ; Bit 7 set already: end of table
A47B .loop_match_char←1← A488 BNE
LDA cmd_table_fs,x ; Next char from table
A47E BMI check_separator ; Bit 7 set: name fully matched
A480 EOR (fs_crc_lo),y ; XOR against the stored command character
A482 AND #&df ; Mask off case bit (5)
A484 BNE skip_entry_chars ; Mismatch (after case mask): skip entry
A486 INY ; Advance command-line offset
A487 INX ; Advance table offset
A488 BNE loop_match_char ; ALWAYS branch: continue matching
A48A .skip_entry_chars←2← A484 BNE← A48E BPL
INX ; Skip remaining name chars
A48B LDA cmd_table_fs,x ; Load next table byte
A48E BPL skip_entry_chars ; Bit 7 clear: continue skipping
A490 LDA (fs_crc_lo),y ; Char on command line at current Y
A492 CMP #'.' ; Is it . (abbreviation)?
A494 BEQ skip_dot_and_spaces ; Yes: accept abbreviated match
A496 .loop_skip_to_next←1← A4AB BNE
INX ; Skip 3-byte handler trailer (flag, lo, hi)
A497 INX ; (continued)
A498 INX ; (continued)
A499 BNE restart_table_scan ; ALWAYS branch: try next entry
A49B .check_separator←1← A47E BMI
TYA ; Save matched-name length on stack
A49C PHA ; Push for stack-based comparison
A49D LDA (fs_crc_lo),y ; Char on command line just past name
A49F LDY #9 ; Y=9: separator-table size - 1
A4A1 .loop_check_sep_table←1← A4A7 BPL
CMP sep_table_data,y ; Compare with separator
A4A4 BEQ separator_char_table ; Match: valid command boundary
A4A6 DEY ; Try next separator
A4A7 BPL loop_check_sep_table ; Loop through 10 separators
A4A9 PLA ; Restore matched-name length
A4AA TAY ; A = matched offset, save in Y
A4AB BNE loop_skip_to_next ; ALWAYS branch: try next entry
A4AD .sep_table_data←1Used as index base by← A4A1 CMP
JSR separator_parse_dispatch ; Dispatch helper (sep_table_data path)
A4B0 BIT parse_separator_flag ; Check separator flag (zp_0026)
A4B2 ROL ; Rotate result bit into carry
A4B3 DEC ; Decrement match counter
A4B4 RTI ; Effective unconditional jump
A4B5 EQUB &0D ; CR (carriage return)
A4B6 .separator_char_table←1← A4A4 BEQ
PLA ; Restore matched-name length
A4B7 TAY ; Y = matched-name length
fall through ↓

Skip trailing spaces from FS command-line args

Reads (fs_crc_lo),Y; on space, falls through to the per-char advance; non-space exits to check_cmd_flags. Shared body with skip_dot_and_spaces at &A4AA (alt-entry that also accepts dots). Single caller (the BNE retry at &A4A9).

On EntryYcurrent command-line offset
A4B8 .loop_skip_trail_spaces←1← A4BF JMP
LDA (fs_crc_lo),y ; Char on command line at current Y
A4BA CMP #' ' ; Is it space?
A4BC BNE check_cmd_flags ; No: check the entry's no-arg flag
A4BE .skip_dot_and_spaces←1← A494 BEQ
INY ; Advance past the space (or .)
A4BF JMP loop_skip_trail_spaces ; Loop: keep skipping
A4C2 .check_cmd_flags←1← A4BC BNE
LDA cmd_table_fs,x ; Load entry's flag byte (post-name)
A4C5 ASL ; Shift bit 7 into C: the no-arg bit
A4C6 BPL clear_v_flag ; C=0: entry allows arguments
A4C8 LDA (fs_crc_lo),y ; Char on command line
A4CA CMP #&0d ; Is it CR (no argument)?
A4CC BNE clear_v_flag ; Argument present, V clear
A4CE BIT always_set_v_byte ; Force V=1: entry validated as match
A4D1 BVS clear_c_flag ; V set: skip the CLV
A4D3 .clear_v_flag←2← A4C6 BPL← A4CC BNE
CLV ; Clear V (no-arg flag not asserted)
A4D4 .clear_c_flag←1← A4D1 BVS
CLC ; Clear C (no error / no-arg path)
A4D5 .return_with_result←1← A4F0 BCS
PLA ; Discard saved Y on stack
A4D6 LDA (fs_crc_lo),y ; A = current command-line char
A4D8 RTS ; Return (Z=1 on match, C and V set per result)
A4D9 .loop_scan_past_word←1← A4E6 BNE
INY ; Advance command-line offset
A4DA .check_char_type←1← A479 BMI
LDA (fs_crc_lo),y ; Char on command line
A4DC CMP #&0d ; Is it CR (end of input)?
A4DE BEQ set_c_and_return ; Yes: set C and return (no match)
A4E0 CMP #'.' ; Is it .?
A4E2 BEQ skip_sep_spaces ; Yes: skip separator spaces
A4E4 CMP #' ' ; Is it space?
A4E6 BNE loop_scan_past_word ; No: keep scanning past word
A4E8 .skip_sep_spaces←2← A4E2 BEQ← A4ED BEQ
INY ; Advance past space
A4E9 LDA (fs_crc_lo),y ; Load next char
A4EB CMP #' ' ; Still space?
A4ED BEQ skip_sep_spaces ; Yes: keep skipping
A4EF .set_c_and_return←1← A4DE BEQ
SEC ; Set C: signal no-match return path
A4F0 BCS return_with_result ; ALWAYS branch to common return
A4F2 .check_urd_present
BIT fs_flags ; Test fs_flags bit 6
A4F5 BVS fscv_2_star_run ; Bit 6 set: take fscv_2_star_run
A4F7 JMP error_bad_command ; Bit 6 clear: raise 'Bad command'

FSCV reason 2: handle *RUN

Saves the OS text pointer via save_ptr_to_os_text, calls mask_owner_access to clear the FS-selection bit, ORs in bit 1 (the *RUN-in-progress flag), and stores back to hazel_fs_lib_flags. Falls through to the run-handling chain that opens the file and starts execution. Reached via the FSCV vector dispatch with reason code 2.

A4FA .fscv_2_star_run←1← A4F5 BVS
JSR save_ptr_to_os_text ; Save text pointer (for GSREAD-driven parsing)
A4FD JSR mask_owner_access ; Reset fs_lib_flags low bits to 5-bit access mask
A500 ORA #2 ; Set bit 1 of A (mark *RUN-style invocation)
A502 STA hazel_fs_lib_flags ; Update hazel_fs_lib_flags with the result
A505 BNE cmd_run_load_mask ; Branch to load the *RUN mask
fall through ↓

*RUN entry for URD-prefixed argument

Reached from cmd_fs_operation at &8E35 when the first character of the *RUN argument is '&' (the URD = User Root Directory prefix). Saves the OS text pointer via save_ptr_to_os_text, masks the access bits via mask_owner_access, clears bit 1 of the result, and stores into hazel_fs_lib_flags. Falls through to cmd_run_load_mask which calls parse_cmd_arg_y0 to begin parsing the rest of the *RUN argument. Single caller; never returns directly (continues into the run flow).

A507 .cmd_run_via_urd←1← 8E4F JMP
JSR save_ptr_to_os_text ; Save current OS text pointer
A50A JSR mask_owner_access ; Mask access bits
A50D AND #&fd ; Clear bit 1 of mask
A50F STA hazel_fs_lib_flags ; Save into fs_lib_flags
A512 .cmd_run_load_mask←1← A505 BNE
JSR parse_cmd_arg_y0 ; Begin parsing the *RUN argument
A515 .open_file_for_run←1← A58C BNE
LDX #1 ; X=1: TX-buffer write index for argument
A517 JSR copy_arg_to_buf ; Copy argument to TX buffer
A51A LDA #2 ; A=2: open-input mode for OSFIND
A51C STA hazel_txcb_data ; Next byte down
A51F LDY #&12 ; Y=&12: cmd code for *RUN
A521 JSR save_net_tx_cb ; Send the request and wait for reply
A524 LDA hazel_txcb_data ; Read reply status from TX[5]
A527 CMP #1 ; Compare with 1 (not-found)
A529 BNE try_library_path ; Loop until all 6 restored
A52B LDX #3 ; Return from svc_8_osword
A52D .loop_check_handles←1← A536 BPL
INC hazel_txcb_size_hi,x ; Increment handle byte
A530 BEQ alloc_run_fcb ; Load handler address low byte
A532 JMP check_exec_addr ; Non-zero: handle valid, execute
A535 .alloc_run_fcb←1← A530 BEQ
DEX ; Decrement X (post-find adjustment)
A536 BPL loop_check_handles ; Loop while X >= 0 (scan all 4 handle slots)
A538 JSR alloc_fcb_or_error ; RTS dispatches to pushed handler
A53B LDX #1 ; X=1: target offset for the *RUN-channel command
A53D STX hazel_txcb_data ; Store X to hazel_txcb_data (cmd byte)
A540 STX hazel_txcb_flag ; Store X to hazel_txcb_flag (cmd flag)
A543 INX ; X=2
A544 JSR copy_arg_to_buf ; Copy filename arg into TX buffer
A547 LDY #6 ; Test station active flag
A549 JSR save_net_tx_cb ; Send re-open request
A54C BCS done_run_dispatch ; C set: error from save_net_tx_cb -- abort *RUN
A54E JMP alloc_run_channel ; Yes: handle clock read
A551 .done_run_dispatch←1← A54C BCS
JMP finalise_and_return ; Jump to finalise and return
A554 .try_library_path←1← A529 BNE
LDA hazel_parse_buf ; Return
A557 CMP #&24 ; Y=&10: length of TXCB to save
A559 BEQ error_bad_command ; Save current TX control block
A55B LDA hazel_fs_lib_flags ; Load library flag byte
A55E BMI library_tried ; Bit 7 set: library already tried
A560 ROL ; Shift bit 7 into carry
A561 ROL ; Rotate mask bit into carry
A562 BMI restore_filename ; Store BCD seconds
A564 BCS error_bad_command ; Carry set: bad command
A566 LDX #&ff ; X=&FF -- start scan from end
A568 .loop_find_name_end←1← A56E BNE
INX ; Convert binary to BCD
A569 LDA hazel_parse_buf,x ; Load filename byte
A56C CMP #&0d ; Compare with CR (terminator)
A56E BNE loop_find_name_end ; Load hours from clock workspace
A570 .loop_shift_name_right←1← A577 BPL
LDA hazel_parse_buf,x ; Shift filename right by 8 bytes
A573 STA hazel_rtc_buffer,x ; Store shifted byte
A576 DEX ; Decrement scan index
A577 BPL loop_shift_name_right ; Clear hours high position
A579 LDX #7 ; Store zero
A57B .loop_copy_lib_prefix←1← A582 BPL
LDA library_dir_prefix,x ; Copy 'Library.' prefix
A57E STA hazel_parse_buf,x ; Store prefix byte
A581 DEX ; Decrement scan index
A582 BPL loop_copy_lib_prefix ; Loop until prefix copied
A584 LDA hazel_fs_lib_flags ; Load library flag
A587 ORA #&60 ; Mark byte as 'argument'
A589 STA hazel_fs_lib_flags ; Restore day+month byte
A58C .retry_with_library←1← A5A3 BNE
BNE open_file_for_run ; Retry file open with library path
A58E .restore_filename←1← A562 BMI
LDX #&ff ; X=&FF -- restart scan from end
A590 .loop_restore_name←1← A599 BNE
INX ; Store BCD month
A591 LDA hazel_rtc_buffer,x ; Load backup byte
A594 STA hazel_parse_buf,x ; Shift high nibble down
A597 EOR #&0d ; 4th shift: isolate high nibble
A599 BNE loop_restore_name ; No: continue restoring
A59B JSR mask_owner_access ; Set owner-only access mask
A59E ORA #&80 ; Mark caller's flags
A5A0 STA hazel_fs_lib_flags ; Copy 7 bytes (Y=6 down to 0)
A5A3 BNE retry_with_library ; No match: retry via the library directory
A5A5 .library_tried←1← A55E BMI
JSR mask_owner_access ; Store to parameter block
A5A8 LDA #2 ; Loop for all 7 bytes
A5AA BIT hazel_fs_lib_flags ; Test hazel_fs_lib_flags bits 6 / 7
A5AD BNE error_bad_command ; Either bit set: this is an invalid command path
A5AF JSR finalise_and_return ; Otherwise finalise and return
A5B2 LDA #&0b ; A=&0B: FSCV reason 11 (filing-system change)
A5B4 JMP call_fscv ; Tail-call FSCV

Raise 'Bad command' BRK error

Loads error code &FE and tail-calls error_bad_inline with the inline string 'command' -- error_bad_inline prepends 'Bad ' to produce the final 'Bad command' message. Used by the FS command parser when no table entry matches the user's input. Never returns.

A5B7 .error_bad_command←4← A4F7 JMP← A559 BEQ← A564 BCS← A5AD BNE
LDA #&fe ; Error code &FE
A5B9 JSR error_bad_inline ; Raise 'Bad command' error
A5BC EQUS "command", &00
fall through ↓

Validate exec address is non-zero

Iterates X = 3..0 over the 4-byte exec-address copy at hazel_txcb_flag..hazel_exec_addr, incrementing each byte. If any byte becomes non-zero (BNE), branches forward to library_path_string (the OSCLI dispatch path). When all four INC operations leave a zero result the address was &FFFFFFFF + 1 = 0 -- not a valid exec address -- and the routine falls through to the no-exec-address handler. Single caller (&A530 in the *RUN handler).

On EntryAexec address bytes already in hazel_txcb_flag..hazel_exec_addr
On ExitX0 if no valid exec; non-zero branch otherwise
A5C4 .check_exec_addr←1← A532 JMP
LDX #3 ; X=3: check 4 execution bytes
A5C6 .loop_check_exec_bytes←1← A5CC BNE
INC hazel_txcb_flag,x ; Increment execution address byte
A5C9 BNE run_copy_arg_to_buf ; Low byte = &6F
A5CB DEX ; Set osword_flag
A5CC BNE loop_check_exec_bytes ; Loop until all checked
A5CE LDA #&93 ; A=&93: error code 'Bad command'
A5D0 JSR error_inline_log ; Generate 'No!' error
A5D3 EQUS "Won't", &00
fall through ↓

Allocate FCB slot for *RUN target file

Loads the saved OSWORD parameter byte at hazel_txcb_data, calls alloc_fcb_slot to obtain a free channel index in A, transfers it into Y, then clears the per-channel attribute byte at hazel_fcb_status,X. Used by the *RUN argument-handling path at &A54C once the file is opened, to reserve a channel for the running program.

On ExitAchannel attribute byte (cleared to 0)
XFCB slot index
YFCB slot index (copy of X)
A5D9 .alloc_run_channel←1← A54E JMP
LDA hazel_txcb_data ; Set workspace pointer high
A5DC JSR alloc_fcb_slot ; Allocate FCB slot
A5DF TAY ; A = parsed character
A5E0 LDA #0 ; Y=OSWORD flag (slot specifier)
A5E2 STA hazel_fcb_status,x ; Clear status in channel table
A5E5 STY hazel_cur_dir_handle ; A=3: start searching from slot 3
A5E8 LDY #3 ; Y=3: skip past 3-byte FS header
A5EA JMP boot_cmd_oscli ; C set: slot invalid, store result
A5ED .library_dir_prefix←1Used as index base by← A57B LDA
EQUS "Library." ; Continue shift
A5F5 .run_copy_arg_to_buf←1← A5C9 BNE
JSR copy_arg_to_buf_x0 ; Copy parsed arg to TX buffer with X=0
A5F8 LDY #0 ; Y=0
A5FA CLC ; For the loop entry
A5FB JSR gsinit ; Transfer found slot to A
A5FE .loop_read_gs_string←1← A601 BCC
JSR gsread ; Store slot number to PB byte 0
A601 BCC loop_read_gs_string ; Always (BCC after CLC) loop back
A603 DEY ; C set: slot invalid, store result
A604 .loop_skip_trailing←1← A609 BEQ
INY ; Advance Y past trailing space
A605 LDA (os_text_ptr),y ; Y=Y-1: adjust workspace offset
A607 CMP #' ' ; Is it space?
A609 BEQ loop_skip_trailing ; Yes: skip it
A60B EOR #&0d ; Test for CR (terminator)
A60D CLC ; Clear C for arithmetic
A60E TYA ; Compare Y with OSWORD flag
A60F ADC os_text_ptr ; Add to text pointer low
A611 STA fs_crc_lo ; Store low byte of (os_text_ptr + Y) -> fs_crc_lo (repurposed as a generic pointer)
A613 LDA os_text_ptr_hi ; Load os_text_ptr_hi for the high-byte add
A615 ADC #0 ; Add carry from low add (no extra increment)
A617 STA fs_crc_hi ; Store result high byte -> fs_crc_hi
A619 JSR save_text_ptr ; Save text pointer for later
A61C LDX #&c0 ; X=&C0: pointer-to-options high byte
A61E STX fs_block_offset ; Y=1: workspace flag offset
A620 LDA #&0e ; Store pending marker to workspace
A622 STA fs_options ; Store as fs_options
A624 STA hazel_retry_counter ; Increment retry counter
A627 LDX #&4a ; X=&4A: FS command table offset
A629 LDY #5 ; Store result A to PB via Y
A62B JSR do_fs_cmd_iteration ; Rotate Econet flags back (restore state)
A62E LDA tube_present ; Return from OSWORD 11 handler
A631 BEQ dispatch_via_vector ; Store to ws_ptr_lo
A633 AND hazel_txcb_tx_status ; Y=&7F: last byte of RX buffer
A636 AND hazel_txcb_osword_flag ; Mask the OSWORD flag byte
A639 CMP #&ff ; All &FF?
A63B BEQ dispatch_via_vector ; X-1: adjust count
A63D JSR tube_claim_c3 ; Claim tube for data transfer
A640 LDX #9 ; X=9: parameter count
A642 LDY #&c1 ; Y=&C1: high byte of TX buffer pointer
A644 LDA #4 ; A=4: option byte for *RUN
A646 JMP tube_addr_data_dispatch ; Relocated execute path
A649 .dispatch_via_vector←2← A631 BEQ← A63B BEQ
LDA #1 ; A=1: dispatch flag
A64B JMP (hazel_exec_addr) ; Indirect jump via workspace vector

FS reply handler: select CSD station

Single-instruction wrapper: JSR find_station_bit3 to record the new current-selected-directory (CSD) station in the table, then JMP return_with_last_flag to clean up and return. Single caller (the FS reply dispatch at &959B).

On ExitAfs_last_byte_flag (loaded by return_with_last_flag)
A64E .fsreply_3_set_csd←1← 959B JMP
JSR find_station_bit3 ; Find station-bit-3 entry
A651 JMP return_with_last_flag ; Return with the last-byte flag

FS reply handler: set library station

Two-instruction wrapper: JSR flip_set_station_boot to record the new library station, then JMP return_with_last_flag. Reached only via the FS reply dispatch table.

A654 .fsreply_5_set_lib
JSR flip_set_station_boot ; Record library station in station table
A657 JMP return_with_last_flag ; Return with the last-byte flag

Find printer server station in table (bit 2)

Scans the 16-entry station table for a slot matching the current station/network address with bit 2 set (printer server active). Sets V if found, clears V if not. Falls through to allocate or update the matching slot with the new station address and status flags.

On ExitV FLAGset if matching slot already had bit 2; clear if newly allocated
Xtable slot index of the matched/allocated entry
A65A .find_station_bit2←1← A6FF JSR
LDX #&10 ; X=&10: scan 16 entries
A65C CLV ; Clear V (no-match marker)
A65D .loop_search_stn_bit2←2← A663 BNE← A66A BEQ
DEX ; Step to previous entry
A65E BMI done_search_bit2 ; Below 0: scan complete
A660 JSR match_station_net ; Compare entry X's stn/net with caller's
A663 BNE loop_search_stn_bit2 ; No match: continue
A665 LDA hazel_fcb_status,x ; Match: read entry's flag byte at hazel_fcb_status+X
A668 AND #4 ; Mask bit 2
A66A BEQ loop_search_stn_bit2 ; Bit 2 clear: keep scanning
A66C TYA ; Bit 2 set: A = matched entry index (Y)
A66D STA hazel_fcb_slot_attr,x ; Store Y at hazel_fcb_slot_attr+X (link entry to slot)
A670 BIT always_set_v_byte ; BIT always_set_v_byte: V <- 1 (match found)
A673 .done_search_bit2←1← A65E BMI
STY hazel_fs_saved_station ; Save Y at hazel_fs_saved_station (matched entry index)
A676 BVS set_flags_bit2 ; V set: skip new-slot alloc
A678 TYA ; A = caller's index
A679 JSR alloc_fcb_slot ; Allocate a fresh FCB slot
A67C STA hazel_fcb_slot_1 ; Save FCB slot index at hazel_fcb_slot_1
A67F BEQ jmp_restore_fs_ctx ; Z set: alloc failed -> restore FS context
A681 .set_flags_bit2←1← A676 BVS
LDA #&26 ; A=&26: workspace flag for bit 2 search
A683 BNE store_stn_flags_restore ; Match: store station flags and restore
fall through ↓

Find file server station in table (bit 3)

Scans the 16-entry station table for a slot matching the current station/network address with bit 3 set (file server active). Sets V if found, clears V if not. Falls through to allocate or update the matching slot with the new station address and status flags.

On ExitV FLAGset if matching slot already had bit 3; clear if newly allocated
Xtable slot index of the matched/allocated entry
A685 .find_station_bit3←3← A64E JSR← A6B7 JSR← A705 JSR
LDX #&10 ; X=&10: scan 16 entries
A687 CLV ; Clear V (no-match marker)
A688 .loop_search_stn_bit3←2← A68E BNE← A695 BEQ
DEX ; Step to previous entry
A689 BMI done_search_bit3 ; Below 0: scan complete
A68B JSR match_station_net ; Compare entry's stn/net with caller's
A68E BNE loop_search_stn_bit3 ; No match: continue
A690 LDA hazel_fcb_status,x ; Match: read entry's flag byte at hazel_fcb_status+X
A693 AND #8 ; Mask bit 3
A695 BEQ loop_search_stn_bit3 ; Bit 3 clear: keep scanning
A697 TYA ; Bit 3 set: A = matched entry index (Y)
A698 STA hazel_fcb_slot_attr,x ; Store Y at hazel_fcb_slot_attr+X (link entry to slot)
A69B BIT always_set_v_byte ; BIT always_set_v_byte: V <- 1 (match found)
A69E .done_search_bit3←1← A689 BMI
STY hazel_fs_context_copy ; Save Y at hazel_fs_context_copy (matched entry index)
A6A1 BVS set_flags_bit3 ; V set: skip new-slot alloc
A6A3 TYA ; A = caller's index
A6A4 JSR alloc_fcb_slot ; Allocate a fresh FCB slot
A6A7 STA hazel_fcb_slot_2 ; Save FCB slot index at hazel_fcb_slot_2
A6AA BEQ jmp_restore_fs_ctx ; Z set: alloc failed -> restore FS context
A6AC .set_flags_bit3←1← A6A1 BVS
LDA #&2a ; A=&2A: workspace flag for bit 3 search
A6AE BNE store_stn_flags_restore ; Match: store station flags and restore
fall through ↓

*Flip command handler

Exchanges the CSD and CSL (library) handles. Saves the current CSD handle from hazel_fs_context_copy, loads the library handle from hazel_fs_prefix_stn into Y, and calls find_station_bit3 to install it as the new CSD. Restores the original CSD handle and falls through to flip_set_station_boot to install it as the new library. Useful when files to be LOADed are in the library and *DIR/*LIB would be inconvenient.

On EntryYcommand line offset in text pointer
A6B0 .cmd_flip
LDA hazel_fs_context_copy ; Load current CSD handle
A6B3 PHA ; Save CSD handle
A6B4 LDY hazel_fs_prefix_stn ; Load library handle into Y
A6B7 JSR find_station_bit3 ; Install library as new CSD
A6BA PLA ; Restore original CSD handle
A6BB TAY ; Y = original CSD (becomes library)
fall through ↓

Set boot option for a station in the table

Scans up to 16 station table entries for one matching the current address with bit 4 set (boot-eligible). Stores the requested boot type in the matching entry and calls restore_fs_context to re-establish the filing system state.

On EntryAboot type code to store
On ExitA, X, Yclobbered
A6BC .flip_set_station_boot←2← A654 JSR← A70B JSR
LDX #&10 ; X=&10: max 16 station entries
A6BE CLV ; Clear V (no match found yet)
A6BF .loop_search_stn_boot←2← A6C5 BNE← A6CC BEQ
DEX ; Decrement station index
A6C0 BMI done_search_boot ; All searched: exit loop
A6C2 JSR match_station_net ; Check if station[X] matches
A6C5 BNE loop_search_stn_boot ; No match: try next station
A6C7 LDA hazel_fcb_status,x ; Load station flags byte
A6CA AND #&10 ; Test bit 4 (active flag)
A6CC BEQ loop_search_stn_boot ; Not active: try next station
A6CE TYA ; Transfer boot type to A
A6CF STA hazel_fcb_slot_attr,x ; Store boot setting for station
A6D2 BIT always_set_v_byte ; Set V flag (station match found)
A6D5 .done_search_boot←1← A6C0 BMI
STY hazel_fs_prefix_stn ; Store boot type
A6D8 BVS set_flags_boot ; V set (matched): skip allocation
A6DA TYA ; Boot type to A
A6DB JSR alloc_fcb_slot ; Allocate FCB slot for new entry
A6DE STA hazel_fcb_slot_3 ; Store allocation result
A6E1 BEQ jmp_restore_fs_ctx ; Zero: allocation failed, exit
A6E3 .set_flags_boot←1← A6D8 BVS
LDA #&32 ; A=&32: station flags (active+boot)
A6E5 .store_stn_flags_restore←2← A683 BNE← A6AE BNE
STA hazel_fcb_status,x ; Store station flags
A6E8 .jmp_restore_fs_ctx←3← A67F BEQ← A6AA BEQ← A6E1 BEQ
JMP restore_fs_context ; Restore FS context and return

FS reply 1: flag boot pending, then fall into handle-copy

Closes all network channels via close_all_net_chans, sets bit 6 of fs_flags (TSB &0D6C, marking the boot-pending state), SECs to signal boot-pending downstream, loads the boot-type byte from the FS reply (hazel_txcb_result) into hazel_fs_flags, pushes it on the stack, and falls through into fsreply_2_copy_handles.

The pushed byte is not consumed by fsreply_2_copy_handles itself — that routine only copies the per-handle table and uses PHP/PLP for its own Carry handling. The matching PLA lives much further down the boot chain, in boot_persist_fs_maybe at &A732, which tests the recovered boot-type byte against 2 to decide whether to call OSBYTE &6D. Anyone following the stack across this fall-through should look past fsreply_2_copy_handles and boot_try_findlib to find the pop.

A6EB .fsreply_1_boot
JSR close_all_net_chans ; Close all network channels
A6EE LDA #&40 ; A=&40: bit-6 mask for fs_flags (boot-pending flag)
A6F0 TSB fs_flags ; Set boot-pending bit on fs_flags (TSB = test-and-set)
A6F3 SEC ; C=1: signal boot-pending to fsreply_2_copy_handles (its BCS at &A6FB takes the boot path)
A6F4 LDA hazel_txcb_result ; Load boot-type byte from FS reply (hazel_txcb_result)
A6F7 STA hazel_fs_flags ; Store boot type as hazel_fs_flags (consumed later by boot_select_cmd)
A6FA PHA ; Push boot-type byte (popped later by boot_persist_fs_maybe at &A732)
fall through ↓

FS reply 2: install handles and (optionally) boot

Records the file-server / printer-server / library handles from the I-AM reply into the station table by calling find_station_bit2, find_station_bit3, and flip_set_station_boot in turn with the three handle bytes loaded from the TXCB reply (hazel_txcb_data, hazel_txcb_flag, hazel_txcb_count). PHP/PLP carry a flag across the calls: when Carry is clear on entry the routine returns via return_with_last_flag; when Carry is set it continues into the boot path at boot_try_findlib, which OSCLIs -NET-FindLib, then falls into boot_persist_fs_maybe (OSBYTE &6D when boot type ≥ 2), clears the auto-boot flag in hazel_fs_lib_flags, and (unless CTRL is held) falls through to boot_select_cmd to execute the !Boot command.

Two entry contracts:

  • Direct dispatch via the FS-reply table entry at &2C — the dispatcher arrives with Carry clear, so BCS at &A6FB is not taken and the routine exits via JMP return_with_last_flag without ever reaching the PLA at &A732. The stack contract is satisfied trivially.
  • Fall-through from fsreply_1_boot — fsreply_1_boot pushes A (the boot-type byte) and SECs before falling in, so BCS is taken and the boot path runs; the PLA at &A732 then pops the boot-type byte cleanly.

Direct dispatch with Carry set is not part of the contract; the boot path requires the pre-pushed A from fsreply_1_boot.

On EntryAboot-type byte (pushed by fsreply_1_boot when arriving via fall-through; ignored on direct-dispatch C-clear path)
CARRYset when boot processing should follow (only legal via fsreply_1_boot fall-through)
A6FB .fsreply_2_copy_handles
PHP ; Save processor status
A6FC LDY hazel_txcb_data ; Load station number from reply
A6FF JSR find_station_bit2 ; Find station entry with bit 2
A702 LDY hazel_txcb_flag ; Load network number from reply
A705 JSR find_station_bit3 ; Find station entry with bit 3
A708 LDY hazel_txcb_count ; Load boot type from reply
A70B JSR flip_set_station_boot ; Set boot config for station
A70E PLP ; Restore processor status
A70F BCS boot_try_findlib ; Carry set: proceed with boot
A711 JMP return_with_last_flag ; Return with last flag

OSCLI command string '-NET-FindLib'<CR>

Passed to OSCLI by boot_try_findlib. The -NET- prefix is the MOS hyphen-bracketed FS-selector form — see that subroutine's description for the convention and why it's used here rather than a plain *FindLib.

A714 .findlib_oscli_cmd
EQUS "-Net-FindLib"
A720 EQUB &0D

If CMOS auto-CLI bit set, OSCLI '-NET-FindLib'

Reads CMOS byte &11 via OSBYTE &A1 and tests bit 1 (the auto-CLI / auto-run-FindLib flag). If clear, returns immediately; if set, OSCLIs findlib_oscli_cmd (-NET-FindLib<CR>). Falls through to boot_persist_fs_maybe in either case.

Why the -NET- prefix

-NET- is MOS's hyphen-bracketed filing-system selector. The general form -FS-COMMAND makes FS the temporary active FS for this OSCLI without touching the currently-selected default, then parses COMMAND. If COMMAND matches an internal MOS command it runs normally; on the unknown-command fallthrough the temp-FS bit suppresses the service-4 ROM broadcast and dispatches via FSCV,3 directly to the selected FS. NET is the short name registered by the NFS / ANFS ROM (others register DISC, ADFS, TAPE).

NFS is already the current FS by the time this code runs. svc_3_autoboot selects it via select_fs_via_cmd_net_fs before the synchronous *I AM exchange on the cold-boot path, and the user-typed *I AM route requires *NET first because *I AM is itself an NFS * command.

The prefix therefore provides defensive routing, not FS selection. A plain *FindLib would broadcast service-4 to all sideways ROMs before falling through to FSCV,3, exposing the command to interception by any ROM that has claimed FindLib (including by abbreviation). The -NET- form sets the temp-FS bit, which suppresses the broadcast and dispatches via FSCV,3 directly to NFS, so the lookup deterministically hits NFS's *RUN-from-library path.

The same routing is used by boot_cmd_load_str (L.-NET-!Boot) and boot_cmd_exec_str (E.-NET-!Boot).

A721 .boot_try_findlib←1← A70F BCS
LDX #&11 ; X=&11: CMOS RAM byte index
A723 JSR osbyte_a1 ; Read CMOS &11 via osbyte_a1
A726 TYA ; Result to A
A727 AND #2 ; Mask bit 1 (auto-CLI flag)
A729 BEQ boot_persist_fs_maybe ; Bit clear: skip auto-CLI
A72B LDX #<(findlib_oscli_cmd) ; -NET-FindLib command pointer (low)
A72D LDY #>(findlib_oscli_cmd) ; -NET-FindLib command pointer (high)
A72F JSR oscli ; OSCLI '-NET-FindLib': dispatch to NFS via FSCV,3 (bypass service-4 broadcast)
A732 .boot_persist_fs_maybe←1← A729 BEQ
PLA ; Pop saved A
A733 CMP #2 ; Compare with 2
A735 BCC check_auto_boot_flag ; Below: skip making FS permanent
A737 LDA #osbyte_make_fs_permanent ; Boot type >= 2 (NFS-resident !Boot): A=&6D to commit NFS as default FS
A739 JSR osbyte ; Make temporary filing system permanent
fall through ↓

Test+clear bit 2 of fs_lib_flags; on CTRL, bail out

Atomically tests and clears bit 2 of hazel_fs_lib_flags — the auto-boot flag — then dispatches based on the test result:

  • Bit 2 was set: skip the CTRL-key check and BNE straight into boot_select_cmd to issue the boot OSCLI.
  • Bit 2 was clear: fall through and test for the CTRL key via OSBYTE &79. CTRL held → fall into boot_cancel_rts (boot cancelled). CTRL not held → BPL into boot_select_cmd anyway.

The body uses the classic 6502 "test bit, modify, restore test result" idiom: load the flags, copy to X, AND with the bit to isolate it, PHP to stash the Z flag from that test, then TXA back, AND with the complement mask to clear the bit, store the modified flags, PLP to recover the original Z. This way the flag byte is updated unconditionally but the dispatch decision is made on the pre-update state — saving a separate read-modify sequence.

A73C .check_auto_boot_flag←1← A735 BCC
LDA hazel_fs_lib_flags ; Load config flags
A73F TAX ; Save copy in X
A740 AND #4 ; Test bit 2 (auto-boot flag)
A742 PHP ; Save bit 2 test result
A743 TXA ; Restore full flags
A744 AND #&fb ; Clear bit 2 (consume flag)
A746 STA hazel_fs_lib_flags ; Store cleared flags
A749 PLP ; Restore bit 2 test result
A74A BNE boot_select_cmd ; Auto-boot flag was set: skip CTRL check, dispatch boot via boot_select_cmd
A74C LDA #osbyte_scan_keyboard ; OSBYTE &79: scan keyboard
A74E LDX #(255 - inkey_key_ctrl) EOR 128 ; X = CTRL key scan code (negative-X INKEY form for OSBYTE &79)
A750 JSR osbyte ; Test for ctrl key pressed
A753 TXA ; Copy X to A
A754 BPL boot_select_cmd ; CTRL not pressed: proceed to boot
A756 .boot_cancel_rts←1← A778 BEQ
RTS ; Cancel boot, return (CTRL held, or boot type 0 via BEQ at &A764)
A757 .boot_cmd_load_str
EQUS "L.-Net-!Boot" ; Boot cmd '*LOAD -NET-!Boot' (load !Boot via NFS, bypassing service-4 broadcast — see boot_try_findlib)
A763 EQUB &0D ; CR terminator
A764 .boot_cmd_exec_str
EQUS "E.-Net-!Boot" ; Boot cmd '*EXEC -NET-!Boot' (exec !Boot via NFS, bypassing service-4 broadcast — see boot_try_findlib)
A770 EQUB &0D
A771 .boot_cmd_lo_table←1Used as index base by← A77A LDX
EQUS "pW^d"

Branch to boot_cancel_rts on boot type 0, else fall into boot_cmd_oscli

Two-instruction gate at the head of the boot-OSCLI dispatch. Loads Y from hazel_fs_flags (the boot-type byte the FS reply stashed in fsreply_1_boot at &A6E1); if zero, BEQs into boot_cancel_rts — the empty / no-op boot case. Otherwise falls through into boot_cmd_oscli with Y already loaded as the index into boot_cmd_lo_table.

Two callers, both from check_auto_boot_flag: the BNE at &A74A (auto-boot flag was set, skip CTRL check) and the BPL at &A740 (CTRL not pressed, proceed to boot).

A775 .boot_select_cmd←2← A74A BNE← A754 BPL
LDY hazel_fs_flags ; Y = boot-type byte from FS reply (0..3)
A778 BEQ boot_cancel_rts ; Z (boot type 0): cancel boot via boot_cancel_rts
fall through ↓

Look up boot command in boot_cmd_lo_table and OSCLI it

Loads X = boot_cmd_lo_table,Y (low byte of the boot-command address), sets Y=&A7 (high byte — boot strings live in &A7xx), then JMPs to oscli with (X,Y) pointing at a CR-terminated command string.

Two entry paths:

  • JMP tail-call from &A5E8 with Y=3 hardcoded — forces the exec-via-NFS boot (boot_cmd_exec_str).
  • Fall-through from boot_select_cmd with Y already loaded from hazel_fs_flags — the normal logon-boot path, dispatching on the FS-supplied boot type.
On EntryYboot-command index (1=load, 2=run-on-current-FS, 3=exec)
A77A .boot_cmd_oscli←1← A5EA JMP
LDX boot_cmd_lo_table,y ; Load boot-command low byte from boot_cmd_lo_table[Y]
A77D LDY #&a7 ; Y=&A7: high byte (boot strings live in &A7xx)
A77F JMP oscli ; Tail-jump to OSCLI to execute the boot command

ANFS *command dispatch tables (5 concatenated sub-tables)

See the comment block immediately above the cmd_table_fs declaration in the driver for the sub-table layout, walker contract, and flag-byte encoding. Each entry's two-byte dispatch word stores target-1; PHA/PHA/RTS arrives at target. Per-entry inline comments below name the command, syntax-template index, and dispatch target.

A782 .cmd_table_fs←11Used as index base by← 8BFF LDA← 8C0E LDA← 8C16 LDA← 8C23 LDA← 946C LDA← 9474 LDA← 95EF LDA← A476 LDA← A47B LDA← A48B LDA← A4C2 LDA
EQUS "Net" ; Econet HW check + select NFS
A785 EQUB &80 ; no syn
A786 EQUW cmd_net_check_hw - 1
A788 EQUS "Pollps" ; syn 8: (<stn. id.>|<ps type>)
A78E EQUB &8C ; syn &8
A78F EQUW cmd_pollps - 1
A791 EQUS "Prot" ; toggle CMOS protection bit
A795 EQUB &80 ; no syn
A796 EQUW cmd_prot - 1
A798 EQUS "PS" ; syn 8: (<stn. id.>|<ps type>)
A79A EQUB &88 ; syn &8
A79B EQUW cmd_ps - 1
A79D EQUS "Roff" ; printer offline
A7A1 EQUB &80 ; no syn
A7A2 EQUW cmd_roff - 1
A7A4 EQUS "Unprot" ; toggle CMOS protection bit
A7AA EQUB &80 ; no syn
A7AB EQUW cmd_unprot - 1
A7AD EQUS "Wdump" ; syn 4 -- *DUMP alias
A7B2 EQUB &C4 ; syn &4, V if no arg
A7B3 EQUW cmd_dump - 1
A7B5 EQUB &80 ; Sub-table 1 end (walker reads &80 -> stop)
A7B6 EQUB &80 ; Padding (alignment before sub-table 2)
A7B7 .cmd_table_nfs
EQUS "Access" ; syn 9: <obj> (L)(W)(R)...
A7BD EQUB &C9 ; syn &9, V if no arg
A7BE EQUW cmd_fs_operation - 1
A7C0 EQUS "Bye" ; log off FS
A7C3 EQUB &80 ; no syn
A7C4 EQUW cmd_bye - 1
A7C6 EQUS "Cdir" ; syn 6 -- create directory
A7CA EQUB &C6 ; syn &6, V if no arg
A7CB EQUW cmd_cdir - 1
A7CD EQUS "Dir" ; syn 1: (<dir>)
A7D0 EQUB &81 ; syn &1
A7D1 EQUW cmd_dir - 1
A7D3 EQUS "Flip" ; swap fs/private workspace
A7D7 EQUB &80 ; no syn
A7D8 EQUW cmd_flip - 1
A7DA EQUS "FS" ; syn &B -- file-server selection
A7DC EQUB &8B ; syn &B
A7DD EQUW cmd_fs - 1
A7DF .cmd_table_nfs_iam←1Used as index base by← 8DE2 LDA
EQUS "I am" ; syn 2: (<stn>) <user>...
A7E3 EQUB &C2 ; syn &2, V if no arg
A7E4 EQUW cmd_iam_save_ctx - 1
A7E6 EQUS "Lcat" ; syn 1: (<dir>) -- *CAT of library
A7EA EQUB &81 ; syn &1
A7EB EQUW cmd_lcat - 1
A7ED EQUS "Lex" ; syn 1: (<dir>) -- *EX of library
A7F0 EQUB &81 ; syn &1
A7F1 EQUW cmd_lex - 1
A7F3 EQUS "Lib" ; syn 5: <dir> -- set library
A7F6 EQUB &C5 ; syn &5, V if no arg
A7F7 EQUW cmd_fs_operation - 1
A7F9 EQUS "Pass" ; syn 7: <pass> ...
A7FD EQUB &C7 ; syn &7, V if no arg
A7FE EQUW cmd_pass - 1
A800 EQUS "Rename" ; syn &A: <old> <new>
A806 EQUB &CA ; syn &A, V if no arg
A807 EQUW cmd_rename - 1
A809 EQUS "Wipe" ; syn 1: (<dir>) -- delete with confirm
A80D EQUB &81 ; syn &1
A80E EQUW cmd_wipe - 1
A810 EQUB &80, &46, &8E, &4F, &6E, &80, &00, &00
A818 EQUS "Net" ; *HELP NET
A81B EQUB &80 ; no syn
A81C EQUW help_net - 1
A81E EQUS "Utils" ; *HELP UTILS
A823 EQUB &80 ; no syn
A824 EQUW help_utils - 1
A826 EQUB &80 ; Sub-table 3 end (walker reads &80 -> stop)
A827 .cmd_table_syntax_help
EQUS "FS" ; FS not selected
A829 EQUB &C1 ; syn &1, V if no arg
A82A EQUW set_fs_or_ps_cmos_station - 1
A82C EQUS "PS" ; PS not selected
A82E EQUB &C3 ; syn &3, V if no arg
A82F EQUW set_fs_or_ps_cmos_station - 1
A831 EQUS "NoSpace" ; caller &9625
A838 EQUB &80 ; no syn
A839 EQUW &9623
A83B EQUS "Space" ; caller &961B
A840 EQUB &80 ; no syn
A841 EQUW &9619
A843 EQUB &80 ; Sub-tables 4/5 separator
A844 EQUS "FS" ; caller &9672
A846 EQUB &81 ; syn &1
A847 EQUW print_fs_address - 1
A849 EQUS "PS" ; caller &9661
A84B EQUB &83 ; syn &3
A84C EQUW print_ps_address - 1
A84E EQUS "Space" ; caller &9643
A853 EQUB &80 ; no syn
A854 EQUW &9642

Service 8: unrecognised OSWORD

Handles MOS service call 8 (unrecognised OSWORD). Filters OSWORD codes &0E-&14 by subtracting &0E (via CLC/SBC &0D) and rejecting values outside 0-6. For valid codes, calls osword_setup_handler to push the dispatch address, then copies 3 bytes from the RX buffer to osword_flag workspace.

On EntryAOSWORD number (from osbyte_a_copy)
Yparameter passed by service-call dispatch
A856 .svc_8_osword
BRA osword_store_svc_state ; BRA osword_store_svc_state -- skip past 22-byte caller-cleanup frame CLC so SBC subtracts value+1
A858 EQUB &A5, ; OSWORD setup state (13 bytes -- constants and &EF ; offsets used by svc_8_osword)
A85A EQUB &E9, &0D ; A = OSWORD - &0E (CLC+SBC = -&0E)
A85C EQUB &30, &2D ; Below &0E: not ours, return
A85E EQUB &C9, &07 ; Index >= 7? (OSWORD > &14)
A860 EQUB &B0, &29 ; Above &14: not ours, return
A862 EQUB &AA ; X=OSWORD handler index (0-6)
A863 EQUB &A0, &06 ; Y=6: save 6 workspace bytes
A865 .loop_save_osword_ws←1← A870 BNE
LDA svc_state,y ; Read svc_state[Y] (frame slot)
A868 PHA ; Save on stack
A869 LDA tx_imm_idx_base,y ; Load OSWORD parameter byte
A86C STA svc_state,y ; Copy parameter to workspace
A86F DEY ; Next slot
A870 .osword_store_svc_state←1← A856 BRA
BNE loop_save_osword_ws ; Loop until Y wraps
A872 JSR osword_setup_handler ; Set up dispatch and save state
A875 LDY #&fa ; Y=&FA: restore 6 workspace bytes
A877 .loop_restore_osword_ws←1← A87C BNE
PLA ; Restore saved workspace byte
A878 STA nmi_buf_idx_base,y ; Store to osword_flag workspace
A87B INY ; Next byte
A87C BNE loop_restore_osword_ws ; Loop until all 6 restored
A87E RTS ; Return from svc_8_osword

Push OSWORD handler address for RTS dispatch

Indexes the OSWORD dispatch table by X to push a handler address (hi then lo) onto the stack. Copies 3 bytes from the osword_flag workspace into the RX buffer, loads PB byte 0 (the OSWORD sub-code), and clears svc_state. The subsequent RTS dispatches to the pushed handler address.

On EntryXOSWORD handler index (0-6)
A87F .osword_setup_handler←1← A872 JSR
LDA osword_subcode_dispatch,x ; X = OSWORD index (0-6)
A882 PHA ; Push for stack frame manipulation
A883 LDA osword_pb_ready,x ; Load handler address low byte
A886 PHA ; Push again
A887 LDA (ws_ptr_hi),y ; Copy 3 bytes (Y=2,1,0)
A889 STY svc_state ; Load from osword_flag workspace
A88B .rts_osword_setup
RTS ; RTS dispatches to pushed handler
A88C .osword_pb_ready←1Used as index base by← A883 LDA
EQUB &99, &8A, &31, &4E, &A6, &BB, &68
A893 .osword_subcode_dispatch←1Used as index base by← A87F LDA
EQUB &A8, &A8, &A9, &A9, &A9, &A9
A899 EQUB &AC ; Load template source pointer

OSWORD &0E (14): real-time-clock request dispatch

Entry for the OSWORD &0E clock request. First calls fs_num_via_osargs and only proceeds if NetFS is the current filing system, so the request is left for other filing systems to service otherwise. Then dispatches on the OSWORD sub-code (in X) to the save-and-convert or write-back path.

A89A .osword_0e_dispatch
JSR fs_num_via_osargs ; Only act if NetFS is the current FS
A89D BNE osword_0e_unclaimed ; Not NetFS: leave for other filing systems
A89F TXA ; A = OSWORD sub-code
A8A0 BEQ save_txcb_done ; Sub-code 0: write-back path
A8A2 DEX ; Sub-code 1?
A8A3 BEQ save_txcb_and_convert ; Yes: save-and-convert path
A8A5 DEX ; Sub-code 3?
A8A6 DEX
A8A7 BEQ save_txcb_done ; Yes: write-back path
A8A9 DEX ; Sub-code 4?
A8AA BEQ save_txcb_and_convert ; Yes: save-and-convert path
A8AC .osword_0e_unclaimed←1← A89D BNE
LDA #8 ; A=8: service state = unclaimed
A8AE STA svc_state ; Store service state
A8B0 RTS ; Return

OSWORD &0E: save TXCB and format the real-time clock reply

Saves the current TX control block, reads the clock/date fields returned in the HAZEL TXCB workspace, converts each to packed BCD via bin_to_bcd, forms the year (7-bit field plus base &51), adjusting into the 20xx century when it reaches 100, and writes the formatted result to the OSWORD reply buffer at (ws_ptr_hi),Y. Reworked in 4.26.

A8B1 .save_txcb_and_convert←3← A8A3 BEQ← A8AA BEQ← A904 JSR
PHY ; Save Y (reply buffer offset)
A8B2 LDY #&10 ; Y=&10: length of TXCB to save
A8B4 JSR save_net_tx_cb ; Save current TX control block
A8B7 PLY ; Restore Y (reply buffer offset)
A8B8 LDX #3 ; X=3: convert 3 clock bytes (secs/mins/hours)
A8BA .loop_convert_clock←1← A8C4 BNE
PHX ; Save loop counter
A8BB LDA hazel_txcb_flag,x ; Load clock byte X from HAZEL TXCB flags
A8BE JSR bin_to_bcd ; Convert binary to BCD
A8C1 PLX ; Restore loop counter
A8C2 PHA ; Stack the BCD result
A8C3 DEX ; Next clock byte
A8C4 BNE loop_convert_clock ; Loop for all 3 clock bytes
A8C6 PHX ; Save X across the day/month conversion
A8C7 LDA hazel_txcb_data ; Load day+month packed byte
A8CA AND #&1f ; Mask low 5 bits: day-of-month
A8CC JSR bin_to_bcd ; Convert day to BCD
A8CF PHA ; Stack BCD day
A8D0 LDA hazel_txcb_flag ; Reload the month/flags byte
A8D3 PHA ; Save it across the shift
A8D4 LSR ; Shift high nibble (month) down to low nibble
A8D5 LSR ; Shift high nibble down (2 of 4)
A8D6 LSR ; Shift high nibble down (3 of 4)
A8D7 LSR ; Shift high nibble down (4 of 4)
A8D8 STA hazel_txcb_flag ; Store the shifted month back
A8DB PLA ; Recover the original byte
A8DC AND #&0f ; Mask low nibble
A8DE JSR bin_to_bcd ; Convert to BCD
A8E1 PHA ; Stack the result
A8E2 LDA hazel_txcb_data ; Reload day+month byte
A8E5 AND #&e0 ; Isolate the high year bits
A8E7 LSR ; Shift into position
A8E8 ORA hazel_txcb_flag ; Combine with the low year bits
A8EB ADC #&51 ; Add year base (&51); range 81..208
A8ED CMP #&64 ; Year >= 100 (i.e. 2000+)?
A8EF BCC year_century_done ; No: 19xx, use the year as-is
A8F1 SBC #&64 ; Yes: subtract 100 for the 20xx century
A8F3 .year_century_done←1← A8EF BCC
PHP ; Save carry (century flag) across the BCD convert
A8F4 JSR bin_to_bcd ; Convert year to BCD
A8F7 PLP ; Restore carry
A8F8 PHA ; Stack the BCD year
A8F9 LDX #&f9 ; X=&F9: 7 stacked bytes to unwind (wraps to 0)
A8FB .loop_store_reply←1← A900 BNE
PLA ; Pull next converted byte
A8FC STA (ws_ptr_hi),y ; Store into the OSWORD reply buffer
A8FE INY ; Advance reply offset
A8FF INX ; Count stacked bytes
A900 BNE loop_store_reply ; Loop until all stacked bytes stored
A902 RTS ; Return

OSWORD &0E: read the CMOS clock and finish the reply

Builds the BCD reply via save_txcb_and_convert, then reads the Master 128 CMOS real-time clock with OSWORD osword_read_cmos_clock. On failure it writes a default century of "20" into the reply. Note: the year conversion adjusts 20xx correctly but does not convert 2100-2107 to 21xx.

A903 .save_txcb_done←2← A8A0 BEQ← A8A7 BEQ
INY ; Advance reply offset
A904 JSR save_txcb_and_convert ; Build the BCD clock/date reply
A907 LDA #2 ; A=2 (reply length/marker)
A909 STA (ws_ptr_hi) ; Store into the reply buffer
A90B LDA #osword_read_cmos_clock ; OSWORD: read CMOS real-time clock
A90D LDX ws_ptr_hi ; X = reply buffer low
A90F LDY table_idx ; Y = reply buffer high
A911 PHP ; Save flags
A912 JSR osword ; Read the CMOS clock Read CMOS clock
A915 PLP ; Restore flags
A916 BCC return_4 ; Success: return
A918 LDY #&0c ; Failure: write default century
A91A LDA #&30 ; '0'
A91C STA (ws_ptr_hi),y ; Store century units
A91E DEY ; Back up one
A91F LDA #&32 ; '2'
A921 STA (ws_ptr_hi),y ; Store century tens -- default "20"
A923 .return_4←1← A916 BCC
RTS

Binary to packed BCD

Converts the binary value in A to packed BCD using decimal-mode addition. Preserves X/Y. Rewritten helper shared by save_txcb_and_convert.

A924 .bin_to_bcd←4← A8BE JSR← A8CC JSR← A8DE JSR← A8F4 JSR
PHP ; Save flags
A925 TAX ; Hold binary value in X
A926 LDA #&99 ; A = &99 (BCD accumulator seed)
A928 SED ; Enter decimal mode
A929 .loop_bcd_add←1← A92D BPL
CLC ; Clear carry for the add loop
A92A ADC #1 ; Add 1 in decimal mode
A92C DEX ; Count down the binary value
A92D BPL loop_bcd_add ; Loop until the BCD total is built
A92F PLP ; Restore flags
A930 RTS ; Return with A = packed BCD
A931 EQUB &00

OSWORD &10 handler: send network packet

ASL on tx_complete_flag shifts the old bit 7 into Carry. When that bit was clear (C=0, TX in progress) the handler stores Y back through the parameter-block pointer at (ws_ptr_hi),Y and RTS, leaving the caller a status byte. When it was set (C=1, TX idle) execution falls through to the start path at setup_ws_rx_ptrs, which seeds the workspace pointers from net_rx_ptr_hi/#&6F, copies 16 bytes of the parameter block into the workspace via copy_pb_byte_to_ws and JMPs to tx_begin to launch the transmission.

On EntryX, YOSWORD parameter block pointer (low, high)
A932 .osword_10_handler
ASL tx_complete_flag ; ASL tx_complete_flag: old bit 7 -> C
A935 TYA ; A = Y (saved index)
A936 BCS setup_ws_rx_ptrs ; C=1 (TX idle): start new transmission
A938 STA (ws_ptr_hi),y ; C=0 (TX busy): write status byte back to PB
A93A RTS ; Return (TX still in progress)
A93B .setup_ws_rx_ptrs←1← A936 BCS
LDA net_rx_ptr_hi ; Read net_rx_ptr_hi
A93D STA ws_ptr_lo ; Copy to ws_ptr_lo
A93F STA nmi_tx_block_hi ; Also set as NMI TX block high
A941 LDA #&6f ; Low byte = &6F
A943 STA osword_flag ; Set osword_flag
A945 STA nmi_tx_block ; Set NMI TX block low
A947 LDX #&0f ; X=&0F: byte count for copy
A949 JSR copy_pb_byte_to_ws ; Copy data and begin transmission
A94C JMP tx_setup_from_txcb ; Jump to begin Econet transmission

OSWORD &11 handler: receive network packet

Reached via the OSWORD dispatch as well as via fall-through from osword_10_handler. Configures the workspace pointer from nfs_workspace_hi, saves the Econet interrupt state via ROR econet_flags, and either uses the slot specified by the caller (Y non-zero) or scans from slot 3 onwards via byte_to_2bit_index to find a free slot. Stores the resulting status byte and the copied PB bytes back into the caller's parameter block.

On EntryX, YOSWORD parameter block pointer (low, high)
A94F .osword_11_handler
LDX nfs_workspace_hi ; Load NFS workspace page high byte
A951 STX ws_ptr_lo ; Set workspace pointer high
A953 STY osword_flag ; Set workspace pointer low from Y
A955 ROR econet_flags ; Rotate Econet flags (save interrupt state)
A958 TAY ; Y=OSWORD flag (slot specifier)
A959 STA work_ae ; Store OSWORD flag
A95B BNE use_specified_slot ; Non-zero: use specified slot
A95D LDA #3 ; A=3: start searching from slot 3
A95F .loop_find_rx_slot←1← A971 BNE
JSR byte_to_2bit_index ; Convert slot to 2-bit workspace index
A962 BCS store_rx_result ; C set: slot invalid, store result
A964 LSR ; Divide by 2
A965 LSR ; Continue shift
A966 TAX ; Index to X
A967 LDA (osword_flag),y ; Load workspace byte at offset
A969 BEQ store_rx_result ; Zero: slot empty, store result
A96B CMP #'?' ; Compare with &3F ('?' marker)
A96D BEQ store_rx_slot_found ; Match: slot found for receive
A96F INX ; Step to next slot
A970 TXA ; Transfer back to A
A971 BNE loop_find_rx_slot ; Loop back (A != 0)
A973 .store_rx_slot_found←1← A96D BEQ
TXA ; Found slot index
A974 LDX #0 ; X=0: index for indirect store
A976 STA (ws_ptr_hi,x) ; Store slot number to PB byte 0
A978 .use_specified_slot←1← A95B BNE
JSR byte_to_2bit_index ; Convert specified slot to workspace index
A97B BCS store_rx_result ; C set: slot invalid, store result
A97D DEY ; Back up scan
A97E STY osword_flag ; Update workspace pointer low
A980 LDA #&c0 ; A=&C0: slot active marker
A982 LDY #1 ; Y=1: result-byte offset
A984 LDX #&0b ; X=&0B: byte count for PB copy
A986 CPY work_ae ; Compare Y with OSWORD flag
A988 ADC (osword_flag),y ; Add workspace byte (check slot state)
A98A BEQ copy_pb_and_mark ; Zero: slot ready, copy PB and mark
A98C BMI increment_and_retry ; Negative: slot busy, increment and retry
A98E .loop_copy_slot_data←1← A99E BNE
CLC ; For the ADC chain
A98F .copy_pb_and_mark←1← A98A BEQ
JSR copy_pb_byte_to_ws ; Copy PB byte to workspace slot
A992 BCS osword_11_done ; C set: copy done, finish
A994 LDA #'?' ; A=&3F: mark slot as pending ('?')
A996 LDY #1 ; Y=1: workspace flag offset
A998 STA (osword_flag),y ; Store pending marker to workspace
A99A BNE osword_11_done ; Branch to OSWORD-11 done
A99C .increment_and_retry←1← A98C BMI
ADC #1 ; Increment retry counter
A99E BNE loop_copy_slot_data ; Non-zero: retry copy loop
A9A0 DEY ; Decrement Y (adjust offset)
A9A1 .store_rx_result←3← A962 BCS← A969 BEQ← A97B BCS
STA (ws_ptr_hi),y ; Store result A to PB via Y
A9A3 .osword_11_done←2← A992 BCS← A99A BNE
ROL econet_flags ; Rotate Econet flags back (restore state)
A9A6 RTS ; Return from OSWORD 11 handler

OSWORD &12 handler: receive packet from workspace

Reads net_rx_ptr_hi into ws_ptr_lo, sets Y=&7F and reads the status byte from the RX block, then Y=&80 to flag the packet as processed. The body proceeds to copy the packet payload from the RX buffer into the OSWORD parameter block via copy_pb_byte_to_ws.

On EntryX, YOSWORD parameter block pointer (low, high)
A9A7 .osword_12_handler
LDA net_rx_ptr_hi ; Set workspace from RX ptr high
A9A9 STA ws_ptr_lo ; Store to ws_ptr_lo
A9AB LDY #&7f ; Y=&7F: last byte of RX buffer
A9AD LDA (net_rx_ptr),y ; Load port/count from RX buffer
A9AF INY ; Y=&80: set workspace pointer
A9B0 STY osword_flag ; Store as osword_flag
A9B2 TAX ; X = port/count value
A9B3 DEX ; X-1: adjust count
A9B4 LDY #0 ; Y=0 for copy
A9B6 JSR copy_pb_byte_to_ws ; Copy workspace data
A9B9 JMP commit_state_byte ; Update state and return
A9BC .osword_13_dispatch
TAX ; X = sub-code
A9BD CMP #&13 ; Sub-code < &13?
A9BF BCS rts_osword_13 ; Out of range: return
A9C1 LDA osword_13_dispatch_hi,x ; Read dispatch hi from osword_13_dispatch_hi+X
A9C4 PHA ; Push hi for RTS dispatch
A9C5 LDA osword_13_dispatch_lo,x ; Read dispatch lo from osword_13_dispatch_lo+X
A9C8 PHA ; Push lo for RTS dispatch
A9C9 .rts_osword_13←1← A9BF BCS
RTS ; RTS -> dispatched OSWORD &13 sub-handler
A9CA .osword_13_dispatch_lo←1Used as index base by← A9C5 LDA
EQUB <(osword_13_read_station - 1) ; sub &00: osword_13_read_ station (read FS station)
A9CB EQUB <(osword_13_set_station - 1) ; sub &01: osword_13_set_ station (set FS station)
A9CC EQUB <(osword_13_read_ws_pair - 1) ; sub &02: osword_13_read_ws_ pair (read workspace pair)
A9CD EQUB <(osword_13_write_ws_pair - 1) ; sub &03: osword_13_write_ws_ pair (write workspace pair)
A9CE EQUB <(osword_13_read_prot - 1) ; sub &04: osword_13_read_prot (read protection mask)
A9CF EQUB <(osword_13_write_prot - 1) ; sub &05: osword_13_write_prot (write protection mask)
A9D0 EQUB <(osword_13_read_handles - 1) ; sub &06: osword_13_read_ handles (read transfer handles)
A9D1 EQUB <(osword_13_set_handles - 1) ; sub &07: osword_13_set_ handles (set transfer handles)
A9D2 EQUB <(osword_13_read_rx_flag - 1) ; sub &08: osword_13_read_rx_ flag (read RX flag)
A9D3 EQUB <(osword_13_read_rx_port - 1) ; sub &09: osword_13_read_rx_ port (read RX port)
A9D4 EQUB <(osword_13_read_error - 1) ; sub &0A: osword_13_read_error (read last error)
A9D5 EQUB <(osword_13_read_context - 1) ; sub &0B: osword_13_read_ context (read context)
A9D6 EQUB <(osword_13_read_csd - 1) ; sub &0C: osword_13_read_csd (read CSD)
A9D7 EQUB <(osword_13_write_csd - 1) ; sub &0D: osword_13_write_csd (write CSD)
A9D8 EQUB <(osword_13_read_free_bufs - 1) ; sub &0E: osword_13_read_free_ bufs (read free buffers)
A9D9 EQUB <(osword_13_read_ctx_3 - 1) ; sub &0F: osword_13_read_ctx_3 (read context byte 3)
A9DA EQUB <(osword_13_write_ctx_3 - 1) ; sub &10: osword_13_write_ctx_ 3 (write context byte 3)
A9DB EQUB <(osword_13_bridge_query - 1) ; sub &11: osword_13_bridge_ query (bridge query)

OSWORD &13 dispatch high-byte table (18 entries)

Read by osword_13_dispatch as LDA &A9BA,X. The dispatcher pushes the hi byte first then the lo, so RTS lands on target (the table stores target-1).

A9DC .osword_13_dispatch_hi←1Used as index base by← A9C1 LDA
EQUB >(osword_13_read_station - 1) ; sub &00: osword_13_read_ station
A9DD EQUB >(osword_13_set_station - 1) ; sub &01: osword_13_set_ station
A9DE EQUB >(osword_13_read_ws_pair - 1) ; sub &02: osword_13_read_ws_ pair
A9DF EQUB >(osword_13_write_ws_pair - 1) ; sub &03: osword_13_write_ws_ pair
A9E0 EQUB >(osword_13_read_prot - 1) ; sub &04: osword_13_read_prot
A9E1 EQUB >(osword_13_write_prot - 1) ; sub &05: osword_13_write_prot
A9E2 EQUB >(osword_13_read_handles - 1) ; sub &06: osword_13_read_ handles
A9E3 EQUB >(osword_13_set_handles - 1) ; sub &07: osword_13_set_ handles
A9E4 EQUB >(osword_13_read_rx_flag - 1) ; sub &08: osword_13_read_rx_ flag
A9E5 EQUB >(osword_13_read_rx_port - 1) ; sub &09: osword_13_read_rx_ port
A9E6 EQUB >(osword_13_read_error - 1) ; sub &0A: osword_13_read_error
A9E7 EQUB >(osword_13_read_context - 1) ; sub &0B: osword_13_read_ context
A9E8 EQUB >(osword_13_read_csd - 1) ; sub &0C: osword_13_read_csd
A9E9 EQUB >(osword_13_write_csd - 1) ; sub &0D: osword_13_write_csd
A9EA EQUB >(osword_13_read_free_bufs - 1) ; sub &0E: osword_13_read_free_ bufs
A9EB EQUB >(osword_13_read_ctx_3 - 1) ; sub &0F: osword_13_read_ctx_3
A9EC EQUB >(osword_13_write_ctx_3 - 1) ; sub &10: osword_13_write_ctx_ 3
A9ED EQUB >(osword_13_bridge_query - 1) ; sub &11: osword_13_bridge_ query

OSWORD &13 sub 0: read file server station

Returns the current file server station and network numbers in PB[1..2]. If ANFS is not active, ensure_fs_selected auto-selects it (raising net checksum on failure) before the body runs.

A9EE .osword_13_read_station
JSR ensure_fs_selected ; Ensure NFS is currently the selected FS
A9F1 .read_station_bytes
LDY #2 ; Y=2: copy 2 bytes
A9F3 .loop_copy_station←1← A9F9 BNE
LDA hazel_minus_1,y ; Load station byte
A9F6 STA (osword_pb_ptr),y ; Store to PB[Y]
A9F8 DEY ; Step back
A9F9 BNE loop_copy_station ; Loop for bytes 2..1
A9FB RTS ; Return

OSWORD &13 sub 1: set file server station

Sets the file server station and network numbers from PB[1..2]. The prologue at &A9DC calls ensure_fs_selected to verify ANFS is active (auto-selecting it if not), then the body at osword_13_set_station_body processes all FCBs and scans the 16-entry FCB table to reassign handles matching the new station.

A9FC .osword_13_set_station
JSR ensure_fs_selected ; Ensure NFS is currently the selected FS
A9FF .osword_13_set_station_body
LDY #0 ; Y=0 for process_all_fcbs
AA01 JSR process_all_fcbs ; Close all open FCBs
AA04 LDY #2 ; Y=2: copy 2 bytes
AA06 .loop_store_station←1← AA0C BNE
LDA (osword_pb_ptr),y ; Load new station byte from PB
AA08 STA hazel_minus_1,y ; Store to fs_server_base
AA0B DEY ; Step back to previous byte
AA0C BNE loop_store_station ; Loop for bytes 2..1
AA0E JSR clear_if_station_match ; Clear handles if station matches
AA11 LDA #&0e ; A=&0E: bits 1..3 (FS-state mask)
AA13 TSB fs_flags ; Set fs_flags bits 1..3
AA16 LDA #&40 ; A=&40: FS-active flag bit
AA18 TRB fs_flags ; Clear FS-active flag (bit 6)
AA1B LDX #&0f ; X=&0F: scan all 16 FCB slots (X = 15 down to 0)
AA1D .scan_fcb_entry←1← AA85 BPL
LDA hazel_fcb_status,x ; Load FCB flags
AA20 TAY ; Save flags in Y
AA21 AND #2 ; Test bit 1 (FCB allocated?)
AA23 BEQ next_fcb_entry ; No: skip to next entry
AA25 TYA ; Entry index to A
AA26 AND #&df ; Mask bit 5
AA28 STA hazel_fcb_status,x ; Store updated flags
AA2B TAY ; Save in Y
AA2C JSR match_station_net ; Does FCB match new station?
AA2F BNE next_fcb_entry ; No match: skip to next
AA31 CLC ; Clear carry
AA32 TYA ; Restore flags
AA33 AND #4 ; Test bit 2 (handle 1 active?)
AA35 BEQ check_handle_2 ; No: check handle 2
AA37 TYA ; Restore flags
AA38 ORA #&20 ; Set bit 5 (handle reassigned)
AA3A TAY ; Copy A to Y
AA3B LDA hazel_fcb_slot_attr,x ; Get FCB high byte
AA3E STA hazel_fs_saved_station ; Store as handle 1 station
AA41 TXA ; FCB index
AA42 ADC #&20 ; Add &20 for FCB table offset
AA44 STA hazel_fcb_slot_1 ; Store as handle 1 FCB index
AA47 LDA #2 ; A=2: fs_flags bit 1 mask
AA49 TRB fs_flags ; Clear fs_flags bit 1
AA4C .check_handle_2←1← AA35 BEQ
TYA ; Y still holds the saved FCB status -- TYA so we can re-test bit 3 (handle-2 active flag)
AA4D AND #8 ; Test bit 3 (handle 2 active?)
AA4F BEQ check_handle_3 ; No: check handle 3
AA51 TYA ; Copy Y to A
AA52 ORA #&20 ; Set bit 5
AA54 TAY ; Copy A to Y
AA55 LDA hazel_fcb_slot_attr,x ; Get FCB high byte
AA58 STA hazel_fs_context_copy ; Store as handle 2 station
AA5B TXA ; FCB index
AA5C ADC #&20 ; Add &20 for FCB table offset
AA5E STA hazel_fcb_slot_2 ; Store as handle 2 FCB index
AA61 LDA #4 ; A=4: fs_flags bit 2 mask
AA63 TRB fs_flags ; Clear fs_flags bit 2
AA66 .check_handle_3←1← AA4F BEQ
TYA ; Y still holds the saved FCB status -- TYA so we can re-test bit 4 (handle-3 active flag)
AA67 AND #&10 ; Test bit 4 (handle 3 active?)
AA69 BEQ store_updated_status ; No: store final flags
AA6B TYA ; Restore flags
AA6C ORA #&20 ; Set bit 5
AA6E TAY ; Save updated flags
AA6F LDA hazel_fcb_slot_attr,x ; Get FCB high byte
AA72 STA hazel_fs_prefix_stn ; Store as handle 3 station
AA75 TXA ; FCB index
AA76 ADC #&20 ; Add &20 for FCB table offset
AA78 STA hazel_fcb_slot_3 ; Store as handle 3 FCB index
AA7B LDA #8 ; A=8: fs_flags bit 3 (FS-error pending)
AA7D TRB fs_flags ; Clear FS-error-pending flag
AA80 .store_updated_status←1← AA69 BEQ
TYA ; A = Y for store
AA81 STA hazel_fcb_status,x ; Store updated status into hazel_fcb_status[X]
AA84 .next_fcb_entry←2← AA23 BEQ← AA2F BNE
DEX ; Decrement entry counter
AA85 BPL scan_fcb_entry ; Loop while X >= 0 (scan all FCBs)
AA87 LDA #&0e ; A=&0E: status flag value
AA89 BIT fs_flags ; Test fs_flags bits 1..3
AA8C BNE return_5 ; Non-zero: skip the FS-active set
AA8E LDA #&40 ; A=&40: FS-active flag bit
AA90 TSB fs_flags ; Set FS-active flag (bit 6 of fs_flags)
AA93 .return_5←1← AA8C BNE
RTS ; Return -- FCB-status update complete

OSWORD &13 sub 12: read CSD path

Reads 5 current selected directory path bytes from the RX workspace at offset &17 into PB[1..5]. Sets carry clear to select the workspace-to-PB copy direction.

AA94 .osword_13_read_csd
CLC ; WS-to-PB direction (read)
AA95 BCC setup_csd_copy ; Skip SEC
fall through ↓

OSWORD &13 sub 13: write CSD path

Writes 5 current selected directory path bytes from PB[1..5] into the RX workspace at offset &17. Sets carry to select the PB-to-workspace copy direction.

AA97 .osword_13_write_csd
SEC ; C=1: PB-to-workspace direction
AA98 .setup_csd_copy←1← AA95 BCC
LDA #&17 ; Workspace offset &17
AA9A STA osword_flag ; Save A as osword_flag (counter)
AA9C LDA net_rx_ptr_hi ; Page from RX pointer high byte
AA9E STA ws_ptr_lo ; Set ws_ptr_hi
AAA0 LDY #1 ; Y=1: first PB data byte
AAA2 LDX #5 ; X=5: copy 5 bytes
fall through ↓

Conditionally copy parameter block byte to workspace

If carry is set, loads a byte from the OSWORD parameter block at offset Y; if clear, uses the value already in A. Stores the result to workspace at the current offset. Decrements X and loops until the requested byte count is transferred.

On EntryCset to load from PB, clear to use A
Xbyte count
YPB source offset
AAA4 .copy_pb_byte_to_ws←5← A949 JSR← A98F JSR← A9B6 JSR← AAB0 BPL← AABD BCC
BCC copy_ws_byte_to_pb ; C=0: skip PB-to-WS copy
AAA6 LDA (ws_ptr_hi),y ; C=1: load from parameter block
AAA8 STA (osword_flag),y ; Store to workspace
AAAA .copy_ws_byte_to_pb←1← AAA4 BCC
LDA (osword_flag),y ; Load from workspace
AAAC STA (ws_ptr_hi),y ; Store to parameter block
AAAE INY ; Next byte
AAAF DEX ; Count down
AAB0 BPL copy_pb_byte_to_ws ; Loop for all bytes
AAB2 RTS ; Return

OSWORD &13 sub 2: read workspace byte pair

Reads 2 bytes from the NFS workspace page starting at offset 1 into PB[1..2]. Uses nfs_workspace_hi as the page and copy_pb_byte_to_ws with carry clear for the workspace-to-PB direction.

AAB3 .osword_13_read_ws_pair
LDA nfs_workspace_hi ; Load workspace page high byte
AAB5 STA ws_ptr_lo ; Set ws_ptr_hi
AAB7 INY ; Y=1
AAB8 TYA ; A = current byte index
AAB9 STA osword_flag ; Set ws_ptr_lo = 1
AABB TAX ; X=1: copy 2 bytes
AABC CLC ; WS-to-PB direction
AABD BCC copy_pb_byte_to_ws ; Copy via copy_pb_byte_to_ws
fall through ↓

OSWORD &13 sub 3: write workspace byte pair

Writes 2 bytes from PB[1..2] into the NFS workspace at offsets 2 and 3. Then calls init_bridge_poll and conditionally clears the workspace byte if the bridge status changed.

AABF .osword_13_write_ws_pair
INY ; Y=1: first PB data byte
AAC0 LDA (ws_ptr_hi),y ; Load PB[1]
AAC2 INY ; Next byte
AAC3 STA (nfs_workspace),y ; Store to (nfs_workspace)+2
AAC5 LDA (ws_ptr_hi),y ; Load PB[2]
AAC7 INY ; Y=3
AAC8 STA (nfs_workspace),y ; Store to (nfs_workspace)+3
AACA JSR init_bridge_poll ; Reinitialise bridge routing
AACD EOR (nfs_workspace),y ; Compare result with workspace
AACF BNE rts_write_ws_pair ; Different: leave unchanged
AAD1 STA (nfs_workspace),y ; Same: clear workspace byte
AAD3 .rts_write_ws_pair←1← AACF BNE
RTS ; Return

OSWORD &13 sub 4: read protection mask

Returns the current protection mask (prot_status) in PB[1].

AAD4 .osword_13_read_prot
LDA prot_status ; Load protection mask
AAD7 JMP store_a_to_pb_1 ; Store to PB[1] and return

OSWORD &13 sub 5: write protection mask

Loads the new protection mask from PB[1] and falls through into set_ws_pair_0d68_0d69 which mirrors it into the ACR/SR-format byte pair at &0D68 / &0D69 that ANFS uses for its own state tracking.

AADA .osword_13_write_prot
INY ; Y=1: PB data offset
AADB LDA (ws_ptr_hi),y ; Load new mask from PB[1]
fall through ↓

Store A in both prot_status and prot_status_save

Copies A to both prot_status and prot_status_save, then RTS. The bytes carry ACR/SR-style flag layouts that ANFS uses internally; nothing in this ROM flushes them to the live System VIA. Two callers: nfs_init_body at &8FA8 (where A is 0 or &FF based on FS-options bit 6) and cmd_prot at &B704 (the *Prot path). A 2-store-and-return convenience to keep both call sites flat.

On EntryAvalue to mirror into both workspace bytes
AADD .set_ws_pair_0d68_0d69←2← 8FC4 JSR← B704 JSR
STA prot_status ; Mirror A into prot_status (ACR-format byte)
AAE0 STA prot_status_save ; Mirror A into prot_status_save (IER-format byte)
AAE3 RTS ; Return

OSWORD &13 sub 6: read FCB handle info

Returns the 3-byte FCB handle/port data from the workspace at C271[1..3] into PB[1..3]. If ANFS is not active, ensure_fs_selected auto-selects it before the body runs.

AAE4 .osword_13_read_handles
JSR ensure_fs_selected ; Ensure NFS is currently the selected FS
AAE7 LDY #3 ; Y=3: copy 3 bytes
AAE9 .loop_copy_handles←1← AAEF BNE
LDA hazel_fs_lib_flags,y ; Load handle byte
AAEC STA (ws_ptr_hi),y ; Store to PB[Y]
AAEE DEY ; Previous byte
AAEF BNE loop_copy_handles ; Loop for bytes 3..1
AAF1 RTS ; Return

OSWORD &13 sub 7: set FCB handles

Validates and assigns up to 3 FCB handles from PB[1..3]. Each handle value (&20-&2F) indexes the channel tables. For valid handles with the appropriate flag bit, stores the station and FCB index, then updates flag bits across all FCB entries via update_fcb_flag_bits.

AAF2 .osword_13_set_handles
JSR ensure_fs_selected ; Ensure NFS is currently the selected FS
AAF5 .start_set_handles
LDY #1 ; Y=1: first handle in PB
AAF7 .validate_handle←1← AB37 BNE
LDA (ws_ptr_hi),y ; Load handle value from PB[Y]
AAF9 CMP #&20 ; Must be >= &20
AAFB BCC handle_invalid ; Below range: invalid
AAFD CMP #&30 ; Must be < &30
AAFF BCS handle_invalid ; Above range: invalid
AB01 TAX ; X = handle value
AB02 LDA hazel_fcb_addr_mid,x ; Load fcb_attr_or_count_mid[handle]
AB05 BNE check_handle_alloc ; Non-zero: FCB exists
AB07 .handle_invalid←3← AAFB BCC← AAFF BCS← AB13 BEQ
LDA #0 ; A=0: invalid-handle marker
AB09 TAX ; Copy A to X
AB0A STA (ws_ptr_hi,x) ; Clear PB[0] status
AB0C BEQ next_handle_slot ; Skip to next handle
AB0E .check_handle_alloc←1← AB05 BNE
LDA hazel_fcb_state_byte,x ; Load fcb_flags[handle] flags
AB11 AND #2 ; Test bit 1 (allocated?)
AB13 BEQ handle_invalid ; Not allocated: invalid
AB15 TXA ; X = handle value
AB16 STA hazel_fs_lib_flags,y ; Store handle to fs_lib_flags+Y
AB19 LDA hazel_fcb_addr_mid,x ; Load station from fcb_attr_or_count_mid
AB1C STA hazel_fs_network,y ; Store station to fs_server_net+Y
AB1F CPY #1 ; Is this handle 1 (Y=1)?
AB21 BNE assign_handle_2 ; No: check handle 2
AB23 TYA ; Save Y for processing
AB24 PHA ; Push Y
AB25 LDY #4 ; Bit mask &04 for handle 1
AB27 JSR update_fcb_flag_bits ; Update flags across all FCBs
AB2A PLA ; Pop saved Y
AB2B TAY ; Back to Y
AB2C LDA hazel_fcb_state_byte,x ; Reload fcb_flags flags
AB2F ORA #&24 ; Set bits 2+5 (active+updated)
AB31 STA hazel_fcb_state_byte,x ; Store updated flags
AB34 .next_handle_slot←3← AB0C BEQ← AB50 BNE← AB63 BNE
INY ; Next handle slot
AB35 CPY #4 ; Compare with 4
AB37 BNE validate_handle ; No: process next handle
AB39 DEY ; Y=3 for return
AB3A RTS ; Return
AB3B .assign_handle_2←1← AB21 BNE
CPY #2 ; Is this handle 2 (Y=2)?
AB3D BNE assign_handle_3 ; No: must be handle 3
AB3F TYA ; Save current Y
AB40 PHA ; Push Y
AB41 LDY #8 ; Y=8 (handle-bit shift index)
AB43 JSR update_fcb_flag_bits ; Update flags across all FCBs
AB46 PLA ; Restore Y
AB47 TAY ; Back to Y
AB48 LDA hazel_fcb_state_byte,x ; Reload fcb_flags flags
AB4B ORA #&28 ; Set bits 3 and 5
AB4D STA hazel_fcb_state_byte,x ; Store updated flags
AB50 BNE next_handle_slot ; Next handle slot
AB52 .assign_handle_3←1← AB3D BNE
TYA ; Handle 3: save Y
AB53 PHA ; Push for save/restore
AB54 LDY #&10 ; Bit mask &10 for handle 3
AB56 JSR update_fcb_flag_bits ; Update flags across all FCBs
AB59 PLA ; Pop saved value
AB5A TAY ; Back to Y
AB5B LDA hazel_fcb_state_byte,x ; Reload fcb_flags flags
AB5E ORA #&30 ; Set bits 4+5 (active+updated)
AB60 STA hazel_fcb_state_byte,x ; Store updated flags
AB63 BNE next_handle_slot ; Next handle slot
fall through ↓

Update FCB flag bits across all entries

Scans all 16 FCB entries in hazel_fcb_status. For each entry with bit 6 set, tests the Y-specified bit mask: if matching, ORs bit 5 into the flags; if not, leaves bit 5 clear. In both cases, inverts and clears the tested bits. Preserves X.

On EntryYflag bit mask to test
Xcurrent FCB index (preserved)
AB65 .update_fcb_flag_bits←3← AB27 JSR← AB43 JSR← AB56 JSR
TXA ; A = caller X
AB66 PHA ; Push X
AB67 LDX #&0f ; X=&0F: scan all 16 FCB slots
AB69 .loop_scan_fcb_flags←1← AB85 BPL
LDA hazel_fcb_status,x ; Load FCB flags
AB6C ROL ; Shift bits 6-7 into bits 7-0
AB6D ROL ; Shift bit into carry for test
AB6E BPL next_flag_entry ; Bit 6 clear: skip entry
AB70 TYA ; Restore Y (bit mask)
AB71 AND hazel_fcb_status,x ; Test mask bits against flags
AB74 BEQ no_flag_match ; Zero: no matching bits
AB76 TYA ; Matching: restore Y
AB77 ORA #&20 ; Set bit 5 (updated)
AB79 BNE clear_flag_bits ; Skip clear path
AB7B .no_flag_match←1← AB74 BEQ
TYA ; No match: restore Y
AB7C .clear_flag_bits←1← AB79 BNE
EOR #&ff ; Invert all bits
AB7E AND hazel_fcb_status,x ; Clear tested bits in flags
AB81 STA hazel_fcb_status,x ; Store updated flags
AB84 .next_flag_entry←1← AB6E BPL
DEX ; Decrement FCB index
AB85 BPL loop_scan_fcb_flags ; Loop for all 16 entries
AB87 PLA ; Restore original X
AB88 TAX ; Back to X
AB89 RTS ; Return

OSWORD &13 sub 8: read RX control block flag

Returns byte 1 of the current RX control block in PB[1].

AB8A .osword_13_read_rx_flag
LDY #1 ; Y=1: PB[1] = RX flag location
AB8C LDA (net_rx_ptr),y ; Load (net_rx_ptr)+1
AB8E LDY #0 ; Y=0
AB90 JMP store_a_to_pb_1 ; Store to PB[1] and return

OSWORD &13 sub 9: read RX port byte

Returns byte &7F of the current RX control block in PB[1], and stores &80 in PB[2].

AB93 .osword_13_read_rx_port
LDY #&7f ; Y=&7F: port byte offset
AB95 LDA (net_rx_ptr),y ; Load (net_rx_ptr)+&7F
AB97 LDY #1 ; Y=1
AB99 STA (ws_ptr_hi),y ; Store to PB[1]
AB9B INY ; Advance index
AB9C LDA #&80 ; A=&80
AB9E STA (ws_ptr_hi),y ; Store &80 to PB[2]
ABA0 RTS ; Return

OSWORD &13 sub 10: read error flag

Returns the latched FS last-error byte (hazel_fs_last_error) in PB[1]. Falls through into store_a_to_pb_1.

ABA1 .osword_13_read_error
LDA hazel_fs_last_error ; Load error flag
fall through ↓

Store A to OSWORD parameter block at offset 1

Increments Y to 1 and stores A into the OSWORD parameter block via (ws_ptr_hi),Y. Used by OSWORD 13 sub-handlers to return a single result byte.

On EntryAvalue to store
On ExitY1
ABA4 .store_a_to_pb_1←4← AAD7 JMP← AB90 JMP← ABAB BPL← ABB3 BCS
INY ; Y=1: parameter block offset 1
ABA5 STA (ws_ptr_hi),y ; Store result to PB[1]
ABA7 RTS ; Return

OSWORD &13 sub 11: read context byte

Returns the FS context/error code (hazel_fs_error_code) in PB[1] when bit 7 is clear; if bit 7 is set the value is left alone (the BPL skips the store). Tail-merges into store_a_to_pb_1.

ABA8 .osword_13_read_context
LDA hazel_fs_error_code ; Load context byte
ABAB BPL store_a_to_pb_1 ; Bit 7 clear: store context to PB
fall through ↓

OSWORD &13 sub 14: read printer buffer free space

Returns the number of free bytes remaining in the printer spool buffer (&6F minus spool_buf_idx) in PB[1]. The buffer starts at offset &25 and can hold up to &4A bytes of spool data.

ABAD .osword_13_read_free_bufs
LDA #&6f ; Total buffers = &6F
ABAF SEC ; PB-to-WS direction (write)
ABB0 SBC spool_buf_idx ; Free = &6F - spool_buf_idx
ABB3 BCS store_a_to_pb_1 ; Non-negative: store free count to PB
fall through ↓

OSWORD &13 sub 15: read retry counts

Returns the three retry count values in PB[1..3]: PB[1] = transmit retry count (default &FF = 255), PB[2] = receive poll count (default &28 = 40), PB[3] = machine peek retry count (default &0A = 10). Setting transmit retries to 0 means retry forever.

ABB5 .osword_13_read_ctx_3←1← ABBD BNE
INY ; Next ctx byte
ABB6 LDA fs_flags,y ; Return
ABB9 STA (ws_ptr_hi),y ; Store to PB[Y]
ABBB CPY #3 ; Done 3 bytes?
ABBD BNE osword_13_read_ctx_3 ; No: loop
ABBF RTS ; Return

OSWORD &13 sub 16: write retry counts

Sets the three retry count values from PB[1..3]: PB[1] = transmit retry count, PB[2] = receive poll count, PB[3] = machine peek retry count.

ABC0 .osword_13_write_ctx_3←1← ABC8 BNE
INY ; Next byte offset
ABC1 LDA (ws_ptr_hi),y ; Load PB[Y]
ABC3 STA fs_flags,y ; Store to tx_retry_count[Y]
ABC6 CPY #3 ; Done 3 bytes?
ABC8 BNE osword_13_write_ctx_3 ; No: loop
ABCA RTS ; Return

OSWORD &13 sub 17: query bridge status

Calls init_bridge_poll, then returns the bridge status. If bridge_status is &FF (no bridge), stores 0 in PB[0]. Otherwise stores bridge_status in PB[1] and conditionally updates PB[3] based on station comparison.

ABCB .osword_13_bridge_query
JSR init_bridge_poll ; Poll for bridge
ABCE LDY #0 ; Y=0
ABD0 LDA bridge_status ; Load bridge status
ABD3 CMP #&ff ; Is it &FF (no bridge)?
ABD5 BNE bridge_found ; No: bridge found
ABD7 TYA ; Copy Y to A
ABD8 STA (ws_ptr_hi),y ; PB[0] = 0 (no bridge)
ABDA INY ; Advance index
ABDB BNE store_bridge_station ; Branch to store the bridge station
ABDD .bridge_found←1← ABD5 BNE
INY ; Y=1
ABDE STA (ws_ptr_hi),y ; PB[1] = bridge status
ABE0 INY ; Advance Y
ABE1 INY ; Y=3
ABE2 LDA (ws_ptr_hi),y ; Load PB[3] (caller value)
ABE4 BEQ use_default_station ; Zero: use default station
ABE6 .compare_bridge_status
EOR bridge_status ; Compare with bridge status
ABE9 BNE rts_bridge_query ; Non-zero: take return path
ABEB BEQ store_bridge_station ; Same: confirm station
ABED .use_default_station←1← ABE4 BEQ
LDA hazel_fs_network ; Load default from fs_server_net
ABF0 .store_bridge_station←2← ABDB BNE← ABEB BEQ
STA (ws_ptr_hi),y ; Store to PB[3]
ABF2 .rts_bridge_query←1← ABE9 BNE
RTS ; Return
ABF3 .bridge_txcb_init_table←1Used as index base by← AC20 LDA
EQUB &82 ; TX 0: ctrl = &82 (immediate mode)
ABF4 EQUB &9C ; TX 1: port = &9C (bridge discovery)
ABF5 EQUB &FF ; TX 2: dest station = &FF (broadcast)
ABF6 EQUB &FF ; TX 3: dest network = &FF (all nets)
ABF7 EQUS "BRIDGE" ; TX 4-9: immediate data payload
ABFD EQUB &9C ; TX init data byte &9C
ABFE EQUB &00 ; TX 11: &00 (terminator)
ABFF .bridge_rxcb_init_data
EQUB &7F ; RX 0: ctrl = &7F (receive)
AC00 EQUB &9C ; RX 1: port = &9C (bridge discovery)
AC01 EQUB &00 ; RX 2: station = &00 (any)
AC02 EQUB &00, &72, &0D ; RX 3: network = &00 (any)
AC05 EQUB &FF ; RX 6: extended addr fill (&FF)
AC06 EQUB &FF, &74 ; RX 7: extended addr fill (&FF)
AC08 EQUB &0D, &FF, &FF ; RX 9: buf end hi (&0D) -> &0D74

Initialise Econet bridge routing table

Checks the bridge status byte: if &FF (uninitialised), broadcasts a bridge query packet and polls for replies. Each reply adds a network routing entry to the bridge table. Skips the broadcast if the table has already been populated from a previous call.

On ExitA, X, Yclobbered when the broadcast path runs
AC0B .init_bridge_poll←5← 8E3B JSR← 902B JSR← A3E7 JSR← AACA JSR← ABCB JSR
LDA bridge_status ; Check bridge status
AC0E CMP #&ff ; Is it &FF (uninitialised)?
AC10 BNE rts_bridge_poll ; No: bridge already active, return
AC12 TYA ; Save Y
AC13 PHA ; Preserve Y on stack
AC14 LDY #&18 ; Y=&18: workspace offset for init
AC16 LDX #&0b ; X=&0B: 12 bytes to copy
AC18 ROR econet_flags ; Rotate econet_flags right (save flag)
AC1B .loop_copy_bridge_init←1← AC27 BPL
LDA bridge_err_table,y ; Load init data byte
AC1E STA (nfs_workspace),y ; Store to workspace
AC20 LDA bridge_txcb_init_table,x ; Load TXCB template byte
AC23 STA txcb_ctrl,x ; Store to TX control block
AC25 INY ; Next workspace byte
AC26 DEX ; Next template byte
AC27 BPL loop_copy_bridge_init ; Loop for all 12 bytes
AC29 STX bridge_status ; Store X (-1) as bridge counter
AC2C ROL econet_flags ; Restore econet_flags flag
AC2F .loop_wait_ws_status←2← AC32 BCC← AC57 BPL
ASL tx_complete_flag ; Shift ws_0d60 left (check status)
AC32 BCC loop_wait_ws_status ; C=0: status clear, retry
AC34 LDA #&82 ; Control byte &82 for TX
AC36 STA txcb_ctrl ; Set in TX control block
AC38 LDA #&c0 ; Data block at &00C0
AC3A STA nmi_tx_block ; Set NMI TX block low
AC3C LDA #0 ; High byte = 0 (page 0)
AC3E STA nmi_tx_block_hi ; Set NMI TX block high
AC40 JSR tx_setup_from_txcb ; Begin Econet transmission
AC43 .loop_wait_tx_done←1← AC45 BMI
BIT txcb_ctrl ; Test TX control block bit 7
AC45 BMI loop_wait_tx_done ; Negative: TX still in progress
AC47 PHX ; Push X (saved across delay)
AC48 LDA #osbyte_vsync ; A=&13: OSBYTE 'wait for VSYNC'
AC4A JSR osbyte ; Wait for vertical sync
AC4D PLX ; Restore caller's X
AC4E LDY #&18 ; Y=&18: status-byte offset
AC50 LDA (nfs_workspace),y ; Load bridge response
AC52 BMI bridge_responded ; Negative: bridge responded
AC54 JSR inx16 ; Advance retry counter by 8
AC57 BPL loop_wait_ws_status ; Positive: retry poll loop
AC59 .bridge_responded←1← AC52 BMI
LDA #&3f ; Set response to &3F (OK)
AC5B STA (nfs_workspace),y ; Store to workspace
AC5D PLA ; Restore saved Y
AC5E TAY ; Result byte to Y
AC5F LDA bridge_status ; Load bridge status
AC62 TAX ; X = bridge status
AC63 EOR #&ff ; Invert (presence -> absence)
AC65 BEQ rts_bridge_poll ; Status was &FF: return (no bridge)
AC67 TXA ; Return bridge station in A
AC68 .rts_bridge_poll←2← AC10 BNE← AC65 BEQ
RTS ; Return

OSWORD &14 handler: bridge poll / station status

Triages by A: A >= 1 branches via BCS to handle_tx_request which reads the station and network from PB[1]/PB[2] into the RX-block destination slots and falls through to the burst-transfer body. A = 0 (the bridge-poll sub-code) falls through here: pushes A, calls ensure_fs_selected to bring ANFS up if needed, pulls A back, sets Y=&23 and calls mask_owner_access to clear FS-selection bits, then runs the bridge-poll body.

On EntryAOSWORD &14 sub-function code
X, YOSWORD parameter block pointer (low, high)
AC69 .osword_14_handler
CMP #1 ; Compare sub-code with 1
AC6B BCS handle_tx_request ; Sub-code >= 1: handle TX request
AC6D PHA ; Save state
AC6E JSR ensure_fs_selected ; Ensure NFS is currently the selected FS
AC71 PLA ; Pop saved A from the stack frame
AC72 LDY #&23 ; Y=&23: workspace offset for params
AC74 JSR mask_owner_access ; Set owner access mask
AC77 .loop_copy_txcb_init←1← AC84 BNE
LDA init_txcb,y ; Load TXCB init byte
AC7A BNE store_txcb_init_byte ; Non-zero: use template value
AC7C LDA hazel_minus_1a,y ; Zero: use workspace default value
AC7F .store_txcb_init_byte←1← AC7A BNE
STA (nfs_workspace),y ; Store to workspace
AC81 DEY ; Next byte down
AC82 CPY #&17 ; Until Y reaches &17
AC84 BNE loop_copy_txcb_init ; Loop for all bytes
AC86 INY ; Next byte
AC87 STY net_tx_ptr ; Set net_tx_ptr low byte
fall through ↓

Store workspace pointer+1 to NFS workspace

Computes ws_ptr_hi + 1 and stores the resulting 16-bit address at workspace offset &1C via store_ptr_at_ws_y. Then reads PB byte 1 (the transfer length) and adds ws_ptr_hi to compute the buffer end pointer, stored at workspace offset &20.

AC89 .store_osword_pb_ptr
LDY #&1c ; Y=&1C: workspace offset for PB pointer
AC8B LDA ws_ptr_hi ; Load PB page number
AC8D ADC #1 ; PB starts at next page boundary (+1)
AC8F JSR store_ptr_at_ws_y ; Store PB start pointer at ws[&1C]
AC92 LDY #1 ; Y=1: PB byte 1 (transfer length)
AC94 LDA (ws_ptr_hi),y ; Load transfer length from PB
AC96 LDY #&20 ; Y=&20: TXCB offset
AC98 ADC ws_ptr_hi ; Add PB base for buffer end address
AC9A JSR store_ptr_at_ws_y ; Store PB pointer to workspace
AC9D LDY #2 ; Y=2: parameter offset
AC9F LDA #&90 ; Control byte &90
ACA1 STA need_release_tube ; Set escapable flag
ACA3 STA (ws_ptr_hi),y ; Store control byte to PB
ACA5 INY ; Advance index
ACA6 INY ; Advance index
ACA7 .loop_copy_ws_to_pb←1← ACAF BNE
LDA hazel_minus_2,y ; Load workspace data
ACAA STA (ws_ptr_hi),y ; Store to parameter block
ACAC INY ; Next byte
ACAD CPY #7 ; Until Y reaches 7
ACAF BNE loop_copy_ws_to_pb ; Loop for 3 bytes (Y=4,5,6)
ACB1 LDA nfs_workspace_hi ; Read nfs_workspace_hi
ACB3 STA net_tx_ptr_hi ; Store to net_tx_ptr_hi
ACB5 JSR enable_irq_and_poll ; Enable interrupts
ACB8 LDY #&20 ; Y=&20: workspace offset
ACBA LDA #&ff ; Set to &FF (pending)
ACBC STA (nfs_workspace),y ; Mark send pending in workspace
ACBE INY ; Advance index
ACBF STA (nfs_workspace),y ; Also mark offset &21
ACC1 LDY #&19 ; Y=&19: control offset
ACC3 LDA #&90 ; Control byte &90
ACC5 STA (nfs_workspace),y ; Store to workspace
ACC7 DEY ; Y=&18: RX control offset
ACC8 LDA #&7f ; Control byte &7F
ACCA STA (nfs_workspace),y ; Store RX control
ACCC JMP wait_net_tx_ack ; Wait for TX acknowledgement

Store 16-bit pointer at workspace offset Y

Writes a 16-bit address to (nfs_workspace)+Y. The low byte comes from A; the high byte is computed from table_idx plus carry, supporting pointer arithmetic across page boundaries.

On EntryApointer low byte
Yworkspace offset
Ccarry for high byte addition
ACCF .store_ptr_at_ws_y←2← AC8F JSR← AC9A JSR
STA (nfs_workspace),y ; Store address low byte at ws[Y]
ACD1 INY ; Advance to high byte offset
ACD2 LDA table_idx ; Load high byte base (table_idx)
ACD4 ADC #0 ; Add carry for page crossing
ACD6 STA (nfs_workspace),y ; Store address high byte at ws[Y+1]
ACD8 RTS ; Return

Sub-code 0: copy PB station/network into RX block, dispatch burst

Sub-code-0 path of osword_14_handler, reached via the BCC handle_tx_request at &AC69 when the caller's A is 0. Reads two bytes from the OSWORD parameter block:

Reg setup Source Stored at
Y=1 PB[1] (parked in X)
Y=2 PB[2] (net_rx_ptr)+&72 (dest network)
Y=3 (saved as osword_flag for the next byte read)
Y=&71 X (PB[1]) (net_rx_ptr)+&71 (dest station)

Wraps the body in PHP/PLP so the entry flags (carry clear from the BCC) survive the workspace stores; the BNE after PLP then dispatches to handle_burst_xfer when the caller's A was non-zero (a defensive branch -- the BCC entry guarantees A=0, but the same body is the entry point the burst path piggy-backs on).

On EntryAOSWORD &14 sub-function code (caller's A; 0 via the BCC entry from osword_14_handler)
WS_PTR_HIOSWORD parameter-block high byte
ACD9 .handle_tx_request←1← AC6B BCS
PHP ; Save processor flags
ACDA LDY #1 ; Y=1: workspace offset
ACDC LDA (ws_ptr_hi),y ; Load station number from PB
ACDE TAX ; X = station number
ACDF INY ; Advance index
ACE0 LDA (ws_ptr_hi),y ; Load network number from PB
ACE2 INY ; Y=3: workspace start offset
ACE3 STY osword_flag ; Store Y as ws_ptr_lo
ACE5 LDY #&72 ; Y=&72: workspace offset for dest
ACE7 STA (net_rx_ptr),y ; Store network to workspace
ACE9 DEY ; Y=&71
ACEA TXA ; A = station (from X)
ACEB STA (net_rx_ptr),y ; Store station to workspace
ACED PLP ; Restore flags from PHP
ACEE BNE handle_burst_xfer ; Non-zero sub-code: handle burst
ACF0 .loop_send_pb_chars←1← AD0C BNE
LDY osword_flag ; Load current offset
ACF2 INC osword_flag ; Advance offset for next byte
ACF4 LDA (ws_ptr_hi),y ; Load next char from PB
ACF6 BEQ return_6 ; Zero: end of data, return
ACF8 LDY #&7d ; Y=&7D: workspace pointer offset
ACFA STA (net_rx_ptr),y ; Store char to RX buffer
ACFC PHA ; Save char for later test
ACFD JSR init_ws_copy_wide ; Init workspace copy for wide xfer
AD00 SEC ; Set carry
AD01 ROR need_release_tube ; Set bit 7: Tube needs release
AD03 JSR enable_irq_and_poll ; Enable IRQ and send packet
AD06 .loop_bridge_tx_delay←1← AD07 BNE
DEX ; Delay countdown
AD07 BNE loop_bridge_tx_delay ; Loop while X != 0
AD09 PLA ; Restore char
AD0A EOR #&0d ; Test if char was CR (&0D)
AD0C BNE loop_send_pb_chars ; Loop while not CR
AD0E .return_6←1← ACF6 BEQ
RTS ; CR sent: return

OSWORD &14 burst-transfer path: extend buffer end and TX

Reached from handle_tx_request's BNE at &ACEE. Calls init_ws_copy_wide to copy the workspace TXCB template into the wide-mode workspace slot, then extends the buffer end-byte at (net_rx_ptr)+&7B by 3 to account for the 3-byte burst header before falling through into enable_irq_and_poll, which re-enables IRQs and tail-jumps to send_net_packet.

On EntryNET_RX_PTRset up by handle_tx_request (dest station/network already stored at +&71/&72)
AD0F .handle_burst_xfer←1← ACEE BNE
JSR init_ws_copy_wide ; Init workspace for wide copy
AD12 LDY #&7b ; Y=&7B: end-byte offset
AD14 LDA (net_rx_ptr),y ; Load buffer size
AD16 ADC #3 ; Add 3 (end-of-buffer adjust)
AD18 STA (net_rx_ptr),y ; Store adjusted size
fall through ↓

Enable interrupts and send Econet packet

Executes CLI to re-enable interrupts, then falls through to send_net_packet. Used after a sequence that ran with interrupts disabled to ensure the packet is sent with normal interrupt handling active.

On EntryI FLAGmay be set (caller had IRQs off); CLI clears it
On ExitI FLAGclear (interrupts enabled)
AD1A .enable_irq_and_poll←2← ACB5 JSR← AD03 JSR
CLI ; Re-enable IRQs
AD1B JMP send_net_packet ; Send packet and return

NETV handler: OSWORD dispatch

Installed as the NETV handler via write_vector_entry. Saves all registers, reads the OSWORD number from the stack, and dispatches OSWORDs 0-8 via push_osword_handler_addr. OSWORDs >= 9 are ignored (registers restored, RTS returns to MOS). The handler's address lives in the extended vector data area together with the other fs_vector_table entries.

On EntryAOSWORD number (read from stacked A on entry)
X, YPB pointer low/high (per OSWORD calling convention)
On ExitA, X, Y, Prestored from stack
AD1E .netv_handler
PHP ; Save processor flags
AD1F PHA ; Save A
AD20 TXA ; Save X
AD21 PHA ; Push X
AD22 TYA ; Save Y
AD23 PHA ; Push Y
AD24 TSX ; Get stack pointer
AD25 LDA stack_page_3,x ; Read OSWORD number from stack
AD28 CMP #9 ; OSWORD >= 9?
AD2A BCS restore_regs_return ; Yes: out of range, restore + return
AD2C TAX ; X = OSWORD number
AD2D JSR push_osword_handler_addr ; Push handler address for dispatch
AD30 .restore_regs_return←1← AD2A BCS
PLA ; Restore Y
AD31 TAY ; Back to Y
AD32 PLA ; Restore X
AD33 TAX ; Back to X
AD34 PLA ; Restore A
AD35 PLP ; Restore flags
AD36 RTS ; Return

Push OSWORD handler address for RTS dispatch

Indexes the OSWORD handler dispatch table using the current OSWORD number to push the handler's address (hi/lo) onto the stack. Reloads the OSWORD number from osbyte_a_copy so the dispatched handler can identify the specific call.

On EntryAOSWORD number (0-8) -- table index
On ExitAOSWORD number (re-loaded for the handler's use)
AD37 .push_osword_handler_addr←1← AD2D JSR
LDA netv_dispatch_hi,x ; Load handler high byte from hi-table column X
AD3A PHA ; Push for the eventual RTS dispatch
AD3B LDA netv_dispatch_lo,x ; Load handler low byte from lo-table column X
AD3E PHA ; Push lo so RTS pulls (lo, hi)+1 -> handler entry
AD3F LDA osbyte_a_copy ; Reload original OSWORD number into A for the handler
AD41 RTS ; RTS jumps to handler with A=OSWORD number
AD42 .netv_dispatch_lo←1Used as index base by← AD3B LDA
EQUB <(dispatch_rts - 1) ; reason &00: dispatch_rts (no-op (RTS only))
AD43 EQUB <(netv_print_data - 1) ; reason &01: netv_print_data (NETV reason 1: print data)
AD44 EQUB <(netv_print_data - 1) ; reason &02: netv_print_data (NETV reason 2: print data (alias))
AD45 EQUB <(netv_print_data - 1) ; reason &03: netv_print_data (NETV reason 3: print data (alias))
AD46 EQUB <(osword_4_handler - 1) ; reason &04: osword_4_handler (NETV reason 4: OSWORD &04)
AD47 EQUB <(netv_spool_check - 1) ; reason &05: netv_spool_check (NETV reason 5: spool check)
AD48 EQUB <(dispatch_rts - 1) ; reason &06: dispatch_rts (no-op (RTS only))
AD49 EQUB <(netv_claim_release - 1) ; reason &07: netv_claim_release (NETV reason 7: claim/release)
AD4A EQUB <(osword_8_handler - 1) ; reason &08: osword_8_handler (NETV reason 8: OSWORD &08)

NETV reason-code dispatch high-byte table (9 entries)

Read by push_osword_handler_addr as LDA &AD29,X. The dispatcher pushes the hi byte first then the lo, so RTS lands on target (the table stores target-1).

AD4B .netv_dispatch_hi←1Used as index base by← AD37 LDA
EQUB >(dispatch_rts - 1) ; reason &00: dispatch_rts
AD4C EQUB >(netv_print_data - 1) ; reason &01: netv_print_data
AD4D EQUB >(netv_print_data - 1) ; reason &02: netv_print_data
AD4E EQUB >(netv_print_data - 1) ; reason &03: netv_print_data
AD4F EQUB >(osword_4_handler - 1) ; reason &04: osword_4_handler
AD50 EQUB >(netv_spool_check - 1) ; reason &05: netv_spool_check
AD51 EQUB >(dispatch_rts - 1) ; reason &06: dispatch_rts
AD52 EQUB >(netv_claim_release - 1) ; reason &07: netv_claim_release
AD53 EQUB >(osword_8_handler - 1) ; reason &08: osword_8_handler

OSWORD &04 handler: clear C, send abort

Reaches the stack via TSX, clears bit 0 of the stacked processor status (ROR stack_page_6,X then ASL stack_page_6,X -- a read-modify cycle that lands the carry-out where bit 0 of the saved P was), so the caller resumes with C=0. Stores the caller's Y into NFS workspace at offset &DA, then falls through to tx_econet_abort with A=0 to transmit a clean disconnect packet.

On EntryYOSWORD parameter byte (saved into nfs_workspace+&DA)
AD54 .osword_4_handler
TSX ; Read the MOS stack frame holding caller flags
AD55 ROR stack_page_6,x ; Shift carry out of caller P (stack[&106+X])
AD58 ASL stack_page_6,x ; Carry is now cleared in caller P
AD5B TYA ; A = original Y
AD5C LDY #&da ; Y=&DA: workspace osword-4 result offset
AD5E STA (nfs_workspace),y ; Store Y at (nfs_workspace)+&DA
AD60 LDA #0 ; A=0: clear A for the abort path
fall through ↓

Send Econet abort/disconnect packet

Stores the abort code in workspace, configures the TX control block with control byte &80 (immediate operation flag), and transmits the abort packet. Used to cleanly disconnect from a remote station during error recovery.

On EntryAabort code (stored in workspace before TX)
AD62 .tx_econet_abort←3← 8B25 JSR← ADB5 JSR← AE1B JSR
LDY #&d9 ; Y=&D9: workspace offset for the abort code byte
AD64 STA (nfs_workspace),y ; Store the abort code (passed in A) at workspace[&D9]
AD66 LDA #&80 ; A=&80: control = immediate-operation flag
AD68 LDY #&0c ; Y=&0C: TXCB control-byte offset
AD6A STA (nfs_workspace),y ; Set TXCB[&0C] = &80 (immediate / abort)
AD6C LDA net_tx_ptr ; Save current net_tx_ptr low (we'll repoint TX at the abort packet)
AD6E PHA ; Push it for restore on exit
AD6F LDA net_tx_ptr_hi ; Save net_tx_ptr high too
AD71 PHA ; Push it
AD72 STY net_tx_ptr ; TX low = &0C (abort packet starts at workspace[&0C])
AD74 LDX nfs_workspace_hi ; Get nfs_workspace high byte
AD76 STX net_tx_ptr_hi ; TX high = workspace page (so net_tx_ptr now points at the abort packet in workspace)
AD78 JSR send_net_packet ; Send the abort packet via the standard TX path
AD7B LDA #&3f ; A=&3F: TXCB status = abort-complete sentinel
AD7D STA (net_tx_ptr,x) ; Write status via (net_tx_ptr,X) -- mark TX done
AD7F PLA ; Pull saved net_tx_ptr high
AD80 STA net_tx_ptr_hi ; Restore
AD82 PLA ; Pull saved net_tx_ptr low
AD83 STA net_tx_ptr ; Restore -- caller's TX state intact
AD85 RTS ; Return

OSWORD 7 handler: claim/release network resources

Handles OSWORD 7 (SOUND) intercepted via NETV. Searches the claim code table in two passes: first 11 entries (state 2), then all 18 (state 3). On match, saves 3 tube state bytes to workspace and sends an abort with the state code. For state 3 matches, also polls workspace for a response and restores the caller's stack frame from the saved bytes.

On EntryAOSWORD 7 number (validated by caller)
AD86 .netv_claim_release
LDY osword_pb_ptr_hi ; Y = OSWORD parameter-block pointer high byte (used as an 'unrecognised' sentinel below)
AD88 CMP #&81 ; Code &81? (compatibility shortcut for one specific claim type)
AD8A BEQ process_match_result ; Yes: skip table scan, use match-result with Y already set non-zero
AD8C LDY #1 ; Y=1: state 2 marker
AD8E LDX #&0a ; X=&0A: scan first 11 entries (table indices 0..&0A)
AD90 JSR match_rx_code ; Look up A in the claim code table
AD93 BEQ process_match_result ; Match: handle as state 2
AD95 DEY ; DEY: Y=0 (state 3 marker, two DEYs from 1)
AD96 DEY ; Y=-1: flag second range
AD97 LDX #&11 ; X=&11: scan all 18 entries (state 3 also accepts the extended range)
AD99 JSR match_rx_code ; Look up A again with extended range
AD9C BEQ process_match_result ; Match: handle as state 3
AD9E INY ; Y=1 again (no match found, will return below)
AD9F .process_match_result←3← AD8A BEQ← AD93 BEQ← AD9C BEQ
LDX #2 ; X=2: default state code passed to tx_econet_abort
ADA1 TYA ; Move match marker (Y) into A for the BEQ test
ADA2 BEQ rts_claim_release ; Y=0 (no match): return without action
ADA4 PHP ; Save flags so we can branch later on Y's sign
ADA5 BPL save_tube_state ; Y > 0 (state 2): skip the X bump
ADA7 INX ; State 3: X=3 (different abort code)
ADA8 .save_tube_state←1← ADA5 BPL
LDY #&dc ; Y=&DC: workspace offset for tube state bytes
ADAA .loop_save_tube_bytes←1← ADB2 BPL
LDA tube_claimed_id,y ; Read tube_claimed_id,Y
ADAD STA (nfs_workspace),y ; Save in workspace[&DC..]
ADAF DEY ; Step backwards
ADB0 CPY #&da ; Done at &DA?
ADB2 BPL loop_save_tube_bytes ; Loop while Y > &DA (saves &DA, &DB, &DC -- 3 bytes)
ADB4 TXA ; Move state code (2 or 3) into A for the abort
ADB5 JSR tx_econet_abort ; Send abort with the state code
ADB8 PLP ; Restore the saved flags (Y's sign)
ADB9 BPL rts_claim_release ; Y was positive (state 2): just return
ADBB LDA #&7f ; A=&7F: 'pending response' control value
ADBD LDY #&0c ; Y=&0C: TXCB control offset
ADBF STA (nfs_workspace),y ; Mark TXCB as pending
ADC1 .loop_poll_ws_status←1← ADC3 BPL
LDA (nfs_workspace),y ; Read TXCB status byte
ADC3 BPL loop_poll_ws_status ; Bit 7 still clear: keep polling for response
ADC5 TSX ; Capture S so we can patch the caller's stack frame
ADC6 LDY #&dd ; Y=&DD: highest workspace offset for the response copy
ADC8 LDA (nfs_workspace),y ; Read first response byte (workspace[&DD])
ADCA ORA #&44 ; Set bit 6 and bit 2
ADCC BNE store_stack_byte ; Always taken (after ORA result is non-zero); store into stack[&106+X] then walk down
ADCE .loop_restore_stack←1← ADD7 BNE
DEY ; Step Y down
ADCF DEX ; Step X down (stack offset)
ADD0 LDA (nfs_workspace),y ; Read next workspace byte
ADD2 .store_stack_byte←1← ADCC BNE
STA stack_page_6,x ; Patch caller's stack frame at &106+X
ADD5 CPY #&da ; Reached &DA (lower workspace bound)?
ADD7 BNE loop_restore_stack ; No: keep restoring
ADD9 .rts_claim_release←2← ADA2 BEQ← ADB9 BPL
RTS ; Return

Search receive code table for match

Scans a table of receive operation codes starting at index X, comparing each against A. Returns with Z set if a match is found, Z clear if the end-of-table marker is reached.

On EntryAreceive code to match
Xstarting table index
On ExitZset if match found
ADDA .match_rx_code←3← AD90 JSR← AD99 JSR← ADE0 BPL
CMP osword_claim_codes,x ; Compare A with table entry at index X
ADDD BEQ rts_match_rx_code ; Match: return with Z set
ADDF DEX ; Step to next earlier table entry
ADE0 BPL match_rx_code ; Loop while X >= 0 (table walked top-down)
ADE2 .rts_match_rx_code←2← ADDD BEQ← ADFD BNE
RTS ; Return; Z reflects last CMP

OSWORD per-claim-code lookup table (18 bytes)

Looked up by match_rx_code when an Econet RX event triggers an OSWORD-related claim. The X register selects an 18-byte slice; bytes encode the claim type (immediate-op, broadcast, port-specific) used by the dispatcher to decide which handler chain to install. Per-byte inline comments document each entry.

ADE3 .osword_claim_codes←1Used as index base by← ADDA CMP
EQUB &04 ; Range 1+2: OSWORD &04
ADE4 EQUB &09 ; Range 1+2: OSWORD &09
ADE5 EQUB &0A ; Range 1+2: OSWORD &0A
ADE6 EQUB &14 ; Range 1+2: OSWORD &14
ADE7 EQUB &15 ; Range 1+2: OSWORD &15
ADE8 EQUB &9A ; OSWORD &9A
ADE9 EQUB &9B ; Range 1+2: OSWORD &9B
ADEA EQUB &E1 ; Range 1+2: OSWORD &E1
ADEB EQUB &E2 ; OSWORD &E2
ADEC EQUB &E3 ; Range 1+2: OSWORD &E3
ADED EQUB &E4 ; Range 1+2: OSWORD &E4
ADEE EQUB &0B ; OSWORD &0B
ADEF EQUB &0C ; Range 2 only: OSWORD &0C
ADF0 EQUB &0F ; Range 2 only: OSWORD &0F
ADF1 EQUB &79 ; Range 2 only: OSWORD &79
ADF2 EQUB &7A ; OSWORD &7A
ADF3 EQUB &86 ; OSWORD &86
ADF4 EQUB &87 ; Range 2 only: OSWORD &87

OSWORD 7/8 handler: copy PB to workspace and abort

Handles OSWORD 7 or 8 by copying 15 bytes from the parameter block to workspace at offset &DB, storing the OSWORD number at offset &DA, setting control value &E9, and sending an abort packet. Returns via tx_econet_abort. Rejects other OSWORD numbers by returning immediately.

On EntryAOSWORD number (must be 7 or 8 to be processed)
ADF5 .osword_8_handler
LDY #&0e ; Y=&0E: scan 15 bytes (offsets 14..0) of the PB
ADF7 CMP #7 ; Is the OSWORD number 7?
ADF9 BEQ copy_pb_to_ws ; Yes: handle as either 7 or 8 -- both copy PB to ws
ADFB CMP #8 ; Is the OSWORD number 8?
ADFD BNE rts_match_rx_code ; Neither 7 nor 8: return early (other OSWORDs handled elsewhere)
ADFF .copy_pb_to_ws←1← ADF9 BEQ
LDX #&db ; X=&DB: workspace offset for the PB copy
AE01 STX nfs_workspace ; Temporarily reuse nfs_workspace as the destination low byte (high byte already points at the workspace page)
AE03 .loop_copy_pb_to_ws←1← AE08 BPL
LDA (osword_pb_ptr),y ; Read PB[Y]
AE05 STA (nfs_workspace),y ; Write to (nfs_workspace),Y -- effectively writes to workspace[&DB+Y]
AE07 DEY ; Step backwards through the 15 bytes
AE08 BPL loop_copy_pb_to_ws ; Loop while Y >= 0
AE0A INY ; Bring Y back to 0 for the next single-byte write
AE0B DEC nfs_workspace ; Decrement nfs_workspace low byte: now points at workspace[&DA] (one before the copied region)
AE0D LDA osbyte_a_copy ; Read original OSWORD number from osbyte_a_copy
AE0F STA (nfs_workspace),y ; Store at workspace[&DA] -- so the abort packet header carries the OSWORD number
AE11 STY nfs_workspace ; Restore nfs_workspace to its proper low byte (Y=0)
AE13 LDY #&14 ; Y=&14: TXCB control offset
AE15 LDA #&e9 ; A=&E9: status code for OSWORD-passthrough abort
AE17 STA (nfs_workspace),y ; Store status at TXCB[&14]
AE19 LDA #1 ; A=1: abort code for tx_econet_abort
AE1B JSR tx_econet_abort ; Send the abort packet
AE1E STX nfs_workspace ; Restore nfs_workspace from X (X is unchanged across tx_econet_abort)
fall through ↓

Initialise workspace copy in wide mode (14 bytes)

Copies 14 bytes to workspace offset &7C. Falls through to the template-driven copy loop which handles &FD (skip), &FE (end), and &FC (page pointer) markers.

On EntryXtemplate source offset (within ws_txcb_template_data)
AE20 .init_ws_copy_wide←2← ACFD JSR← AD0F JSR
LDX #&0d ; X=&0D: 14 template bytes to process
AE22 LDY #&7c ; Y=&7C: workspace destination offset for wide variant
AE24 BIT always_set_v_byte ; BIT &FF unconditionally sets V (the always_set_v_byte trick)
AE27 BVS loop_copy_ws_template ; V=1 always: skip the narrow-mode prologue and CLV
fall through ↓

Initialise workspace copy in narrow mode (27 bytes)

Sets up a 27-byte copy to workspace offset &17, then falls through to ws_copy_vclr_entry for the template-driven copy loop. Used for the compact workspace initialisation variant.

On EntryXtemplate source offset
AE29 .init_ws_copy_narrow←1← 9872 JSR
LDY #&17 ; Y=&17: workspace destination offset for narrow variant
AE2B LDX #&1a ; X=&1A: 27 template bytes to process; fall into ws_copy_vclr_entry which CLVs
fall through ↓

Template-driven workspace copy with V clear

Processes a template byte array to initialise workspace. Special marker bytes: &FE terminates the copy, &FD skips the current offset, and &FC substitutes the workspace page pointer. All other values are stored directly to the workspace at the current offset.

On EntryXtemplate source offset
Ydestination offset within NFS workspace
V FLAGclear (controls a downstream branch in the shared body; init_ws_copy_wide / _narrow enter with V=0)
On ExitA, X, Yclobbered
AE2D .ws_copy_vclr_entry←1← AEEE JSR
CLV ; Clear V: narrow mode (writes via nfs_workspace pointer)
AE2E .loop_copy_ws_template←2← AE27 BVS← AE4F BPL
LDA ws_txcb_template_data,x ; Read next template byte
AE31 CMP #&fe ; &FE: end-of-template marker?
AE33 BEQ done_ws_template_copy ; Yes: finalise and return
AE35 CMP #&fd ; &FD: skip-this-offset marker?
AE37 BEQ advance_template_idx ; Yes: advance index without storing
AE39 CMP #&fc ; &FC: substitute-workspace-page- pointer marker?
AE3B BNE select_store_target ; No special marker: store this byte verbatim
AE3D LDA net_rx_ptr_hi ; Wide path: page pointer is net_rx_ptr's high byte
AE3F BVS store_tx_ptr_hi ; V=1 (wide): keep the rx_ptr high byte
AE41 LDA nfs_workspace_hi ; V=0 (narrow): use nfs_workspace high byte instead
AE43 .store_tx_ptr_hi←1← AE3F BVS
STA net_tx_ptr_hi ; Stash whichever page byte we picked into net_tx_ptr_hi
AE45 .select_store_target←1← AE3B BNE
BVS store_via_rx_ptr ; V=1 (wide): store via net_rx_ptr,Y
AE47 STA (nfs_workspace),y ; V=0 (narrow): store via nfs_workspace,Y
AE49 BVC advance_template_idx ; Always branch: V is still clear here
AE4B .store_via_rx_ptr←1← AE45 BVS
STA (net_rx_ptr),y ; Wide-mode store via net_rx_ptr
AE4D .advance_template_idx←2← AE37 BEQ← AE49 BVC
DEY ; Step Y down (workspace offset)
AE4E DEX ; Step X down (template index)
AE4F BPL loop_copy_ws_template ; Loop while X >= 0
AE51 .done_ws_template_copy←1← AE33 BEQ
INY ; Bump Y back to first written offset
AE52 STY net_tx_ptr ; Save it as net_tx_ptr low for the caller
AE54 RTS ; Return

Workspace TXCB template (39 bytes, three overlapping regions)

Three overlapping copy regions indexed by different callers:

Caller X / Y / V Range Destination
Wide X=&0D, Y=&7C, V=1 bytes 0..13 ws+&6F..&7C via net_rx_ptr
Narrow X=&1A, Y=&17, V=0 bytes 14..26 ws+&0C..&17 via nfs_workspace
Vclr X=&26, Y=&20, V=0 bytes 27..38 ws+&15..&20 via nfs_workspace

Per-byte inline comments below describe each entry's role in the TXCB it ends up in.

AE55 .ws_txcb_template_data←1Used as index base by← AE2E LDA
EQUB &85 ; Wide &6F: ctrl=&85
AE56 EQUB &00
AE57 EQUB &FD ; Wide &71: skip (dest station)
AE58 EQUB &FD
AE59 EQUB &7D
AE5A EQUB &FC ; Wide &74: buf start hi=page ptr
AE5B EQUB &FF ; Wide &75: buf start ext lo
AE5C EQUB &FF ; Wide &76: buf start ext hi
AE5D EQUB &7E ; Wide &77: buf end lo=&7E
AE5E EQUB &FC
AE5F EQUB &FF
AE60 EQUB &FF ; Wide &7A: buf end ext hi
AE61 EQUB &00 ; Wide &7B: zero
AE62 EQUB &00 ; Wide &7C: zero
AE63 EQUB &FE ; Narrow stop (&FE terminator)
AE64 EQUB &80
AE65 EQUB &93
AE66 EQUB &FD ; Narrow &0E: skip (dest station)
AE67 EQUB &FD
AE68 EQUB &D9
AE69 EQUB &FC ; Narrow &11: buf start hi=page ptr
AE6A EQUB &FF ; Narrow &12: buf start ext lo
AE6B EQUB &FF ; Narrow &13: buf start ext hi
AE6C EQUB &DE ; Narrow &14: buf end lo=&DE
AE6D EQUB &FC
AE6E EQUB &FF
AE6F EQUB &FF ; Narrow &17: buf end ext hi
AE70 EQUB &FE ; Spool stop (&FE terminator)
AE71 EQUB &D1
AE72 EQUB &FD
AE73 EQUB &FD ; Spool &03: skip (dest network)
AE74 EQUB &22
AE75 EQUB &FD
AE76 EQUB &FF ; Spool &06: buf start ext lo
AE77 EQUB &FF ; Spool &07: buf start ext hi
AE78 EQUB &FD ; Spool &08: skip (buf end lo)
AE79 EQUB &FD
AE7A EQUB &FF
AE7B EQUB &FF ; Spool &0B: buf end ext hi

OSWORD 5 handler: check spool PB and reset buffer

Handles OSWORD 5 intercepted via NETV. Checks if X-1 matches osword_pb_ptr and bit 0 of &00D0 is clear. If both conditions are met, falls through to reset_spool_buf_state to reinitialise the spool buffer for new data.

On EntryXOSWORD parameter block low byte (X-1 compared against osword_pb_ptr)
AE7C .netv_spool_check
DEX ; Step counter
AE7D CPX osword_pb_ptr ; Match osword_pb_ptr?
AE7F BNE rts_spool_reset ; No: return (not our PB)
AE81 LDA vdu_status ; Load spool state byte
AE83 ROR ; Shift bit 0 into C
AE84 BCS rts_spool_reset ; C=1: already active, return
fall through ↓

Reset spool buffer to initial state

Sets the spool buffer pointer (spool_buf_idx) to &21 and the control byte (ws_0d6a) to &41 (ready for new data). Called after processing a complete spool data block.

On ExitA, Yclobbered
AE86 .reset_spool_buf_state←2← 8FB1 JSR← AED7 JMP
LDA #&22 ; Buffer start offset = &21
AE88 STA spool_buf_idx ; Store as buffer pointer
AE8B LDA #&41 ; Control state &41
AE8D STA ws_0d6a ; Store as spool control state
AE90 .rts_spool_reset←4← AE7F BNE← AE84 BCS← AE93 BNE← AEA7 BCS
RTS ; Return

OSWORD 1-3 handler: drain printer buffer

Handles OSWORDs 1-3 intercepted via NETV. When X=1, drains the printer buffer (OSBYTE &91, buffer 3) into the receive buffer, sending packets via process_spool_data when the buffer exceeds &6E bytes. When X>1, routes to handle_spool_ctrl_byte for spool state control.

On EntryX1 = drain printer buffer; >1 = control byte path
AE91 .netv_print_data
CPY #4 ; Check Y == 4
AE93 BNE rts_spool_reset ; Non-zero: nothing to print, return
AE95 TXA ; A = X (control byte)
AE96 DEX ; Step counter back
AE97 BNE handle_spool_ctrl_byte ; Non-zero: handle spool ctrl byte
AE99 TSX ; Read MOS stack frame
AE9A ORA stack_page_6,x ; OR with stack value
AE9D STA stack_page_6,x ; Store back to stack
AEA0 .loop_drain_printer_buf←2← AEAF BCC← AEB4 BCC
LDA #osbyte_read_buffer ; OSBYTE &91: read buffer
AEA2 LDX #buffer_printer ; X=3: printer buffer
AEA4 JSR osbyte ; Read character from buffer Read character from buffer X
AEA7 BCS rts_spool_reset ; C set: return path
AEA9 TYA ; A = extracted character
AEAA JSR append_byte_to_rxbuf ; Add byte to RX buffer
AEAD CPY #&6e ; Buffer past &6E limit?
AEAF BCC loop_drain_printer_buf ; No: read more from buffer
AEB1 JSR process_spool_data ; Print accumulated spool data
AEB4 BCC loop_drain_printer_buf ; More room: continue reading
fall through ↓

Append byte to receive buffer

Stores A in the receive buffer at the current buffer index (ws_ptr_lo), then increments the index. Used to accumulate incoming spool data bytes before processing.

On EntryAbyte to append
AEB6 .append_byte_to_rxbuf←3← AEAA JSR← AED1 JSR← AF87 JSR
LDY spool_buf_idx ; Y = spool_buf_idx
AEB9 STA (net_rx_ptr),y ; Store A at (net_rx_ptr)+Y
AEBB INC spool_buf_idx ; Advance spool_buf_idx
AEBE RTS ; Return

Handle spool control byte and flush buffer

Rotates bit 0 of the control byte into carry for mode selection (print vs spool), appends the byte to the buffer, calls process_spool_data to transmit the accumulated data, and resets the buffer state ready for the next block.

On EntryAcontrol byte (bit 0 selects mode: 0 = print, 1 = spool)
On ExitA, X, Yclobbered
AEBF .handle_spool_ctrl_byte←2← 9028 JSR← AE97 BNE
ROR ; Rotate bit 0 into carry
AEC0 BCC check_spool_state ; C clear: take check_spool_state path
AEC2 LDA ws_0d6a ; Load spool control state Equal: take fill path
AEC5 PHA ; Save state byte
AEC6 ROR ; Rotate bit 0 into carry
AEC7 PLA ; Restore state
AEC8 BCS done_spool_ctrl ; C=1: already started, reset
AECA ORA #3 ; Set bits 0-1 (active + pending)
AECC STA ws_0d6a ; Store updated state Stop: process_spool_data and return
AECF LDA #3 ; A=3: spool-data result code
AED1 JSR append_byte_to_rxbuf ; Append result to RX buffer
AED4 JSR process_spool_data ; Process the accumulated spool data
AED7 .done_spool_ctrl←1← AEC8 BCS
JMP reset_spool_buf_state ; Reset spool buffer state

Transmit accumulated spool buffer data

Copies the workspace state to the TX control block, sends a disconnect reply if the previous transfer requires acknowledgment, then handles the spool output sequence by setting up and sending the pass-through TX buffer.

On ExitATX result (from setup_pass_txbuf)
AEDA .process_spool_data←4← AEB1 JSR← AED4 JSR← AF1D BCC← AF8A JSR
LDY #8 ; Y=8: buf_start_lo TXCB offset
AEDC LDA spool_buf_idx ; Load current spool-buffer index
AEDF STA (nfs_workspace),y ; Store at workspace+8 (buf_start_lo)
AEE1 LDA net_rx_ptr_hi ; Load RX page (= net_rx_ptr_hi)
AEE3 INY ; Advance index
AEE4 STA (nfs_workspace),y ; Store at workspace+9 (buf_start_hi)
AEE6 LDY #5 ; Y=5: alt buf_start_hi offset
AEE8 STA (nfs_workspace),y ; Store at workspace+5 (also buf-start hi)
AEEA LDY #&0b ; Y=&0B: TXCB offset for following copy
AEEC LDX #&26 ; X=&26: template offset for vclr region
AEEE JSR ws_copy_vclr_entry ; Copy 12-byte ws-template region (V-clear)
AEF1 DEY ; Step back to offset &0A
AEF2 LDA ws_0d6a ; Read shadow ACR (ws_0d6a)
AEF5 PHA ; Save state
AEF6 ROL ; Shift bit 7 into C
AEF7 PLA ; Restore state
AEF8 EOR #&80 ; Toggle bit 7
AEFA STA ws_0d6a ; Store updated shadow back to ws_0d6a
AEFD ROL ; Shift bit 0 into bit 1
AEFE STA (nfs_workspace),y ; Store at workspace+&0A
AF00 LDA vdu_status ; Read vdu_status
AF02 PHA ; Push for later restore
AF03 AND #&fe ; Clear bit 0 of vdu_status
AF05 STA vdu_status ; Store updated
AF07 LDY #&22 ; Y=&22: spool_buf_idx reset value
AF09 STY spool_buf_idx ; Reset spool_buf_idx
AF0C LDA #0 ; A=0
AF0E TAX ; X=0
AF0F LDY nfs_workspace_hi ; Y = workspace high page
AF11 CLI ; Re-enable IRQs (NMI window over)
AF12 JSR send_disconnect_reply ; Send disconnect reply
AF15 PLA ; Restore vdu_status
AF16 STA vdu_status ; Restore vdu_status
AF18 RTS ; Return
AF19 .check_spool_state←1← AEC0 BCC
LDA ws_0d6a ; Read shadow ACR
AF1C ROR ; Shift bit 0 into C
AF1D BCC process_spool_data ; C clear: re-process spool data
AF1F LDA vdu_status ; Read vdu_status
AF21 PHA ; Push for restore
AF22 AND #&fe ; Clear bit 0 of vdu_status
AF24 STA vdu_status ; Store updated
AF26 LDA #&14 ; A=&14: TX command byte
AF28 .start_spool_retry←1← AF98 BNE
PHA ; Save TX command
AF29 LDX #&0b ; X=&0B: tx_econet_txcb_template offset
AF2B LDY #&2d ; Y=&2D: dest TXCB offset
AF2D .loop_copy_spool_tx←1← AF34 BPL
LDA tx_econet_txcb_template,x ; Read template byte at tx_econet_txcb_template+X
AF30 STA (net_rx_ptr),y ; Store at (net_rx_ptr)+Y
AF32 DEY ; Decrement Y
AF33 DEX ; Decrement X
AF34 BPL loop_copy_spool_tx ; Loop until X wraps below 0
AF36 STX need_release_tube ; Store X (= &FF) as need_release_tube
AF38 LDY #2 ; Y=2: workspace offset for source
AF3A LDA (nfs_workspace),y ; Read (nfs_workspace)+2
AF3C PHA ; Save station
AF3D INY ; Y=3
AF3E LDA (nfs_workspace),y ; Read (nfs_workspace)+3
AF40 LDY #&25 ; Y=&25: dest offset in TXCB
AF42 STA (net_rx_ptr),y ; Store at (net_rx_ptr)+Y
AF44 DEY ; Y=&23
AF45 PLA ; Restore station
AF46 STA (net_rx_ptr),y ; Store at (net_rx_ptr)+Y
AF48 LDX #&0b ; X=&0B: rx_palette_txcb_template offset
AF4A LDY #&0b ; Y=&0B: dest offset in workspace
AF4C .loop_copy_spool_rx←1← AF5D BPL
LDA rx_palette_txcb_template,x ; Read template byte at rx_palette_txcb_template+X
AF4F CMP #&fd ; Compare with &FD (skip-byte marker)
AF51 BEQ advance_spool_rx_idx ; Equal: skip this byte
AF53 CMP #&fc ; Compare with &FC (page-ptr marker)
AF55 BNE store_spool_rx_byte ; Not &FC: store as-is
AF57 LDA net_rx_ptr_hi ; &FC: substitute net_rx_ptr_hi
AF59 .store_spool_rx_byte←1← AF55 BNE
STA (nfs_workspace),y ; Store at (nfs_workspace)+Y
AF5B .advance_spool_rx_idx←1← AF51 BEQ
DEY ; Next dest
AF5C DEX ; Next source
AF5D BPL loop_copy_spool_rx ; Loop until X wraps
AF5F LDA #&22 ; A=&22: TXCB control byte
AF61 STA net_tx_ptr ; Store at net_tx_ptr lo
AF63 LDA net_rx_ptr_hi ; Read net_rx_ptr_hi
AF65 STA net_tx_ptr_hi ; Store as net_tx_ptr hi
AF67 JSR setup_pass_txbuf ; Set up the pass-through TX buffer
AF6A JSR send_net_packet ; Send the TX packet
AF6D LDA #0 ; A=0: clear net_tx_ptr lo
AF6F STA net_tx_ptr ; Store -> net_tx_ptr lo
AF71 LDA nfs_workspace_hi ; Read nfs_workspace_hi
AF73 STA net_tx_ptr_hi ; Store -> net_tx_ptr hi
AF75 JSR wait_net_tx_ack ; Wait for TX ack
AF78 LDY #&2e ; Y=&2E: spool result-byte offset
AF7A LDA (net_rx_ptr),y ; Read result via (net_rx_ptr)+Y
AF7C TAX ; Copy A to X
AF7D AND #7 ; Mask the low 3 bits
AF7F BNE spool_pop_cmd ; Other: take retry path
AF81 .spool_tx_succeeded
PLA ; Discard saved TX cmd
AF82 PLA ; Restore vdu_status
AF83 STA vdu_status ; Restore vdu_status
AF85 LDA #0 ; A=0: success-return code
AF87 JSR append_byte_to_rxbuf ; Append byte to RX buffer
AF8A JSR process_spool_data ; Recurse: process_spool_data
AF8D LDA ws_0d6a ; Read shadow ACR
AF90 AND #&f0 ; Mask high nibble
AF92 STA ws_0d6a ; Store updated shadow
AF95 RTS ; Return
AF96 .spool_pop_cmd←1← AF7F BNE
PLA ; Pop saved TX cmd
AF97 DEC ; Decrement
AF98 BNE start_spool_retry ; Non-zero: retry from start_spool_retry
AF9A DEX ; Decrement counter
AF9B BNE check_err_code_5 ; Not 1: take printer_busy_msg path
fall through ↓

Raise 'Printer busy' error

Loads error code &A6 and tail-calls error_inline_log with the inline string 'Printer busy'. Called when an attempt is made to enable a printer server while one is already active. Never returns.

AF9D .err_printer_busy←1← B3E8 JMP
LDA #&a6 ; A=&A6: 'Printer busy' error code
AF9F JSR error_inline_log ; Raise via error_inline_log (never returns)
AFA2 EQUS "Printer busy", &00
AFAF .check_err_code_5←1← AF9B BNE
CMP #5 ; Error code = 5?
AFB1 BNE err_printer_jammed ; Not 1: take printer_busy_msg path
AFB3 LDA #&ab ; A=&AB: 'Printer off line' error code
AFB5 JSR error_inline_log ; Raise via error_inline_log (never returns)
AFB8 EQUS "Printer off line", &00
AFC9 .err_printer_jammed←1← AFB1 BNE
LDA #&a7 ; A=&A7: 'Printer jammed' error code
AFCB JSR error_inline_log ; Raise via error_inline_log (never returns)
AFCE EQUS "Printer jammed", &00
fall through ↓

Send Econet disconnect reply packet

Sets up the TX pointer, copies station addresses, matches the station in the table, and sends the response. Waits for acknowledgment before returning.

On ExitATX result code
AFDD .send_disconnect_reply←3← 97E6 JSR← AF12 JSR← BD14 JSR
STX net_tx_ptr ; X = caller's TX-ptr low byte
AFDF STY net_tx_ptr_hi ; Y = caller's TX-ptr high byte
AFE1 PHA ; Save A (the disconnect status to send)
AFE2 ORA #0 ; Test if A=0 (broadcast disconnect)
AFE4 BEQ send_disconnect_status ; Yes: skip the per-station scan
AFE6 LDX #&ff ; X=&FF: scan counter -- INX in loop bumps to 0
AFE8 TAY ; Y=A: status code (also used as station-table key)
AFE9 .loop_scan_disconnect←2← AFF2 BNE← AFFC BNE
TYA ; Restore status into A for the compare
AFEA INX ; Step station-table index
AFEB CMP hazel_fcb_slot_attr,x ; Compare with table[X] at &C230 (per-station status)
AFEE BEQ verify_stn_match ; Match: verify station address still matches
AFF0 CPX #&0f ; Reached end of 16-slot table?
AFF2 BNE loop_scan_disconnect ; No: keep scanning
AFF4 LDA #0 ; All slots tested, no match: A=0
AFF6 BEQ send_disconnect_status ; Always taken: jump to send-status
AFF8 .verify_stn_match←1← AFEE BEQ
TAY ; Y = matching index
AFF9 JSR match_station_net ; Verify station/network at this slot still matches caller
AFFC BNE loop_scan_disconnect ; Mismatch: station moved, keep scanning
AFFE LDA hazel_fcb_status,x ; Read connection-active flag at &C260+X
B001 AND #1 ; Mask to bit 0 (active flag)
B003 .send_disconnect_status←2← AFE4 BEQ← AFF6 BEQ
LDY #0 ; Y=0: TX[0] = control byte
B005 ORA (net_tx_ptr),y ; OR active-flag bit into the status
B007 PHA ; Save the combined status
B008 STA (net_tx_ptr),y ; Write it to TX[0]
B00A JSR send_net_packet ; Send the disconnect packet via four-way handshake
B00D LDA #&ff ; A=&FF: sentinel
B00F LDY #8 ; Y=8: TX[8] / TX[9] = packet trailer markers
B011 STA (net_tx_ptr),y ; Write &FF at TX[8]
B013 INY ; Step Y
B014 STA (net_tx_ptr),y ; Write &FF at TX[9]
B016 PLA ; Pull the saved status
B017 TAX ; Move into X for the test
B018 LDY #&d1 ; Y=&D1: control byte for ack-mode TXCB[1]
B01A PLA ; Pull caller's original A again (was double-saved)
B01B PHA ; Push it back
B01C BEQ store_tx_ctrl_byte ; A=0: skip the override
B01E LDY #&90 ; Non-zero: use Y=&90 (FS reply port instead)
B020 .store_tx_ctrl_byte←1← B01C BEQ
TYA ; Move chosen control/port into A
B021 LDY #1 ; Y=1: TX[1] is the port byte
B023 STA (net_tx_ptr),y ; Write to TX[1]
B025 TXA ; Move saved status into A
B026 DEY ; Y=0: TX[0] for ack poll
B027 PHA ; Push the status (we'll EOR with reply below)
B028 .loop_wait_disc_tx_ack←1← B034 BCS
LDA #&7f ; A=&7F: marker pattern
B02A STA (net_tx_ptr),y ; Write to TX[0]
B02C JSR wait_net_tx_ack ; Wait for the TX/RX flip
B02F PLA ; Pull saved status (peek without consuming)
B030 PHA ; Push it back
B031 EOR (net_tx_ptr),y ; EOR with TX[0]: zero iff reply matches saved
B033 ROR ; Rotate result; C set if bit 0 differs
B034 BCS loop_wait_disc_tx_ack ; C set: keep waiting
B036 PLA ; Discard saved status
B037 PLA ; Discard caller's saved A
B038 RTS ; Return

Spool / disconnect TX control-block template (12 bytes)

12-byte Econet TXCB initialisation template used by the spool / disconnect TX paths. Copied into the workspace TXCB at offsets &21..&2C via (net_rx_ptr),Y. Destination station and network are filled in afterwards by the caller. Per-byte inline comments identify each TXCB field.

B039 .tx_econet_txcb_template←1Used as index base by← AF2D LDA
EQUB &80 ; ctrl=&80 (standard TX)
B03A EQUB &9F ; port=&9F
B03B EQUB &00 ; dest station=&00 (filled later)
B03C EQUB &00 ; dest network=&00 (filled later)
B03D EQUB &B9
B03E EQUB &8E ; buf start hi (&8E); start = &8EB9
B03F EQUB &FF ; buf start ext lo=&FF
B040 EQUB &FF ; buf start ext hi=&FF
B041 EQUB &C1
B042 EQUB &8E ; buf end hi (&8E); end = &8EA7
B043 EQUB &FF ; buf end ext lo=&FF
B044 EQUB &FF ; buf end ext hi=&FF

Palette-RX control-block template (12 bytes)

12-byte template used by the *PS / palette-RX paths. Copied with marker processing: &FD skips the destination byte (preserving the existing field), &FC substitutes net_rx_ptr_hi (the caller's RX-buffer page). Filled in over the workspace TXCB by the broadcast-RX setup before the request is dispatched.

B045 .rx_palette_txcb_template←1Used as index base by← AF4C LDA
EQUB &7F ; ctrl=&7F (RX listen)
B046 EQUB &9E ; port=&9E
B047 EQUB &FD
B048 EQUB &FD
B049 EQUB &2E
B04A EQUB &FC ; buf start hi=page ptr (&FC)
B04B EQUB &FF ; buf start ext lo=&FF
B04C EQUB &FF ; buf start ext hi=&FF
B04D EQUB &31
B04E EQUB &FC
B04F EQUB &FF ; buf end ext lo=&FF
B050 EQUB &FF ; buf end ext hi=&FF

Language reply 2: save palette / VDU state

Reached via the language-reply dispatch table when a remote sends reply code 2 ('save palette and VDU state'). Saves the current template byte from osword_flag on the stack, sets up the workspace pointer (nfs_workspace) to the appropriate offset, and copies the palette / VDU state from MOS workspace at &0350 into the workspace transmit buffer for forwarding back to the station.

B051 .lang_2_save_palette_vdu
LDA osword_flag ; Read osword_flag (preserved across the dispatch)
B053 PHA ; Save state byte
B054 LDA #&e9 ; A=&E9: workspace start lo for palette save
B056 STA nfs_workspace ; Store as nfs_workspace lo
B058 LDY #0 ; Y=0
B05A STY osword_flag ; Reset osword_flag = 0
B05C LDA vdu_screen_mode ; Read vdu_screen_mode (MOS state byte)
B05F STA (nfs_workspace),y ; Store at (nfs_workspace)+0
B061 INC nfs_workspace ; Advance nfs_workspace lo
B063 LDA vdu_display_start_hi ; Read vdu_display_start_hi (next MOS byte)
B066 PHA ; Save another byte
B067 TYA ; A=0 for first palette entry
B068 .loop_read_palette←1← B087 BNE
STA (nfs_workspace),y ; Store at (nfs_workspace)
B06A LDX nfs_workspace ; Read updated nfs_workspace lo
B06C LDY nfs_workspace_hi ; Read nfs_workspace hi
B06E LDA #osword_read_palette ; A=&0B: OSWORD &0B = read palette entry
B070 JSR osword ; Read palette entry Read palette
B073 PLA ; Restore inner saved
B074 LDY #0 ; Y=0
B076 STA (nfs_workspace),y ; Store palette result at workspace
B078 INY ; Y=1: physical colour offset
B079 LDA (nfs_workspace),y ; Re-read palette result
B07B PHA ; Save for next iteration
B07C LDX nfs_workspace ; Read updated workspace lo
B07E INC nfs_workspace ; Advance workspace
B080 INC osword_flag ; Increment osword_flag (palette index)
B082 DEY ; Y=0
B083 LDA osword_flag ; Read updated osword_flag
B085 CPX #&f9 ; Compare with &F9 (last palette entry)
B087 BNE loop_read_palette ; Not done: loop
B089 PLA ; Restore outer saved
B08A STY osword_flag ; Reset osword_flag = 0 after palette loop
B08C INC nfs_workspace ; Advance workspace
B08E JSR serialise_palette_entry ; Serialise the next palette entry
B091 INC nfs_workspace ; Advance workspace
B093 PLA ; Restore final saved
B094 STA osword_flag ; Save osword_flag
fall through ↓

Copy current state byte to committed state

Reads the working state byte from workspace and stores it to the committed state location. Used to finalise a state transition after all related workspace fields have been updated.

On ExitA= the committed value
B096 .commit_state_byte←4← 9856 JMP← 987E JSR← 98B6 JSR← A9B9 JMP
LDA prot_status_save ; Read saved copy of prot_status from prot_status_save
B099 STA prot_status ; Store back to prot_status
B09C RTS ; Return

Serialise palette register to workspace

Reads the current logical colour for a palette register via OSBYTE &0B and stores both the palette value and the display mode information in the workspace block. Used during remote screen state capture.

On EntryXpalette register index (0-15)
Ydestination workspace offset (palette + mode pair)
On ExitYadvanced past the 2-byte pair
A, Xclobbered (OSBYTE)
B09D .serialise_palette_entry←1← B08E JSR
LDA vdu_mode ; Read vdu_mode (current palette index)
B0A0 TAX ; Copy A to X
B0A1 ORA #&40 ; Mark as palette entry
B0A3 STA (nfs_workspace),y ; Store at (nfs_workspace)+Y
B0A5 INC nfs_workspace ; Advance workspace
B0A7 TYA ; A = current Y (= 0)
B0A8 STA (nfs_workspace),y ; Store 0 at (nfs_workspace)+Y
B0AA LDA read_osbyte_table,x ; Read lookup byte from read_osbyte_table+X
B0AD LDX #0 ; X=0: indexed-indirect mode
B0AF INC nfs_workspace ; Advance workspace
B0B1 STA (nfs_workspace,x) ; Store at (nfs_workspace,X)
B0B3 JSR read_osbyte_to_ws_x0 ; Read OSBYTE result via x=0 helper
fall through ↓

Read OSBYTE with X=0 and store to workspace

Sets X=0 then falls through to read_osbyte_to_ws to issue the OSBYTE call and store the result. Used when the OSBYTE parameter X must be zero.

On EntryYdestination workspace offset
On ExitYincremented past the stored byte
A, Xclobbered (OSBYTE)
B0B6 .read_osbyte_to_ws_x0←1← B0B3 JSR
LDX #0 ; X=0: zero-arg helper entry
fall through ↓

Issue OSBYTE from table and store result

Loads the OSBYTE function code from the next entry in the OSBYTE table, issues the call, and stores the Y result in workspace at the current offset. Advances the table pointer for the next call.

On EntryXOSBYTE X parameter
Ydestination workspace offset
On ExitYincremented past the stored byte
A, Xclobbered
B0B8 .read_osbyte_to_ws
LDY osword_flag ; Y = osword_flag (OSBYTE-table index)
B0BA INC osword_flag ; Increment osword_flag for next call
B0BC INC nfs_workspace ; Advance nfs_workspace
B0BE LDA read_osbyte_return,y ; Load OSBYTE number from read_osbyte_return+Y
B0C1 LDY #&ff ; Y=&FF -- OSBYTE arg (read mode)
B0C3 JSR osbyte ; Issue OSBYTE
B0C6 TXA ; Result to A
B0C7 LDX #0 ; X=0: indexed-indirect mode
B0C9 STA (nfs_workspace,x) ; Store at (nfs_workspace,X)
B0CB RTS ; Return
B0CC .read_osbyte_return←1Used as index base by← B0BE LDA
EQUB &C2, &C3
B0CE .read_osbyte_table←1Used as index base by← B0AA LDA
EQUS "000@XX`"
B0D5 .cmd_cdir_indirect_dispatch
JMP (cdir_unused_dispatch_table,x) ; JMP (cdir_unused_dispatch_ table,X) -- never executed; see cmd_cdir Save command line offset Push onto stack
B0D8 JSR mask_owner_access ; Set owner-only access mask
B0DB JSR skip_to_next_arg ; Skip to optional size argument
B0DE CMP #&0d ; End of line?
B0E0 BNE parse_cdir_size ; No: parse size argument
B0E2 LDX #2 ; Default allocation size index = 2
B0E4 BNE done_cdir_size ; Branch when the *CDIR size is complete
B0E6 .parse_cdir_size←1← B0E0 BNE
LDA #&ff ; A=&FF: mark as decimal parse
B0E8 STA fs_work_4 ; Store decimal parse flag
B0EA JSR parse_addr_arg ; Parse numeric size argument
B0ED LDX #&1b ; X=&1B: top of 26-entry size table
B0EF .loop_find_alloc_size←1← B0F3 BCC
DEX ; Try next lower index
B0F0 CMP cdir_size_thresholds,x ; Compare size with threshold
B0F3 BCC loop_find_alloc_size ; A < threshold: keep searching
B0F5 .done_cdir_size←1← B0E4 BNE
STX hazel_txcb_data ; Store allocation size index
B0F8 PLA ; Restore command line offset
B0F9 TAY ; Transfer to Y
B0FA JSR save_ptr_to_os_text ; Save text pointer for filename parse
B0FD JSR parse_filename_arg ; Parse directory name argument
B100 LDX #1 ; X=1: one argument to copy
B102 JSR copy_arg_to_buf ; Copy directory name to TX buffer
B105 LDY #&1b ; Y=&1B: *CDir FS command code
B107 .cdir_dispatch_col
JMP save_net_tx_cb ; Send command to file server

*CDir allocation size threshold table (26 entries)

26 thresholds dividing 0-255 into size classes for the *CDir directory-size argument. Table base is at cdir_dispatch_col+2 (overlapping the JMP operand high byte just before the table); the search loop (LDX #&1B / DEX / CMP table,X / BCC) scans indices 26 down to 0. Index 0 reads &94 from the JMP and is unreachable because index 1 (threshold &00) always matches first. The resulting X (1-26) is the allocation size class sent to the file server. Default when no size argument is given: index 2.

B10A .cdir_alloc_size_table
EQUB &00 ; Index 1: threshold 0 (catch-all)
B10B EQUB &0A ; Index 2: threshold 10 (default)
B10C EQUB &14 ; Index 3: threshold 20
B10D EQUB &1D ; Index 4: threshold 29
B10E EQUB &27 ; Index 5: threshold 39
B10F EQUB &31 ; Index 6: threshold 49
B110 EQUB &3B ; Index 7: threshold 59
B111 EQUB &45 ; Index 8: threshold 69
B112 EQUB &4F ; Index 9: threshold 79
B113 EQUB &58 ; Index 10: threshold 88
B114 EQUB &62 ; Index 11: threshold 98
B115 EQUB &6C ; Index 12: threshold 108
B116 EQUB &76 ; Index 13: threshold 118
B117 EQUB &80 ; Index 14: threshold 128
B118 EQUB &8A ; Index 15: threshold 138
B119 EQUB &94 ; Index 16: threshold 148
B11A EQUB &9D ; Index 17: threshold 157
B11B EQUB &A7 ; Index 18: threshold 167
B11C EQUB &B1 ; Index 19: threshold 177
B11D EQUB &BB ; Index 20: threshold 187
B11E EQUB &C5 ; Index 21: threshold 197
B11F EQUB &CF ; Index 22: threshold 207
B120 EQUB &D8 ; Index 23: threshold 216
B121 EQUB &E2 ; Index 24: threshold 226
B122 EQUB &EC ; Index 25: threshold 236
B123 .cdir_size_done←1Used as index base by← 8FA2 LDA
EQUB &F6 ; Index 26: threshold &F6 (246) -- last cdir-size threshold; doubles as cdir_size_done[0] (unread by init loop)
B124 EQUB &FF ; cdir_size_done[1] = &FF -> tx_retry_count (retry counter init)
B125 EQUB &28 ; cdir_size_done[2] = &28 -> rx_wait_timeout (40 retries)
B126 EQUB &0A ; cdir_size_done[3] = &0A -> peek_retry_count (10 retries)

*LCat command handler

Rotates the caller's carry into bit 7 of hazel_fs_lib_flags (the dispatch path enters with C=1 so this sets the 'library' flag), then SEC / BCS unconditionally jumps to cat_set_lib_flag inside cmd_ex to catalogue the library directory with three entries per column.

On EntryYcommand line offset in text pointer
C1 (set by the cmd_table_fs dispatch path)
B127 .cmd_lcat
ROR hazel_fs_lib_flags ; Rotate carry into lib flag bit 7
B12A SEC ; Set carry (= library directory)
B12B BCS cat_set_lib_flag ; C set: set the library flag
fall through ↓

*LEx command handler

Rotates the caller's carry into bit 7 of hazel_fs_lib_flags (the dispatch path enters with C=1 so this sets the 'library' flag), then jumps to ex_set_lib_flag inside cmd_ex to examine the library directory with one entry per line.

On EntryYcommand line offset in text pointer
C1 (set by the cmd_table_fs dispatch path)
B12D .cmd_lex
ROR hazel_fs_lib_flags ; Rotate carry into lib flag bit 7
B130 SEC ; Set carry (= library directory)
B131 BCS ex_set_lib_flag ; C set: set the library flag
B133 .ps_scan_resume
JSR set_text_and_xfer_ptr ; Set OS text pointer and FS-options transfer ptr
B136 LDY #0 ; Y=0: TX-buffer offset for the first byte
fall through ↓

*Ex command handler

Unified handler for *Ex, *LCat, and *LEx. Sets the library flag from carry (CLC for current, SEC for library). Configures column format: 1 entry per line for Ex (command 3), 3 per column for Cat (command &0B). Sends the examine request (code &12), then prints the directory header: title, cycle number, Owner/Public label, option name, Dir. and Lib. paths. Paginates through entries, printing each via ex_print_col_sep until the server returns zero entries.

On EntryYcommand line offset in text pointer
B138 .cmd_ex
ROR hazel_fs_lib_flags ; Rotate carry into lib flag bit 7
B13B CLC ; Clear carry (= current directory)
B13C .ex_set_lib_flag←1← B131 BCS
ROL hazel_fs_lib_flags ; Rotate carry back, clearing bit 7
B13F LDA #&ff ; A=&FF: initial column counter
B141 STA fs_spool_handle ; Store column counter
B143 LDA #1 ; One entry per line (Ex format)
B145 STA fs_work_7 ; Store entries per page
B147 LDA #3 ; FS command code 3: Examine
B149 STA fs_work_5 ; Store command code
B14B BNE setup_ex_request ; Branch to set up the *EX request
fall through ↓

FSCV reason 5: catalogue (*CAT)

Sets up transfer parameters via set_xfer_params, clears the library bit in hazel_fs_lib_flags via the ROR/CLC/ROL idiom that uses carry to preserve other flags, and falls through to cat_set_lib_flag to issue the FS examine request. Reached via the FSCV vector with reason code 5.

B14D .fscv_5_cat
JSR set_xfer_params ; Set transfer parameters
B150 LDY #0 ; Y=0: start from entry 0
B152 ROR hazel_fs_lib_flags ; Rotate carry into lib flag
B155 CLC ; Clear carry (= current directory)
B156 .cat_set_lib_flag←1← B12B BCS
ROL hazel_fs_lib_flags ; Rotate carry back, clearing bit 7
B159 LDA #3 ; Three entries per column (Cat)
B15B STA fs_spool_handle ; Store column counter
B15D STA fs_work_7 ; Store entries per page
B15F LDA #&0b ; FS command code &0B: Catalogue
B161 STA fs_work_5 ; Store command code
B163 .setup_ex_request←1← B14B BNE
JSR save_ptr_to_os_text ; Save text pointer
B166 LDA #&ff ; A=&FF: enable escape checking
B168 STA need_release_tube ; Set escapable flag
B16A LDA #6 ; Command code 6
B16C STA hazel_txcb_data ; Store in TX buffer
B16F JSR parse_filename_arg ; Parse directory argument
B172 LDX #1 ; X=1: offset in buffer
B174 JSR copy_arg_to_buf ; Copy argument to TX buffer
B177 LDA hazel_fs_lib_flags ; Get library/FS flags
B17A LSR ; Shift bit 0 to carry
B17B BCC store_owner_flags ; Bit 0 clear: skip
B17D ORA #&40 ; Set bit 6 (owner access flag)
B17F .store_owner_flags←1← B17B BCC
ROL ; Rotate back
B180 STA hazel_fs_lib_flags ; Store modified flags
B183 LDY #&12 ; Y=&12: FS command for examine
B185 JSR save_net_tx_cb ; Send request to file server
B188 LDX #3 ; X=3: offset to directory title
B18A JSR print_10_chars ; Print directory title (10 chars)
B18D JSR print_inline_no_spool ; Print '('
B190 EQUS "("
B191 LDA hazel_txcb_objtype ; Load FS object-type code from hazel_txcb_objtype (file/dir/etc)
B194 JSR print_dec_3dig_no_spool ; Print a 3-digit decimal (no spool)
B197 JSR print_inline_no_spool ; Print ') ' to close the type-code field
B19A EQUS ") "
B1A0 LDY hazel_txcb_type ; Read hazel_txcb_type (FS reply opcode)
B1A3 BNE print_public_label ; Non-zero (private library): take the public-label branch
B1A5 JSR print_inline_no_spool ; Print 'Owner' + CR
B1A8 EQUS "Owner", &0D
B1AE BNE cat_after_label_print ; Non-zero: branch to cat_after_label_print
B1B0 .print_public_label←1← B1A3 BNE
JSR print_inline_no_spool ; Print 'Public' + CR
B1B3 EQUS "Public", &0D
B1BA .cat_after_label_print←1← B1AE BNE
LDA hazel_fs_lib_flags ; Read hazel_fs_lib_flags
B1BD PHA ; Push for stack-based saves
B1BE JSR mask_owner_access ; Mask owner access bits
B1C1 LDY #&15 ; Y=&15: FS command for dir info
B1C3 JSR save_net_tx_cb ; Send request to file server
B1C6 INX ; Advance X past header
B1C7 LDY #&10 ; Y=&10: print 16 chars
B1C9 JSR print_chars_from_buf ; Print file entry
B1CC JSR print_inline_no_spool ; Print ' Option '
B1CF EQUS " Option "
B1DA LDA hazel_fs_flags ; Read hazel_fs_flags
B1DD TAX ; Transfer to X for table lookup
B1DE JSR print_hex_byte_no_spool ; Print option as hex
B1E1 JSR print_inline_no_spool ; Print ' ('
B1E4 EQUS " ("
B1E6 LDY option_str_offset_data,x ; Look up option-string offset for index X
B1E9 .loop_print_option←1← B1F2 BNE
LDA option_offset_table,y ; Look up option byte at the resolved offset
B1EC BMI print_dir_header ; Bit 7 of A set (negative): print directory header
B1EE JSR print_char_no_spool ; Print char (no spool)
B1F1 INY ; Advance Y
B1F2 BNE loop_print_option ; Loop until Y wraps
B1F4 .print_dir_header←1← B1EC BMI
JSR print_inline_no_spool ; Print ')\rDir. ' header for the directory listing
B1F7 EQUS ")", &0D, "Dir. "
B1FE LDX #&11 ; X=&11: filename offset in TX buffer
B200 JSR print_10_chars ; Print 10-char filename
B203 JSR print_inline_no_spool ; Print inline 'attr-bits' fragment
B206 EQUS " Lib. " ; label for *Ex output
B210 LDX #&1b ; X=&1B: extension offset in TX buffer
B212 JSR print_10_chars ; Print 10-char extension
B215 JSR print_newline_no_spool ; Print newline
B218 PLA ; Pop saved counter
B219 STA hazel_fs_lib_flags ; Store as fs_lib_flags
B21C .setup_ex_pagination←1← B24D BNE
STY hazel_txcb_flag ; Save Y as hazel_txcb_flag (next-entry index)
B21F STY fs_work_4 ; Save Y as fs_work_4
B221 LDX fs_work_5 ; Load fs_work_5 (page count)
B223 STX hazel_txcb_count ; Store at hazel_txcb_count
B226 LDX fs_work_7 ; Load fs_work_7
B228 STX hazel_txcb_data ; Store at hazel_txcb_data
B22B LDX #3 ; X=3: TX[3] is start of arg buffer
B22D JSR copy_arg_to_buf ; Copy filename arg
B230 LDY #3 ; Y=3: cmd code 3 (catalog)
B232 JSR save_net_tx_cb ; Send TX request
B235 INX ; X advances entry counter
B236 LDA hazel_txcb_data ; Read reply status
B239 BEQ jmp_osnewl ; Z: empty reply -> exit cat
B23B PHA ; Push reply status
B23C .loop_scan_entry_data←1← B240 BPL
INY ; Advance Y
B23D LDA hazel_txcb_data,y ; Read entry byte from hazel_txcb_data+Y
B240 BPL loop_scan_entry_data ; Bit 7 clear: keep scanning
B242 STA hazel_txcb_lib,y ; Store with high-bit clear at hazel_txcb_lib+Y
B245 JSR ex_print_col_sep ; Print column separator
B248 PLA ; Pop saved status
B249 CLC ; Clear carry for the ADC below
B24A ADC fs_work_4 ; Add fs_work_4 (page accumulator)
B24C TAY ; New index
B24D BNE setup_ex_pagination ; Non-zero: continue paging
fall through ↓

Print 10 characters from reply buffer

Sets Y=10 and falls through to print_chars_from_buf. Used by cmd_ex to print fixed-width directory title, directory name, and library name fields.

On EntryXbuffer offset to start printing from
B24F .print_10_chars←3← B18A JSR← B200 JSR← B212 JSR
LDY #&0a ; Y=10: ten characters to print (fixed-width field)
fall through ↓

Print Y characters from buffer via OSASCI

Loops Y times, loading each byte from fs_cmd_data+X and printing it via OSASCI. Advances X after each character, leaving X pointing past the last printed byte.

On EntryXbuffer offset
Ycharacter count
B251 .print_chars_from_buf←2← B1C9 JSR← B259 BNE
LDA hazel_txcb_data,x ; Read next character from reply buffer at offset X
B254 JSR print_char_no_spool ; Print via OSASCI, bypassing the *SPOOL file
B257 INX ; Step buffer offset
B258 DEY ; Step character counter
B259 BNE print_chars_from_buf ; Loop until Y=0
B25B RTS ; Return; X points just past the last printed byte
B25C .jmp_osnewl←1← B239 BEQ
JMP print_newline_no_spool ; Tail-call print_newline_no_spool

Parse command argument from offset zero

Sets Y=0 and falls through to parse_filename_arg for GSREAD-based filename parsing with prefix character handling.

On ExitYadvanced past the parsed argument
B25F .parse_cmd_arg_y0←3← A00E JSR← A512 JSR← B39C JSR
LDY #0 ; Y=0: scan from start of command line
fall through ↓

Parse filename via GSREAD with prefix handling

Calls gsread_to_buf to read the command-line string into hazel_parse_buf (the 4.21 HAZEL parse buffer at &C030), then falls through to parse_access_prefix to process '&', ':', '.' and '#' prefix characters.

On EntryYcurrent command-line offset (consumed by gsread_to_buf)
On ExitYadvanced past the parsed argument
B261 .parse_filename_arg←3← B0FD JSR← B16F JSR← B728 JSR
JSR gsread_to_buf ; Read the GSREAD-style filename argument into the &C030 buffer, then fall into parse_access_prefix
fall through ↓

Parse access and FS selection prefix characters

Examines the first character(s) of the parsed buffer at &C030 for prefix characters: '&' sets the FS selection flag (bit 6 of hazel_fs_lib_flags) and strips the prefix, ':' with '.' also triggers FS selection, '#' is accepted as a channel prefix. Raises 'Bad file name' for invalid combinations like '&.' followed by CR.

B264 .parse_access_prefix←4← 9433 JSR← 94D6 JSR← 950C JSR← 9C2B JSR
LDA hazel_parse_buf ; Read first parsed-buffer character (the candidate prefix)
B267 EOR #'&' ; EOR with '&'; Z set iff the byte was '&'
B269 BNE check_colon_prefix ; Not '&': try ':' (and '#') instead
B26B LDA hazel_fs_lib_flags ; Read fs_lib_flags
B26E ORA #&40 ; Set bit 6 (URD-relative resolution flag)
B270 STA hazel_fs_lib_flags ; Write back updated flags
B273 JSR strip_token_prefix ; Strip the '&' from the buffer (shift left + trim)
B276 DEX ; Step caller's X back to account for the consumed character
B277 LDA hazel_parse_buf ; Re-read the (now first) buffer byte after the strip
B27A EOR #'.' ; EOR with '.'; Z set iff '&.' pair (URD root)
B27C BNE check_hash_prefix ; Not '&.': just '&' alone -- check for trailing '#'
B27E LDA hazel_parse_buf_1 ; It was '&.': peek the byte after the dot
B281 EOR #&0d ; EOR with CR; Z set iff '&.<CR>' (illegal: dot needs a name to follow)
B283 BEQ error_bad_prefix ; '&.<CR>' is invalid: raise 'Bad filename'
B285 DEX ; Valid '&.<name>': step X back for the dot too
fall through ↓

Strip first character from parsed token buffer

Shifts all bytes in the &C030 buffer left by one position (removing the first character), then trims any trailing spaces by replacing them with CR terminators. Used after consuming a prefix character like '&' or ':'.

On ExitXpreserved (saved/restored via PHA/PLA)
Aclobbered
B286 .strip_token_prefix←5← 9460 JSR← 94F5 JSR← 94FA JSR← B273 JSR← B2C8 JSR
TXA ; Save caller's X (TX buffer offset)
B287 PHA ; Push it
B288 LDX #&ff ; X=&FF: INX in loop bumps to 0 for first byte
B28A .loop_shift_str_left←1← B293 BNE
INX ; Step to next byte position
B28B LDA hazel_parse_buf_1,x ; Read byte X+1 (the next character)
B28E STA hazel_parse_buf,x ; Store it back at byte X (shifting left by one)
B291 EOR #&0d ; EOR with CR; Z set if we just shifted the terminator
B293 BNE loop_shift_str_left ; More to shift: continue
B295 TXA ; X is now the buffer length (excluding CR)
B296 BEQ done_strip_prefix ; Empty after shift: skip trim, restore X, return
B298 .loop_trim_trailing←1← B2A5 BNE
LDA hazel_parse_buf_m1,x ; Read last buffer byte (X-1 because we count from 0)
B29B EOR #' ' ; EOR with space; Z set iff it's a trailing space
B29D BNE done_strip_prefix ; Not a space: trim done, restore X, return
B29F LDA #&0d ; It is a space: replace with CR (truncate the string)
B2A1 STA hazel_parse_buf_m1,x ; Store CR at the now-trimmed position
B2A4 DEX ; Step backwards
B2A5 BNE loop_trim_trailing ; Loop while X > 0
B2A7 .done_strip_prefix←2← B296 BEQ← B29D BNE
PLA ; Restore caller's TX buffer offset
B2A8 TAX ; Transfer back to X
B2A9 .rts_strip_prefix←3← B2AC BEQ← B2B3 BNE← B2BE BNE
RTS ; Return
B2AA .check_hash_prefix←1← B27C BNE
EOR #'#' ; Test for '#' prefix (3 ^ &23 = 0)
B2AC BEQ rts_strip_prefix ; Equal: '#' was the prefix, return
B2AE .error_bad_prefix←2← B283 BEQ← B2E4 BEQ
JMP error_bad_filename ; Other: not a recognised prefix -> error
B2B1 .check_colon_prefix←1← B269 BNE
EOR #&1c ; Test for ':' (&3F ^ &1C)
B2B3 BNE rts_strip_prefix ; Different: caller had no prefix, return
B2B5 LDA hazel_parse_buf_2 ; ':' confirmed -- read next char from parse buffer
B2B8 EOR #'.' ; Test for '.' (path separator)
B2BA BEQ set_fs_select_flag ; Equal: ':.' qualified prefix
B2BC EOR #'#' ; Test for '#'
B2BE BNE rts_strip_prefix ; Other: no recognised tail prefix, return
B2C0 .set_fs_select_flag←1← B2BA BEQ
LDA hazel_fs_lib_flags ; Recognised: load fs_lib_flags
B2C3 ORA #&40 ; Set bit 6 (FS-select pending)
B2C5 STA hazel_fs_lib_flags ; Store updated fs_lib_flags
B2C8 JSR strip_token_prefix ; Recurse to strip the trailing component
B2CB DEX ; Decrement X (consume processed char)
B2CC RTS ; Return
B2CD .option_str_offset_data←1Used as index base by← B1E6 LDY
EQUB &00 ; Data: option string offset table
B2CE EQUS "/<c"
B2D1 .option_offset_table←1Used as index base by← B1E9 LDA
EQUS "Off"

Copy argument to TX buffer from offset zero

Sets X=0 and falls through to copy_arg_to_buf then copy_arg_validated. Provides the simplest entry point for copying a single parsed argument into the TX buffer at position zero.

On ExitXTX buffer offset just past the copied argument
Yadvanced past the source argument
B2D4 .copy_arg_to_buf_x0←5← 8DEF JSR← 8E52 JSR← 9C39 JSR← 9E29 JSR← A5F5 JSR
LDX #0 ; X=0: place the argument at the start of the TX buffer; fall into copy_arg_to_buf
fall through ↓

Copy argument to TX buffer with Y=0

Sets Y=0 and falls through to copy_arg_validated with carry set, enabling '&' character validation. X must already contain the destination offset within the TX buffer.

On EntryXdestination offset within the TX buffer
On ExitXTX buffer offset just past the copied argument
Yadvanced past the source argument
B2D6 .copy_arg_to_buf←11← 9CEB JSR← 9E22 JSR← 9E45 JSR← A013 JSR← A517 JSR← A544 JSR← B102 JSR← B174 JSR← B22D JSR← B3A1 JSR← B73D JSR
LDY #0 ; Y=0: scan from start of command line (CLC entry skips '&' validation)
fall through ↓

Copy command line characters to TX buffer

Copies characters from (fs_crc_lo)+Y to fs_cmd_data+X until a CR terminator is reached. With carry set, validates each character against '&' — raising 'Bad file name' if found — to prevent FS selector characters from being embedded in filenames.

On EntryXTX buffer destination offset
Ycommand line source offset
Cset to enable '&' validation
B2D8 .copy_arg_validated←3← 8DEA JSR← 955A JSR← 9590 JSR
SEC ; Set C: this entry validates against '&'
B2D9 .loop_copy_char←1← B2EC BNE
LDA (fs_crc_lo),y ; Read next source byte through fs_crc_lo pointer
B2DB STA hazel_txcb_data,x ; Store into TX buffer at offset X
B2DE BCC advance_positions ; Validation off (C clear): just advance positions
B2E0 CMP #'!' ; Test against '!' to bias the EOR comparison
B2E2 EOR #'&' ; EOR with '&'; Z set iff source byte was '&'
B2E4 BEQ error_bad_prefix ; '&' inside the argument is illegal: raise 'Bad filename'
B2E6 .restore_after_check
EOR #'&' ; Restore A by undoing the EOR (so the loop terminator test below sees the original byte)
B2E8 .advance_positions←1← B2DE BCC
INX ; Advance TX buffer offset
B2E9 INY ; Advance command-line offset
B2EA EOR #&0d ; EOR with CR; Z set iff we just stored the terminator
B2EC BNE loop_copy_char ; More to copy: continue
B2EE .loop_trim_trailing_spaces←1← B2FB BNE
LDA hazel_txcb_network,x ; Look at the byte just before the CR we stopped on
B2F1 EOR #' ' ; EOR with space; Z set iff that byte was a trailing space
B2F3 BNE done_trim_spaces ; Not a space: trim done
B2F5 DEX ; Step back over the space
B2F6 LDA #&0d ; A=&0D: replace the trailing space with CR
B2F8 STA hazel_txcb_lib,x ; Store CR at the now-truncated end
B2FB BNE loop_trim_trailing_spaces ; Always taken (A=&0D from LDA #&0D so Z is clear); look at the next byte back
B2FD .done_trim_spaces←1← B2F3 BNE
LDA #0 ; All trailing spaces consumed (or none present)
B2FF .rts_copy_arg←1← B315 BMI
RTS ; Return
B300 EQUS "Load"

Clear FS selection flags from options word

ANDs the &C271 (hazel_fs_lib_flags) byte with &1F, clearing the FS selection flag (bit 6) and other high bits to retain only the 5-bit owner-access mask. Called before parsing to reset the prefix state from a previous command. 12 callers.

On ExitAmasked value
B304 .mask_owner_access←15← 8E6D JSR← 94D0 JSR← 9508 JSR← 9C28 JSR← A05E JSR← A17B JSR← A4FD JSR← A50A JSR← A59B JSR← A5A5 JSR← AC74 JSR← B0D8 JSR← B1BE JSR← B38C JSR← B71E JSR
LDA hazel_fs_lib_flags ; Read fs_lib_flags (&C271)
B307 AND #&1f ; Keep only the 5-bit owner access mask
B309 STA hazel_fs_lib_flags ; Store back, clearing FS-selection and other high bits
B30C RTS ; Return
B30D EQUS "Run"
B310 .ex_init_scan_x0
LDX #0 ; X=0: scan from start of TX entry
B312 .loop_scan_entries←1← B332 BNE
LDA hazel_txcb_data,x ; Read entry byte at hazel_txcb_data+X
B315 BMI rts_copy_arg ; Bit 7 set: end-of-entries -> return
B317 BNE print_col_cr ; Non-printable: take CR-newline path at col_sep_print_cr
fall through ↓

Print column separator or newline for *Ex/*Cat

In *Cat mode, increments a column counter modulo 4 and prints a two-space separator between entries, with a newline at the end of each row. In *Ex mode (fs_spool_handle negative), prints a newline after every entry. Scans the entry data and loops back to print the next entry's characters.

B319 .ex_print_col_sep←1← B245 JSR
LDY fs_spool_handle ; Read fs_spool_handle (also column counter in *Cat mode)
B31B BMI col_sep_eol_check ; Negative: *Ex mode (one-per-line) -- skip column logic, just print newline
B31D INY ; Bump column counter
B31E TYA ; Get the new value into A
B31F AND #3 ; Wrap to 0..3 (4 columns per row)
B321 STA fs_spool_handle ; Save the new column index
B323 BEQ col_sep_eol_check ; Wrapped to 0: end of row, print newline
B325 JSR print_inline_no_spool ; Mid-row: print 2-space column separator via inline
B328 EQUS " "
B32A BNE col_sep_print_char ; Non-zero: take col_sep_print_char tail
B32C .col_sep_eol_check←2← B31B BMI← B323 BEQ
LDA #&0d ; A=&0D: CR character
B32E .print_col_cr←1← B317 BNE
JSR print_char_no_spool ; Print CR (no spool)
B331 .col_sep_print_char←1← B32A BNE
INX ; Next entry
B332 BNE loop_scan_entries ; Loop until X wraps
B334 EOR zp_0078 ; XOR with zp_0078
B336 ADC zp_0063 ; Add zp_0063
fall through ↓

Print 3-digit decimal via *SPOOL-bypassing print

As print_decimal_3dig (&B32A) but each digit is emitted via print_char_no_spool, which closes the *SPOOL handle around OSASCI so the digit doesn't appear in any active capture. Always prints all three digits (no leading-zero suppression).

On EntryAvalue 0-255
B338 .print_dec_3dig_no_spool←1← B194 JSR
TAY ; Y = value to convert (digits read off via successive divisions)
B339 LDA #&64 ; Divisor for hundreds digit
B33B JSR print_dec_digit_no_spool ; Print hundreds digit
B33E LDA #&0a ; Divisor for tens digit
B340 JSR print_dec_digit_no_spool ; Print tens digit
B343 LDA #1 ; Divisor for units digit (always print at least the units to avoid the empty 0 case)
fall through ↓

Print one decimal digit, *SPOOL-bypassing

As print_decimal_digit (&B36B) but emits via print_char_no_spool. fs_error_ptr is used as scratch storage for the divisor and is preserved across the print.

On EntryAdivisor (100, 10, or 1)
Yvalue to divide
On ExitYremainder after division
B345 .print_dec_digit_no_spool←2← B33B JSR← B340 JSR
STA fs_error_ptr ; Stash divisor in fs_error_ptr (the SBC target below)
B347 TYA ; Convert remaining value to A
B348 LDX #&2f ; X = '0'-1: digit counter, INX in the loop steps to '0' first
B34A SEC ; Set carry
B34B .loop_divide_decimal_digit←1← B34E BCS
INX ; Step quotient digit
B34C SBC fs_error_ptr ; Subtract divisor
B34E BCS loop_divide_decimal_digit ; No underflow: keep dividing
B350 ADC fs_error_ptr ; Underflow: add divisor back to recover the remainder
B352 TAY ; Remainder -> Y, ready for the next digit
B353 TXA ; Move digit ('0'-'9') from X into A for printing
B354 LDX fs_error_ptr ; Save divisor in X across the print (print_char_no_spool preserves X is not guaranteed)
B356 JSR print_char_no_spool ; Print the digit, bypassing *SPOOL
B359 STX fs_error_ptr ; Restore divisor from X
B35B RTS ; Return

Print decimal number with leading zero suppression

Sets V=1 via BIT always_set_v_byte (the &FF constant at &9769, whose bit 6 sets V) to enable leading-zero suppression in print_decimal_3dig, then falls through to that routine. Used by print_station_id for compact station number display.

On EntryAnumber to print (0-255)
B35C .print_num_no_leading←4← 8AB3 JSR← 90E6 JSR← 966F JSR← 967C JSR
BIT always_set_v_byte ; Set V (suppress leading zeros)
fall through ↓

Print byte as 3-digit decimal via OSASCI

Extracts hundreds, tens and units digits by successive calls to print_decimal_digit. The V flag controls leading zero suppression: if set, zero digits are skipped until a non-zero digit appears. V is always cleared before the units digit to ensure at least one digit is printed.

On EntryAnumber to print (0-255)
Vset to suppress leading zeros
B35F .print_decimal_3dig←2← B598 JSR← B5AF JMP
TAY ; Transfer value to Y (remainder)
B360 LDA #&64 ; A=100: hundreds divisor
B362 JSR print_decimal_digit ; Print hundreds digit
B365 LDA #&0a ; A=10: tens divisor
B367 JSR print_decimal_digit ; Print tens digit
B36A CLV ; Clear V (always print units)
B36B LDA #1 ; A=1: units divisor
fall through ↓

Print one decimal digit by repeated subtraction

Initialises X to '0'-1 and loops, incrementing X while subtracting the divisor from Y. On underflow, adds back the divisor to get the remainder in Y. If V is set, suppresses leading zeros by skipping the OSASCI call when the digit is '0'.

On EntryAdivisor
Yvalue to divide
On ExitYremainder after division
B36D .print_decimal_digit←2← B362 JSR← B367 JSR
STA fs_error_ptr ; Store divisor
B36F TYA ; Get remaining value
B370 LDX #&2f ; X='0'-1: digit counter
B372 SEC ; Set carry for subtraction
B373 PHP ; Save V flag for leading zero check
B374 .loop_divide_digit←1← B377 BCS
INX ; Count quotient digit
B375 SBC fs_error_ptr ; Subtract divisor
B377 BCS loop_divide_digit ; No underflow: continue dividing
B379 ADC fs_error_ptr ; Add back divisor (get remainder)
B37B TAY ; Remainder to Y for next digit
B37C TXA ; Digit character to A
B37D PLP ; Restore V flag
B37E BVC print_nonzero_digit ; V clear: always print digit
B380 CMP #'0' ; V set: is digit '0'?
B382 BEQ rts_print_digit ; Yes: suppress leading zero
B384 .print_nonzero_digit←1← B37E BVC
LDX fs_error_ptr ; Save divisor across OSASCI call
B386 JSR osasci ; Print the digit
B389 STX fs_error_ptr ; Restore divisor
B38B .rts_print_digit←1← B382 BEQ
RTS ; Return

*Info command handler

Dispatched from the star-command table at index &28. Clears the owner-only access bits via mask_owner_access, then writes the two-byte FS command prefix 'i' '.' into hazel_txcb_data/hazel_txcb_flag, saves the command-line pointer with save_ptr_to_os_text, parses the *Info argument via parse_cmd_arg_y0, copies it into the TX buffer at offset 2 with copy_arg_to_buf, and JMPs to send_cmd_and_dispatch to send the request to the file server.

On EntryYcommand-line offset in text pointer
B38C .cmd_info_dispatch
JSR mask_owner_access ; Clear owner-only access bits before checking the URD
B38F LDA #'i' ; A=&69: 'i' character (info prefix)
B391 STA hazel_txcb_data ; Store 'i' as start of FS command name in the TX buffer
B394 LDA #'.' ; A='.': abbreviation terminator
B396 STA hazel_txcb_flag ; Store '.' as command-name terminator
B399 JSR save_ptr_to_os_text ; Save the command-line pointer for the dispatcher
B39C JSR parse_cmd_arg_y0 ; Parse the *Info argument from the command line
B39F LDX #2 ; X=2: TX-buffer offset to copy the arg into (after 'i.')
B3A1 JSR copy_arg_to_buf ; Append parsed argument to the TX command buffer
B3A4 TAY ; A = next index
B3A5 JMP send_cmd_and_dispatch ; Send the FS command and dispatch the reply

Copy text pointer to OS text pointer workspace

Saves fs_crc_lo/hi into the MOS text pointer locations at &00F2/&00F3. Preserves A on the stack. Called before GSINIT/GSREAD sequences that need to parse from the current command line position.

On ExitApreserved (PHA/PLA)
B3A8 .save_ptr_to_os_text←9← A064 JSR← A4FA JSR← A507 JSR← B0FA JSR← B163 JSR← B399 JSR← B431 JSR← B5E0 JSR← B725 JSR
PHA ; Save A
B3A9 LDA fs_crc_lo ; Copy text pointer low byte
B3AB STA os_text_ptr ; To OS text pointer low
B3AD LDA fs_crc_hi ; Copy text pointer high byte
B3AF STA os_text_ptr_hi ; To OS text pointer high
B3B1 PLA ; Restore A
B3B2 RTS ; Return
B3B3 .loop_advance_char←1← B3BE BNE
INY ; Advance past current character
fall through ↓

Advance past spaces to the next command argument

Scans (fs_crc_lo)+Y for space characters, advancing Y past each one. Returns with A holding the first non-space character, or CR if the end of line is reached. Used by *CDir and *Remove to detect extra arguments.

On EntryYstarting offset (where to begin scanning)
On ExitAfirst non-space character or CR
Yoffset of that character
B3B4 .skip_to_next_arg←1← B0DB JSR
LDA (fs_crc_lo),y ; Load char from command line
B3B6 CMP #' ' ; Space?
B3B8 BEQ loop_skip_space_chars ; Yes: skip trailing spaces
B3BA CMP #&0d ; CR (end of line)?
B3BC BEQ rts_skip_arg ; Yes: return (at end)
B3BE BNE loop_advance_char ; Loop to the next character
B3C0 .loop_skip_space_chars←2← B3B8 BEQ← B3C5 BEQ
INY ; Advance past space
B3C1 LDA (fs_crc_lo),y ; Load next character
B3C3 CMP #' ' ; Still a space?
B3C5 BEQ loop_skip_space_chars ; Yes: skip multiple spaces
B3C7 .rts_skip_arg←1← B3BC BEQ
RTS ; Return (at next argument)

Copy text pointer to spool buffer pointer

Saves fs_crc_lo/hi into fs_options/fs_block_offset for use as the spool buffer pointer. Preserves A on the stack. Called by *PS and *PollPS before parsing their arguments.

On ExitApreserved (PHA/PLA)
B3C8 .save_ptr_to_spool_buf←2← B3EE JSR← B5CB JSR
PHA ; Save A
B3C9 LDA fs_crc_lo ; Copy text pointer low byte
B3CB STA fs_options ; To spool buffer pointer low
B3CD LDA fs_crc_hi ; Copy text pointer high byte
B3CF STA fs_block_offset ; To spool buffer pointer high
B3D1 PLA ; Restore A
B3D2 RTS ; Return

Initialise spool drive page pointers

Calls get_ws_page to read the workspace page number for the current ROM slot, stores it as the spool drive page high byte (addr_work), and clears the low byte (work_ae) to zero. Preserves Y on the stack.

On ExitA0
Ypreserved (PHY/PLY)
B3D3 .init_spool_drive←2← B3EB JSR← B5C0 JSR
TYA ; Save Y
B3D4 PHA ; Push it
B3D5 JSR get_ws_page ; Get workspace page number
B3D8 STA addr_work ; Store as spool drive page high
B3DA PLA ; Restore Y
B3DB TAY ; Transfer to Y
B3DC LDA #0 ; A=0
B3DE STA work_ae ; Clear spool drive page low
B3E0 RTS ; Return

*PS command handler

Checks the printer server availability flag; raises 'Printer busy' if unavailable. Initialises the spool drive and buffer pointer, then dispatches on argument type: no argument branches to no_ps_name_given, a leading digit branches to save_ps_cmd_ptr as a station number, otherwise parses a named PS address via load_ps_server_addr and parse_fs_ps_args.

On EntryYcommand line offset in text pointer
B3E1 .cmd_ps
LDA #1 ; A=1: check printer ready
B3E3 BIT ws_0d6a ; Test printer server workspace flag
B3E6 BNE done_ps_available ; Non-zero: printer available
B3E8 JMP err_printer_busy ; Printer not available: error
B3EB .done_ps_available←1← B3E6 BNE
JSR init_spool_drive ; Initialise spool drive
B3EE JSR save_ptr_to_spool_buf ; Save pointer to spool buffer
B3F1 PHY ; Save Y
B3F2 LDY #&21 ; Y=&21: workspace flag offset
B3F4 LDA #0 ; A=0
B3F6 STA (nfs_workspace),y ; Clear the workspace flag at &21
B3F8 PLY ; Restore Y
B3F9 LDA (fs_options),y ; Read fs_options[Y]
B3FB CMP #&0d ; End of command line?
B3FD BEQ no_ps_name_given ; Yes: no argument given
B3FF CLV ; Clear V (= explicit PS name given)
B400 JSR is_decimal_digit ; Is first char a decimal digit?
B403 BCC save_ps_cmd_ptr ; C clear: save ptr and continue
B405 PHY ; Save Y
B406 JSR load_ps_server_addr ; Load PS server address
B409 PLY ; Restore Y
B40A JSR parse_fs_ps_args ; Parse FS/PS arguments
B40D JMP store_ps_station ; Jump to store station address

Copy printer server template at offset &18

Sets Y=&18 and falls through to copy_ps_data. Called during workspace initialisation (svc_2_private_workspace) to set up the printer server template at the standard offset.

On ExitY&20 (advanced past the copied 8 bytes)
B410 .copy_ps_data_y1c←1← 8F79 JSR
LDY #&18 ; Y=&18: standard offset for the PS template; fall into copy_ps_data
fall through ↓

Copy 8-byte printer server template to RX buffer

Copies 8 bytes of default printer server data into the RX buffer at the current Y offset. Uses indexed addressing: LDA ps_template_base,X with X starting at &F8, so the effective read address is ps_template_base+&F8 = ps_template_data (&8EB9). The 6502 trick reaches data 248 bytes past the base label in a single instruction; the base address (ps_template_base) deliberately falls inside the operand byte of a JSR instruction at &8DA6 -- see docs/analysis/authors-easter-egg.md.

On EntryYdestination offset within the RX buffer
On ExitYadvanced by 8
X0 (loop terminator)
Alast template byte
B412 .copy_ps_data
LDX #&f8 ; X=&F8: walks 0..7 via wraparound (loads from ps_template_base+&F8 = ps_template_data &8EB9)
B414 .loop_copy_ps_tmpl←1← B41B BNE
LDA ps_template_base,x ; Read template byte from ps_template_data + (X-&F8)
B417 STA (net_rx_ptr),y ; Store into RX buffer at offset Y
B419 INY ; Step destination
B41A INX ; Step source -- wraps from &FF to &00 to terminate
B41B BNE loop_copy_ps_tmpl ; Loop while X != 0 (8 iterations: &F8..&FF)
B41D RTS ; Return
B41E .no_ps_name_given←1← B3FD BEQ
BIT always_set_v_byte ; Set V (= no explicit PS name)
B421 .save_ps_cmd_ptr←1← B403 BCC
STY ws_ptr_hi ; Save Y at ws_ptr_hi
B423 BVS done_ps_name_parse ; V set: skip PS name parsing
B425 LDX #6 ; Max 6 characters for PS name
B427 LDY #&18 ; Buffer offset &1C for PS name
B429 LDA #' ' ; Space character
B42B .loop_pad_ps_name←1← B42F BNE
STA (net_rx_ptr),y ; Fill buffer with space
B42D INY ; Advance Y past padding
B42E DEX ; Count down
B42F BNE loop_pad_ps_name ; Loop while Y wraps
B431 JSR save_ptr_to_os_text ; Save text pointer
B434 LDY ws_ptr_hi ; Restore Y from ws_ptr_hi
B436 JSR gsinit ; Initialise string reading
B439 BEQ done_ps_name_parse ; Empty string: skip to send
B43B LDX #6 ; X=6: scan up to 6 PS slots
B43D STY ws_ptr_hi ; Save updated string pointer
B43F LDY #&18 ; Buffer offset for PS name
B441 STY table_idx ; Save buffer position
B443 .loop_read_ps_char←1← B44F BNE
LDY ws_ptr_hi ; Restore string pointer
B445 JSR gsread ; Read next character
B448 STY ws_ptr_hi ; Save updated pointer
B44A BCS done_ps_name_parse ; C set: end of slots
B44C JSR store_char_uppercase ; Store char uppercased in buffer
B44F BNE loop_read_ps_char ; Loop for more characters
B451 .done_ps_name_parse←3← B423 BVS← B439 BEQ← B44A BCS
JSR reverse_ps_name_to_tx ; Copy reversed PS name to TX
B454 JSR send_net_packet ; Send PS status request
B457 JSR pop_requeue_ps_scan ; Pop and requeue PS scan
B45A JSR load_ps_server_addr ; Load PS server address
B45D LDA #0 ; A=0
B45F TAX ; Copy A to X
B460 LDY #&20 ; Offset &24 in buffer
B462 STA (net_rx_ptr),y ; Clear PS status byte
B464 .loop_pop_ps_slot←1← B48E BNE
PLA ; Pop saved slot index
B465 BEQ done_ps_scan ; Zero: all slots done
B467 PHA ; Push it back (for retry)
B468 TAY ; Transfer to Y
B469 LDA (nfs_workspace),y ; Read slot status
B46B BPL done_ps_slot_mark ; Bit 7 clear: slot inactive
B46D JSR advance_y_by_4 ; Advance Y by 4 (next slot)
B470 LDA (nfs_workspace),y ; Read ws byte at (nfs_workspace)+Y
B472 STA work_ae ; Save as work_ae lo
B474 LDA (work_ae,x) ; Read indirect via (work_ae,X)
B476 AND #7 ; Mask the low 3 bits
B478 BNE done_ps_slot_mark ; Other than 3: skip slot mark
B47A .read_ps_station_addr
DEY ; Back up to network byte
B47B LDA (nfs_workspace),y ; Read network byte
B47D STA fs_work_6 ; Save as fs_work_6
B47F DEY ; Back up to station byte
B480 LDA (nfs_workspace),y ; Read station byte
B482 STA fs_work_5 ; Save as fs_work_5
B484 LDY #&20 ; Y=&20: PS marker offset
B486 STA (net_rx_ptr),y ; Store station to (net_rx_ptr)+&20
B488 .done_ps_slot_mark←2← B46B BPL← B478 BNE
PLA ; Pop saved slot index
B489 TAY ; Transfer to Y
B48A LDA #'?' ; A=&3F: 'processed' marker
B48C STA (nfs_workspace),y ; Mark slot as processed
B48E BNE loop_pop_ps_slot ; Loop to pop the next PS slot
B490 .done_ps_scan←1← B465 BEQ
JSR print_printer_server_is ; Print 'Printer server is ' fragment
B493 LDY #&20 ; Y=&20: marker offset
B495 LDA (net_rx_ptr),y ; Read marker byte
B497 BNE print_ps_now ; Non-zero: print 'now <stn>'
B499 JSR print_inline ; Print 'still ' fragment
B49C EQUS "still "
B4A2 CLV ; Bit-7 terminator (next opcode)
B4A3 BVC ps_print_info_newline ; V clear: branch onward
B4A5 .print_ps_now←1← B497 BNE
JSR print_inline ; Print 'now ' fragment
B4A8 EQUS "now "
B4AC LDY #&21 ; Y=&21: workspace flag offset
B4AE TYA ; Copy Y to A
B4AF STA (nfs_workspace),y ; Store the workspace flag at &21
B4B1 .ps_print_info_newline←1← B4A3 BVC
JSR print_fs_info_newline ; Print station number and newline
fall through ↓

Write printer-server station number into NFS workspace

Stores fs_work_5/fs_work_6 (the parsed station/network bytes) into nfs_workspace offsets 2 and 3 (the printer-server slot's station/ net pair). Single caller (cmd_ps's parse-success path at &B3D2).

B4B4 .store_ps_station←1← B40D JMP
LDY #2 ; Y=2: workspace offset for stored station
B4B6 LDA fs_work_5 ; Load station number
B4B8 STA (nfs_workspace),y ; Store at (nfs_workspace)+2
B4BA INY ; Advance index
B4BB LDA fs_work_6 ; Load network number
B4BD STA (nfs_workspace),y ; Store at (nfs_workspace)+3
B4BF RTS ; Return

Print 'File server ' prefix

Uses print_inline to output 'File' then falls through to the shared ' server is ' suffix at print_printer_server_is.

On ExitA, X, Yclobbered (OSASCI via print_inline)
B4C0 .print_file_server_is←1← A3CE JSR
JSR print_inline ; Print 'File' via inline string
B4C3 EQUS "File"
B4C7 CLV ; Clear V so the BVC below is taken
B4C8 BVC print_server_is_suffix ; Always taken (V was just cleared); skip the 'Printer' prologue and reach the shared ' server is ' suffix
fall through ↓

Print 'Printer server is ' prefix

Uses print_inline to output the full label 'Printer server is ' with trailing space.

On ExitA, X, Yclobbered (OSASCI via print_inline)
B4CA .print_printer_server_is←2← B490 JSR← B60A JSR
JSR print_inline ; Print 'Printer' via inline string
B4CD EQUS "Printer"
B4D4 NOP ; NOP -- bit-7 terminator + harmless resume opcode
B4D5 .print_server_is_suffix←1← B4C8 BVC
JSR print_inline ; Print ' server is ' via inline string
B4D8 EQUS " server is " ; fragment for 'File/Printer server is ...' messages
B4E3 NOP ; NOP -- bit-7 terminator + harmless resume opcode
B4E4 RTS ; Return; caller now prints the actual server (file or printer) address

Load printer server address from workspace

Reads the station and network bytes from workspace offsets 2 and 3 into the station/network variables.

On ExitA, Yclobbered
B4E5 .load_ps_server_addr←3← B406 JSR← B45A JSR← B62D JSR
LDY #2 ; Y=2: workspace offset of PS station byte
B4E7 LDA (nfs_workspace),y ; Read station byte
B4E9 STA fs_work_5 ; Stash in fs_work_5 (PS station)
B4EB INY ; Y=3: workspace offset of PS network byte
B4EC LDA (nfs_workspace),y ; Read network byte
B4EE STA fs_work_6 ; Stash in fs_work_6 (PS network)
B4F0 RTS ; Return

Pop return address and requeue PS slot scan

Converts the PS slot flags to a workspace index, writes slot data, and jumps back into the PS scan loop to continue processing.

On EntryAPS slot flags byte to convert into a workspace index
B4F1 .pop_requeue_ps_scan←2← B457 JSR← B607 JSR
PLA ; Pull saved upper byte of ws_ptr_lo+osword_flag pair
B4F2 STA osword_flag ; Save into osword_flag
B4F4 PLA ; Pull lower byte
B4F5 STA ws_ptr_lo ; Save into ws_ptr_lo
B4F7 LDA #0 ; Push 0 -- placeholder, will be the stacked return marker
B4F9 PHA ; Push it
B4FA LDA #&84 ; ws_ptr_hi base = &84 (start of PS slot table area)
B4FC STA ws_ptr_hi ; Save base
B4FE LSR econet_flags ; Shift bit 0 of econet_flags into C (saved scan state)
B501 LDA #3 ; A=3: PS slot index counter
B503 .loop_scan_ps_slots←1← B515 BNE
JSR byte_to_2bit_index ; Convert slot index to 12-byte-aligned table offset
B506 BCS done_ps_slot_scan ; Out of range (clamped to 0): all slots scanned
B508 LSR ; A /= 2 (shift down)
B509 LSR ; A /= 2 again (now slot index * 4 / 4 = slot index)
B50A TAX ; X = slot index
B50B LDA (nfs_workspace),y ; Read slot's status byte at workspace[Y]
B50D BEQ done_ps_slot_scan ; Slot empty (0): scan done
B50F CMP #'?' ; Slot is '?' (uninitialised marker)?
B511 BEQ reinit_ps_slot ; Yes: re-init this slot's data
fall through ↓

Advance to next PS slot, wrap if all 256 done

INX / TXA / BNE loop_scan_ps_slots. Slot index in X advances; the BNE re-enters the scan unless X has wrapped to zero (all 256 slots scanned). Single caller (the no-match path at &B501 in the PS slot scanner).

On EntryXcurrent slot index
B513 .skip_next_ps_slot←1← B53C JMP
INX ; Step slot index
B514 TXA ; Move to A for next iteration
B515 BNE loop_scan_ps_slots ; Loop while X != 0 (wraps when all slots done)
B517 .reinit_ps_slot←1← B511 BEQ
TYA ; Save Y (slot table offset)
B518 PHA ; Push it
B519 LDA #&7f ; A=&7F: slot status 'busy/active'
B51B STA (nfs_workspace),y ; Mark slot active
B51D INY ; Step Y to control byte
B51E LDA #&9e ; A=&9E: control byte (PS-init pattern)
B520 STA (nfs_workspace),y ; Store control byte
B522 LDA #0 ; A=0: zero-fill the next two bytes
B524 JSR write_two_bytes_inc_y ; Write two zeros, advance Y
B527 LDA ws_ptr_hi ; Read current ws_ptr_hi
B529 STA (nfs_workspace),y ; Store as buffer-link low byte
B52B CLC ; Clear C ready for the +3
B52C PHP ; Save flags so the ADC's C doesn't leak
B52D ADC #3 ; Bump ws_ptr_hi by 3 (next slot's base)
B52F PLP ; Restore flags
B530 STA ws_ptr_hi ; Save updated ws_ptr_hi
B532 JSR write_ps_slot_byte_ff ; Write buffer page + two &FF sentinels
B535 LDA ws_ptr_hi ; Read ws_ptr_hi (now updated)
B537 STA (nfs_workspace),y ; Store as second-link byte
B539 .write_ps_slot_hi_link
JSR write_ps_slot_byte_ff ; Write another buffer page + two &FF sentinels
B53C JMP skip_next_ps_slot ; Continue scanning slots
B53F .done_ps_slot_scan←2← B506 BCS← B50D BEQ
ASL econet_flags ; Restore bit 0 of econet_flags via ASL (recovers from the LSR at &B4C3)
B542 LDA ws_ptr_lo ; Pull saved ws_ptr_lo
B544 PHA ; Push it back (the caller's return-resume sequence)
B545 LDA osword_flag ; Pull saved osword_flag
B547 PHA ; Push it back
B548 LDA #&0a ; A=&0A: outer counter
B54A TAY ; Y=&0A: inner counter
B54B TAX ; X=&0A: middle counter
B54C STA fs_work_4 ; Save outer in fs_work_4
B54E .loop_ps_delay←3← B54F BNE← B552 BNE← B556 BNE
DEY ; Decrement inner counter
B54F BNE loop_ps_delay ; Inner not zero: keep delaying
B551 DEX ; Decrement middle
B552 BNE loop_ps_delay ; Middle not zero: refresh inner and continue
B554 DEC fs_work_4 ; Decrement outer in fs_work_4
B556 BNE loop_ps_delay ; Outer not zero: another full sweep (~1000 cycles)
B558 RTS ; Return

Write buffer page byte and two &FF markers

Stores the buffer page byte at the current Y offset in workspace, followed by two &FF sentinel bytes. Advances Y after each write.

On EntryAbuffer page byte to store at workspace+Y
Ystarting workspace offset
On ExitA&FF (the sentinel value left in A)
Yworkspace offset advanced by 3 (one byte + two markers)
B559 .write_ps_slot_byte_ff←2← B532 JSR← B539 JSR
INY ; Step Y to next workspace slot byte
B55A LDA addr_work ; Load buffer page byte from addr_work
B55C STA (nfs_workspace),y ; Write at offset Y
B55E LDA #&ff ; A=&FF: sentinel; fall into write_two_bytes_inc_y to store two of them
fall through ↓

Write A to two consecutive workspace bytes

Stores A at the current Y offset via (nfs_workspace),Y then again at Y+1, advancing Y after each write.

On EntryAbyte to store
Yworkspace offset
B560 .write_two_bytes_inc_y←1← B524 JSR
INY ; Step Y to next destination
B561 STA (nfs_workspace),y ; Write A at workspace offset Y
B563 INY ; Step Y again
B564 STA (nfs_workspace),y ; Write A at the next offset (two consecutive copies)
B566 INY ; Final INY leaves Y pointing past the second write
B567 RTS ; Return

Reverse-copy printer server name to TX buffer

Copies 8 bytes from the RX buffer at offsets &18..&1F ((net_rx_ptr)+&18..+&1F) to the TX buffer at offsets &10..&17 ((net_rx_ptr)+&10..+&17) in reversed byte order. Implementation: pushes the 8 RX bytes onto the stack, then pops them back to the TX area; the LIFO order achieves the reversal.

On ExitA, X, Yclobbered
B568 .reverse_ps_name_to_tx←2← B451 JSR← B5C3 JSR
LDY #&18 ; Y=&18: source offset (start of PS name in RX buffer)
B56A .loop_push_ps_name←1← B570 BNE
LDA (net_rx_ptr),y ; Read RX byte at offset Y
B56C PHA ; Push it (the stack reverses the order)
B56D INY ; Step source
B56E CPY #&20 ; Reached &20 (one past the 8-byte name)?
B570 BNE loop_push_ps_name ; No: continue pushing
B572 LDY #&17 ; Y=&17: destination offset for the reversed name
B574 .loop_pop_ps_name←1← B57A BNE
PLA ; Pull next pushed byte (LIFO -> reversed order)
B575 STA (net_rx_ptr),y ; Store at destination offset Y
B577 DEY ; Step destination back
B578 CPY #&0f ; Reached &0F (one before the destination range)?
B57A BNE loop_pop_ps_name ; No: continue popping
B57C LDA net_rx_ptr_hi ; Copy net_rx_ptr_hi as the TX page (TX shares the same page as RX for this packet)
B57E STA net_tx_ptr_hi ; Set net_tx_ptr_hi
B580 LDA #&0c ; TX low byte = &0C: skip past the TX header to where the reversed name lives
B582 STA net_tx_ptr ; Set net_tx_ptr lo
B584 LDY #3 ; Y=3: copy 4-byte TX header (offsets 3..0)
B586 .loop_copy_tx_hdr←1← B58C BPL
LDA ps_tx_header_template,y ; Read template byte
B589 STA (net_tx_ptr),y ; Write to TX buffer at offset Y
B58B DEY ; Step backwards
B58C BPL loop_copy_tx_hdr ; Loop while Y >= 0
B58E RTS ; Return

Printer-server TX header template (4 bytes)

Four bytes copied to the head of the printer-server transmit buffer by reverse_ps_name_to_tx: control byte &80 (immediate-TX request), port &D1 (printer block port), function-code stub, and reply-port byte. Filled-in destination fields follow from the caller's PS slot.

B58F .ps_tx_header_template←1Used as index base by← B586 LDA
EQUB &80 ; Control byte &80 (immediate TX)
B590 EQUB &9F ; Port &9F (printer server)
B591 EQUB &FF ; Station &FF (any)
B592 EQUB &FF ; Network &FF (any)

Print station address as decimal net.station

If the network number is zero, prints only the station number. Otherwise prints network.station separated by a dot. V flag controls padding with leading spaces for column alignment.

On EntryV FLAGset = no leading-space padding; clear = pad to align in a column
On ExitA, X, Yclobbered (print_decimal_3dig and OSASCI)
B593 .print_station_addr←4← A3D4 JSR← B633 JSR← B653 JSR← B6CB JSR
PHP ; Save caller's V (controls leading-zero padding via the BVS at &B566)
B594 LDA fs_work_6 ; Read network number (fs_work_6)
B596 BEQ skip_if_local_net ; Network 0 means local: skip the 'NN.' prefix
B598 JSR print_decimal_3dig ; Network non-zero: print as 3-digit decimal
B59B LDA #'.' ; A='.': separator between network and station
B59D JSR osasci ; Print the dot
B5A0 BIT always_set_v_byte ; Set V so the next BVS branches over the padding (we just printed digits, no padding needed)
B5A3 .skip_if_local_net←1← B596 BEQ
BVS local_net_prefix ; V set: skip leading-space padding
B5A5 JSR print_inline ; V clear (caller wanted padding): print 4 leading spaces via inline string
B5A8 EQUS " "
B5AC .local_net_prefix←1← B5A3 BVS
LDA fs_work_5 ; Read station number (fs_work_5)
B5AE PLP ; Restore caller's V (so print_decimal_3dig honours its own leading-zero suppression)
B5AF JMP print_decimal_3dig ; Tail-call print_decimal_3dig for the station number

Printer-server slot TXCB template (12 bytes)

12-byte Econet TXCB template for printer-server slot buffers. Copied by init_ps_slot_from_rx into workspace offsets &78-&83 via indexed addressing from write_ps_slot_link_addr (write_ps_slot_hi_link+1). Substitutes net_rx_ptr_hi at offsets &7D and &81 (the hi bytes of the two buffer pointers) so they point into the current RX buffer page.

Structure: 4-byte header (control, port, station, network) followed by two 4-byte buffer descriptors (lo address, hi page, end lo, end hi). End bytes &FF are placeholders filled in later by the caller.

B5B2 .ps_slot_txcb_template
EQUB &80 ; Offset 0: txcb_ctrl = &80 (standard)
B5B3 EQUB &9F ; Offset 1: txcb_port = &9F (PS port)
B5B4 EQUB &00 ; Offset 2: dest station (placeholder, &00)
B5B5 EQUB &00 ; Offset 3: dest network (placeholder, &00)
B5B6 EQUB &10 ; Offset 4: buf1 start lo = &10
B5B7 EQUB &00 ; Offset 5: buf1 start hi (page from net_rx_ptr)
B5B8 EQUB &FF ; Offset 6: buf1 end lo placeholder = &FF
B5B9 EQUB &FF ; Offset 7: buf1 end hi placeholder = &FF
B5BA EQUB &18 ; Offset 8: buf2 start lo = &18
B5BB EQUB &00 ; Offset 9: buf2 start hi (page from net_rx_ptr)
B5BC EQUB &FF ; Offset 10: buf2 end lo placeholder = &FF
B5BD EQUB &FF ; Offset 11: buf2 end hi placeholder = &FF

*Pollps command handler

Initialises the spool drive, copies the PS name to the TX buffer, and parses an optional station number or PS name argument. Sends a poll request, then prints the server address and name. Iterates through PS slots, displaying each station's status as 'ready', 'busy' (with client station), or 'jammed'. Marks processed slots with &3F.

On EntryYcommand line offset in text pointer
B5BE .cmd_pollps
STY ws_ptr_hi ; Save command line pointer high
B5C0 JSR init_spool_drive ; Initialise spool/print drive
B5C3 JSR reverse_ps_name_to_tx ; Copy PS name to TX buffer
B5C6 JSR init_ps_slot_from_rx ; Init PS slot from RX data
B5C9 LDY ws_ptr_hi ; Restore command line pointer
B5CB JSR save_ptr_to_spool_buf ; Save pointer to spool buffer
B5CE LDA (fs_options),y ; Get first argument character
B5D0 CMP #&0d ; End of command line?
B5D2 BEQ done_poll_name_parse ; Yes: no argument given
B5D4 LDX #6 ; Max 6 characters for PS name
B5D6 LDY #&10 ; Buffer offset for PS name
B5D8 LDA #' ' ; Space character
B5DA .loop_pad_poll_name←1← B5DE BNE
STA (net_rx_ptr),y ; Fill buffer position with space
B5DC INY ; Next position
B5DD DEX ; Count down
B5DE BNE loop_pad_poll_name ; Loop until 6 spaces filled
B5E0 JSR save_ptr_to_os_text ; Save pointer to OS text
B5E3 LDY ws_ptr_hi ; Restore command line pointer
B5E5 JSR gsinit ; Initialise string reading
B5E8 BEQ done_poll_name_parse ; Empty string: skip to send
B5EA LDX #6 ; Max 6 characters
B5EC STY ws_ptr_hi ; Save updated string pointer
B5EE LDY #&10 ; Buffer offset for PS name
B5F0 STY table_idx ; Save buffer position
B5F2 .loop_read_poll_char←1← B5FE BNE
LDY ws_ptr_hi ; Restore string pointer
B5F4 JSR gsread ; Read next char from string
B5F7 STY ws_ptr_hi ; Save updated string pointer
B5F9 BCS done_poll_name_parse ; End of string: go to send
B5FB JSR store_char_uppercase ; Store char uppercased in buffer
B5FE BNE loop_read_poll_char ; Loop if more chars to copy
B600 .done_poll_name_parse←3← B5D2 BEQ← B5E8 BEQ← B5F9 BCS
LDA #&80 ; Enable escape checking
B602 STA need_release_tube ; Set escapable flag
B604 JSR send_net_packet ; Send the poll request packet
B607 JSR pop_requeue_ps_scan ; Pop and requeue PS scan
B60A JSR print_printer_server_is ; Print 'Printer server '
B60D LDY #&21 ; Y=&21: PS-entry flag offset in workspace
B60F LDA (nfs_workspace),y ; Load PS-entry flag
B611 BEQ poll_load_server ; Zero: slot empty, skip display
B613 JSR print_inline ; Print ' "'
B616 EQUS &22
B617 LDY #&18 ; Y=&18: name field offset in RX buffer
B619 .loop_print_poll_name←1← B625 BNE
LDA (net_rx_ptr),y ; Get character from name field
B61B CMP #' ' ; Is it a space?
B61D BEQ done_poll_name_print ; Yes: end of name
B61F JSR osasci ; Print character of PS name
B622 INY ; Next character
B623 CPY #&1e ; Past end of name field?
B625 BNE loop_print_poll_name ; No: continue printing name
B627 .done_poll_name_print←1← B61D BEQ
JSR print_inline ; Print '"' + CR
B62A EQUS &22, " "
B62C NOP ; Bit-7 terminator from preceding stringhi
B62D .poll_load_server←1← B611 BEQ
JSR load_ps_server_addr ; Load this PS server's address for display
B630 BIT always_set_v_byte ; Set V (always) via always_set_v_byte
B633 JSR print_station_addr ; Print the server station address
B636 JSR osnewl ; Print newline
B639 .loop_next_poll_slot←1← B69F BNE
PLA ; Pop saved slot index
B63A BEQ return_7 ; Zero: all slots done, return
B63C PHA ; Save slot offset
B63D TAY ; Transfer to Y
B63E LDA (nfs_workspace),y ; Read slot status byte
B640 BPL poll_mark_slot ; Bit 7 clear: slot inactive
B642 INY ; Advance to station number
B643 INY ; Offset+2 in slot
B644 LDA (nfs_workspace),y ; Read station number low
B646 STA fs_work_5 ; Store station low
B648 INY ; Next byte (offset+3)
B649 LDA (nfs_workspace),y ; Read network number
B64B STA fs_work_6 ; Store network number
B64D INY ; Next byte (offset+4)
B64E LDA (nfs_workspace),y ; Read status page pointer
B650 STA work_ae ; Store pointer low
B652 CLV ; Clear V flag
B653 JSR print_station_addr ; Print station address (V=0)
B656 JSR print_inline ; Print ' is '
B659 EQUS " is "
B65D LDX #0 ; X=0: indexed-indirect access mode
B65F LDA (work_ae,x) ; Read printer status byte
B661 BNE poll_test_status ; Non-zero: not ready
B663 JSR print_inline ; Print 'ready'
B666 EQUS "ready"
B66B CLV ; Ensure V clear so next BVC always taken
B66C BVC poll_entry_done ; Status ready printed: branch to end-of-entry (always)
B66E .poll_test_status←1← B661 BNE
ASL ; Shift status byte left to test its flag bits...
B66F ASL ; ...
B670 ASL ; ...
B671 ASL ; ...(4 shifts move bits 4-7 into C/N)
B672 BCS poll_print_jammed ; C set: status jammed
B674 BMI poll_print_offline ; N set: status off line
B676 CMP #&60 ; Status = 2?
B678 BNE poll_check_busy ; No: check for busy
B67A .poll_print_offline←1← B674 BMI
JSR print_inline ; Print 'jammed'
B67D EQUS "off line"
B685 CLV ; Clear V
B686 BVC poll_entry_done ; Off-line printed: branch to end-of-entry (always)
B688 .poll_check_busy←1← B678 BNE
CMP #&10 ; Status = 1?
B68A BEQ poll_print_busy ; Status = 1: print at cb6a0
B68C .poll_print_jammed←1← B672 BCS
JSR print_inline ; Print 'jammed'
B68F EQUS "jammed"
B695 NOP ; bit-7 terminator + resume opcode
B696 .poll_entry_done←4← B66C BVC← B686 BVC← B6AF BEQ← B6CF BVC
JSR osnewl ; Print newline after the status line
B699 .poll_mark_slot←1← B640 BPL
PLA ; Pull saved slot index
B69A TAY ; Y = slot index
B69B LDA #'?' ; &3F: 'slot processed' marker
B69D STA (nfs_workspace),y ; Store the &3F marker in the workspace slot
B69F BNE loop_next_poll_slot ; Not 1 or 2: default to jammed
B6A1 .return_7←1← B63A BEQ
RTS ; Return
B6A2 .poll_print_busy←1← B68A BEQ
JSR print_inline ; Print 'busy'
B6A5 EQUS "busy"
B6A9 INC work_ae ; Advance work_ae to next status byte (lo)
B6AB LDA (work_ae,x) ; Read client station number
B6AD STA fs_work_5 ; Store station low
B6AF BEQ poll_entry_done ; Zero: no client info, skip
B6B1 JSR print_inline ; Print ' with station '
B6B4 EQUS " with station "
B6C2 INC work_ae ; Advance work_ae to next status byte (lo)
B6C4 LDA (work_ae,x) ; Read network number byte via (work_ae,X)
B6C6 STA fs_work_6 ; Store network number
B6C8 BIT always_set_v_byte ; Set V flag
B6CB JSR print_station_addr ; Print client station address
B6CE CLV ; Clear V for the unconditional branch
B6CF BVC poll_entry_done ; Branch to end-of-entry (always)
fall through ↓

Initialise PS slot buffer from template data

Copies the 12-byte ps_slot_txcb_template into workspace at offsets &78-&83 via indexed addressing from write_ps_slot_link_addr (write_ps_slot_hi_link+1). Substitutes net_rx_ptr_hi at offsets &7D and &81 (the hi bytes of the two buffer pointers) so they point into the current RX buffer page.

On ExitA, X, Yclobbered
B6D1 .init_ps_slot_from_rx←1← B5C6 JSR
LDY #&78 ; Start at offset &78
B6D3 .loop_copy_slot_tmpl←1← B6E5 BNE
LDA ps_print_template,y ; Load template byte
B6D6 CPY #&7d ; At offset &7D?
B6D8 BEQ subst_rx_page_byte ; Yes: substitute RX page
B6DA CPY #&81 ; At offset &81?
B6DC BNE store_slot_tmpl_byte ; No: use template byte
B6DE .subst_rx_page_byte←1← B6D8 BEQ
LDA net_rx_ptr_hi ; Use RX buffer page instead
B6E0 .store_slot_tmpl_byte←1← B6DC BNE
STA (work_ae),y ; Store byte in slot buffer
B6E2 INY ; Next offset
B6E3 CPY #&84 ; Past end of slot (&84)?
B6E5 BNE loop_copy_slot_tmpl ; No: continue copying
B6E7 RTS ; Return

Convert to uppercase and store in RX buffer

If the character in A is lowercase (&61-&7A), converts to uppercase by clearing bit 5. Stores the result in the RX buffer at the current position, advances the buffer pointer, and decrements the character count.

On EntryAcharacter to store
B6E8 .store_char_uppercase←2← B44C JSR← B5FB JSR
LDY table_idx ; Y = current buffer position
B6EA AND #&7f ; Strip high bit
B6EC CMP #'a' ; Is it lowercase 'a' or above?
B6EE BCC done_uppercase_store ; Below 'a': not lowercase
B6F0 CMP #'{' ; Above 'z'?
B6F2 BCS done_uppercase_store ; Yes: not lowercase
B6F4 AND #&5f ; Convert to uppercase
B6F6 .done_uppercase_store←2← B6EE BCC← B6F2 BCS
STA (net_rx_ptr),y ; Store in RX buffer
B6F8 INY ; Next buffer position
B6F9 STY table_idx ; Update buffer position
B6FB DEX ; Decrement character count
B6FC RTS ; Return (Z set if count=0)

*Prot command handler

Loads A=&FF (full protection mask) and falls through (via an always-taken BNE) to the shared protection-update body at &B6D8, which:

  1. Saves the new flag (Z=0 for *Prot, Z=1 for *Unprot) on the stack via PHP.
  2. Calls set_via_shadow_pair to mirror A into the workspace shadow ACR (prot_status) and shadow IER (prot_status_save).
  3. Reads CMOS RAM byte &11 (Econet station/protection flags) via osbyte_a1 into Y, copies to A.
  4. Restores the saved flag and selects:
    • *Prot path: ORA #&40 (set bit 6 = protection on).
    • *Unprot path: AND #&BF (clear bit 6).
  5. Writes the updated byte back to CMOS via OSBYTE &A2 (write CMOS RAM).

The ANFS protection state lives in CMOS bit 6 of byte &11, so it survives BREAK and power-cycle until explicitly toggled.

On EntryYcommand line offset (unused; *Prot takes no args)
B6FD .cmd_prot
LDA #&ff ; Load &FF (protect)
B6FF BNE unprot_clear ; Branch to the unprotect-clear path
fall through ↓

*Unprot command handler

Loads A=&00 (no protection) and falls through to the shared protection-update body at &B6D8, which clears bit 6 of CMOS RAM byte &11 (the Econet protection flag). See cmd_prot for the full body description.

On EntryYcommand line offset (unused; *Unprot takes no args)
B701 .cmd_unprot
LDA #0 ; Load &00 (unprotect)
B703 .unprot_clear←1← B6FF BNE
PHP ; Save Z flag (1 = unprot, 0 = prot) for later
B704 JSR set_ws_pair_0d68_0d69 ; Mirror A into prot_status / prot_status_save pair
B707 LDX #&11 ; X=&11: CMOS offset for Econet flags
B709 JSR osbyte_a1 ; OSBYTE &A1 reads CMOS byte &11 -> Y
B70C TYA ; A = current CMOS byte
B70D PLP ; Restore the saved Z flag
B70E BEQ unprot_check ; Z=1: unprot path
B710 ORA #&40 ; Set bit 6 (protection on)
B712 BNE unprot_apply ; ALWAYS branch to write-back
B714 .unprot_check←1← B70E BEQ
AND #&bf ; Clear bit 6 (protection off)
B716 .unprot_apply←1← B712 BNE
TAY ; Y = new flag byte
B717 LDA #osbyte_write_cmos_ram ; OSBYTE &A2: write CMOS byte
B719 .loop_match_prot_attr
LDX #&11 ; X=&11: CMOS offset for Econet flags
B71B JMP osbyte ; Tail-call OSBYTE Write CMOS RAM byte

*Wipe command handler

Setup half of *Wipe. Masks owner access via mask_owner_access, zeroes the file-iteration counter fs_work_5, preserves the command-line pointer with save_ptr_to_os_text, parses the wildcard filename via parse_filename_arg, and records the end-of-argument offset (X+1) in fs_work_6. Falls through to request_next_wipe, which drives the per-file examine/prompt/delete loop until the wildcard is exhausted.

On EntryYcommand line offset in text pointer
B71E .cmd_wipe
JSR mask_owner_access ; Reset access flags before parsing the new argument
B721 LDA #0 ; A=0: clear the file-iteration counter
B723 STA fs_work_5 ; Store iteration counter (steps to next file each loop)
B725 JSR save_ptr_to_os_text ; Save text pointer for re-reading the wildcard each iteration
B728 JSR parse_filename_arg ; Parse the wildcard filename into the &C030 buffer
B72B INX ; Step X past the CR terminator (so X = filename length+1)
B72C STX fs_work_6 ; Save end-of-buffer offset
fall through ↓

Build 'examine directory' TXCB for next wipe iteration

Issues FS function-code 1 ('examine directory entry') for the current iteration in fs_work_5. Writes the function code into TXCB[5] and TXCB[7], copies the iteration index to TXCB[6], and falls through to the TXCB-build / send sequence. Single caller (the BNE retry at &B768 that loops cmd_wipe over each match).

B72E .request_next_wipe←1← B76A JMP
LDA #1 ; FS function code byte 0 = 1 (examine)
B730 STA hazel_txcb_data ; TXCB[5] = 1: 'examine directory entry'
B733 STA hazel_txcb_count ; TXCB[7] = 1: ditto for the second buffer slot
B736 LDX fs_work_5 ; Load current iteration index
B738 STX hazel_txcb_flag ; TXCB[6] = iteration index (which directory entry)
B73B LDX #3 ; X=3: copy starting at TX[3] (after the FS header bytes)
B73D JSR copy_arg_to_buf ; Copy the parsed filename into the TX buffer
B740 LDY #3 ; Y=3: FS function code 'Examine'
B742 LDA #&80 ; A=&80: set bit 7 of need_release_tube to flag long-lived TX
B744 STA need_release_tube ; Store flag
B746 JSR save_net_tx_cb ; Send the examine request and wait for reply
B749 LDA hazel_txcb_data ; Read FS reply byte 0 (status code)
B74C BNE check_wipe_attr ; Non-zero status: process the response
B74E LDA #osbyte_flush_buffer_class ; OSBYTE &0F: flush input buffer class
B750 LDX #1 ; X=1: flush keyboard buffer
B752 JSR osbyte ; Flush keyboard buffer (clear pending Y/N keypress) Flush selected class of buffer
B755 LDA #osbyte_scan_keyboard_from_16 ; OSBYTE &7A: scan keyboard from key 16 (clear keypress queue)
B757 JSR osbyte ; Run the scan Scan keyboard from key 16
B75A LDY #0 ; Y=0: no key
B75C LDA #osbyte_write_keys_pressed ; OSBYTE &78: write keys-pressed state
B75E JMP osbyte ; Tail-call OSBYTE: clean up and return Write all keys pressed information
B761 .check_wipe_attr←1← B74C BNE
LDA hazel_txcb_end ; Read attribute byte from FS reply (TXCB[&2F])
B764 .loop_check_if_locked←1← B774 BNE
CMP #'L' ; Is it 'L' (locked)?
B766 BNE check_wipe_dir ; Not locked: check for directory
B768 .skip_wipe_locked←1← B7E5 JMP
INC fs_work_5 ; Locked: skip this file, advance to next
B76A JMP request_next_wipe ; Loop back to request the next directory entry
B76D .check_wipe_dir←1← B766 BNE
CMP #'D' ; Is it 'D' (directory)?
B76F BNE show_wipe_prompt ; Not a directory: prompt the user
B771 LDA hazel_examine_attr ; Directory: check second attribute byte (size)
B774 BNE loop_check_if_locked ; Loop back to attribute test (re-checks if non-empty)
B776 .show_wipe_prompt←1← B76F BNE
LDX #1 ; X=1: scan name starting at TX[1]
B778 LDY fs_work_6 ; Y = end-of-buffer offset (saved earlier in fs_work_6)
B77A .loop_copy_wipe_name←1← B787 BNE
LDA hazel_txcb_flag,x ; Read filename byte from TX[6+X]
B77D JSR print_char_no_spool ; Print via *SPOOL-bypassing OSASCI
B780 STA hazel_parse_buf,y ; Also store into the parse buffer for later use
B783 INY ; Step parse-buffer offset
B784 INX ; Step TX-buffer offset
B785 CPX #&0c ; Reached &0C (12 chars)?
B787 BNE loop_copy_wipe_name ; No: continue copying
B789 JSR print_inline_no_spool ; Print '(?/' prompt prefix and read response
B78C EQUS "(?/" ; Inline string '(?/' is read by the hook above
B78F NOP ; NOP -- bit-7 terminator + resume opcode for the '(?/' stringhi
B790 JSR prompt_yn ; Print 'Y/N) ' via prompt_yn (reads keypress)
B793 CMP #'?' ; Was the keypress '?' (help)?
B795 BNE check_wipe_response ; Not '?': process Y/N response
B797 LDA #&0d ; '?': print CR before help text
B799 JSR print_byte_no_spool ; Print CR character
B79C LDX #2 ; X=2: start of name in TX[2]
B79E .loop_print_wipe_info←1← B7A7 BNE
LDA hazel_txcb_data,x ; Read name byte from TX[5+X] (FS reply)
B7A1 JSR print_char_no_spool ; Print name char (no spool)
B7A4 INX ; Advance index
B7A5 CPX #&3e ; End of TX[5+X] name field at offset &3E?
B7A7 BNE loop_print_wipe_info ; No: continue printing
B7A9 JSR print_inline_no_spool ; Print 'Wipe? ' help suffix via inline string
B7AC EQUS " ("
B7AE NOP ; Bit-7 terminator + resume
B7AF JSR prompt_yn ; Re-prompt user with prompt_yn
B7B2 .check_wipe_response←1← B795 BNE
AND #&df ; Mask to upper-case ('A'..'Z' map to themselves)
B7B4 CMP #'Y' ; Was the response 'Y'?
B7B6 BNE skip_wipe_to_next ; No: skip this entry, advance to next
B7B8 JSR print_char_no_spool ; Yes: echo the keypress
B7BB LDX #0 ; X=0: start scanning the parse-buffer name
B7BD LDA hazel_parse_buf,x ; Read first parse-buffer byte at hazel_parse_buf
B7C0 CMP #&0d ; Is it CR (no path component)?
B7C2 BEQ use_wipe_leaf_name ; Yes: use leaf-name only path at &B7E8
B7C4 .loop_build_wipe_cmd←1← B7D9 BNE
LDA hazel_parse_buf,x ; Read parse-buffer byte at hazel_parse_buf+X
B7C7 CMP #&0d ; Is it CR (end of name)?
B7C9 BNE skip_if_not_space ; No: check for space separator
B7CB LDA #'.' ; CR: substitute '.' so the dir prefix terminates with a separator
B7CD .skip_if_not_space←1← B7C9 BNE
CMP #' ' ; Is it space?
B7CF BNE store_wipe_tx_char ; No: store byte as-is
B7D1 .set_wipe_cr_end←1← B7F4 BEQ
LDA #&0d ; Yes: substitute CR (end-of-cmd)
B7D3 .store_wipe_tx_char←1← B7CF BNE
STA hazel_txcb_data,x ; Store byte into TX[5+X] (delete-command buffer)
B7D6 INX ; Advance index
B7D7 CMP #&0d ; Was that byte CR (just stored)?
B7D9 BNE loop_build_wipe_cmd ; No: continue copying
B7DB LDY #&14 ; Y=&14: FS function code &14 = delete
B7DD JSR save_net_tx_cb ; Send the delete request and wait for reply
B7E0 DEC fs_work_5 ; Decrement iteration counter so we re-examine the now-shifted-up slot
B7E2 .skip_wipe_to_next←1← B7B6 BNE
JSR print_newline_no_spool ; Print newline before next entry
B7E5 JMP skip_wipe_locked ; Loop back to skip_wipe_locked (= request next entry)
B7E8 .use_wipe_leaf_name←1← B7C2 BEQ
DEX ; DEX: pre-decrement before the INX in the loop
B7E9 .loop_copy_wipe_leaf←1← B7F2 BNE
INX ; Advance index
B7EA LDA hazel_parse_buf_1,x ; Read parse-buffer byte at hazel_parse_buf_1+X (skip CR at hazel_parse_buf)
B7ED STA hazel_txcb_data,x ; Store into TX[5+X] (delete-command buffer)
B7F0 CMP #' ' ; Reached space (end-of-leaf)?
B7F2 BNE loop_copy_wipe_leaf ; No: continue copying
B7F4 BEQ set_wipe_cr_end ; Branch to set the wipe CR-end
fall through ↓

Print Y/N prompt and read user response

Prints 'Y/N) ' via inline string, flushes the input buffer, and reads a single character from the keyboard.

On ExitAcharacter read from keyboard (after the 'Y/N) ' prompt)
B7F6 .prompt_yn←2← B790 JSR← B7AF JSR
JSR print_inline_no_spool ; Print 'Y/N) ' via the inline-string helper
B7F9 EQUS "Y/N) " ; Inline string body — bytes consumed by print_inline_no_spool (above)
fall through ↓

Flush keyboard buffer and read one character

Calls OSBYTE &0F to flush the input buffer, then OSRDCH to read a single character. Raises an escape error if escape was pressed (carry set on return).

On ExitAcharacter read from keyboard
X, Yclobbered (OSBYTE/OSRDCH)
B7FE .flush_and_read_char
LDA #osbyte_flush_buffer_class ; OSBYTE &0F: flush buffer class
B800 LDX #1 ; X=1: flush input buffers
B802 JSR osbyte ; Flush keyboard buffer before read Flush selected class of buffer
B805 JSR osrdch ; Read character from input stream
B808 BCC return_8 ; C clear: character read OK
B80A JMP raise_escape_error ; Escape pressed: raise error
B80D .return_8←1← B808 BCC
RTS ; Return with character in A

Initialise channel allocation table

Clears all 256 bytes of the table, then marks available channel slots based on the count from the receive buffer. Sets the first slot to &C0 (active channel marker).

On ExitA, X, Yclobbered
B80E .init_channel_table←1← 8BC7 JSR
LDA #0 ; A=0: clear value
B810 TAY ; Y=0: start index
B811 .loop_clear_chan_table←1← B815 BNE
STA hazel_fcb_addr_lo,y ; Clear channel table entry
B814 INY ; Next entry
B815 BNE loop_clear_chan_table ; Loop until all 256 bytes cleared
B817 LDY #&0b ; Offset &0F in receive buffer
B819 LDA (net_rx_ptr),y ; Get number of available channels
B81B SEC ; Prepare subtraction
B81C SBC #&0c ; Subtract 'Z' to get negative count
B81E TAY ; Y = negative channel count (index)
B81F LDA #&40 ; Channel marker &40 (available)
B821 .loop_mark_chan_avail←1← B827 BPL
STA hazel_fcb_addr_lo,y ; Mark channel slot as available
B824 DEY ; Previous channel slot
B825 CPY #&b8 ; Reached start of channel range?
B827 BPL loop_mark_chan_avail ; No: continue marking channels
B829 INY ; Point to first channel slot
B82A LDA #&c0 ; Active channel marker &C0
B82C STA hazel_fcb_addr_lo,y ; Mark first channel as active
B82F RTS ; Return

Convert channel attribute to table index

Subtracts &20 from the attribute byte and clamps to the range 0-&0F. Returns &FF if out of range. Preserves processor flags via PHP/PLP.

On EntryAchannel attribute byte
On ExitAtable index (0-&0F) or &FF if invalid
B830 .attr_to_chan_index←6← 9405 JSR← 941B JSR← 9F59 JSR← 9F89 JSR← 9FC1 JSR← BB0E JSR
PHP ; Save flags
B831 SEC ; Prepare subtraction
B832 SBC #&20 ; Subtract &20 to get table index
B834 BMI error_chan_out_of_range ; Negative: out of valid range
B836 CMP #&10 ; Above maximum channel index &0F?
B838 BCC return_chan_index ; In range: valid index
B83A .error_chan_out_of_range←1← B834 BMI
LDA #&ff ; Out of range: return &FF (invalid)
B83C .return_chan_index←1← B838 BCC
PLP ; Restore flags
B83D TAX ; X = channel index (or &FF)
B83E RTS ; Return

Validate channel character and look up entry

Characters below '0' are looked up directly in the channel table. Characters '0' and above are converted to a table index via attr_to_chan_index. Raises 'Net channel' error if invalid.

On EntryAchannel character
B83F .check_chan_char←2← A0AC JSR← B8B7 JSR
CMP #' ' ; Below space?
B841 BCC err_net_chan_invalid ; Yes: invalid channel character
B843 CMP #'0' ; Below '0'?
B845 BCC lookup_chan_by_char ; In range &20-&2F: look up channel
fall through ↓

Raise 'Net channel' error (saving channel char on stack)

Pushes the bad channel character on the stack, then falls through to error_chan_not_found which loads error code &DE and tail-calls error_inline_log with the inline string 'Net channel'. The PHA at entry differs from the &B81F error_chan_not_found alt-entry: this form is reached when the caller has the channel character in A and wants it preserved on the stack for the error handler to inspect. Never returns -- error_inline_log triggers a BRK.

On EntryAchannel character (saved on stack)
B847 .err_net_chan_invalid←2← 9EC2 JMP← B841 BCC
PHA ; Save channel character
B848 .error_chan_not_found←1← B87A BEQ
LDA #&de ; Error code &DE
B84A .err_net_chan_not_found
JSR error_inline_log ; Generate 'Net channel' error
B84D EQUS "Net channel", &00
B859 JSR false_ref_6f6e ; Error string continuation (unreachable)
B85C STZ tx_buffer_scratch,x ; Clear tx_buffer_scratch+X scratch
B85E EQUS "on this file server"
B871 EQUB &00
fall through ↓

Look up channel by character code

Subtracts &20 from the character to produce a table index (inlining the same arithmetic as attr_to_chan_index without the bounds check), loads the channel slot's hazel_fcb_slot_attr byte; on zero raises error_chan_not_found. Otherwise verifies station/network via match_station_net and returns the slot's flags in A.

On EntryAchannel character
On ExitAchannel flags
B872 .lookup_chan_by_char←2← A1F6 JSR← B845 BCC
PHA ; Save channel character
B873 SEC ; Prepare subtraction
B874 SBC #&20 ; Convert char to table index
B876 TAX ; X = channel table index
B877 LDA hazel_fcb_slot_attr,x ; Look up network number for channel
B87A BEQ error_chan_not_found ; Zero: channel not found, raise error
B87C JSR match_station_net ; Check station/network matches current
B87F BNE error_chan_not_here ; No match: build detailed error msg
B881 PLA ; Discard saved channel character
B882 LDA hazel_fcb_status,x ; Load channel status flags
B885 RTS ; Return; A = channel flags
B886 .error_chan_not_here←1← B87F BNE
LDA #&de ; Error code &DE
B888 STA error_text ; Store error code in error block
B88B LDA #0 ; BRK opcode
B88D STA error_block ; Store BRK at start of error block
B890 TAX ; X=0: copy index
B891 .loop_copy_chan_err_str←1← B898 BNE
INX ; Advance copy position
B892 LDA net_chan_err_strings,x ; Load 'Net channel' string byte
B895 STA error_text,x ; Copy to error text
B898 BNE loop_copy_chan_err_str ; Continue until NUL terminator
B89A STX fs_load_addr_2 ; Save end-of-string position
B89C STX fs_work_4 ; Save for suffix append
B89E PLA ; Retrieve channel character
B89F JSR append_space_and_num ; Append ' N' (channel number)
B8A2 LDY fs_work_4 ; Load 'Net channel' end position
B8A4 .loop_append_err_suffix←1← B8AC BNE
INY ; Skip past NUL to suffix string
B8A5 INX ; Advance destination position
B8A6 LDA net_chan_err_strings,y ; Load ' not on this...' suffix byte
B8A9 STA error_text,x ; Append to error message
B8AC BNE loop_append_err_suffix ; Continue until NUL
B8AE JMP error_block ; Raise the constructed error

Store channel attribute and check not directory

Writes the current channel attribute to the receive buffer, then tests the directory flag (bit 1). Raises 'Is a dir.' error if the attribute refers to a directory rather than a file.

On EntryAchannel attribute byte to store and check
B8B1 .store_result_check_dir←2← BB9F JSR← BC25 JSR
LDA hazel_chan_attr ; Load current channel attribute
B8B4 JSR store_rx_attribute ; Store channel attribute to RX buffer
fall through ↓

Validate channel is not a directory

Calls check_chan_char to validate the channel, then tests the directory flag (bit 1). Raises 'Is a dir.' error if the channel refers to a directory.

On EntryAchannel character (validated by check_chan_char)
B8B7 .check_not_dir←2← 9EF4 JSR← A198 JSR
JSR check_chan_char ; Validate and look up channel
B8BA AND #2 ; Test directory flag (bit 1)
B8BC BEQ return_9 ; Not a directory: return OK
B8BE LDA #&a8 ; Error code &A8
B8C0 JSR error_inline_log ; Generate 'Is a dir.' error
B8C3 EQUS "Is a directory", &00
B8D2 .return_9←1← B8BC BEQ
RTS ; Return

Allocate a free file control block slot

Scans FCB slots &20-&2F for an empty entry. Returns Z=0 with X=slot index on success, or Z=1 with A=0 if all slots are occupied.

On ExitXslot index (if Z=0)
Z0=success, 1=no free slot
B8D3 .alloc_fcb_slot←7← A052 JSR← A087 JSR← A5DC JSR← A679 JSR← A6A4 JSR← A6DB JSR← B90A JSR
PHA ; Save channel attribute
B8D4 LDX #&20 ; Start scanning from FCB slot &20
B8D6 .loop_scan_fcb_slots←1← B8DE BNE
LDA hazel_fcb_addr_mid,x ; Load FCB station byte
B8D9 BEQ done_found_free_slot ; Zero: slot is free, use it
B8DB INX ; Try next slot
B8DC CPX #&30 ; Past last FCB slot &2F?
B8DE BNE loop_scan_fcb_slots ; No: check next slot
B8E0 PLA ; No free slot: discard saved attribute
B8E1 LDA #0 ; A=0: return failure (Z set)
B8E3 RTS ; Return
B8E4 .done_found_free_slot←1← B8D9 BEQ
PLA ; Restore channel attribute
B8E5 STA hazel_fcb_addr_mid,x ; Store attribute in FCB slot
B8E8 LDA #0 ; A=0: clear value
B8EA STA hazel_fcb_addr_lo_minus20,x ; Clear FCB transfer count low
B8ED STA hazel_fcb_addr_mid_minus20,x ; Clear FCB transfer count mid
B8F0 STA hazel_fcb_addr_lo,x ; Clear FCB transfer count high
B8F3 LDA hazel_fs_station ; Load current station number
B8F6 STA hazel_fcb_addr_hi,x ; Store station in FCB
B8F9 LDA hazel_fs_network ; Load current network number
B8FC STA hazel_fcb_slot_attr,x ; Store network in FCB
B8FF TXA ; Get FCB slot index
B900 PHA ; Save slot index
B901 SEC ; Prepare subtraction
B902 SBC #&20 ; Convert slot to channel index (0-&0F)
B904 TAX ; X = channel index
B905 PLA ; Restore A = FCB slot index
B906 RTS ; Return; A=slot, X=channel, Z clear

Allocate FCB slot or raise error

Calls alloc_fcb_slot and raises 'No more FCBs' if no free slot is available. Preserves the caller's argument on the stack.

On EntryAcaller's argument byte (saved/restored via PHA/PLA across the alloc call)
On ExitXnewly allocated FCB slot index (&20-&2F)
Apreserved
B907 .alloc_fcb_or_error←2← A001 JSR← A538 JSR
PHA ; Save argument
B908 LDA #0 ; A=0: allocate any available slot
B90A JSR alloc_fcb_slot ; Try to allocate an FCB slot
B90D BNE return_alloc_success ; Success: slot allocated
B90F LDA #&c0 ; Error code &C0
B911 JSR error_inline_log ; Generate 'No more FCBs' error
B914 EQUS "Too many open files", &00
B928 .return_alloc_success←1← B90D BNE
PLA ; Restore argument
B929 RTS ; Return

Close all network channels for current station

Scans FCB slots &0F down to 0, closing those matching the current station. C=0 closes all matching entries; C=1 closes with write-flush.

On EntryC0=close all, 1=close with write-flush
B92A .close_all_net_chans←3← 9785 JSR← 9813 JSR← A6EB JSR
CLC ; C=0: close all matching channels
B92B .skip_set_carry
BIT always_set_v_byte ; Branch always to scan entry
fall through ↓

Scan FCB slot flags from &10 downward

Iterates through FCB slots starting at &10, checking each slot's flags byte. Returns when all slots have been processed.

On ExitXlast scanned FCB index
Z FLAGset if a matching slot was found (via fall-through into match_station_net)
B92E .scan_fcb_flags←1← A0C1 JSR
LDX #&10 ; Start from FCB slot &10
B930 .loop_scan_fcb_down←4← B93C BVC← B946 BVS← B94B BNE← B955 BEQ
DEX ; Previous FCB slot
B931 BPL skip_if_slots_done ; More slots to check
B933 RTS ; All FCB slots processed, return
B934 .skip_if_slots_done←1← B931 BPL
LDA hazel_fcb_status,x ; Load channel flags for this slot
B937 TAY ; Save flags in Y
B938 AND #2 ; Test active flag (bit 1)
B93A BEQ done_check_station ; Not active: check station match
B93C BVC loop_scan_fcb_down ; V clear (close all): next slot
B93E BCC done_check_station ; C clear: check station match
B940 TYA ; Restore original flags
B941 AND #&df ; Clear write-pending flag (bit 5)
B943 STA hazel_fcb_status,x ; Update channel flags
B946 BVS loop_scan_fcb_down ; Next slot (V always set here)
B948 .done_check_station←2← B93A BEQ← B93E BCC
JSR match_station_net ; Check if channel belongs to station
B94B BNE loop_scan_fcb_down ; No match: skip to next slot
B94D LDA #0 ; A=0: clear channel
B94F STA hazel_fcb_status,x ; Clear channel flags (close it)
B952 STA hazel_fcb_slot_attr,x ; Clear network number
B955 BEQ loop_scan_fcb_down ; Continue to next slot
fall through ↓

Check FCB slot matches current station/network

Compares the station and network numbers in the FCB at slot X against the current values using EOR. Returns Z=1 if both match, Z=0 if either differs.

On EntryXFCB slot index
On ExitZ1=match, 0=no match
B957 .match_station_net←7← A660 JSR← A68B JSR← A6C2 JSR← AA2C JSR← AFF9 JSR← B87C JSR← B948 JSR
LDA hazel_fcb_state_byte,x ; Load FCB station number
B95A EOR hazel_fs_station ; Compare with current station
B95D BNE rts_match_stn ; Different: Z=0, no match
B95F LDA hazel_fcb_network,x ; Load FCB network number
B962 EOR hazel_fs_network ; Compare with current network
B965 .rts_match_stn←1← B95D BNE
RTS ; Return; Z=1 if match, Z=0 if not

Find next open FCB slot for current connection

Scans from the current index, wrapping around at the end. On the first pass finds active entries matching the station; on the second pass finds empty slots for new allocations.

On EntryXstarting FCB index (search wraps)
On ExitXFCB slot index of the matched (active) or first empty slot
Z FLAGmatch status (set when an entry was found)
B966 .find_open_fcb←2← BA65 JSR← BB53 JSR
LDX hazel_cur_fcb_index ; Load current FCB index
B969 BIT always_set_v_byte ; Set V flag (first pass marker)
B96C .loop_find_fcb←4← B97B BVC← B981 BPL← B99E BVS← B9A5 BNE
INX ; Next FCB slot
B96D CPX #&10 ; Past end of table (&10)?
B96F BNE skip_if_no_wrap ; No: continue checking
B971 LDX #0 ; Wrap around to slot 0
B973 .skip_if_no_wrap←1← B96F BNE
CPX hazel_cur_fcb_index ; Back to starting slot?
B976 BNE done_check_fcb_status ; No: check this slot
B978 BVC loop_scan_empty_fcb ; V clear (second pass): scan empties
B97A CLV ; Clear V for second pass
B97B BVC loop_find_fcb ; Continue scanning
B97D .done_check_fcb_status←1← B976 BNE
LDA hazel_fcb_flags,x ; Load FCB status flags
B980 ROL ; Shift bit 7 (in-use) into carry
B981 BPL loop_find_fcb ; Not in use: skip
B983 AND #4 ; Test bit 2 (modified flag)
B985 BNE skip_if_modified_fcb ; Modified: check further conditions
B987 .done_select_fcb←1← B9A7 BEQ
DEX ; Adjust for following INX
B988 .loop_scan_empty_fcb←2← B978 BVC← B993 BPL
INX ; Next FCB slot
B989 CPX #&10 ; Past end of table?
B98B BNE done_test_empty_slot ; No: continue
B98D LDX #0 ; Wrap around to slot 0
B98F .done_test_empty_slot←1← B98B BNE
LDA hazel_fcb_flags,x ; Load FCB status flags
B992 ROL ; Shift bit 7 into carry
B993 BPL loop_scan_empty_fcb ; Not in use: continue scanning
B995 SEC ; Set carry
B996 ROR ; Restore original flags
B997 STA hazel_fcb_flags,x ; Save flags back (mark as found)
B99A LDX hazel_cur_fcb_index ; Restore original FCB index
B99D RTS ; Return with found slot in X
B99E .skip_if_modified_fcb←1← B985 BNE
BVS loop_find_fcb ; V set (first pass): skip modified
B9A0 LDA hazel_fcb_flags,x ; Load FCB status flags
B9A3 AND #&20 ; Test bit 5 (offset pending)
B9A5 BNE loop_find_fcb ; Bit 5 set: skip this slot
B9A7 BEQ done_select_fcb ; Use this slot
fall through ↓

Initialise byte counters for wipe/transfer

Sets hazel_pass_counter to 1 and clears hazel_byte_counter_lo, hazel_offset_counter and hazel_transfer_flag. Then stores &FF sentinels in hazel_sentinel_cd / hazel_sentinel_ce. The HAZEL FS-state region is at &C2xx.

On ExitXsmall loop counter (last DEX value)
Y0 (cleared by the TYA path)
B9A9 .init_wipe_counters←2← B9EC JSR← BA97 JSR
LDY #1 ; Initial pass count = 1
B9AB STY hazel_pass_counter ; Store pass counter
B9AE DEY ; Y=0
B9AF STY hazel_byte_counter_lo ; Clear byte counter low
B9B2 STY hazel_offset_counter ; Clear offset counter
B9B5 STY hazel_transfer_flag ; Clear transfer flag
B9B8 TYA ; A=0
B9B9 LDX #2 ; Clear 3 counter bytes
B9BB .loop_clear_counters←1← B9BF BPL
STA hazel_xfer_init_zeros,x ; Clear counter byte
B9BE DEX ; Next byte
B9BF BPL loop_clear_counters ; Loop for indices 2, 1, 0
B9C1 STX hazel_sentinel_cd ; Store &FF as sentinel in xfer_sentinel_1
B9C4 STX hazel_sentinel_ce ; Store &FF as sentinel in xfer_sentinel_2
B9C7 LDX #&ca ; X=&CA: workspace offset
B9C9 LDY #&c2 ; Y=&C2: HAZEL workspace page &C2
B9CB RTS ; Return; X/Y point to &C2CA

Start wipe pass for current FCB

Verifies the workspace checksum, saves the station context (pushing station low/high), initialises transfer counters via init_wipe_counters, and sends the initial request via send_and_receive. Clears the active and offset flags on completion.

On EntryXFCB slot index
B9CC .start_wipe_pass←2← BA5C JSR← BB86 JSR
JSR verify_ws_checksum ; Verify workspace checksum integrity
B9CF STX hazel_cur_fcb_index ; Save current FCB index
B9D2 LDA hazel_fcb_flags,x ; Load FCB status flags
B9D5 ROR ; Shift bit 0 (active) into carry
B9D6 BCC done_clear_fcb_active ; Not active: clear status and return
B9D8 LDA hazel_station_lo ; Save current station low to stack
B9DB PHA ; Push station low
B9DC LDA hazel_station_hi ; Save current station high
B9DF PHA ; Push station high
B9E0 LDA hazel_fcb_station_lo,x ; Load FCB station low
B9E3 STA hazel_station_lo ; Set as working station low
B9E6 LDA hazel_fcb_station_hi,x ; Load FCB station high
B9E9 STA hazel_station_hi ; Set as working station high
B9EC JSR init_wipe_counters ; Reset transfer counters
B9EF DEC hazel_offset_counter ; Set offset to &FF (no data yet)
B9F2 DEC hazel_pass_counter ; Set pass counter to 0 (flush mode)
B9F5 LDX hazel_cur_fcb_index ; Reload FCB index
B9F8 TXA ; Transfer to A
B9F9 CLC ; Prepare addition
B9FA ADC #&c3 ; Add &11 for buffer page offset
B9FC STA hazel_buf_addr_hi ; Store buffer address high byte
B9FF LDA hazel_fcb_flags,x ; Load FCB status flags
BA02 AND #&20 ; Test bit 5 (has saved offset)
BA04 BEQ done_restore_offset ; No offset: skip restore
BA06 LDA hazel_fcb_offset_save,x ; Load saved byte offset
BA09 STA hazel_offset_counter ; Restore offset counter
BA0C .done_restore_offset←1← BA04 BEQ
LDA hazel_fcb_attr_ref,x ; Load FCB attribute reference
BA0F STA hazel_chan_ref ; Store as current reference
BA12 TAX ; Transfer to X
BA13 JSR read_rx_attribute ; Read saved receive attribute
BA16 PHA ; Push to stack
BA17 TXA ; Restore attribute to A
BA18 STA (net_rx_ptr),y ; Set attribute in receive buffer
BA1A LDX #&ca ; X=&CA: workspace offset
BA1C LDY #&c2 ; Y=&C2: HAZEL workspace page &C2
BA1E LDA #0 ; A=0: standard transfer mode
BA20 JSR send_and_receive ; Send data and receive response
BA23 LDX hazel_cur_fcb_index ; Reload FCB index
BA26 PLA ; Restore saved receive attribute
BA27 JSR store_rx_attribute ; Restore receive attribute
BA2A PLA ; Restore station high
BA2B STA hazel_station_hi ; Store station high
BA2E PLA ; Restore station low
BA2F STA hazel_station_lo ; Store station low
BA32 .done_clear_fcb_active←1← B9D6 BCC
LDA #&dc ; Mask &DC: clear bits 0, 1, 5
BA34 AND hazel_fcb_flags,x ; Clear active and offset flags
BA37 STA hazel_fcb_flags,x ; Update FCB status
BA3A RTS ; Return

Save FCB context and process pending slots

Copies 13 bytes from the TX buffer (&0F00) and fs_load_addr workspace to temporary storage at &10D9. If Y=0, skips to the restore loop. Otherwise scans for pending FCB slots (bits 7+6 set), flushes each via start_wipe_pass, allocates new slots via find_open_fcb, and sends directory requests. Falls through to restore_catalog_entry.

On EntryYfilter attribute (0=process all)
BA3B .save_fcb_context←2← BAFE JSR← BCB7 JSR
LDX #&0c ; Copy 13 bytes (indices 0 to &0C)
BA3D .loop_save_tx_context←1← BA47 BPL
LDA hazel_txcb_port,x ; Load TX buffer byte
BA40 STA hazel_ctx_buffer,x ; Save to context buffer at &10D9
BA43 LDA fs_load_addr,x ; Load workspace byte from fs_load_addr
BA45 PHA ; Save to stack
BA46 DEX ; Next byte down
BA47 BPL loop_save_tx_context ; Loop for all 13 bytes
BA49 CPY #0 ; Y=0? (no FCB to process)
BA4B BNE done_save_context ; Non-zero: scan and process FCBs
BA4D JMP loop_restore_workspace ; Y=0: skip to restore workspace
BA50 .done_save_context←1← BA4B BNE
PHP ; Save flags
BA51 LDX #&ff ; X=&FF: start scanning from -1
BA53 .loop_find_pending_fcb←2← BA57 BPL← BA5A BPL
INX ; Next FCB slot
BA54 LDA hazel_fcb_flags,x ; Load FCB status flags
BA57 BPL loop_find_pending_fcb ; Bit 7 clear: not pending, skip
BA59 ASL ; Shift bit 6 to bit 7
BA5A BPL loop_find_pending_fcb ; Bit 6 clear: skip
BA5C JSR start_wipe_pass ; Flush this FCB's pending data
BA5F LDA #&40 ; Pending marker &40
BA61 STA hazel_fcb_flags,x ; Mark FCB as pending-only
BA64 PHP ; Save flags
BA65 JSR find_open_fcb ; Find next available FCB slot
BA68 PLP ; Restore flags
BA69 LDA hazel_chan_attr ; Load current channel attribute
BA6C STA hazel_chan_ref ; Store as current reference
BA6F PHA ; Save attribute
BA70 SEC ; Prepare attribute-to-channel conversion
BA71 SBC #&20 ; Convert attribute (&20+) to channel index
BA73 TAY ; Y = attribute index
BA74 LDA hazel_fcb_slot_attr,y ; Load station for this attribute
BA77 STA hazel_txcb_data ; Store station in TX buffer
BA7A PLA ; Restore attribute
BA7B STA hazel_fcb_attr_ref,x ; Store attribute in FCB slot
BA7E LDA hazel_station_lo ; Load working station low
BA81 STA hazel_fcb_station_lo,x ; Store in TX buffer
BA84 LDA hazel_station_hi ; Load working station high
BA87 STA hazel_fcb_station_hi,x ; Store in TX buffer
BA8A TXA ; Get FCB slot index
BA8B CLC ; Prepare addition
BA8C ADC #&c3 ; Add &11 for buffer page offset
BA8E STA hazel_buf_addr_hi ; Store buffer address high byte
BA91 PLP ; Restore flags
BA92 BVC done_init_wipe ; V clear: skip directory request
BA94 JSR flush_fcb_if_stn_known ; Command byte = 0
BA97 .done_init_wipe←1← BA92 BVC
JSR init_wipe_counters ; Reset transfer counters
BA9A JSR read_rx_attribute ; Read saved receive attribute
BA9D PHA ; Function code &0D
BA9E LDA hazel_chan_ref ; Load current reference
BAA1 STA (net_rx_ptr),y ; Set in receive buffer
BAA3 LDY #&c2 ; Y=&C2: HAZEL workspace page &C2
BAA5 LDA #2 ; A=2: transfer mode 2
BAA7 JSR send_and_receive ; Send and receive data
BAAA PLA ; Restore receive attribute
BAAB JSR store_rx_attribute ; Restore receive attribute
BAAE LDX hazel_cur_fcb_index ; Reload FCB index
BAB1 LDA hazel_pass_counter ; Load pass counter
BAB4 BNE done_calc_offset ; Non-zero: data received, calc offset
BAB6 LDA hazel_offset_counter ; Load offset counter
BAB9 BEQ done_set_fcb_active ; Zero: no data received at all
BABB .done_calc_offset←1← BAB4 BNE
LDA hazel_offset_counter ; Load offset counter
BABE EOR #&ff ; Negate (ones complement)
BAC0 CLC ; Clear carry for add
BAC1 ADC #1 ; Complete twos complement negation
BAC3 STA hazel_fcb_offset_save,x ; Store negated offset in FCB
BAC6 LDA #&20 ; Set bit 5 (has saved offset)
BAC8 ORA hazel_fcb_flags,x ; Add to FCB flags
BACB STA hazel_fcb_flags,x ; Update FCB status
BACE LDA hazel_buf_addr_hi ; Load buffer address high byte
BAD1 STA fs_load_addr_3 ; Set pointer high byte
BAD3 LDA #0 ; A=0: pointer low byte and clear val
BAD5 STA fs_load_addr_2 ; Set pointer low byte
BAD7 LDY hazel_fcb_offset_save,x ; Load negated offset (start of clear)
BADA .loop_clear_buffer←1← BADD BNE
STA (fs_load_addr_2),y ; Clear buffer byte
BADC INY ; Next byte
BADD BNE loop_clear_buffer ; Loop until page boundary
BADF .done_set_fcb_active←1← BAB9 BEQ
LDA #2 ; Set bit 1 (active flag)
BAE1 ORA hazel_fcb_flags,x ; Add active flag to status
BAE4 STA hazel_fcb_flags,x ; Update FCB status
BAE7 LDY #0 ; Y=0: start restoring workspace
fall through ↓

Pop 13 saved workspace bytes back to fs_load_addr+

Y=0..&0C loop: PLA / STA fs_load_addr,Y / INY / CPY #&0D / BNE. Restores the 13-byte FS-options block that save_fcb_context pushed on the stack, undoing the protection the wipe/scan path put in place. Two callers: the JMP at &BA4B (close-and-restore exit) and the BNE retry at &BABE.

BAE9 .loop_restore_workspace←2← BA4D JMP← BAF0 BNE
PLA ; Restore workspace byte from stack
BAEA STA fs_load_addr,y ; Store to fs_load_addr workspace
BAED INY ; Next byte
BAEE CPY #&0d ; Restored all 13 bytes?
BAF0 BNE loop_restore_workspace ; No: continue restoring
fall through ↓

Restore saved catalog entry to TX buffer

Copies 13 bytes (Y=&0C..0) from hazel_ctx_buffer back to the TX buffer starting at hazel_txcb_port. Falls through to find_matching_fcb.

BAF2 .restore_catalog_entry←1← BCE7 JSR
LDY #&0c ; Copy 13 bytes (indices 0 to &0C)
BAF4 .loop_restore_tx_buf←1← BAFB BPL
LDA hazel_ctx_buffer,y ; Load saved catalog byte from &10D9
BAF7 STA hazel_txcb_port,y ; Restore to TX buffer
BAFA DEY ; Next byte down
BAFB BPL loop_restore_tx_buf ; Loop for all bytes
BAFD RTS ; Return

Save FCB context, fall into find_matching_fcb

Single-instruction wrapper at the top of the per-iteration FCB search retry: JSR save_fcb_context to preserve the current attempt's state (offset, station, network), then fall through into find_matching_fcb. Single caller (the BNE retry at &BAED). Used once the first scan past slot &0F has failed and the search needs to restart from slot 0 with the saved context restored.

BAFE .loop_save_before_match←1← BB1D JMP
JSR save_fcb_context ; Save current context first
fall through ↓

Find FCB slot matching channel attribute

Scans FCB slots 0-&0F for an active entry whose attribute reference matches hazel_chan_attr. Converts the attribute to a channel index, then verifies the station and network numbers. On the first scan past slot &0F, saves context via save_fcb_context and restarts. Returns Z=0 if the FCB has saved offset data (bit 5 set).

On ExitXmatching FCB index
Z0=has offset data, 1=no offset
BB01 .find_matching_fcb←3← A13D JSR← BBBA JSR← BC57 JSR
LDX #&ff ; X=&FF: start scanning from -1
BB03 .loop_reload_attr←2← BB3F BNE← BB47 BNE
LDY hazel_chan_attr ; Load channel attribute to match
BB06 .loop_next_fcb_slot←2← BB25 BEQ← BB2B BNE
INX ; Next FCB slot
BB07 CPX #&10 ; Past end of table (&10)?
BB09 BNE done_test_fcb_active ; No: check this slot
BB0B LDA hazel_chan_attr ; Load channel attribute
BB0E JSR attr_to_chan_index ; Convert to channel index
BB11 LDA hazel_fcb_addr_hi,x ; Load station for this channel
BB14 STA hazel_station_hi ; Store as match target station high
BB17 LDA hazel_fcb_addr_mid,x ; Load port for this channel
BB1A STA hazel_station_lo ; Store as match target station low
BB1D JMP loop_save_before_match ; Save context and rescan from start
BB20 .done_test_fcb_active←1← BB09 BNE
LDA hazel_fcb_flags,x ; Load FCB status flags
BB23 AND #2 ; Test active flag (bit 1)
BB25 BEQ loop_next_fcb_slot ; Not active: skip to next
BB27 TYA ; Get attribute to match
BB28 CMP hazel_fcb_attr_ref,x ; Compare with FCB attribute ref
BB2B BNE loop_next_fcb_slot ; No attribute match: skip
BB2D STX hazel_cur_fcb_index ; Save matching FCB index
BB30 PHP ; Save flags from attribute compare
BB31 SEC ; Prepare subtraction
BB32 SBC #&20 ; Convert attribute to channel index
BB34 PLP ; Restore flags from attribute compare
BB35 TAY ; Y = channel index
BB36 LDX hazel_cur_fcb_index ; Reload FCB index
BB39 LDA hazel_fcb_addr_mid,y ; Load channel station byte
BB3C CMP hazel_fcb_station_lo,x ; Compare with FCB station
BB3F BNE loop_reload_attr ; Station mismatch: try next
BB41 LDA hazel_fcb_addr_hi,y ; Load channel network byte
BB44 CMP hazel_fcb_station_hi,x ; Compare with FCB network
BB47 BNE loop_reload_attr ; Network mismatch: try next
BB49 LDA hazel_fcb_flags,x ; Load FCB flags
BB4C BPL return_test_offset ; Bit 7 clear: no pending flush
BB4E AND #&7f ; Clear pending flag (bit 7)
BB50 STA hazel_fcb_flags,x ; Update FCB status
BB53 JSR find_open_fcb ; Find new open FCB slot
BB56 LDA hazel_fcb_flags,x ; Reload FCB flags
BB59 .return_test_offset←1← BB4C BPL
AND #&20 ; Test bit 5 (has offset data)
BB5B RTS ; Return; Z=1 no offset, Z=0 has data

Increment 3-byte FCB transfer count

Increments hazel_fcb_addr_lo+X (low), cascading overflow to hazel_fcb_addr_mid+X (mid) and hazel_fcb_addr_hi+X (high).

On EntryXFCB slot index
BB5C .inc_fcb_byte_count←2← BBFF JSR← BC97 JSR
INC hazel_fcb_addr_lo,x ; Increment byte count low
BB5F BNE rts_inc_fcb_count ; No overflow: done
BB61 INC hazel_fcb_addr_mid,x ; Increment byte count mid
BB64 BNE rts_inc_fcb_count ; No overflow: done
BB66 INC hazel_fcb_addr_hi,x ; Increment byte count high
BB69 .rts_inc_fcb_count←2← BB5F BNE← BB64 BNE
RTS ; Return

Process all active FCB slots

Saves 9 zero-page bytes (&00B4&00BC, i.e. fs_work_4+0..+8) on the stack via a PHX/PHY/loop preamble using the &FFBD,X indexing-wrap trick (X = &F7..&FF wraps to &00B4..&00BC), then scans FCB slots &0F down to 0. Calls start_wipe_pass for each active entry matching the filter attribute in Y (0 = match all). Restores all saved context on completion. Also contains the OSBGET/OSBPUT inline logic for reading and writing bytes through file channels.

On EntryYfilter attribute (0=process all)
BB6A .process_all_fcbs←9← 8DB7 JSR← 9081 JSR← 9778 JSR← 9EC9 JSR← 9F0C JSR← 9FD9 JSR← A093 JSR← A19C JSR← AA01 JSR
PHX ; Save X on entry
BB6B PHY ; Save Y across the body
BB6C LDX #&f7 ; X=&F7: save 9 workspace bytes (&F7..&FF)
BB6E .loop_save_fcb_workspace←1← BB73 BMI
LDA fcb_workspace_idx_base,x ; Load workspace byte
BB71 PHA ; Push fs_options
BB72 INX ; Next byte
BB73 BMI loop_save_fcb_workspace ; X<0: more bytes to save
BB75 LDX #&0f ; Start from FCB slot &0F
BB77 STX hazel_cur_fcb_index ; Store as current FCB index
BB7A .loop_process_fcb←1← BB8D BPL
LDX hazel_cur_fcb_index ; Load current FCB index
BB7D TYA ; Get filter attribute
BB7E BEQ done_flush_fcb ; Zero: process all FCBs
BB80 CMP hazel_fcb_attr_ref,x ; Compare with FCB attribute ref
BB83 BNE done_advance_fcb ; No match: skip this FCB
BB85 .done_flush_fcb←1← BB7E BEQ
PHA ; Save filter attribute
BB86 JSR start_wipe_pass ; Flush pending data for this FCB
BB89 PLY ; Restore Y
BB8A .done_advance_fcb←1← BB83 BNE
DEC hazel_cur_fcb_index ; Previous FCB index
BB8D BPL loop_process_fcb ; More slots: continue loop
BB8F LDX #8 ; X=8: restore 9 workspace bytes
BB91 .loop_restore_fcb_ws←1← BB95 BPL
PLA ; Restore fs_block_offset
BB92 STA fs_work_4,x ; Restore workspace byte
BB94 DEX ; Next byte down
BB95 BPL loop_restore_fcb_ws ; More bytes: continue restoring
BB97 PLY ; Restore Y
BB98 PLX ; Restore X
BB99 RTS ; Return

BGETV vector handler: read byte from open file

Reached via the BGETV vector at &0216, which the fs_vector_table entries copy into the MOS extended vector area. Saves caller's Y in hazel_chan_attr (channel attribute slot), pushes X, calls store_result_check_dir to validate the channel, then either reads a byte from the FCB buffer (returning it in A with C=0) or signals end-of-file (C=1).

On EntryYchannel handle
On ExitAbyte read (when C=0)
C0 = byte returned, 1 = EOF / error
BB9A .bgetv_handler
STY hazel_chan_attr ; Save channel attribute
BB9D TXA ; Save caller's X
BB9E PHA ; Push X
BB9F JSR store_result_check_dir ; Store result and check not directory
BBA2 LDA hazel_fcb_status,x ; Load channel flags
BBA5 AND #&20 ; Test write-only flag (bit 5)
BBA7 BEQ done_read_fcb_byte ; Not write-only: proceed with read
BBA9 LDA #&d4 ; Error code &D4
BBAB JSR error_inline_log ; Generate 'Write only' error
BBAE EQUS "Write only", &00
BBB9 .done_read_fcb_byte←1← BBA7 BEQ
CLV ; Clear V (first-pass matching)
BBBA JSR find_matching_fcb ; Find FCB matching this channel
BBBD BEQ done_load_from_buf ; No offset: read byte from buffer
BBBF LDA hazel_fcb_addr_lo,y ; Load byte count for matching FCB
BBC2 CMP hazel_fcb_offset_save,x ; Compare with buffer offset limit
BBC5 BCC done_load_from_buf ; Below offset: data available
BBC7 LDA hazel_fcb_status,y ; Load channel flags for FCB
BBCA TAX ; Transfer to X for testing
BBCB AND #&40 ; Test bit 6 (EOF already signalled)
BBCD BNE error_end_of_file ; EOF already set: raise error
BBCF TXA ; Restore flags
BBD0 ORA #&40 ; Set EOF flag (bit 6)
BBD2 STA hazel_fcb_status,y ; Update channel flags with EOF
BBD5 LDA #0 ; A=0: clear receive attribute
BBD7 JSR store_rx_attribute ; Clear receive attribute (A=0)
BBDA PLA ; Restore caller's X
BBDB TAX ; X restored
BBDC LDA #&fe ; A=&FE: EOF marker byte
BBDE LDY hazel_chan_attr ; Restore channel attribute
BBE1 SEC ; C=1: end of file
BBE2 RTS ; Return
BBE3 .error_end_of_file←1← BBCD BNE
LDA #&df ; Error code &DF
BBE5 JSR error_inline_log ; Generate 'End of file' error
BBE8 EQUS "End of file", &00
BBF4 .done_load_from_buf←2← BBBD BEQ← BBC5 BCC
LDA hazel_fcb_addr_lo,y ; Load current byte count (= offset)
BBF7 PHA ; Save byte count
BBF8 TYA ; Get FCB slot index
BBF9 TAX ; X = FCB slot for byte count inc
BBFA LDA #0 ; A=0: clear receive attribute
BBFC JSR store_rx_attribute ; Clear receive attribute (A=0)
BBFF JSR inc_fcb_byte_count ; Increment byte count for this FCB
BC02 PLA ; Restore byte count (= buffer offset)
BC03 TAY ; Y = offset into data buffer
BC04 LDA hazel_cur_fcb_index ; Load current FCB index
BC07 CLC ; Prepare addition
BC08 ADC #&c3 ; Add &11 for buffer page offset
BC0A STA fs_load_addr_3 ; Set pointer high byte
BC0C LDA #0 ; A=0: pointer low byte
BC0E STA fs_load_addr_2 ; Set pointer low byte
BC10 PLA ; Restore caller's X
BC11 TAX ; X restored
BC12 LDA (fs_load_addr_2),y ; Read data byte from buffer
BC14 LDY hazel_chan_attr ; Restore channel attribute
BC17 CLC ; C=0: byte read successfully
BC18 RTS ; Return; A = data byte

BPUTV vector handler: write byte to open file

Reached via the BPUTV vector at &0218. Saves caller's Y in hazel_chan_attr, pushes the data byte and X, then routes to the FCB buffer-write path: stores the byte in the channel's transmit buffer, increments the byte count via inc_fcb_byte_count, and exits via done_inc_byte_count.

On EntryAbyte to write
Ychannel handle
On ExitC0 = written, 1 = error
BC19 .bputv_handler
STY hazel_chan_attr ; Save channel attribute
BC1C PHA ; Save data byte
BC1D TAY ; Y = data byte
BC1E TXA ; Save caller's X
BC1F PHA ; Push X
BC20 TYA ; Restore data byte to A
BC21 PHA ; Push data byte for later
BC22 STA hazel_saved_byte ; Save data byte in workspace
BC25 JSR store_result_check_dir ; Store result and check not directory
BC28 LDA hazel_fcb_status,x ; Load channel flags
BC2B BMI done_test_write_flag ; Bit 7 set: channel open, proceed
BC2D LDA #&c1 ; Error &C1: Not open for update
BC2F JSR error_inline_log ; Raise error with inline string
BC32 EQUS "Not open for update", &00
BC46 .done_test_write_flag←1← BC2B BMI
AND #&20 ; Test write flag (bit 5)
BC48 BEQ done_find_write_fcb ; Not write-capable: use buffer path
BC4A LDY hazel_fcb_slot_attr,x ; Load reply port for this channel
BC4D PLA ; Restore data byte
BC4E JSR send_wipe_request ; Send byte directly to server
BC51 JMP done_inc_byte_count ; Update byte count and return
BC54 .done_find_write_fcb←1← BC48 BEQ
BIT always_set_v_byte ; Set V flag (alternate match mode)
BC57 JSR find_matching_fcb ; Find matching FCB for channel
BC5A LDA hazel_fcb_addr_lo,y ; Load byte count for FCB
BC5D CMP #&ff ; Buffer full (&FF bytes)?
BC5F BNE done_check_buf_offset ; No: store byte in buffer
BC61 JSR flush_fcb_with_init ; Save X
BC64 .done_check_buf_offset←1← BC5F BNE
CMP hazel_fcb_offset_save,x ; Push Y
BC67 BCC done_set_dirty_flag ; Below offset: skip offset update
BC69 ADC #0 ; Carry set from BCS/BCC above
BC6B STA hazel_fcb_offset_save,x ; Update buffer offset in FCB
BC6E BNE done_set_dirty_flag ; Non-zero: keep offset flag
BC70 LDA #&df ; Mask &DF: clear bit 5
BC72 AND hazel_fcb_flags,x ; Clear offset flag
BC75 STA hazel_fcb_flags,x ; Update FCB status
BC78 .done_set_dirty_flag←2← BC67 BCC← BC6E BNE
LDA #1 ; Set bit 0 (dirty/active)
BC7A ORA hazel_fcb_flags,x ; Add to FCB flags
BC7D STA hazel_fcb_flags,x ; Update FCB status
BC80 LDA hazel_fcb_addr_lo,y ; Load byte count (= write position)
BC83 PHA ; Save count
BC84 TYA ; Get FCB slot index
BC85 TAX ; X = FCB slot
BC86 PLA ; Restore byte count
BC87 TAY ; Y = buffer write offset
BC88 LDA hazel_cur_fcb_index ; Load current FCB index
BC8B CLC ; Prepare addition
BC8C ADC #&c3 ; Add &11 for buffer page offset
BC8E STA fs_load_addr_3 ; Set pointer high byte
BC90 LDA #0 ; A=0: pointer low byte
BC92 STA fs_load_addr_2 ; Set pointer low byte
BC94 PLA ; Restore data byte
BC95 STA (fs_load_addr_2),y ; Write data byte to buffer
fall through ↓

Increment FCB byte count, clear rx attr, restore caller

JSRs inc_fcb_byte_count for the active FCB, then A=0 / JSR store_rx_attribute (clears the receive-attribute byte). Pulls saved X back into X (caller's value), discards the saved data byte on the stack and returns. Single caller (the OSBPUT/PRINT path at &BC1F).

BC97 .done_inc_byte_count←1← BC51 JMP
JSR inc_fcb_byte_count ; Increment byte count for this FCB
BC9A LDA #0 ; A=0: clear receive attribute
BC9C JSR store_rx_attribute ; Clear receive attribute (A=0)
BC9F PLA ; Restore caller's X
BCA0 TAX ; X restored
BCA1 PLA ; Discard saved data byte
BCA2 LDY hazel_chan_attr ; Restore channel attribute
BCA5 RTS ; Return

Flush FCB byte count to server if station is set

Saves all registers, checks if the FCB has a known station. If yes, sends the accumulated byte count as a flush request to the file server. If no station is set, falls through to flush_fcb_with_init which saves FCB context first.

On EntryYchannel index (FCB slot)
On ExitApreserved
Xpreserved
Ypreserved
BCA6 .flush_fcb_if_stn_known←1← BA94 JSR
PHA ; Save A
BCA7 PHX ; Save X
BCA8 PHY ; Save Y
BCA9 LDA hazel_fcb_slot_attr,y ; Read FCB slot attribute byte
BCAC BNE store_station_and_flush ; Non-zero: station known -> store_station_and_flush
fall through ↓

Save FCB context and flush byte count to server

Saves all registers and the current FCB context, copies the FCB byte count into the TX command buffer, and sends a flush/close request to the file server. Restores the catalog entry and all registers on return.

On EntryYchannel index (FCB slot)
On ExitApreserved
Xpreserved
Ypreserved
BCAE .flush_fcb_with_init←1← BC61 JSR
PHA ; Save attribute byte (saved-station-test path)
BCAF PHX ; Save X again
BCB0 PHY ; Save Y
BCB1 LDA hazel_fcb_slot_attr,y ; Load station for this channel
BCB4 PHA ; Save station on stack
BCB5 LDY #0 ; Y=0: reset index
BCB7 JSR save_fcb_context ; Save current FCB context
BCBA PLA ; Restore station from stack
BCBB .store_station_and_flush←1← BCAC BNE
STA hazel_txcb_data ; Store station in command buffer
BCBE PLY ; Restore Y
BCBF PHY ; Save Y again for the next iteration
BCC0 PHA ; Save station for later restore
BCC1 LDX #0 ; X=0
BCC3 STX hazel_txcb_flag ; Clear function code
BCC6 LDA hazel_fcb_addr_lo,y ; Load byte count lo from FCB
BCC9 STA hazel_txcb_count ; Store as data byte count
BCCC LDA hazel_fcb_addr_mid,y ; Load byte count mid from FCB
BCCF STA hazel_txcb_result ; Store as reply command byte
BCD2 LDA hazel_fcb_addr_hi,y ; Load byte count hi from FCB
BCD5 STA hazel_exec_addr ; Store as load vector field
BCD8 LDY #&0d ; Y=&0D: TX command byte offset
BCDA LDX #5 ; X=5: send 5 bytes
BCDC JSR save_net_tx_cb ; Send flush request to server
BCDF PLA ; Restore station from stack
BCE0 TAY ; Y=station for wipe request
BCE1 LDA hazel_saved_byte ; Load saved data byte
BCE4 JSR send_wipe_request ; Send close/wipe request to server
BCE7 JSR restore_catalog_entry ; Restore catalog state after flush
BCEA PLY ; Restore Y
BCEB PLX ; Restore X
BCEC PLA ; Restore A
BCED RTS ; Return

Send wipe/close request packet

Sets up the TX control block with function code &90, the reply port from Y, and the data byte from A. Sends via send_disconnect_reply, then checks the error code — raises the server error if non-zero.

On EntryAdata byte to send
Yreply port
BCEE .send_wipe_request←2← BC4E JSR← BCE4 JSR
STY hazel_net_reply_buf_2 ; Store reply port
BCF1 STA hazel_net_reply_buf_3 ; Store data byte
BCF4 TYA ; Save Y
BCF5 PHA ; Push Y to stack
BCF6 TXA ; Save X
BCF7 PHA ; Push X to stack
BCF8 LDA #&90 ; Function code &90
BCFA STA hazel_net_reply_buf_0 ; Store in send buffer
BCFD JSR init_txcb ; Initialise TX control block
BD00 LDA #&dc ; TX start address low = &DC
BD02 STA txcb_start ; Set TX start in control block
BD04 LDA #&e0 ; TX end address low = &E0
BD06 STA txcb_end ; Set TX end in control block
BD08 LDA #9 ; Expected reply port = 9
BD0A STA hazel_net_reply_buf_1 ; Store reply port in buffer
BD0D LDX #&c0 ; TX control = &C0
BD0F LDY #0 ; Y=0: no timeout
BD11 LDA hazel_net_reply_buf_2 ; Load reply port for addressing
BD14 JSR send_disconnect_reply ; Send packet to server
BD17 LDA hazel_net_reply_buf_1 ; Load reply status
BD1A BEQ done_toggle_station ; Zero: success
BD1C STA hazel_fs_last_error ; Store error code
BD1F LDX #0 ; X=0: copy index
BD21 .loop_copy_wipe_err_msg←1← BD2C BNE
LDA hazel_net_reply_buf_0,x ; Load error message byte
BD24 STA error_block,x ; Copy to error block
BD27 CMP #&0d ; Is it CR (end of message)?
BD29 BEQ done_terminate_wipe_err ; Yes: terminate string
BD2B INX ; Next byte
BD2C BNE loop_copy_wipe_err_msg ; Continue copying error message
BD2E .done_terminate_wipe_err←1← BD29 BEQ
LDA #0 ; NUL terminator
BD30 STA error_block,x ; Terminate error string in block
BD33 DEX ; Back up position for error check
BD34 JMP check_net_error_code ; Process and raise network error
BD37 .done_toggle_station←1← BD1A BEQ
LDX hazel_chan_attr ; Load channel attribute index
BD3A LDA hazel_fcb_state_byte,x ; Load station number for channel
BD3D EOR #1 ; Toggle bit 0 (alternate station)
BD3F STA hazel_fcb_state_byte,x ; Update station number
BD42 PLA ; Restore X
BD43 TAX ; X restored
BD44 PLA ; Restore Y
BD45 TAY ; Y restored
BD46 RTS ; Return

Set up FS options and transfer workspace

Calls set_options_ptr to configure the FS options pointer, then jumps to setup_transfer_workspace to initialise the transfer and send the request.

On EntryAtransfer mode
Xworkspace offset low
Yworkspace page
BD47 .send_and_receive←2← BA20 JSR← BAA7 JSR
JSR set_options_ptr ; Set up FS options pointer
BD4A JMP setup_transfer_workspace ; Set up transfer workspace and return

Read receive attribute byte from RX buffer

Reads byte at offset &0A in the network receive control block, used to track which channel owns the current receive buffer.

On ExitAreceive attribute byte
Y&0A
BD4D .read_rx_attribute←4← 99DF JSR← 99F2 JSR← BA13 JSR← BA9A JSR
LDY #&0a ; Y=&0A: receive attribute offset
BD4F LDA (net_rx_ptr),y ; Read byte from receive buffer
BD51 RTS ; Return

Store receive attribute byte to RX buffer

Writes A to offset &0A in the network receive control block, marking which channel owns the current receive buffer.

On EntryAattribute byte to store
On ExitY&0A
BD52 .store_rx_attribute←10← 8BBE JSR← 9EF8 JSR← 9FE1 JSR← A025 JSR← B8B4 JSR← BA27 JSR← BAAB JSR← BBD7 JSR← BBFC JSR← BC9C JSR
LDY #&0a ; Y=&0A: receive attribute offset
BD54 STA (net_rx_ptr),y ; Store byte to receive buffer
BD56 RTS ; Return

Test escape flag and abort if pressed

Checks the escape flag byte; returns immediately if bit 7 is clear. If escape has been pressed, falls through to the escape abort handler which acknowledges the escape via OSBYTE &7E.

BD57 .abort_if_escape←1← BD8B JSR
BIT escape_flag ; Test bit 7 of escape flag
BD59 BMI error_escape_pressed ; Escape pressed: handle abort
BD5B RTS ; No escape: return
BD5C .error_escape_pressed←1← BD59 BMI
JSR close_ws_file ; Close the open file
BD5F .escape_error_close←1← 9729 JMP
JSR osnewl ; Print newline
BD62 LDA #osbyte_acknowledge_escape ; Acknowledge escape condition
BD64 JSR osbyte ; Clear escape condition and perform escape effects
BD67 LDA #&11 ; Error number &11
BD69 JSR error_inline ; Generate 'Escape' BRK error
BD6C EQUS "Escape", &00
fall through ↓

*Dump command handler

Opens the file via open_file_for_read, allocates a 21-byte buffer on the stack, and parses the address range via init_dump_buffer. Loops reading 16 bytes per line, printing each as a 4-byte hex address, 16 hex bytes with spaces, and a 16-character ASCII column (non-printable chars shown as '.'). Prints a column header at every 256-byte boundary.

On EntryYcommand line offset in text pointer
BD73 .cmd_dump
JSR open_file_for_read ; Open the file (handle stored in ws_page)
BD76 LDX #&14 ; X=&14: 21-byte stack buffer for dump line state
BD78 LDA #0 ; A=0: zero-fill
BD7A .loop_push_zero_buf←1← BD7C BPL
PHA ; Push zero
BD7B DEX ; Step counter
BD7C BPL loop_push_zero_buf ; Loop while X >= 0 (21 zeros)
BD7E TSX ; Capture stack pointer for later restore
BD7F JSR init_dump_buffer ; Parse address range and validate against file extent
BD82 LDA (work_ae),y ; Read low nibble of starting address
BD84 AND #&f0 ; Mask high nibble (top 4 bits)
BD86 BEQ loop_dump_line ; Aligned (high nibble zero): skip the header print
BD88 JSR print_dump_header ; Print 'Address: 00 01 ... 0F: ASCII data' header
fall through ↓

*DUMP per-line read loop

Body of cmd_dump's outer line loop. Calls abort_if_escape, then reads up to 16 bytes from the open file via OSBGET into the line buffer at (work_ae). On EOF mid-line, breaks to clean-up; on a full line, falls through to the formatting and print stage. Reachable from the alignment branch at &BD54 and the per-line tail at &BE29.

BD8B .loop_dump_line←2← BD86 BEQ← BE2B JMP
JSR abort_if_escape ; Test escape and abort if pressed
BD8E LDA #&ff ; A=&FF: count counter starts here so first INC -> 0
BD90 STA osword_flag ; Save counter (-1)
BD92 .loop_read_dump_byte←1← BDA1 BNE
LDY ws_page ; Y = file handle
BD94 JSR osbget ; Read one byte via OSBGET (C set on EOF)
BD97 BCS done_check_dump_eof ; EOF: finish off this line then exit
BD99 INC osword_flag ; Increment count counter
BD9B LDY osword_flag ; Y = current count (also buffer offset)
BD9D STA (work_ae),y ; Store byte in 16-byte line buffer at (work_ae)+Y
BD9F CPY #&0f ; Done all 16 bytes?
BDA1 BNE loop_read_dump_byte ; No: read next byte
BDA3 CLC ; C clear: not EOF (clean line)
BDA4 .done_check_dump_eof←1← BD97 BCS
PHP ; Save the EOF/clean flag
BDA5 LDA osword_flag ; Reload counter byte
BDA7 BPL done_check_boundary ; Bit 7 clear (counter is 0..&7F): bytes were read
BDA9 LDX #&15 ; EOF and no bytes: clean up and exit
fall through ↓

Drain saved bytes off stack and close

Pulls X+1 bytes off the 6502 stack (clearing the temporary 21-byte buffer cmd_dump uses to render each line) and tail-jumps to close_ws_file. Reached from the in-line BPL at &BDAB and the fall-through tail at &BE2E.

On EntryXstack-byte count - 1 (caller sets it to &14 or &15)
BDAB .loop_pop_stack_buf←2← BDAD BPL← BE30 JMP
PLA ; Restore one stack byte
BDAC DEX ; Step
BDAD BPL loop_pop_stack_buf ; Loop while X >= 0 (22 pulls)
BDAF JMP close_ws_file ; Tail-jump to close_ws_file
BDB2 .done_check_boundary←1← BDA7 BPL
LDY #&10 ; Y=&10: read displayed-address byte 0
BDB4 LDA (work_ae),y ; Read low byte
BDB6 AND #&f0 ; Top nibble
BDB8 BNE done_start_dump_addr ; Non-zero: not a 256-byte boundary, skip header
BDBA JSR print_dump_header ; Boundary: print column header
BDBD .done_start_dump_addr←1← BDB8 BNE
LDY #&13 ; Y=&13: highest byte of 4-byte address
BDBF .loop_print_addr_byte←1← BDC9 BNE
LDA (work_ae),y ; Read address byte (highest first)
BDC1 PHA ; Save it (print_hex_byte clobbers A)
BDC2 JSR print_hex_byte ; Print as 2 hex digits
BDC5 PLA ; Restore A
BDC6 DEY ; Step backwards
BDC7 CPY #&0f ; Reached low byte (offset &0F)?
BDC9 BNE loop_print_addr_byte ; No: continue printing
BDCB INY ; Y=&10: low byte of address
BDCC CLC ; Clear C
BDCD ADC #&10 ; Bump address by 16 bytes for next line
BDCF PHP ; Save C from the add
BDD0 .loop_inc_dump_addr←1← BDDB BNE
PLP ; Restore C from previous step
BDD1 STA (work_ae),y ; Store updated address byte
BDD3 INY ; Step Y up
BDD4 LDA (work_ae),y ; Read next byte
BDD6 ADC #0 ; Add carry from below
BDD8 PHP ; Save C
BDD9 CPY #&14 ; Done all 4 bytes (Y=&14)?
BDDB BNE loop_inc_dump_addr ; No: continue propagating
BDDD PLP ; Restore final C
BDDE JSR print_inline ; Print ' : ' separator before hex byte field
BDE1 EQUS " : "
BDE4 LDY #0 ; Y=0: start of buffer
BDE6 LDX osword_flag ; X = byte counter (-1 initially, INC'd to 0..&0F)
BDE8 .loop_print_dump_hex←1← BDF3 BPL
LDA (work_ae),y ; Read byte from buffer
BDEA JSR print_hex_and_space ; Print as hex + space
fall through ↓

*DUMP per-column advance and end-of-line check

INY (next buffer offset), CPY #&10. End -> done_print_separator. Otherwise DEX (decrement byte counter); BPL loop_print_dump_hex to print the next byte. Single caller (the BPL at &BDFC after short-line padding).

On EntryXremaining bytes - 1
Ybuffer offset
BDED .loop_next_dump_col←1← BDFE JMP
INY ; Step buffer offset
BDEE CPY #&10 ; Done all 16?
BDF0 BEQ done_print_separator ; Yes: print separator before ASCII field
BDF2 DEX ; Step counter (Y was off-by-one from line read)
BDF3 BPL loop_print_dump_hex ; Have a real byte? Print it
BDF5 PHY ; End of partial line: pad with 3 spaces
BDF6 JSR print_inline ; Print ' ' inline
BDF9 EQUS " "
BDFC NOP ; NOP -- bit-7 terminator + harmless resume opcode
BDFD PLY ; Restore Y
BDFE JMP loop_next_dump_col ; Continue padding the rest of the hex column
BE01 .done_print_separator←1← BDF0 BEQ
DEX ; Counter has finished -- step it once more for the ASCII test
BE02 JSR print_inline ; Print ': ' inline (ASCII field separator)
BE05 EQUS ": "
BE07 LDY #0 ; Y=0: rewind to start of line buffer
BE09 JSR inx16 ; Skip 16 padding spaces if needed (inx16)
BE0C .loop_print_dump_ascii←1← BE23 BPL
LDA (work_ae),y ; Read line buffer byte
BE0E AND #&7f ; Mask off bit 7 (DEL/inverted)
BE10 CMP #' ' ; Below ' '? (control char)
BE12 BCS done_test_del ; Yes: skip to substitution
BE14 .skip_non_printable←1← BE18 BEQ
LDA #'.' ; Substitute '.' for non-printables
BE16 .done_test_del←1← BE12 BCS
CMP #&7f ; Compare with DEL
BE18 BEQ skip_non_printable ; Equal: also non-printable, substitute '.'
BE1A JSR osasci ; Print the (possibly substituted) character
BE1D INY ; Step Y
BE1E CPY #&10 ; Done 16 chars?
BE20 BEQ done_end_dump_line ; Yes: end this line
BE22 DEX ; Step counter back
BE23 BPL loop_print_dump_ascii ; Loop while X >= 0
BE25 .done_end_dump_line←1← BE20 BEQ
JSR osnewl ; Print newline at end of line
BE28 PLP ; Restore EOF flag
BE29 BCS done_dump_eof ; EOF: tidy up and exit
BE2B JMP loop_dump_line ; More to dump: jump to next line
BE2E .done_dump_eof←1← BE29 BCS
LDX #&14 ; X=&14: balance the loop_pop_stack_buf counter
BE30 JMP loop_pop_stack_buf ; Tail-jump to clean up the 21-byte stack buffer and close the file

Print hex dump column header line

Outputs the starting address followed by 16 hex column numbers (00-0F), each separated by a space. Provides the column alignment header for *Dump output.

On ExitA, X, Yclobbered (print_hex_byte + OSASCI loop)
BE33 .print_dump_header←2← BD88 JSR← BDBA JSR
LDA (work_ae),y ; Read low nibble of starting address from (work_ae),Y
BE35 PHA ; Save it (we'll print it 16 times incrementing each iteration)
BE36 JSR print_inline ; Print '<CR>Address : ' header via inline string
BE39 EQUS &0D, "Address : " ; *Dump column header
BE45 LDX #&0f ; X=&0F: print 16 column-number digits
BE47 PLA ; Pull the starting low nibble back into A
BE48 .loop_print_hex_row←1← BE51 BPL
JSR print_hex_and_space ; Print A as two hex digits + space
BE4B SEC ; Set C ready for the increment
BE4C ADC #0 ; A += 1 (column index increments, with C set on entry)
BE4E AND #&0f ; Wrap to nibble (0..15)
BE50 DEX ; Step column counter
BE51 BPL loop_print_hex_row ; Loop while X >= 0 (16 iterations)
BE53 JSR print_inline ; Print ': ASCII data<CR><CR>' trailer via inline
BE56 EQUS ": ASCII data", &0D, &0D ; *Dump trailer
BE67 NOP ; NOP -- bit-7 terminator + harmless resume opcode
BE68 RTS ; Return

Print hex byte followed by space

Saves A, prints it as a 2-digit hex value via print_hex_byte, outputs a space character, then restores A from the stack. Used by cmd_dump and print_dump_header for column-aligned hex output.

On EntryAbyte value to print
BE69 .print_hex_and_space←2← BDEA JSR← BE48 JSR
PHA ; Save A so the caller can re-use the value
BE6A JSR print_hex_byte ; Print A as two hex digits
BE6D LDA #' ' ; A=' ': trailing column separator
BE6F JSR osasci ; Print the space via OSASCI
BE72 .done_print_hex_space
PLA ; Restore caller's A
BE73 RTS ; Return

Parse hex address for dump range

Reads up to 4 hex digits from the command line into a 4-byte accumulator, stopping at CR or space. Each digit shifts the accumulator left by 4 bits before ORing in the new nybble.

On EntryYcurrent command-line offset
On ExitYadvanced past the parsed digits
Afirst non-hex character (CR or space)
BE74 .parse_dump_range←2← BEE4 JSR← BF70 JSR
TYA ; Move command-line offset Y into A for the X copy
BE75 TAX ; X = current command-line offset (live cursor)
BE76 LDA #0 ; A=0: zero-fill value
BE78 TAY ; Y=0: accumulator index
BE79 .loop_clear_hex_accum←1← BE7E BNE
STA (work_ae),y ; Zero accumulator byte at (work_ae)+Y
BE7B INY ; Step accumulator
BE7C CPY #4 ; Done all 4 bytes?
BE7E BNE loop_clear_hex_accum ; No: continue clearing
fall through ↓

*DUMP / *LIST hex-address parser per-character body

Reload command-line offset from X, INX (step cursor), TAY (use as indirect index), read (os_text_ptr),Y. Branches: CR -> done; space -> end of token; otherwise validate hex digit and shift it into the 4-byte accumulator. Single caller (the BNE retry at &BE95).

On EntryXcurrent command-line offset
BE80 .loop_parse_hex_digit←1← BEC7 JMP
TXA ; Reload command-line offset
BE81 INX ; Step cursor
BE82 TAY ; Y = stepped cursor (for the indirect read)
BE83 LDA (os_text_ptr),y ; Read next command-line byte
BE85 CMP #&0d ; CR? (end of address)
BE87 BEQ done_test_hex_space ; Yes: range parsed -- exit via space-skip
BE89 CMP #' ' ; Space?
BE8B BEQ done_test_hex_space ; Yes: also a separator -- exit
BE8D CMP #'0' ; Below '0'?
BE8F BCC error_bad_hex_value ; Yes: not hex -- raise 'Bad hex'
BE91 CMP #':' ; Above '9'?
BE93 BCC done_mask_hex_digit ; No: it's '0'-'9' -- skip the letter handling
BE95 AND #&5f ; Force uppercase via AND #&5F
BE97 ADC #&b8 ; Add &B8: 'A' (=&41) becomes &F9 with C set; 'F' becomes &FE; this maps 'A'-'F' to &FA-&FF in C
BE99 BCS error_bad_hex_value ; Carry out of ADC: digit was below 'A' -> bad hex
BE9B CMP #&fa ; Below &FA? (i.e. before 'A' in mapped range)
BE9D BCC error_bad_hex_value ; Yes (out of [&FA,&FF]): bad hex
BE9F .done_mask_hex_digit←1← BE93 BCC
AND #&0f ; Keep low nibble (0-15)
BEA1 PHA ; Push the new nibble
BEA2 TXA ; Push X (current command-line offset)
BEA3 PHA ; Preserve on stack
BEA4 LDX #4 ; X=4: rotate the 4-byte accumulator left 4 times
BEA6 .loop_shift_nibble←1← BEBC BNE
LDY #0 ; Y=0: byte index for the rotate
BEA8 TYA ; A=0 (and C clear from TYA's flags)
BEA9 .loop_rotate_hex_accum←1← BEB5 BNE
PHA ; Save A onto stack so we can use PHP/PLP to round-trip carry through the rotate
BEAA PLP ; Pull flags (effectively C clear from the TYA above; on later iterations C carries the bit shifted out)
BEAB LDA (work_ae),y ; Read next accumulator byte
BEAD ROL ; Shift in C from below, shift out top bit to C
BEAE STA (work_ae),y ; Write back
BEB0 PHP ; Save the new C
BEB1 PLA ; Pull A back (PHA earlier)
BEB2 INY ; Step accumulator byte
BEB3 CPY #4 ; Done all 4 bytes?
BEB5 BNE loop_rotate_hex_accum ; No: rotate next byte
BEB7 PHA ; PHA/PLP: bring saved C into flag register
BEB8 PLP ; C = overflow bit
BEB9 BCS error_hex_overflow ; C set: a bit fell off the top -- overflow
BEBB DEX ; Step rotate counter
BEBC BNE loop_shift_nibble ; Loop while X != 0 (4 rotates total)
BEBE PLA ; Pull saved X (command-line offset)
BEBF TAX ; Restore X
BEC0 PLA ; Pull saved nibble into A
BEC1 LDY #0 ; Y=0: low byte of accumulator
BEC3 ORA (work_ae),y ; OR new nibble into accumulator[0]
BEC5 STA (work_ae),y ; Write back
BEC7 JMP loop_parse_hex_digit ; Loop for next hex digit
BECA .error_hex_overflow←1← BEB9 BCS
PLA ; Discard saved nibble
BECB PLA ; Discard saved X
BECC SEC ; Set C: signal overflow to caller
BECD RTS ; Return with C=1
BECE .error_bad_hex_value←3← BE8F BCC← BE99 BCS← BE9D BCC
JSR close_ws_file ; Close the dump file before raising the error
BED1 JMP err_bad_hex ; Raise 'Bad hex' error; never returns
BED4 .loop_skip_hex_spaces←1← BED9 BEQ
INY ; Step past current space
BED5 .done_test_hex_space←2← BE87 BEQ← BE8B BEQ
LDA (os_text_ptr),y ; Read next byte
BED7 CMP #' ' ; Still a space?
BED9 BEQ loop_skip_hex_spaces ; Yes: keep skipping
BEDB CLC ; Clear C: signal success
BEDC RTS ; Return

Initialise dump buffer and parse address range

Parses the start and end addresses from the command line via parse_dump_range. If no end address is given, defaults to the file extent. Validates both addresses against the file size, raising 'Outside file' if either exceeds the extent.

On EntryYcommand-line offset of the address arguments
BEDD .init_dump_buffer←1← BD7F JSR
INX ; Step Y past the *Dump command name into the argument
BEDE STX work_ae ; Save the cursor offset
BEE0 LDX #1 ; Set bit 0 of addr_work to 1 -- 'mode' flag for parse_dump_range below
BEE2 STX addr_work ; Save mode flag
BEE4 JSR parse_dump_range ; Parse the start address (max 4 hex digits)
BEE7 BCS error_outside_file ; Overflow: too many digits
BEE9 TYA ; Save current Y (cursor after start address)
BEEA PHA ; Push it
BEEB LDY ws_page ; Y = file handle saved in ws_page
BEED LDX #&aa ; X=&AA: zero-page address for OSARGS result
BEEF LDA #2 ; A=2: OSARGS sub-fn 2 = read sequential file extent
BEF1 JSR osargs ; Get file size into 4 bytes at &AA Get length of file into zero page address X (A=2)
BEF4 LDY #3 ; Y=3: compare 4-byte values (high to low)
BEF6 .loop_cmp_file_length←1← BEFE BPL
LDA osword_flag,y ; Read file size byte at &AA+Y
BEF9 CMP (work_ae),y ; Compare with parsed start address (work_ae+Y)
BEFB BNE done_check_outside ; Mismatch: branch decides which is bigger
BEFD DEY ; Step to next byte
BEFE BPL loop_cmp_file_length ; Loop while Y >= 0 (covers indices 3, 2, 1, 0)
BF00 BMI done_advance_start ; All bytes equal: start = extent (allowed); jump to the post-validation path
BF02 .done_check_outside←1← BEFB BNE
BCC error_outside_file ; C clear: parsed_start > file_size -- reject
BF04 LDY #&ff ; Y=&FF: signal 'no copy needed' to the loop below
BF06 BNE done_advance_start ; Always taken: skip directly to advance phase
BF08 .error_outside_file←2← BEE7 BCS← BF02 BCC
JSR close_ws_file ; Close the file before raising
BF0B LDA #&b7 ; A=&B7: 'Outside file' error code
BF0D JSR error_inline ; Raise via inline string; never returns
BF10 EQUS "Outside file", &00 ; *Dump range error
BF1D .loop_copy_osword_data←1← BF25 BNE
.loop_copy_start_addr←1← BF25 BNE
LDA (work_ae),y ; Copy file-extent byte from osword_flag to (work_ae)
BF1F STA osword_flag,y ; Store it (used as default end address)
BF22 .done_advance_start←2← BF00 BMI← BF06 BNE
INY ; Step Y
BF23 CPY #4 ; Done all 4 bytes?
BF25 BNE loop_copy_osword_data ; No: continue copying
BF27 LDX #&aa ; X=&AA: zero-page source for the OSARGS write-back
BF29 LDY ws_page ; Y = file handle
BF2B LDA #1 ; A=1: OSARGS sub-fn 1 = write sequential file pointer
BF2D JSR osargs ; Set the file's read pointer to the parsed start Write sequential file pointer from zero page address X (A=1)
BF30 PLA ; Pull saved cursor offset
BF31 TAY ; Restore into Y
BF32 LDA (os_text_ptr),y ; Read next command-line byte
BF34 CMP #&0d ; CR (end of args)?
BF36 BNE done_parse_disp_base ; No: there's a second arg -- handle below
BF38 LDY #1 ; Y=1: copy os_text_ptr (2 bytes) to work_ae as a displacement-base hint
BF3A .loop_copy_osfile_ptr←1← BF40 BPL
LDA os_text_ptr,y ; Read os_text_ptr+Y
BF3D STA (work_ae),y ; Save in work_ae+Y
BF3F DEY ; Step backwards
BF40 BPL loop_copy_osfile_ptr ; Loop while Y >= 0
BF42 LDA #osfile_read_catalogue_info ; A=5: OSFILE sub-fn 5 = read catalogue info
BF44 LDX work_ae ; X = filename pointer low (work_ae)
BF46 LDY addr_work ; Y = filename pointer high (addr_work)
BF48 JSR osfile ; Read load address into work_ae+0..3 osfile: read catalogue info
BF4B LDY #2 ; Y=2: shift 3 bytes down 2 positions to drop the first 2 bytes (action code + a flag)
BF4D .loop_shift_osfile_data←1← BF58 BNE
LDA (work_ae),y ; Read source byte
BF4F DEY ; Y -= 2 (destination)
BF50 DEY ; Continue decrement
BF51 STA (work_ae),y ; Store at destination
BF53 INY ; Y += 3 to advance to next source
BF54 INY ; (continued)
BF55 INY ; (continued)
BF56 CPY #6 ; Done 6 bytes shifted?
BF58 BNE loop_shift_osfile_data ; No: continue
BF5A DEY ; Y -= 2: position at high byte of load address
BF5B DEY ; Y=4: check from buf[4] downward
BF5C .loop_check_ff_addr←1← BF63 BNE
LDA (work_ae),y ; Read load-address byte at Y
BF5E CMP #&ff ; Is it &FF (signals no real load address)?
BF60 BNE done_add_disp_base ; No: have a real load address; add it as displacement
BF62 DEY ; Yes: step back to next higher byte
BF63 BNE loop_check_ff_addr ; Loop until Y=0
BF65 LDY #3 ; All four bytes were &FF: zero out the load address
BF67 LDA #0 ; A=0
BF69 .loop_zero_load_addr←1← BF6C BPL
STA (work_ae),y ; Zero work_ae+Y
BF6B DEY ; Step backwards
BF6C BPL loop_zero_load_addr ; Loop while Y >= 0
BF6E BMI done_add_disp_base ; Always taken (after BPL drops out): skip second-arg path
BF70 .done_parse_disp_base←1← BF36 BNE
JSR parse_dump_range ; Parse end-address argument
BF73 BCC done_add_disp_base ; Success: continue with displacement-add
BF75 JSR close_ws_file ; Parse error: close file then raise 'Bad address'
BF78 LDA #&fc ; A=&FC: 'Bad address' error code
BF7A JSR error_bad_inline ; Raise; never returns
BF7D EQUS "address", &00
BF85 .done_add_disp_base←3← BF60 BNE← BF6E BMI← BF73 BCC
LDY #0 ; Y=0: start of work_ae
BF87 LDX #4 ; X=4: 4-byte add
BF89 CLC ; Clear C for the add
BF8A .loop_add_disp_bytes←1← BF94 BNE
LDA (work_ae),y ; Read low byte of address from (work_ae)+Y
BF8C ADC osword_flag,y ; Add osword_flag+Y (low byte of length, with carry propagating)
BF8F STA osword_flag,y ; Store sum back to osword_flag+Y
BF92 INY ; Advance to next byte
BF93 DEX ; Decrement byte counter
BF94 BNE loop_add_disp_bytes ; Loop until 4 bytes added
BF96 LDY #&14 ; Y=&14: target offset = workspace+&13 (top of end-addr field, stored hi-byte-first)
BF98 LDX #3 ; X=3: source = osword_flag+3 (top byte of sum)
BF9A .loop_store_disp_addr←1← BFA0 BPL
DEY ; Pre-decrement Y (so first store is to offset &13)
BF9B LDA osword_flag,x ; Read sum byte from osword_flag+X
BF9D STA (work_ae),y ; Store at (work_ae)+Y
BF9F DEX ; Decrement source index
BFA0 BPL loop_store_disp_addr ; Loop until X wraps below 0
BFA2 RTS ; Return

Close file handle stored in workspace

Loads the file handle from ws_page and closes it via OSFIND with A=0.

On ExitA, X, Yclobbered (OSFIND)
BFA3 .close_ws_file←5← BD5C JSR← BDAF JMP← BECE JSR← BF08 JSR← BF75 JSR
LDY ws_page ; Y = saved file handle from ws_page
BFA5 LDA #osfind_close ; A=0: OSFIND close
BFA7 JMP osfind ; Tail-call OSFIND to close the handle osfind: close one or all files

Open file for reading via OSFIND

Computes the filename address from the command text pointer plus the Y offset, calls OSFIND with A=&40 (open for input). Stores the handle in ws_page. Raises 'Not found' if the returned handle is zero.

On EntryYoffset within the command line of the filename to open
On ExitA, X, Yclobbered
BFAA .open_file_for_read←1← BD73 JSR
PHP ; Save flags so caller's NZC survive
BFAB TYA ; Move command-line offset Y into A for the add
BFAC CLC ; Clear C for the 16-bit add
BFAD ADC os_text_ptr ; A = os_text_ptr_lo + Y (filename address low byte)
BFAF PHA ; Push it (we need to restore os_text_ptr after OSFIND)
BFB0 TAX ; Move filename low into X (OSFIND wants the address in X/Y)
BFB1 LDA #0 ; A=0: zero high byte before the carry-add
BFB3 ADC os_text_ptr_hi ; Add os_text_ptr_hi with carry from the low add
BFB5 PHA ; Push filename high byte for the restore
BFB6 TAY ; Move filename high into Y
BFB7 LDA #osfind_open_input ; A=&40: OSFIND open-for-input mode
BFB9 JSR osfind ; Open the file; returns handle in A (zero on failure) osfind: open file for input
BFBC TAY ; Copy returned handle into Y (also sets Z if zero)
BFBD STA ws_page ; Stash the handle in ws_page for later close
BFBF BNE restore_text_ptr ; Non-zero: open succeeded, skip error path
BFC1 LDA #&d6 ; A=&D6: 'Not found' error code
BFC3 JSR error_inline ; Raise the error with the inline string below; never returns
BFC6 EQUS "Not found", &00
BFD0 .restore_text_ptr←1← BFBF BNE
PLA ; Restore the saved filename high byte into os_text_ptr_hi -- but wait, this writes the FILENAME address into os_text_ptr; the caller intentionally moves os_text_ptr to scan past the filename below
BFD1 STA os_text_ptr_hi ; Store as os_text_ptr_hi
BFD3 PLA ; Restore filename low byte into os_text_ptr_lo (so (os_text_ptr) now points at the filename)
BFD4 STA os_text_ptr ; Store as os_text_ptr lo
BFD6 LDY #0 ; Y=0: scan from start of filename
BFD8 .loop_skip_filename←1← BFE1 BNE
INY ; Step to next byte
BFD9 LDA (os_text_ptr),y ; Read filename byte
BFDB CMP #&0d ; Hit CR? End of command line
BFDD BEQ done_skip_filename ; Yes: filename ended at CR (no trailing spaces)
BFDF CMP #' ' ; Hit space? End of filename
BFE1 BNE loop_skip_filename ; No (still inside filename): keep scanning
BFE3 .loop_skip_fn_spaces←1← BFE8 BEQ
INY ; Step past spaces
BFE4 LDA (os_text_ptr),y ; Read next byte
BFE6 .hazel_minus_1a←1Used as index base by← AC7C LDA
CMP #' ' ; Still a space?
BFE8 .hazel_idx_bases
.skip_fn_space_cont
BEQ loop_skip_fn_spaces ; Yes: keep skipping
BFEA .done_skip_filename←1← BFDD BEQ
PLP ; Done: Y points just past the filename and any spaces
BFEB RTS ; Restore caller's flags

Increment X 16 times

JSR inx8, then fall through into inx8 for a second pass — 16 INX instructions in total.

On EntryXvalue to advance
On ExitXinput + 16
A, Ypreserved
BFEC .inx16←3← 9EDA JSR← AC54 JSR← BE09 JSR
JSR inx8 ; JSR inx8; on RTS, fall through into inx8 for the second 8
fall through ↓

Increment X 8 times

JSR inx4, then fall through into inx4 for a second pass — 8 INX instructions in total.

On EntryXvalue to advance
On ExitXinput + 8
A, Ypreserved
BFEF .inx8←1← BFEC JSR
JSR inx4 ; JSR inx4; on RTS, fall through into inx4 for the second 4
fall through ↓

Increment X 4 times

Four consecutive INX instructions then RTS. Building block for inx8 and inx16 via JSR/fall-through chaining.

On EntryXvalue to advance
On ExitXinput + 4
A, Ypreserved
N, Z FLAGSreflect new X
BFF2 .inx4←1← BFEF JSR
INX ; X += 4
BFF3 INX ; (continued)
BFF4 INX ; (continued)
BFF5 INX ; (continued)
BFF6 RTS ; Return — total X advance depends on the entry: 4 (inx4), 8 (inx8), or 16 (inx16)

Read current filing-system number via OSARGS

New in 4.26. Reads the current filing-system number with OSARGS (A=Y=0) and compares it with 5 (NetFS), returning Z=1 (EQ) when NetFS is current. osword_0e_dispatch uses this to service the clock OSWORD only under NetFS, leaving it for other filing systems otherwise. Occupies ROM-tail space that was &FF padding in 4.25; its final bytes double as the hazel_minus_2 / hazel_minus_1 indexing-base anchors.

BFF7 .fs_num_via_osargs←1← A89A JSR
PHA ; Save A
BFF8 TYA ; A = Y = 0 (OSARGS: read current FS number)
BFF9 JSR osargs ; Call OSARGS Get length of file into zero page address X (A=2)
BFFC PLX ; Restore X
BFFD .fs_num_check
CMP #5 ; Current FS = 5 (NetFS)? (sets Z)
BFFF .hazel_minus_1←2Used as index base by← A9F3 LDA← AA08 STA
RTS ; Return with flags set