Acorn ANFS 4.24

Updated 25 Jul 2026

← All Acorn NFS and Acorn ANFS versions

ANFS ROM 4.24 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.24"
8018 EQUB &00 ; NUL terminator
8019 .copyright
EQUB &00 ; NUL preceding copyright string
801A .copyright_string
EQUS "(C)1986 Acorn"
8027 EQUB &00 ; NUL terminator
8028 EQUB &24, &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← 9043 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← 8A0C 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← 89A3 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← 8952 JMP← 89BC 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← 860F 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 l8494,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 .sub_c8492
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 common data-receive path at &81AF. 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: machine-type query

Sets up the response buffer for a machine-type query immediate operation (4-byte response: machine code + version digits). Falls through to set_rx_buf_len_hi to configure the buffer dimensions, then branches to set_tx_reply_flag. The machine-type query (&88) just returns fixed identity data, so it is serviced inline here -- not deferred like the execute-class operations &83-&87.

84C7 .rx_imm_machine_type
LDA #&2e ; Buffer length hi = 1
84C9 .set_rx_buf_len_hi
STA port_ws_offset ; Set buffer length hi
84CB LDA #&0d ; Buffer length lo = &FC
84CD STA rx_buf_offset ; Set buffer length lo
84CF LDA #2 ; Buffer start lo = &EE
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
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
84F2 EQUB &A9, &01, &85, &A3, &A9, ; Unreferenced code-form &FC, &85, &A2, &A0, &02, ; fragment: builds the &B9, &27, &80, &99, &37, ; machine-type response &0D, &88, &10, &F7, &AD, ; buffer (len &1FC; copies &71, &0D, &8D, &36, &0D, ; 3-byte machine identity &A9, &3A, &85, &A4, &A9, ; from &8027 to &0D37; buffer &0C, &85, &A5, &D0, &C4, ; -> &0C3A) then branches &C6, &BB, &9D, &9D, &9D, ; into rx_imm_machine_type; &E0, &E0, &F1 ; not reached by traced flow
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 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_machine_type (&84D4), tx_ctrl_proc (&87A4).

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

Purpose unknown. Unreferenced, unreachable.

863A .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.

864A .tx_setup_from_txcb←3← 9BC1 JSR← A94A JMP← AC3E JSR
TXA ; Save X on stack
864B PHA ; Push X
864C LDY #2 ; Y=2: TXCB offset for dest station
864E LDA (nmi_tx_block),y ; Load dest station from TX control block
8650 STA tx_dst_stn ; Store to TX scout buffer
8653 INY ; Advance to destination network byte
8654 LDA (nmi_tx_block),y ; Load dest network from TX control block
8656 STA tx_dst_net ; Store to TX scout buffer
8659 LDY #0 ; Y=0: first byte of TX control block
865B LDA (nmi_tx_block),y ; Load control/flag byte
865D BMI tx_imm_op_setup ; Bit7 set: immediate operation ctrl byte
865F JMP tx_bad_ctrl_error ; Bit7 clear: normal data transfer
8662 .tx_imm_op_setup←1← 865D BMI
STA tx_ctrl_byte ; Store control byte to TX scout buffer
8665 TAX ; X = control byte for range checks
8666 INY ; Y=1: port byte offset
8667 LDA (nmi_tx_block),y ; Load port byte from TX control block
8669 STA tx_port ; Store port byte to TX scout buffer
866C BNE tx_line_idle_check ; Port != 0: skip immediate op setup
866E CPX #&83 ; Ctrl < &83: PEEK/POKE need address calc
8670 BCS tx_ctrl_range_check ; Ctrl >= &83: skip to range check
8672 SEC ; Init borrow for 4-byte subtract
8673 PHP ; Save carry on stack for loop
8674 LDY #8 ; Y=8: high pointer offset in TXCB
8676 .calc_peek_poke_size←1← 868A BCC
LDA (nmi_tx_block),y ; Load TXCB[Y] (end addr byte)
8678 DEY ; Y -= 4: back to start addr offset
8679 DEY ; (continued)
867A DEY ; (continued)
867B DEY ; (continued)
867C PLP ; Restore borrow from stack
867D SBC (nmi_tx_block),y ; end - start = transfer size byte
867F STA tx_data_start,y ; Store result to tx_data_start
8682 INY ; Y += 5: advance to next end byte
8683 INY ; (continued)
8684 INY ; (continued)
8685 INY ; (continued)
8686 INY ; (continued)
8687 PHP ; Save borrow for next byte
8688 CPY #&0c ; Done all 4 bytes? (Y reaches &0C)
868A BCC calc_peek_poke_size ; No: next byte pair
868C PLP ; Discard final borrow
868D .tx_ctrl_range_check←1← 8670 BCS
CPX #&81 ; Ctrl < &81: not an immediate op
868F BCC tx_bad_ctrl_error ; Below range: normal data transfer
8691 .check_imm_range
CPX #&89 ; Ctrl >= &89: out of immediate range
8693 BCS tx_bad_ctrl_error ; Above range: normal data transfer
8695 LDY #&0c ; Y=&0C: start of extra data in TXCB
8697 .copy_imm_params←1← 869F BCC
LDA (nmi_tx_block),y ; Load extra parameter byte from TXCB
8699 STA imm_param_base,y ; Copy to NMI shim workspace at &0D1A+Y
869C INY ; Next byte
869D CPY #&10 ; Done 4 bytes? (Y reaches &10)
869F BCC copy_imm_params ; No: continue copying
86A1 .tx_line_idle_check←1← 866C BNE
LDA #&20 ; A=&20: mask for SR2 INACTIVE bit
86A3 BIT econet_control23_or_status2 ; Test SR2 if line is idle
86A6 BNE tx_no_clock_error ; Line not idle: handle as line jammed
86A8 LDA #&fd ; A=&FD: high byte of timeout counter
86AA PHA ; Push timeout high byte to stack
86AB LDA #6 ; Scout frame = 6 address+ctrl bytes
86AD STA rx_ctrl ; Store scout frame length
86B0 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)
86B2 .inactive_poll
STA rx_remote_addr ; Save TX index
86B5 PHA ; Push timeout byte 1 on stack
86B6 PHA ; Push timeout byte 2 on stack
86B7 LDY #&e7 ; Y=&E7: CR2 value for TX prep (RTS|CLR_TX_ST|CLR_RX_ST|FC_TDRA|2_1_ BYTE|PSE)
86B9 .tx_irq_off←3← 86E8 BNE← 86ED BNE← 86F2 BNE
PHP ; Save interrupt state
86BA SEI ; Disable interrupts for ADLC access
86BB LDA #&40 ; A=&40: 'line inactive' status code
86BD STA l0d1c ; Store net poll-status byte
86C0 BIT disable_net_nmis ; INTOFF -- disable NMIs
86C3 LDA #4 ; A=&04: INACTIVE bit mask for SR2 test
86C5 BIT econet_control23_or_status2 ; Z = &04 AND SR2 -- tests INACTIVE
86C8 BEQ set_line_jammed ; INACTIVE not set -- re-enable NMIs and loop
86CA LDA econet_control1_or_status1 ; Read SR1 (acknowledge pending interrupt)
86CD BMI set_line_jammed ; N set: line still driven, report jammed
86CF LDA #&67 ; CR2=&67: CLR_TX_ST|CLR_RX_ST|FC_ TDRA|2_1_BYTE|PSE
86D1 STA econet_control23_or_status2 ; Write CR2: clear status, prepare TX
86D4 LDA #&10 ; A=&10: CTS mask for SR1 bit4
86D6 BIT econet_control1_or_status1 ; Test SR1 CTS present
86D9 BNE tx_prepare ; CTS set -- clock hardware detected, start TX
86DB .set_line_jammed←2← 86C8 BEQ← 86CD BMI
LDA #&2c ; A=&2C: 'line jammed' status code
86DD STA l0d1c ; Store net poll-status byte
86E0 .inactive_retry
BIT enable_net_nmis ; INTON -- re-enable NMIs (Master &FE3C)
86E3 PLP ; Restore interrupt state
86E4 TSX ; 3-byte timeout counter on stack
86E5 INC error_text,x ; Increment timeout counter byte 1
86E8 BNE tx_irq_off ; Not overflowed: retry INACTIVE test
86EA INC stack_page_2,x ; Increment timeout counter byte 2
86ED BNE tx_irq_off ; Not overflowed: retry INACTIVE test
86EF INC stack_page_3,x ; Increment timeout counter byte 3
86F2 BNE tx_irq_off ; Not overflowed: retry INACTIVE test
86F4 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)
86F6 .tx_bad_ctrl_error←3← 865F JMP← 868F BCC← 8693 BCS
LDA #&44 ; Error &44: control byte out of valid range
86F8 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.
86FA .tx_line_jammed←1← 86F4 BEQ
LDA #7 ; CR2=&07: FC_TDRA | 2_1_BYTE | PSE (abort TX)
86FC STA econet_control23_or_status2 ; Write CR2 to abort TX
86FF PLA ; Clean 3 bytes of timeout loop state
8700 PLA ; Pop saved register
8701 PLA ; Pop saved register
8702 LDA #&40 ; Error &40 = 'Line Jammed'
8704 BNE store_tx_error ; ALWAYS branch to shared error handler
8706 .tx_no_clock_error←1← 86A6 BNE
LDA #&43 ; Error &43 = 'No Clock'
8708 .store_tx_error←2← 86F8 BNE← 8704 BNE
LDY #0 ; Offset 0 = error byte in TX control block
870A STA (nmi_tx_block),y ; Store error code in TX CB byte 0
870C LDA #&80 ; &80 = TX complete flag
870E STA tx_complete_flag ; Signal TX operation complete
8711 PLA ; Restore X saved by caller
8712 TAX ; Move to X register
8713 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)
8714 .tx_prepare←1← 86D9 BNE
STY econet_control23_or_status2 ; Write CR2 = Y (&E7: RTS|CLR_TX_ST|CLR_RX_ST| FC_TDRA|2_1_BYTE|PSE)
8717 LDX #&44 ; CR1=&44: RX_RESET | TIE (TX active, TX interrupts enabled)
8719 STX econet_control1_or_status1 ; Write to ADLC CR1
871C LDX #<(nmi_tx_data) ; Next NMI handler address (low)
871E LDY #&87 ; High byte of NMI handler address
8720 STX nmi_jmp_lo ; Write NMI vector low byte directly
8723 STY nmi_jmp_hi ; Write NMI vector high byte directly
8726 SEC ; SEC: prepare carry for ROR into bit 7
8727 ROR prot_flags ; Rotate carry into bit 7 of prot_flags (Tube-claimed)
8729 LDA #&2c ; A=&2C: 'line jammed' status code
872B .tx_enable_nmis
STA l0d1c ; Store net poll-status byte
872E BIT enable_net_nmis ; INTON -- NMIs now fire for TDRA (Master &FE3C)
8731 LDA tx_port ; Load destination port number
8734 BNE setup_data_xfer ; Port != 0: standard data transfer
8736 LDY tx_ctrl_byte ; Port 0: load control byte for table lookup
8739 LDA l85c1,y ; Look up tx_flags from table
873C STA net_frame_flags ; Store operation flags
873F LDA c85b9,y ; Look up tx_length from table
8742 STA rx_ctrl ; Store expected transfer length
8745 LDA #&87 ; A=&87: high byte of tx_ctrl_* dispatch target
8747 PHA ; Push high byte for PHA/PHA/RTS dispatch
8748 LDA l872d,y ; Look up handler address low from table
874B PHA ; Push low byte for PHA/PHA/RTS dispatch
874C RTS ; RTS dispatches to control-byte handler
874D EQUB &A9, &03
874F 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)
8751 .tx_ctrl_peek
LDA #3 ; A=3: scout_status for PEEK op
8753 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)
8755 .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..&0D2D.
  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)
8757 .tx_ctrl_store_and_add←1← 8753 BNE
STA rx_port ; Store scout status
875A CLC ; Clear carry for 4-byte addition
875B PHP ; Save carry on stack
875C LDY #&0c ; Y=&0C: start at offset 12
875E .add_bytes_loop←1← 876B BCC
LDA tx_addr_base,y ; Load workspace address byte
8761 PLP ; Restore carry from previous byte
8762 ADC (nmi_tx_block),y ; Add TXCB address byte
8764 STA tx_addr_base,y ; Store updated address byte
8767 INY ; Next byte
8768 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.

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

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
87B6 .nmi_tx_data
LDY rx_remote_addr ; Load TX buffer index
87B9 BIT econet_control1_or_status1 ; SR1: V=bit6(TDRA), N=bit7(IRQ)
87BC .tx_fifo_write←1← 87D7 BMI
BVC tx_fifo_not_ready ; TDRA not set -- TX error
87BE LDA tx_dst_stn,y ; Load byte from TX buffer
87C1 STA econet_data_continue_frame ; Write to TX_DATA (continue frame)
87C4 INY ; Next TX buffer byte
87C5 LDA tx_dst_stn,y ; Load second byte from TX buffer
87C8 INY ; Advance TX index past second byte
87C9 STY rx_remote_addr ; Save updated TX buffer index
87CC STA econet_data_continue_frame ; Write second byte to TX_DATA
87CF CPY rx_ctrl ; Compare index to TX length
87D2 BCS tx_last_data ; Frame complete -- go to TX_LAST_DATA
87D4 BIT econet_control1_or_status1 ; Check if we can send another pair
87D7 BMI tx_fifo_write ; IRQ set -- send 2 more bytes (tight loop)
87D9 JMP nmi_rti ; Wait for next NMI
87DC .tx_error←1← 881F BEQ
LDA #&42 ; Error &42
87DE BNE tx_store_error ; Branch to store TX error
87E0 .tx_fifo_not_ready←1← 87BC BVC
LDA #&67 ; CR2=&67: clear status, return to listen
87E2 STA econet_control23_or_status2 ; Write CR2: clear status, idle listen
87E5 LDA #&41 ; Error &41 (TDRA not ready)
87E7 .tx_store_error←1← 87DE BNE
LDY tx_src_stn ; INTOFF: disable NMIs (Master &FE38)
87EA .delay_nmi_disable←1← 87ED BNE
PHA ; PHA/PLA delay loop (256 iterations for NMI disable)
87EB PLA ; PHA/PLA delay (~7 cycles each)
87EC INY ; Increment delay counter
87ED BNE delay_nmi_disable ; Loop 256 times for NMI disable
87EF 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.

87F2 .tx_last_data←1← 87D2 BCS
LDA #&3f ; CR2=&3F: TX_LAST_DATA | CLR_RX_ST | FLAG_IDLE | FC_TDRA | 2_1_BYTE | PSE
87F4 STA econet_control23_or_status2 ; Write to ADLC CR2
87F7 LDA #<(nmi_tx_switch_rx) ; Install TX->RX switch handler (low)
87F9 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
87FC .nmi_tx_complete
.nmi_tx_switch_rx
LDA #&82 ; Jump to error handler
87FE STA econet_control1_or_status1 ; Write CR1 to switch from TX to RX
8801 BIT net_frame_flags ; Test workspace flags
8804 BVC check_handshake_bit ; bit6 not set -- check bit0
8806 JMP tx_result_ok ; bit6 set -- TX completion
8809 .check_handshake_bit←1← 8804 BVC
LDA #1 ; A=1: mask for bit0 test
880B BIT net_frame_flags ; Test tx_flags bit0 (handshake)
880E BEQ install_reply_scout ; bit0 clear: install reply handler
8810 JMP handshake_await_ack ; bit0 set -- four-way handshake data phase
8813 .install_reply_scout←1← 880E BEQ
LDA #<(nmi_reply_scout) ; Install nmi_reply_scout (low)
8815 LDY #>(nmi_reply_scout) ; Install nmi_reply_scout (high)
8817 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).

881A .nmi_reply_scout
LDA #1 ; A=&01: AP mask for SR2
881C BIT econet_control23_or_status2 ; Test SR2 AP (Address Present)
881F BEQ tx_error ; No AP -- error
8821 LDA econet_data_continue_frame ; Read first RX byte (destination station)
8824 CMP tx_src_stn ; Compare to our station ID (workspace copy)
8827 BNE reject_reply ; Not our station -- error/reject
8829 LDA #<(nmi_reply_cont) ; Install reply-continuation handler (low)
882B 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.

882E .nmi_reply_cont
BIT econet_control23_or_status2 ; Read RX byte (destination station)
8831 BPL reject_reply ; No RDA -- error
8833 LDA econet_data_continue_frame ; Read destination network byte
8836 BNE reject_reply ; Non-zero -- network mismatch, error
8838 LDA #<(nmi_reply_validate) ; Next NMI handler address (low)
883A BIT econet_control1_or_status1 ; Test SR1 IRQ (N=bit7) -- more data ready?
883D BMI nmi_reply_validate ; IRQ set -- fall through to &8848
883F 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 &8773 takes them to tx_result_fail (which stores the error and returns to idle).

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

8842 .reject_reply←7← 8827 BNE← 8831 BPL← 8836 BNE← 8848 BPL← 8850 BNE← 8858 BNE← 885F 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).

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

888D .nmi_scout_ack_src
LDA tx_src_stn ; Load our station ID from workspace copy
8890 BIT econet_control1_or_status1 ; Test SR1 TDRA
8893 .tx_check_tdra_ready←1← 887B BVC
BVC data_tx_check_fifo ; TDRA not ready -- error
8895 STA econet_data_continue_frame ; Write our station to TX FIFO
8898 LDA #0 ; Write network=0 to TX FIFO
889A 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 &87EB (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).

889D .data_tx_begin←1← 8349 JMP
LDA #2 ; Test bit 1 of tx_flags
889F BIT net_frame_flags ; Check if immediate-op or data-transfer
88A2 BNE install_imm_data_nmi ; Bit 1 set: immediate op, use alt handler
88A4 LDA #<(nmi_data_tx_alt) ; Install nmi_data_tx alt-entry (low)
88A6 LDY #&88 ; Y=&88: high byte of nmi_data_tx
88A8 JMP set_nmi_vector ; Install and return via set_nmi_vector
88AB .install_imm_data_nmi←1← 88A2 BNE
LDA #<(nmi_data_tx_tube) ; Next NMI handler address (low)
88AD LDY #>(nmi_data_tx_tube) ; Next NMI handler address (high)
88AF 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 &87EB (used by data_tx_begin) skips the page-counter check and starts at the byte-count load.

88B2 .nmi_data_tx←1← 88BC BEQ
LDY port_buf_len_hi ; Y = buffer offset, resume from last position
88B4 BEQ data_tx_last ; No pages left: send final partial page
88B6 LDY port_buf_len ; Load remaining byte count
88B8 BEQ check_tdra_status ; Zero bytes left: skip to TDRA check
88BA .nmi_data_tx_alt
LDY port_buf_len ; Load remaining byte count (alt entry)
88BC BEQ nmi_data_tx ; Zero: loop back to top of handler
88BE .check_tdra_status←1← 88B8 BEQ
BIT econet_control1_or_status1 ; Test SR1 TDRA (V=bit6)
88C1 .data_tx_check_fifo←2← 8893 BVC← 88F1 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.
88C3 LDA acccon ; Save current ACCCON on stack
88C6 PHA ; Push ACCCON snapshot
88C7 LDA escapable ; Load desired ACCCON from workspace &97
88C9 STA acccon ; Set ACCCON for the upcoming buffer reads
88CC LDA (open_port_buf),y ; Write data byte to TX FIFO
88CE STA econet_data_continue_frame ; Write first byte of pair to FIFO
88D1 INY ; Advance buffer offset
88D2 BNE write_second_tx_byte ; No page crossing
88D4 DEC port_buf_len_hi ; Page crossing: decrement page count
88D6 BEQ frame_end_restore ; No pages left: send last data
88D8 INC open_port_buf_hi ; Increment buffer high byte
88DA .write_second_tx_byte←1← 88D2 BNE
LDA (open_port_buf),y ; Load second byte of pair
88DC STA econet_data_continue_frame ; Write second byte to FIFO
88DF INY ; Advance buffer offset
88E0 STY port_buf_len ; Save updated buffer position
88E2 BNE check_fifo_loop ; No page crossing
88E4 DEC port_buf_len_hi ; Page crossing: decrement page count
88E6 BEQ frame_end_restore ; No pages left: send last data
88E8 INC open_port_buf_hi ; Increment buffer high byte
88EA .check_fifo_loop←1← 88E2 BNE
PLA ; Pull saved ACCCON from stack
88EB STA acccon ; Restore caller's ACCCON between byte pairs
88EE .check_irq_loop
BIT econet_control1_or_status1 ; Test ADLC SR1 IRQ flag for next byte pair
88F1 BMI data_tx_check_fifo ; IRQ still set: more bytes to send
88F3 JMP nmi_rti ; IRQ cleared: return from NMI
88F6 .frame_end_restore←2← 88D6 BEQ← 88E6 BEQ
PLA ; Pull saved ACCCON (frame-end path)
88F7 STA acccon ; Restore caller's ACCCON before TX_LAST_DATA
88FA .data_tx_last←3← 88B4 BEQ← 892D BEQ← 8943 BEQ
LDA #&3f ; CR2=&3F: TX_LAST_DATA (close data frame)
88FC STA econet_control23_or_status2 ; Write CR2 to close frame
88FF LDA net_frame_flags ; Check tx_flags for next action
8902 BPL install_saved_handler ; Bit7 clear: error, install saved handler
8904 LDA #<(discard_reset_rx) ; Next NMI handler address (low)
8906 LDY #>(discard_reset_rx) ; Next NMI handler address (high)
8908 JMP set_nmi_vector ; Set NMI vector and return
890B .install_saved_handler←1← 8902 BPL
LDA saved_nmi_lo ; Load saved next handler low byte
890E LDY saved_nmi_hi ; Load saved next handler high byte
8911 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 &8848), 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.

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

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

8961 .nmi_final_ack
LDA #1 ; A=&01: AP mask
8963 BIT econet_control23_or_status2 ; Test SR2 AP
8966 BEQ tx_result_fail ; No AP -- error
8968 LDA econet_data_continue_frame ; Read dest station
896B CMP tx_src_stn ; Compare to our station (workspace copy)
896E BNE tx_result_fail ; Not our station -- error
8970 LDA #<(nmi_final_ack_net) ; Next NMI handler address (low)
8972 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
8975 .nmi_final_ack_net
BIT econet_control23_or_status2 ; Test SR2 RDA
8978 BPL tx_result_fail ; No RDA -- error
897A LDA econet_data_continue_frame ; Read dest network
897D BNE tx_result_fail ; Non-zero -- network mismatch, error
897F LDA #&89 ; Install nmi_final_ack_validate handler
8981 BIT econet_control1_or_status1 ; Test SR1 IRQ -- more data ready?
8984 BMI nmi_final_ack_validate ; IRQ set -- fall through to validate
8986 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.

8989 .nmi_final_ack_validate←1← 8984 BMI
BIT econet_control23_or_status2 ; Test SR2 RDA
898C BPL tx_result_fail ; No RDA -- error
898E LDA econet_data_continue_frame ; Read source station
8991 CMP tx_dst_stn ; Compare to TX dest station (&0D20)
8994 BNE tx_result_fail ; Mismatch -- error
8996 LDA econet_data_continue_frame ; Read source network
8999 CMP tx_dst_net ; Compare to TX dest network (&0D21)
899C BNE tx_result_fail ; Mismatch -- error
899E LDA net_frame_flags ; Load TX flags for next action
89A1 BPL check_fv_final_ack ; bit7 clear: no data phase
89A3 JMP install_data_rx_handler ; Install data RX handler
89A6 .check_fv_final_ack←1← 89A1 BPL
LDA #2 ; A=&02: FV mask for SR2 bit1
89A8 BIT econet_control23_or_status2 ; Test SR2 FV -- frame must be complete
89AB 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)
89AD .tx_result_ok←2← 82F7 JMP← 8806 JMP
LDA #0 ; A=0: success result code
89AF 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)
89B1 .tx_result_fail←11← 821D JMP← 8842 JMP← 8950 BPL← 8966 BEQ← 896E BNE← 8978 BPL← 897D BNE← 898C BPL← 8994 BNE← 899C BNE← 89AB 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)
89B3 .tx_store_result←2← 87EF JMP← 89AF BEQ
LDY #0 ; Y=0: index into TX control block
89B5 STA (nmi_tx_block),y ; Store result/error code at (nmi_tx_block),0
89B7 LDA #&80 ; A=&80: TX-complete signal for tx_complete_flag
89B9 STA tx_complete_flag ; Signal TX complete
89BC 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
89BF .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)
89C1 STA econet_control1_or_status1 ; Write CR1 to ADLC register 0
89C4 LDA #&1e ; CR4=&1E (via AC=1): 8-bit RX word length, abort extend enabled, NRZ encoding
89C6 STA econet_data_terminate_frame ; Write CR4 to ADLC register 3
89C9 LDA #0 ; CR3=&00 (via AC=1): no loop-back, no AEX, NRZ, no DTR
89CB 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
89CE .adlc_rx_listen←2← 83FA JSR← 89FE JMP
LDA #&82 ; CR1=&82: TX_RESET | RIE (TX in reset, RX interrupts enabled)
89D0 STA econet_control1_or_status1 ; Write to ADLC CR1
89D3 LDA #&67 ; CR2=&67: CLR_TX_ST | CLR_RX_ST | FC_TDRA | 2_1_BYTE | PSE
89D5 STA econet_control23_or_status2 ; Write to ADLC CR2
89D8 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
89D9 .wait_idle_and_reset
BIT econet_init_flag ; Check if Econet has been initialised
89DC BPL reset_enter_listen ; Not initialised: skip to RX listen
89DE .poll_nmi_idle←2← 89E3 BNE← 89EA BNE
LDA nmi_jmp_lo ; Read current NMI handler low byte
89E1 CMP #&9d ; Expected: &B3 (nmi_rx_scout low)
89E3 BNE poll_nmi_idle ; Not idle: spin and wait
89E5 LDA nmi_jmp_hi ; Read current NMI handler high byte
89E8 CMP #&80 ; Compare to &80 (line-idle threshold)
89EA BNE poll_nmi_idle ; Not idle: spin and wait
89EC LDA #&40 ; A=&40: 'line inactive' status code
89EE STA l0d1c ; Store net poll-status byte
89F1 BIT disable_net_nmis ; INTOFF: disable NMIs
89F4 LDA #0 ; A=0
89F6 STA tx_complete_flag ; TX not in progress
89F9 STA econet_init_flag ; Econet not initialised
89FC LDY #5 ; Y=5: service call workspace page
89FE .reset_enter_listen←1← 89DC 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.

8A01 .nmi_bootstrap_entry
BIT disable_net_nmis ; INTOFF: force /NMI high (clear NMI flip-flop)
8A04 PHA ; Save A
8A05 TYA ; Transfer Y to A
8A06 PHA ; Save Y (via A)
8A07 LDA #0 ; ROM bank 0 (patched during init for actual bank)
8A09 STA romsel ; Select Econet ROM bank via ROMSEL
8A0C 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.

8A0F .rom_set_nmi_vector
STY nmi_jmp_hi ; Store handler high byte at &0D0D
8A12 STA nmi_jmp_lo ; Store handler low byte at &0D0C
8A15 LDA romsel_copy ; Restore NFS ROM bank
8A17 STA romsel ; Page in via hardware latch
8A1A PLA ; Restore Y from stack
8A1B TAY ; Transfer ROM bank to Y
8A1C 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.

8A1D .nmi_return_inton
BIT enable_net_nmis ; INTON: guaranteed /NMI edge if ADLC IRQ asserted
8A20 RTI ; Return from interrupt
8A21 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.

8A23 .svc_dispatch_lo←1Used as index base by← 8E82 LDA
EQUB &0C ; &00: placeholder (never reached)
8A24 EQUB <(dispatch_rts - 1) ; &01: no-op (RTS only)
8A25 EQUB <(svc_dispatch_idx_2 - 1) ; &02: workspace claim helper (CMOS bit 0)
8A26 EQUB <(svc_2_priv_ws - 1) ; &03: svc &02: private workspace pages
8A27 EQUB <(svc_3_autoboot - 1) ; &04: svc &03: auto-boot
8A28 EQUB <(svc_4_star_command - 1) ; &05: svc &04: unrecognised *command
8A29 EQUB <(svc5_irq_check - 1) ; &06: svc &05: IRQ check
8A2A EQUB <(dispatch_rts - 1) ; &07: no-op (RTS only)
8A2B EQUB <(svc_7_osbyte - 1) ; &08: svc &07: unrecognised OSBYTE
8A2C EQUB <(svc_8_osword_disp - 1) ; &09: svc &08: OSWORD dispatch
8A2D EQUB <(svc_9_help - 1) ; &0A: svc &09: *HELP
8A2E EQUB <(dispatch_rts - 1) ; &0B: no-op (RTS only)
8A2F EQUB <(econet_restore - 1) ; &0C: svc &0B: NMI release
8A30 EQUB <(wait_idle_and_reset - 1) ; &0D: svc &0D: wait idle and reset
8A31 EQUB <(svc_18_fs_select - 1), &8F ; &0E: svc &12: FS select
8A33 EQUB <(raise_y_to_c8 - 1) ; &10: svc &21: static workspace claim
8A34 EQUB <(set_rom_ws_page - 1) ; &11: svc &22: dynamic workspace offer
8A35 EQUB <(store_ws_page_count - 1) ; &12: svc &23: top-of-static- workspace
8A36 EQUB <(noop_dey_rts - 1) ; &13: svc &24: dynamic workspace claim
8A37 EQUB <(copy_template_to_zp - 1) ; &14: svc &25: FS name + info reply
8A38 EQUB <(svc_26_close_all_files - 1) ; &15: svc &26: close all files
8A39 EQUB <(nfs_init_body - 1) ; &16: svc &27: post-hard-reset re-init
8A3A EQUB <(print_fs_ps_help - 1) ; &17: svc &28: print *FS/*PS no-arg syntax help
8A3B EQUB <(svc_29_status - 1) ; &18: svc &29: *STATUS handler
8A3C EQUB <(lang_0_insert_key - 1) ; &19: language reply 0: insert remote key
8A3D EQUB <(lang_1_remote_boot - 1) ; &1A: language reply 1: remote boot
8A3E EQUB <(lang_2_save_palette_vdu - 1) ; &1B: language reply 2: save palette/VDU
8A3F EQUB <(lang_3_exec_0100 - 1) ; &1C: language reply 3: execute at &0100
8A40 EQUB <(lang_4_validated - 1) ; &1D: language reply 4: remote validated
8A41 EQUB <(fscv_0_opt_entry - 1) ; &1E: FSCV 0: *OPT
8A42 EQUB <(fscv_1_eof - 1) ; &1F: FSCV 1: EOF
8A43 EQUB <(cmd_run_via_urd - 1) ; &20: FSCV 2: *RUN
8A44 EQUB <(fscv_3_star_cmd - 1) ; &21: FSCV 3: *command
8A45 EQUB <(cmd_run_via_urd - 1) ; &22: FSCV 4: *RUN (alias)
8A46 EQUB <(fscv_5_cat - 1) ; &23: FSCV 5: *CAT
8A47 EQUB <(fscv_6_shutdown - 1) ; &24: FSCV 6: shutdown
8A48 EQUB <(fscv_7_read_handles - 1) ; &25: FSCV 7: read handles
8A49 EQUB <(dispatch_rts - 1) ; &26: no-op (RTS only)
8A4A EQUB <(ps_scan_resume - 1) ; &27: PS scan tail (after pop_requeue)
8A4B EQUB <(cmd_info_dispatch - 1) ; &28: *Info dispatch
8A4C EQUB <(check_urd_present - 1) ; &29: URD-present check
8A4D EQUB <(ex_init_scan_x0 - 1) ; &2A: *Ex scan init
8A4E EQUB <(fsreply_1_boot - 1) ; &2B: FS reply 1: copy handles + boot
8A4F EQUB <(fsreply_2_copy_handles - 1) ; &2C: FS reply 2: copy handles
8A50 EQUB <(fsreply_3_set_csd - 1) ; &2D: FS reply 3: set CSD
8A51 EQUB <(cmd_run_via_urd - 1) ; &2E: FS reply 4: *RUN (alias)
8A52 EQUB <(fsreply_5_set_lib - 1) ; &2F: FS reply 5: set library
8A53 EQUB <(net_1_read_handle - 1) ; &30: net handle 1: read handle
8A54 EQUB <(net_2_read_entry - 1) ; &31: net handle 2: read handle entry
8A55 EQUB <(net_3_close_handle - 1) ; &32: net handle 3: close handle

svc_dispatch high-byte table (51 entries + 1 padding)

High-byte half of the PHA/PHA/RTS dispatch table read as LDA &8A56,X. The dispatcher pushes the high byte first then the low, so RTS lands on target (each stored value is handler-1).

8A56 .svc_dispatch_hi←1Used as index base by← 8E7E LDA
EQUB &0F ; &00: placeholder (never reached)
8A57 EQUB >(dispatch_rts - 1) ; &01: no-op (RTS only)
8A58 EQUB >(svc_dispatch_idx_2 - 1) ; &02: workspace claim helper (CMOS bit 0)
8A59 EQUB >(svc_2_priv_ws - 1) ; &03: svc &02: private workspace pages
8A5A EQUB >(svc_3_autoboot - 1) ; &04: svc &03: auto-boot
8A5B EQUB >(svc_4_star_command - 1) ; &05: svc &04: unrecognised *command
8A5C EQUB >(svc5_irq_check - 1) ; &06: svc &05: IRQ check
8A5D EQUB >(dispatch_rts - 1) ; &07: no-op (RTS only)
8A5E EQUB >(svc_7_osbyte - 1) ; &08: svc &07: unrecognised OSBYTE
8A5F EQUB >(svc_8_osword_disp - 1) ; &09: svc &08: OSWORD dispatch
8A60 EQUB >(svc_9_help - 1) ; &0A: svc &09: *HELP
8A61 EQUB >(dispatch_rts - 1) ; &0B: no-op (RTS only)
8A62 EQUB >(econet_restore - 1) ; &0C: svc &0B: NMI release
8A63 EQUB >(wait_idle_and_reset - 1) ; &0D: svc &0D: wait idle and reset
8A64 EQUB >(svc_18_fs_select - 1), &96 ; &0E: svc &12: FS select
8A66 EQUB >(raise_y_to_c8 - 1) ; &10: svc &21: static workspace claim
8A67 EQUB >(set_rom_ws_page - 1) ; &11: svc &22: dynamic workspace offer
8A68 EQUB >(store_ws_page_count - 1) ; &12: svc &23: top-of-static- workspace
8A69 EQUB >(noop_dey_rts - 1) ; &13: svc &24: dynamic workspace claim
8A6A EQUB >(copy_template_to_zp - 1) ; &14: svc &25: FS name + info reply
8A6B EQUB >(svc_26_close_all_files - 1) ; &15: svc &26: close all files
8A6C EQUB >(nfs_init_body - 1) ; &16: svc &27: post-hard-reset re-init
8A6D EQUB >(print_fs_ps_help - 1) ; &17: svc &28: print *FS/*PS no-arg syntax help
8A6E EQUB >(svc_29_status - 1) ; &18: svc &29: *STATUS handler
8A6F EQUB >(lang_0_insert_key - 1) ; &19: language reply 0: insert remote key
8A70 EQUB >(lang_1_remote_boot - 1) ; &1A: language reply 1: remote boot
8A71 EQUB >(lang_2_save_palette_vdu - 1) ; &1B: language reply 2: save palette/VDU
8A72 EQUB >(lang_3_exec_0100 - 1) ; &1C: language reply 3: execute at &0100
8A73 EQUB >(lang_4_validated - 1) ; &1D: language reply 4: remote validated
8A74 EQUB >(fscv_0_opt_entry - 1) ; &1E: FSCV 0: *OPT
8A75 EQUB >(fscv_1_eof - 1) ; &1F: FSCV 1: EOF
8A76 EQUB >(cmd_run_via_urd - 1) ; &20: FSCV 2: *RUN
8A77 EQUB >(fscv_3_star_cmd - 1) ; &21: FSCV 3: *command
8A78 EQUB >(cmd_run_via_urd - 1) ; &22: FSCV 4: *RUN (alias)
8A79 EQUB >(fscv_5_cat - 1) ; &23: FSCV 5: *CAT
8A7A EQUB >(fscv_6_shutdown - 1) ; &24: FSCV 6: shutdown
8A7B EQUB >(fscv_7_read_handles - 1) ; &25: FSCV 7: read handles
8A7C EQUB >(dispatch_rts - 1) ; &26: no-op (RTS only)
8A7D EQUB >(ps_scan_resume - 1) ; &27: PS scan tail (after pop_requeue)
8A7E EQUB >(cmd_info_dispatch - 1) ; &28: *Info dispatch
8A7F EQUB >(check_urd_present - 1) ; &29: URD-present check
8A80 EQUB >(ex_init_scan_x0 - 1) ; &2A: *Ex scan init
8A81 EQUB >(fsreply_1_boot - 1) ; &2B: FS reply 1: copy handles + boot
8A82 EQUB >(fsreply_2_copy_handles - 1) ; &2C: FS reply 2: copy handles
8A83 EQUB >(fsreply_3_set_csd - 1) ; &2D: FS reply 3: set CSD
8A84 EQUB >(cmd_run_via_urd - 1) ; &2E: FS reply 4: *RUN (alias)
8A85 EQUB >(fsreply_5_set_lib - 1) ; &2F: FS reply 5: set library
8A86 EQUB >(net_1_read_handle - 1) ; &30: net handle 1: read handle
8A87 EQUB >(net_2_read_entry - 1) ; &31: net handle 2: read handle entry
8A88 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
8A8A .service_handler←1← 8003 JMP
PHA ; Save service call number
8A8B CMP #&0f ; Service call &0F (vectors claimed)?
8A8D BNE restore_rom_slot_entry ; No: skip vectors-claimed handling
8A8F PHY ; Save Y on stack across the version-check
8A90 LDA #osbyte_read_os_version ; OSBYTE 0: read OS version
8A92 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) │
; └───┴───────────────────────────────────────────┘
8A94 JSR osbyte ; X=1, OS 1.20 or American OS
8A97 LDA l8a1e,x ; X is the host OS version (0=1.00, 1=1.20, 3=Master, 4=Master Terminal, 5=Master Compact)
8A9A STA spool_control_flag ; Store the spool-control flag
8A9D CPX #3 ; OS 3.2/3.5 (Master 128)?
8A9F BCS restore_rom_slot ; C set: not ours, restore ROM slot and return
8AA1 TXA ; Transfer OS version to A
8AA2 PHP ; Save flags (Z set if OS 1.00) across print
8AA3 JSR print_inline ; Print '<CR>Bad ROM ' to mark non-Master OS
8AA6 EQUS &0D, "Bad ROM " ; svc 13 fail path
8AAF LDA romsel_copy ; Load this ROM's slot number
8AB1 JSR print_num_no_leading ; Print slot number as decimal
8AB4 JSR print_newline_no_spool ; Print trailing newline, bypassing *SPOOL
8AB7 LDX romsel_copy ; Reload ROM slot for workspace clearing
8AB9 PLP ; Restore flags
8ABA BEQ clear_workspace_byte ; OS 1.00: skip INX (table starts at slot 0)
8ABC INX ; Adjust index for OS 1.20/2.00/5.00 layout
8ABD .clear_workspace_byte←1← 8ABA BEQ
LDA #0 ; A=0
8ABF STA rom_type_table,x ; Clear workspace byte for this ROM
8AC2 .restore_rom_slot←1← 8A9F BCS
LDX romsel_copy ; Restore ROM slot to X
8AC4 PLY ; Restore Y from stack
8AC5 .restore_rom_slot_entry←1← 8A8D BNE
PLA ; Pop service call number into A
8AC6 PHA ; Re-save service call number
8AC7 .check_adlc_flag
LDA rom_ws_pages,x ; Load ROM slot flag byte
8ACA ASL ; Shift bit 7 (ADLC absent) into carry
8ACB PLA ; Restore service call number
8ACC BCC dispatch_svc_with_state ; ADLC present: continue dispatch
8ACE RTS ; ADLC absent: decline service, return
8ACF .dispatch_svc_with_state←1← 8ACC BCC
TAX ; Transfer service number to X
8AD0 LDA svc_state ; Save current service state
8AD2 PHA ; Push old state
8AD3 TXA ; Restore service number to A
8AD4 STA svc_state ; Store as current service state
8AD6 CMP #&0d ; Service < 13?
8AD8 BCC dispatch_svc_index ; Yes: use as dispatch index directly
8ADA SBC #5 ; Subtract 5 (map 13-17 to 8-12)
8ADC CMP #&0d ; Mapped value = 13? (original was 18)
8ADE BEQ dispatch_svc_index ; Yes: valid service 18 (FS select)
8AE0 BCC dispatch_svc_state_check ; C clear: service number was below the prior CMP threshold, take dispatch fall-through
8AE2 SBC #5 ; Subtract 5 to remap service range
8AE4 CMP #&0e ; Compare with &0E
8AE6 BEQ dispatch_svc_index ; Equal: dispatch directly
8AE8 BCC dispatch_svc_state_check ; Below: take dispatch fall-through
8AEA SBC #8 ; Subtract 8 to remap further
8AEC CMP #&0f ; Compare with &0F
8AEE BCC dispatch_svc_state_check ; Below: dispatch fall-through
8AF0 CMP #&18 ; Compare with &18
8AF2 BCC dispatch_svc_index ; Below: dispatch index now in A
8AF4 .dispatch_svc_state_check←3← 8AE0 BCC← 8AE8 BCC← 8AEE BCC
LDA #0 ; Unknown service: set index to 0
8AF6 .dispatch_svc_index←4← 8AD8 BCC← 8ADE BEQ← 8AE6 BEQ← 8AF2 BCC
TAX ; Transfer dispatch index to X
8AF7 BEQ restore_svc_state ; Index 0: unhandled service, skip
8AF9 LDA ws_page ; Save current workspace page
8AFB PHA ; Push old page
8AFC STY ws_page ; Set workspace page from Y parameter
8AFE TYA ; Transfer Y to A
8AFF LDY #0 ; Y=0 for dispatch offset
8B01 JSR svc_dispatch ; Dispatch to service handler via table
8B04 PLA ; Restore old workspace page
8B05 STA ws_page ; Store it back
8B07 .restore_svc_state←1← 8AF7 BEQ
LDX svc_state ; Get service state (return code)
8B09 PLA ; Restore old service state
8B0A STA svc_state ; Store it back
8B0C TXA ; Transfer return code to A
8B0D .restore_romsel_rts
LDX romsel_copy ; Restore ROM slot to X
8B0F 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
8B10 .cmd_roff
LDY #0 ; Offset 0 in receive block
8B12 LDA (net_rx_ptr),y ; Load remote operation flag
8B14 BEQ clear_svc_and_ws ; Zero: already off, skip to cleanup
8B16 LDA #0 ; A=0
8B18 TAX ; Copy A to X
8B19 STA (net_rx_ptr),y ; Clear remote operation flag
8B1B TAY ; Copy A to Y
8B1C LDA #osbyte_econet_kbd_disable ; OSBYTE &C9: keyboard disable
8B1E JSR osbyte ; osbyte: econet kbd disable
8B21 LDA #&0a ; A=&0A: workspace init parameter
8B23 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)
8B26 .scan_remote_keys←1← 986D JSR
STX nfs_workspace ; Save X in workspace
8B28 LDA #osbyte_econet_oscall_intercept ; A=&CE: start of key range
8B2A .loop_scan_key_range←1← 8B35 BEQ
LDX nfs_workspace ; Restore X from workspace
8B2C LDY #&7f ; Y=&7F: OSBYTE scan parameter
8B2E JSR osbyte ; OSBYTE: scan keyboard osbyte: econet oscall intercept
8B31 ADC #1 ; Advance to next key code
8B33 CMP #&d0 ; Reached &D0?
8B35 BEQ loop_scan_key_range ; No: loop back (scan &CE and &CF)
8B37 .clear_svc_and_ws←1← 8B14 BEQ
LDA #0 ; A=0
8B39 STA svc_state ; Clear service state
8B3B STA nfs_workspace ; Clear workspace byte
8B3D 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
8B3E .save_text_ptr←3← 8C6B JSR← 8C96 JSR← A617 JSR
PHA ; Save A
8B3F LDA os_text_ptr ; Copy OS text pointer low
8B41 STA fs_crc_lo ; to fs_crc_lo
8B43 LDA os_text_ptr_hi ; Copy OS text pointer high
8B45 STA fs_crc_hi ; to fs_crc_hi
8B47 PLA ; Restore A
8B48 .rts_save_text_ptr←2← 8B6D BNE← 8B76 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
8B49 .cmd_net_fs←2← 8B78 JSR← 9014 JSR
JSR get_ws_page ; Get workspace page for this ROM slot
8B4C STA fs_load_addr_hi ; Store as high byte of load address
8B4E LDA #0 ; A=0
8B50 STA fs_load_addr ; Clear low byte of load address
8B52 CLC ; Clear carry for addition
8B53 LDY #&76 ; Y=&76: checksum range end
8B55 .loop_sum_rom_bytes←1← 8B58 BPL
ADC (fs_load_addr),y ; Add byte to running checksum
8B57 DEY ; Decrement index
8B58 BPL loop_sum_rom_bytes ; Loop until all bytes summed
8B5A LDY #&77 ; Y=&77: checksum storage offset
8B5C EOR (fs_load_addr),y ; Compare with stored checksum
8B5E RTS ; Return -- last instruction of cmd_net_fs body
8B5F .cmd_net_check_hw
LDA #&20 ; A=&20: ADLC IRQ-status mask (CR2 bit 5)
8B61 BIT econet_control23_or_status2 ; Read ADLC CR2/SR2 (&FEA1)
8B64 BEQ select_fs_via_cmd_net_fs ; Z set (no carrier): proceed to FS-select
8B66 LDA #3 ; A=3: 'ROM has no NFS' error code
8B68 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
8B6B .svc_18_fs_select
CPY #5 ; Service 18 carries FS number in Y; Econet is FS 5
8B6D BNE rts_save_text_ptr ; Not us: pass the call on (RTS via shared return)
8B6F LDA #0 ; A=0 to claim the service
8B71 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)
8B73 .ensure_fs_selected←6← 8EA7 JSR← A9EC JSR← A9FA JSR← AAE2 JSR← AAF0 JSR← AC6C JSR
BIT fs_flags ; Test fs_flags bit 7 (ANFS active)
8B76 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
8B78 .select_fs_via_cmd_net_fs←2← 8B64 BEQ← 8D02 JSR
JSR cmd_net_fs ; Auto-select ANFS via the *NFS handler
8B7B BEQ select_fs_cmd_net_fs ; Z=1 (A=0): selection succeeded
8B7D JMP error_net_checksum ; Otherwise raise 'net checksum' error
8B80 .select_fs_cmd_net_fs←1← 8B7B BEQ
LDA osword_pb_ptr_hi ; Read osword_pb_ptr_hi
8B82 PHA ; Push it
8B83 LDA osword_pb_ptr ; Read osword_pb_ptr lo
8B85 PHA ; Push it
8B86 .done_rom_checksum
JSR notify_new_fs ; Call FSCV with A=6 (new FS)
8B89 LDY #9 ; Y=9: end of FS context block
8B8B .loop_copy_fs_ctx←1← 8B93 BNE
LDA (net_rx_ptr),y ; Load byte from receive block
8B8D STA hazel_minus_2,y ; Store into FS workspace
8B90 DEY ; Decrement index
8B91 CPY #1 ; Reached offset 1?
8B93 BNE loop_copy_fs_ctx ; No: continue copying
8B95 ROL fs_flags ; Shift bit 7 of FS flags into carry
8B98 CLC ; Clear carry
8B99 ROR fs_flags ; Clear bit 7 of FS flags
8B9C LDY #&0d ; Y=&0D: vector table size - 1
8B9E .loop_set_vectors←1← 8BA5 BPL
LDA fs_vector_table,y ; Load FS vector address
8BA1 STA filev,y ; Store into FILEV vector table
8BA4 DEY ; Decrement index
8BA5 BPL loop_set_vectors ; Loop until all vectors installed
8BA7 JSR init_adlc_and_vectors ; Initialise ADLC and NMI workspace
8BAA LDY #&1b ; Y=&1B: extended vector offset
8BAC LDX #7 ; X=7: two more vectors to set up
8BAE JSR write_vector_entry ; Set up extended vectors
8BB1 LDA #0 ; A=0
8BB3 STA hazel_fs_pending_state ; Clear FS state byte
8BB6 STA hazel_chan_attr ; Clear workspace byte
8BB9 STA hazel_fs_lib_flags ; Clear workspace byte
8BBC JSR store_rx_attribute ; Clear receive attribute byte
8BBF STA hazel_fs_error_code ; Clear workspace byte
8BC2 JSR setup_ws_ptr ; Set up workspace pointers
8BC5 JSR init_channel_table ; Initialise FS state
8BC8 LDY #&77 ; Y=&77: workspace block size - 1
8BCA .loop_copy_ws_page←1← 8BD0 BPL
LDA (fs_ws_ptr),y ; Load byte from source workspace
8BCC STA hazel_fcb_addr_lo,y ; Store to the HAZEL &C2 FCB shadow copy
8BCF DEY ; Decrement index
8BD0 BPL loop_copy_ws_page ; Loop until all bytes copied
8BD2 LDA #&80 ; A=&80: FS selected flag
8BD4 TSB fs_flags ; Set bit 0 of fs_flags (= NFS active)
8BD7 JSR issue_svc_15 ; Issue Master service call &0F (vector update)
8BDA PLA ; Pop saved osword_pb_ptr lo
8BDB STA osword_pb_ptr ; Restore osword_pb_ptr lo
8BDD PLA ; Pop saved osword_pb_ptr hi
8BDE STA osword_pb_ptr_hi ; Restore osword_pb_ptr hi
8BE0 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 &8C6E).

On ExitX&35 + advance through the table
8BE1 .help_print_nfs_cmds←1← 8C93 JMP
LDX #&35 ; X=&35: NFS command table offset
8BE3 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
8BE6 .help_utils
LDX #0 ; X=0: utility command table offset
8BE8 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)
8BEA .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
8BEC .print_cmd_table←2← 8BE3 JSR← 8BE8 BEQ
BVC print_table_newline ; V clear: take newline-only path (skip version header)
8BEE PHX ; Save X (cmd-table offset)
8BEF PHY ; Save Y (text-pointer offset)
8BF0 JSR print_version_header ; Print the version-banner header
8BF3 PLY ; Restore Y
8BF4 PLX ; Restore X
8BF5 CLV ; Clear overflow flag
8BF6 BVC help_table_walk_entry ; V clear: walk the next table entry
8BF8 .print_table_newline←1← 8BEC 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.

8BFB .help_table_walk_entry←2← 8BF6 BVC← 8C86 JSR
PHY ; Save Y
8BFC 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
8BFD .loop_next_entry←1← 8C47 JMP
LDA cmd_table_fs,x ; Load byte from command table
8C00 BPL print_indent ; Bit 7 clear: valid entry, continue
8C02 JMP done_print_table ; End of table: finish up
8C05 .print_indent←1← 8C00 BPL
JSR print_inline ; Print two-space indent
8C08 EQUS " "
8C0A LDY #9 ; Y=9: cmd_table_fs sub-table 1 offset
8C0C LDA cmd_table_fs,x ; Read cmd_table_fs+X (entry name byte)
8C0F .loop_print_cmd_name←1← 8C17 BPL
JSR osasci ; Print character
8C12 INX ; Advance table pointer
8C13 DEY ; Decrement padding counter
8C14 LDA cmd_table_fs,x ; Load next character
8C17 BPL loop_print_cmd_name ; Bit 7 clear: more chars, continue
8C19 .loop_pad_spaces←1← 8C1F BPL
LDA #' ' ; Pad with spaces
8C1B JSR osasci ; Print character
8C1E DEY ; Decrement remaining pad count
8C1F BPL loop_pad_spaces ; More padding needed: loop
8C21 LDA cmd_table_fs,x ; Load syntax descriptor byte
8C24 AND #&1f ; Mask to get syntax string index
8C26 TAY ; Use index as Y
8C27 LDA cmd_syntax_table,y ; Look up syntax string offset
8C2A 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 &8C13 (continue with current char) and the BEQ at &8C19 (fall-through from the line-wrap path).

On EntryYcurrent index into syn_opt_dir
8C2B .loop_print_syntax←2← 8C38 JMP← 8C3E JMP
INY ; Advance to next character
8C2C LDA cmd_syntax_strings,y ; Load syntax string character
8C2F BEQ done_entry_newline ; Zero terminator: end of syntax
8C31 CMP #&0d ; Carriage return: line continuation
8C33 BNE print_syntax_char ; No: print the character
8C35 JSR help_wrap_if_serial ; Handle line wrap in syntax output
8C38 JMP loop_print_syntax ; Continue with next character
8C3B .print_syntax_char←1← 8C33 BNE
JSR osasci ; Print character
8C3E JMP loop_print_syntax ; Continue with next character
8C41 .done_entry_newline←1← 8C2F BEQ
JSR osnewl ; Print newline
8C44 INX ; X += 3: skip syntax descriptor and address
8C45 INX ; (continued)
8C46 INX ; (continued)
8C47 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
8C4A .done_print_table←1← 8C02 JMP
PLP ; Restore processor status
8C4B PLA ; Restore Y
8C4C TAY ; Transfer to Y
8C4D 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)
8C4E .help_wrap_if_serial←1← 8C35 JSR
LDA vdu_mode ; Read output stream type
8C51 BEQ rts_help_wrap ; Stream 0 (VDU): no wrapping
8C53 CMP #3 ; Stream 3 (printer)?
8C55 BEQ rts_help_wrap ; Yes: no wrapping
8C57 PHY ; Save Y across OS call
8C58 JSR osnewl ; Print newline
8C5B LDY #&0b ; Y=&0B: indent width - 1
8C5D LDA #' ' ; Space character
8C5F .loop_indent_spaces←1← 8C63 BPL
JSR osasci ; Print character
8C62 DEY ; Decrement indent counter
8C63 BPL loop_indent_spaces ; More spaces needed: loop
8C65 PLY ; Restore Y
8C66 .rts_help_wrap←2← 8C51 BEQ← 8C55 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
8C67 .svc_4_star_command
LDX #0 ; X=0: start of utility command table
8C69 LDY ws_page ; Get command line offset
8C6B .svc4_dispatch_lookup←2← 95EA JMP← 9685 JMP
JSR save_text_ptr ; Save text pointer to fs_crc
8C6E JSR match_fs_cmd ; Try to match command in table
8C71 BCS svc_return_unclaimed ; No match: return to caller
8C73 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
8C76 .svc_9_help
JSR check_credits_easter_egg ; Check for credits Easter egg
8C79 LDY ws_page ; Get command line offset
8C7B LDA (os_text_ptr),y ; Load character at offset
8C7D CMP #&0d ; Is it CR (bare *HELP)?
8C7F BNE check_help_topic ; No: check for specific topic
8C81 JSR print_version_header ; Print version string
8C84 LDX #&91 ; X=&91: start of help command list
8C86 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 &95BD that hand a request back to MOS without acting on it.

On ExitYws_page (restored command-line offset)
8C89 .svc_return_unclaimed←5← 8C71 BCS← 8CB6 BEQ← 8CFA BNE← 95BD JMP← 9680 JMP
LDY ws_page ; Restore Y (command line offset)
8C8B RTS ; Return unclaimed
8C8C .check_help_topic←1← 8C7F BNE
BIT always_set_v_byte ; Test for topic match (sets flags)
8C8F CMP #'.' ; Is first char '.' (abbreviation)?
8C91 BNE match_help_topic ; No: try topic-specific help
8C93 JMP help_print_nfs_cmds ; '.' found: show full command list
8C96 .match_help_topic←1← 8C91 BNE
JSR save_text_ptr ; Save text pointer to fs_crc
8C99 .loop_dispatch_help←1← 8CB4 BNE
PHP ; Save flags
8C9A LDX #&96 ; X=&96: help command table start
8C9C JSR match_fs_cmd ; Try to match help topic in table
8C9F BCS skip_if_no_match ; No match: try next topic
8CA1 PLP ; Restore flags
8CA2 LDA #&8c ; Push return address high (&8C)
8CA4 PHA ; Push it for RTS dispatch
8CA5 LDA #&98 ; Push return address low (&74)
8CA7 PHA ; Push it for RTS dispatch
8CA8 LDA cmd_dispatch_hi_table,x ; Load dispatch address high
8CAB PHA ; Push dispatch high for RTS
8CAC LDA cmd_dispatch_lo_table,x ; Load dispatch address low
8CAF PHA ; Push dispatch low for RTS
8CB0 RTS ; Dispatch via RTS (returns to &8CA5)
8CB1 .skip_if_no_match←1← 8C9F BCS
PLP ; Restore flags from before match
8CB2 CMP #&0d ; End of command line?
8CB4 BNE loop_dispatch_help ; No: try matching next topic
8CB6 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.24" + 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)
8CB8 .print_version_header←2← 8BF0 JSR← 8C81 JSR
JSR print_inline ; Print version string via inline
8CBB .version_string_cr
EQUS &0D, "Advanced NFS 4.24", &0D
8CCE NOP ; NOP -- bit-7 terminator + harmless resume opcode
8CCF 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 &8CD8&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)
8CD2 .get_ws_page←4← 8B49 JSR← 8CE2 JSR← 8F47 JSR← B3D3 JSR
LDY romsel_copy ; Y = current ROM slot number from MOS copy at &F4
8CD4 LDA rom_ws_pages,y ; Load workspace page byte for this ROM slot
8CD7 TAY ; Hold a copy of the slot byte in Y while we test bit 6
8CD8 ROL ; ROL puts pre-ROL bit 6 into the post-ROL N flag (and pre-ROL bit 7 into C)
8CD9 PHP ; Save those flags so the upcoming ROR doesn't lose N
8CDA ROR ; ROR restores A to its original value (using the saved C)
8CDB PLP ; Restore the ROL flags: N is now pre-ROL bit 6
8CDC BPL get_ws_page_loop ; Bit 6 clear: skip the OR (no ADLC-absent flag)
8CDE ORA #&80 ; Bit 6 set: re-set bit 7 in the returned page byte (the ADLC-absent flag uses bit 7 in callers)
8CE0 .get_ws_page_loop←1← 8CDC BPL
TAY ; Transfer to Y
8CE1 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
8CE2 .setup_ws_ptr←2← 8BC2 JSR← 8F67 JSR
JSR get_ws_page ; Get workspace page for ROM slot
8CE5 STY nfs_temp ; Store page in nfs_temp
8CE7 LDA #0 ; A=0
8CE9 STA fs_ws_ptr ; Clear low byte of pointer
8CEB .rts_setup_ws_ptr←1← 8D11 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)
8CEC .svc_3_autoboot
LDA #osbyte_scan_keyboard_from_16 ; OSBYTE &7A: scan keyboard from key 16
8CEE JSR osbyte ; Scan keyboard from key 16
8CF1 TXA ; Copy X to A
8CF2 BMI select_net_fs ; No key pressed: select Net FS
8CF4 CMP #&19 ; Key &19 (N)?
8CF6 BEQ write_key_state ; Yes: write key state and boot
8CF8 EOR #&55 ; EOR with &55: maps to zero if 'N'
8CFA BNE svc_return_unclaimed ; Not N key: return unclaimed
8CFC .write_key_state←1← 8CF6 BEQ
TAY ; Copy A to Y
8CFD LDA #osbyte_write_keys_pressed ; OSBYTE &78: write keys pressed
8CFF JSR osbyte ; Write all keys pressed information
8D02 .select_net_fs←1← 8CF2 BMI
JSR select_fs_via_cmd_net_fs ; Select NFS as current filing system
8D05 JSR print_station_id ; Print the station identity line
8D08 JSR osnewl ; Print newline
8D0B LDA #0 ; A=0: clear svc_state marker
8D0D STA svc_state ; Store -> svc_state
8D0F LDX ws_page ; Get workspace page
8D11 BNE rts_setup_ws_ptr ; Non-zero: already initialised, return
8D13 LDA #4 ; A=4: library-selected flag bit
8D15 TSB hazel_fs_lib_flags ; Set the library-selected flag
8D18 LDX #&3e ; X=&3E: boot filename address low
8D1A LDY #&8d ; Y=&8D: boot filename address high
8D1C 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 (&8b86 inside the FS-selection sequence).

On ExitA6 (clobbered by FSCV handler)
8D1F .notify_new_fs←1← 8B86 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
8D21 .call_fscv←1← A5B2 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
8D24 .issue_svc_15←1← 8BD7 JSR
LDX #&0f ; X=&0F: service 15 (vectors claimed)
8D26 .issue_svc_osbyte
LDA #osbyte_issue_service_request ; A=&8F: OSBYTE 'Issue paged-ROM service request'
8D28 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)
8D2B .svc_dispatch_idx_2
PHY ; Save Y on stack
8D2C LDX #&11 ; X=&11: CMOS offset for Econet station-flags
8D2E JSR osbyte_a1 ; Read CMOS byte: result in Y
8D31 TYA ; A = CMOS byte
8D32 PLY ; Restore caller's Y
8D33 AND #1 ; Isolate bit 0 (page-&0B fallback flag)
8D35 BEQ return_1 ; Bit clear: keep caller's Y
8D37 CPY #&10 ; Caller's Y already >= &10?
8D39 BCS return_1 ; Yes: keep it
8D3B LDY #&10 ; Y < &10 with bit set: clamp to &10
8D3D .return_1←2← 8D35 BEQ← 8D39 BCS
RTS ; Return
8D3E EQUS "i .Boot"
8D45 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.

8D46 .check_credits_easter_egg←1← 8C76 JSR
LDY ws_page ; Y = ws_page (workspace high page)
8D48 LDX #5 ; X=5: start of credits keyword
8D4A .loop_match_credits←1← 8D53 BNE
LDA (os_text_ptr),y ; Load character from command line
8D4C CMP credits_keyword_start,x ; Compare with credits keyword
8D4F BNE done_credits_check ; Mismatch: check if keyword complete
8D51 INY ; Advance command line pointer
8D52 INX ; Advance keyword pointer
8D53 BNE loop_match_credits ; Continue matching
8D55 .done_credits_check←1← 8D4F BNE
CPX #&0c ; Reached end of keyword (X=&0C)?
8D57 BNE rts_credits_check ; No: keyword not fully matched, return
8D59 JSR print_inline ; Print the credits string inline (high-bit terminated)
8D5C .credits_keyword_start←1Used as index base by← 8D4C CMP
EQUS &0D, "The authors of ANFS are;", &0D, "B Cockburn", &0D, "J Dunn", &0D, "B Robertson", &0D, "J Wills", &0D, &0D
8D9D NOP ; bit-7 terminator + resume opcode
8D9E .rts_credits_check←1← 8D57 BNE
RTS ; Return
8D9F .cmd_iam_save_ctx
PHY ; Save caller Y
8DA0 LDA fs_last_byte_flag ; Read fs_last_byte_flag (work_bd)
8DA2 LDX fs_options ; Read fs_options (work_bb)
8DA4 LDY fs_block_offset ; Read fs_block_offset (work_bc)
8DA6 PHA ; Push fs_last_byte_flag for restore on return
8DA7 PHX ; Save X
8DA8 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
8DA9 .cmd_iam
LDA #osbyte_close_spool_exec ; OSBYTE &77: close SPOOL/EXEC
8DAB STA hazel_fs_pending_state ; Store as pending operation marker
8DAE JSR osbyte ; Close any SPOOLed or EXECed files
8DB1 LDY #0 ; Y=0
8DB3 STY fs_work_4 ; Clear password entry flag
8DB5 JSR process_all_fcbs ; Reset FS connection state
8DB8 STZ hazel_fs_pending_state ; Clear hazel_fs_pending_state (connection-attempt flag)
8DBB PLY ; Restore Y
8DBC PLX ; Restore X
8DBD 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 &B3D9 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.

8DBE .load_transfer_params
JSR set_xfer_params ; Set up transfer parameters
8DC1 PLY ; Restore Y
8DC2 LDA (fs_options),y ; Load first option byte
8DC4 JSR is_decimal_digit ; Parse station number if present
8DC7 BCC cmd_pass ; Not a digit: skip to password entry
8DC9 JSR parse_addr_arg ; Parse user ID string
8DCC BCS skip_no_fs_addr ; No user ID: go to password
8DCE STA hazel_fs_network ; Store file server station low
8DD1 JSR clear_if_station_match ; Check and store FS network
8DD4 INY ; Skip separator
8DD5 JSR parse_addr_arg ; Parse next argument
8DD8 .skip_no_fs_addr←1← 8DCC BCS
BEQ cmd_pass ; No FS address: skip to password
8DDA STA hazel_fs_station ; Store file server station high
8DDD LDX #&ff ; X=&FF: pre-decrement for loop
8DDF .loop_copy_logon_cmd←1← 8DE6 BPL
INX ; Advance index
8DE0 LDA cmd_table_nfs_iam,x ; Load logon command template byte
8DE3 STA hazel_txcb_data,x ; Store into transmit buffer
8DE6 BPL loop_copy_logon_cmd ; Bit 7 clear: more bytes, loop
8DE8 JSR copy_arg_validated ; Send logon with file server lookup
8DEB 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)
8DED .cmd_pass←2← 8DC7 BCC← 8DD8 BEQ
JSR copy_arg_to_buf_x0 ; Build FS command packet
8DF0 .scan_pass_prompt←1← 8DEB BEQ
LDY #&ff ; Y=&FF: pre-increment for loop
8DF2 .loop_scan_colon←1← 8DFC BNE
INY ; Advance to next byte
8DF3 LDA hazel_txcb_data,y ; Load byte from reply buffer
8DF6 CMP #&0d ; Is it CR (end of prompt)?
8DF8 BEQ send_pass_to_fs ; Yes: no colon found, skip to send
8DFA CMP #':' ; Is it ':' (password prompt)?
8DFC BNE loop_scan_colon ; No: keep scanning
8DFE JSR print_byte_no_spool ; Print byte no-spool
8E01 STY fs_work_4 ; Save position of colon
8E03 .read_pw_char←4← 8E13 BNE← 8E17 BEQ← 8E1A BNE← 8E26 BNE
LDA #&ff ; A=&FF: mark as escapable
8E05 STA need_release_tube ; Set escape flag
8E07 JSR check_escape_and_classify ; Check for escape condition
8E0A JSR osrdch ; Read a character (password entry, no echo)
8E0D CMP #&15 ; Ctrl-U (&15): line-delete?
8E0F BNE check_pw_special ; Not NAK (&15): check other chars
8E11 LDY fs_work_4 ; Restore colon position
8E13 BNE read_pw_char ; Non-zero: restart from colon
8E15 .loop_erase_pw←1← 8E1E BEQ
CPY fs_work_4 ; At colon position?
8E17 BEQ read_pw_char ; Yes: restart password input
8E19 DEY ; Backspace: move back one character
8E1A BNE read_pw_char ; If not at start: restart input
8E1C .check_pw_special←1← 8E0F BNE
CMP #&7f ; Delete key (&7F)?
8E1E BEQ loop_erase_pw ; Yes: handle backspace
8E20 STA hazel_txcb_data,y ; Store character in password buffer
8E23 INY ; Advance buffer pointer
8E24 CMP #&0d ; Is it CR (end of password)?
8E26 BNE read_pw_char ; No: read another character
8E28 JSR print_newline_no_spool ; Print newline no-spool
8E2B .send_pass_to_fs←1← 8DF8 BEQ
TYA ; Transfer string length to A
8E2C PHA ; Save string length
8E2D JSR init_txcb ; Set up transmit control block
8E30 JSR init_tx_ptr_for_pass ; Send to file server and get reply
8E33 PLX ; Restore X
8E34 INX ; Include terminator
8E35 LDY #0 ; Y=0
8E37 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
8E39 .clear_if_station_match←2← 8DD1 JSR← AA0C JSR
JSR init_bridge_poll ; Ensure bridge initialised; A=spool_control_flag (bridge status)
8E3C EOR hazel_fs_network ; EOR with hazel_fs_network: zero result if equal
8E3F BNE rts_station_match ; Different: return without clearing
8E41 STA hazel_fs_network ; Same: clear station byte
8E44 .rts_station_match←1← 8E3F 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 &959C).

8E45 .check_urd_prefix←1← 959C JMP
LDY #0 ; Y=0: first character offset
8E47 LDA (fs_crc_lo),y ; Load first character of command text
8E49 CMP #'&' ; Is it '&' (URD prefix)?
8E4B BNE pass_send_cmd ; No: send as normal FS command
8E4D JMP cmd_run_via_urd ; Yes: route via *RUN for URD prefix handling
8E50 .pass_send_cmd←1← 8E4B BNE
JSR copy_arg_to_buf_x0 ; Build FS command packet
8E53 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)
8E54 .send_cmd_and_dispatch←3← 8E37 BEQ← 9465 JMP← B3A3 JMP
JSR save_net_tx_cb ; Set up command and send to FS
8E57 LDX hazel_txcb_network ; Load reply function code
8E5A BEQ dispatch_rts ; Zero: no reply, return
8E5C LDA hazel_txcb_data ; Load first reply byte
8E5F LDY #&25 ; Y=&25: logon dispatch offset
8E61 BNE svc_dispatch ; Not matched: fall to service dispatch
8E63 .fscv_handler
JSR set_xfer_params ; Parse reply as decimal number
8E66 CMP #&0c ; Result >= 8?
8E68 BCS dispatch_rts ; Yes: out of range, return
8E6A TAX ; Transfer handle to X
8E6B JSR mask_owner_access ; Look up in open files table
8E6E TYA ; Transfer result to A
8E6F LDY #&1d ; Y=&1D: handle dispatch offset
8E71 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)
8E73 .dir_op_dispatch←1← 8581 JSR
CPX #5 ; Handle >= 5?
8E75 BCS dispatch_rts ; Yes: out of range, return
8E77 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
8E79 .svc_dispatch←5← 8B01 JSR← 8E61 BNE← 8E71 BNE← 8E7B BPL← 8EFE JMP
INX ; Advance X to target index
8E7A DEY ; Decrement Y offset counter
8E7B BPL svc_dispatch ; Y still positive: continue counting
8E7D TAY ; Y=&FF: will be ignored by caller
8E7E LDA svc_dispatch_hi,x ; Load dispatch address high byte
8E81 PHA ; Push high byte for RTS dispatch
8E82 .push_dispatch_lo
LDA svc_dispatch_lo,x ; Load dispatch address low byte
8E85 PHA ; Push low byte for RTS dispatch
8E86 LDX fs_options ; Load FS options pointer
8E88 .dispatch_rts←3← 8E5A BEQ← 8E68 BCS← 8E75 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.

8E89 .noop_dey_rts
DEY ; Claim 1 page (DEY = decrement Y by 1)
8E8A 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.

8E8B .copy_template_to_zp
LDX #&0a ; X = 10 (top of 11-byte template)
8E8D .loop_copy_return_template←1← 8E94 BPL
LDA fs_info_template,x ; Load template byte X from &8E97+X
8E90 STA (os_text_ptr),y ; Store at (&F2),Y
8E92 INY ; Advance destination cursor
8E93 DEX ; Step to previous template byte
8E94 BPL loop_copy_return_template ; Loop until X has wrapped past 0
8E96 .fs_template_done←1← 8EA5 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).

8E97 .fs_info_template←1Used as index base by← 8E8D LDA
EQUB &05
8E98 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.

8EA2 .svc_26_close_all_files
BIT fs_flags ; Test bit 6 of fs_flags (NFS currently selected?)
8EA5 BVC fs_template_done ; Clear: return without acting
8EA7 JSR ensure_fs_selected ; Ensure NFS is the selected FS
8EAA LDA #0 ; A=0
8EAC TAY ; Y=0 -- FILEV 'close all files' sub-call
8EAD 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 (&8FBB, inside nfs_init_body's CMOS-read sequence).

On ExitYCMOS byte 0 (returned by OSBYTE &A1)
8EB0 .read_cmos_byte_0←1← 8FD7 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
8EB2 .osbyte_a1←19Referenced by← 8D2E JSR← 8F2B JSR← 8F7C JSR← 8F84 JSR← 8F8C JSR← 8F96 JSR← 8FB8 JSR← 95F6 JSR← 95FD JSR← 961A JSR← 9624 JSR← 9643 JSR← 9669 JSR← 9676 JSR← A109 JSR← A721 JSR← B707 JSRUsed as index base by← 9056 LDA← 905C LDA
LDA #osbyte_read_cmos_ram ; A=&A1: OSBYTE &A1 = read CMOS RAM
8EB4 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 =
; &8EB7). 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.
8EB7 .ps_template_data
EQUS "PRINT " ; PS template: default name "PRINT "
8EBD 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.

8EBF .fs_vector_table←1Used as index base by← 8B9E LDA
EQUW ev_filev ; FILEV dispatch
8EC1 EQUW ev_argsv ; ARGSV dispatch
8EC3 EQUW ev_bgetv ; BGETV dispatch
8EC5 EQUW ev_bputv ; BPUTV dispatch
8EC7 EQUW ev_gbpbv ; GBPBV dispatch
8EC9 EQUW ev_findv ; FINDV dispatch
8ECB EQUW ev_fscv ; FSCV dispatch
8ECD EQUW filev_handler ; FILEV handler
8ECF EQUB &4A ; (ROM bank — not read)
8ED0 EQUW argsv_handler ; ARGSV handler
8ED2 EQUB &44 ; (ROM bank — not read)
8ED3 EQUW bgetv_handler ; BGETV handler
8ED5 EQUB &57 ; (ROM bank — not read)
8ED6 EQUW bputv_handler ; BPUTV handler
8ED8 EQUB &42 ; (ROM bank — not read)
8ED9 EQUW gbpbv_handler ; GBPBV handler
8EDB EQUB &41 ; (ROM bank — not read)
8EDC EQUW findv_handler ; FINDV handler
8EDE EQUB &52 ; (ROM bank — not read)
8EDF 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
8EE1 .osbyte_x0←3← 805F JSR← 9048 JSR← 99FB 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
8EE3 .osbyte_yff←1← 8069 JSR
LDY #&ff ; Y=&FF: 'read' parameter for OSBYTE
8EE5 .jmp_osbyte←1← 8EEE BEQ
JMP osbyte ; Tail-call OSBYTE
8EE8 EQUB &1C, &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
8EEA .osbyte_x0_y0←1← 9A0E JSR
LDX #0 ; X=0: clear OSBYTE X arg
8EEC LDY #0 ; Y=0
8EEE 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)
8EF0 .svc_7_osbyte
LDA osbyte_a_copy ; Get original OSBYTE A parameter
8EF2 SBC #&31 ; Subtract &31 (map &32-&35 to 1-4)
8EF4 CMP #4 ; In range 0-3?
8EF6 BCS rts_raise_y_to_c8 ; No: not ours, return unclaimed
8EF8 TAX ; Transfer to X as dispatch index
8EF9 STZ svc_state ; Clear svc_state
8EFB TYA ; Transfer Y to A (OSBYTE Y param)
8EFC LDY #&2f ; Y=&2F: OSBYTE dispatch offset
8EFE 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)
8F01 .raise_y_to_c8
CPY #&c8 ; Y already >= &C8?
8F03 BCS rts_raise_y_to_c8 ; Yes: return Y unchanged
8F05 LDY #&c8 ; No: raise Y to &C8
8F07 .rts_raise_y_to_c8←2← 8EF6 BCS← 8F03 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
8F08 .store_ws_page_count
TYA ; Transfer Y to A
8F09 PHA ; Push for save
8F0A CMP #&d3 ; Y >= &D3?
8F0C BCC done_cap_ws_count ; No: use Y as-is
8F0E LDA #&d3 ; Cap at &D3
8F10 .done_cap_ws_count←1← 8F0C BCC
LDY #&0b ; Offset &0B in receive block
8F12 STA (net_rx_ptr),y ; Store workspace page count
8F14 PLY ; Pop -- save Y temporarily
8F15 RTS ; Return -- ws_page count saved
8F16 .set_rom_ws_page←1← 8F4D BCS
TYA ; Caller's page (in Y) into A
8F17 LDY romsel_copy ; Y = current ROM slot from romsel_copy
8F19 PHA ; Push restored value
8F1A AND #&7f ; Mask bit 7 (workspace flag)
8F1C STA rom_ws_pages,y ; Publish page into rom_ws_pages[slot] (bit 7 cleared = workspace claimed)
8F1F LDY fdc_1770_data ; Discarded read of 1770 data reg (&FE2B)
8F22 LDY fdc_1770_command_or_status ; Discarded read of 1770 status reg (&FE28)
8F25 PLY ; Pop saved Y
8F26 INY ; Increment for next page
8F27 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
8F28 .svc_2_priv_ws
PHY ; Save Y on stack (caller's claim)
8F29 LDX #&11 ; X=&11: CMOS RAM byte index
8F2B JSR osbyte_a1 ; Read CMOS &11 via osbyte_a1
8F2E TYA ; A = CMOS &11 value
8F2F AND #4 ; Mask bit 2 (workspace-size flag)
8F31 BNE private_ws_set_bit ; Bit 2 set: keep caller's Y, advance by 2
8F33 LDA #&0b ; Bit 2 clear: A=&0B (use 11-page minimum)
8F35 BRA commit_workspace_pages ; BRA to common tail
8F37 .private_ws_set_bit←1← 8F31 BNE
PLY ; Bit-2-set path: restore Y
8F38 TYA ; TYA / INY / INY -- raise Y by 2 pages
8F39 INY ; Y += 1
8F3A INY ; Y += 1 again (total +2)
8F3B PHY ; Push raised Y
8F3C .commit_workspace_pages←1← 8F35 BRA
STA net_rx_ptr_hi ; Store final page count high to net_rx_ptr_hi
8F3E INC ; Increment for nfs_workspace_hi
8F3F STA nfs_workspace_hi ; Store workspace high page
8F41 LDA #0 ; A=0: clear-byte for the lo halves below
8F43 STA net_rx_ptr ; Clear net_rx_ptr_lo (page-aligned)
8F45 STA nfs_workspace ; Clear nfs_workspace_lo (page-aligned)
8F47 JSR get_ws_page ; Compute workspace start page via get_ws_page
8F4A CPY #&dc ; Y >= &DC?
8F4C PLY ; Restore Y from stack
8F4D BCS set_rom_ws_page ; Yes: jump to set_rom_ws_page (error path)
8F4F RTS ; Return

ANFS initialisation body

Reached only via PHA/PHA/RTS dispatch (table index 22 in the svc_dispatch table at &8A23 / &8A20). 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.

8F50 .nfs_init_body
LDA #0 ; A=0: clear-byte for the next four stores
8F52 STA ws_page ; Clear ws_page (workspace page count)
8F54 STA tx_complete_flag ; Clear tx_complete_flag
8F57 LDY #0 ; Y=0: receive-block offset 0 (remote-op flag)
8F59 STA (net_rx_ptr),y ; Clear remote-op flag at (net_rx_ptr)+0
8F5B LDA last_break_type ; Read l028D (current ROM number)
8F5E BNE nfs_init_check_fs_flags ; Non-zero (re-init): take nfs_init_check_fs_flags path
8F60 LDA #&10 ; A=&10: fs_flags bit 4 mask (checks 'workspace already set up')
8F62 BIT fs_flags ; Test the FS flags
8F65 BEQ alloc_post_restore_check ; Zero: first ROM init, skip FS setup
8F67 .nfs_init_check_fs_flags←1← 8F5E BNE
JSR setup_ws_ptr ; Set up workspace pointers
8F6A STA fs_flags ; Clear FS flags
8F6D LDY #&21 ; Y=&21: workspace flag offset
8F6F STA (nfs_workspace),y ; Clear the workspace flag at &21
8F71 TAY ; A=0, transfer to Y
8F72 .loop_zero_workspace←1← 8F75 BNE
STA (fs_ws_ptr),y ; Clear byte in FS workspace
8F74 INY ; Next workspace byte
8F75 BNE loop_zero_workspace ; Loop until full page (256 bytes) cleared
8F77 JSR copy_ps_data_y1c ; Copy initial PS template (1C bytes) into ws
8F7A LDX #1 ; X=1: CMOS &01 = port number
8F7C JSR osbyte_a1 ; Read CMOS &01
8F7F STY hazel_fs_station ; Store at hazel_fs_station (workspace+0)
8F82 LDX #2 ; X=2: CMOS &02 = network number
8F84 JSR osbyte_a1 ; Read CMOS &02
8F87 STY hazel_fs_network ; Store at hazel_fs_network
8F8A LDX #3 ; X=3: CMOS &03 = FS station
8F8C JSR osbyte_a1 ; Read CMOS &03
8F8F TYA ; A = FS station
8F90 LDY #2 ; Y=2: nfs_workspace offset for FS station
8F92 STA (nfs_workspace),y ; Store FS station at (nfs_workspace)+2
8F94 LDX #4 ; X=4: CMOS &04 = FS network
8F96 JSR osbyte_a1 ; Read CMOS &04 (FS network)
8F99 TYA ; A = FS network
8F9A LDY #3 ; Y=3: nfs_workspace offset for FS network
8F9C STA (nfs_workspace),y ; Store at NFS workspace offset 2
8F9E LDX #3 ; X=3: init data byte count
8FA0 .loop_copy_init_data←1← 8FA7 BNE
LDA cdir_size_done,x ; Load initialisation data byte
8FA3 STA fs_flags,x ; Store in workspace
8FA6 DEX ; Decrement counter
8FA7 BNE loop_copy_init_data ; More bytes: loop
8FA9 STX hazel_fs_messages_flag ; Clear workspace flag
8FAC STX hazel_fs_flags ; Clear workspace byte
8FAF JSR reset_spool_buf_state ; Initialise ADLC protection table
8FB2 DEX ; X=&FF (underflow from X=0)
8FB3 STX bridge_status ; Initialise workspace flag to &FF
8FB6 LDX #&11 ; X=&11: CMOS &11 (ANFS settings)
8FB8 JSR osbyte_a1 ; Read CMOS &11
8FBB TYA ; A = settings byte
8FBC AND #&40 ; Mask bit 6 (CMOS protection-state flag)
8FBE BEQ init_copy_skip_cmos ; Bit clear: skip the &FF substitution
8FC0 LDA #&ff ; A=&FF -- enable protection
8FC2 .init_copy_skip_cmos←1← 8FBE BEQ
JSR set_ws_pair_0d68_0d69 ; Set prot_status/prot_status_save pair
8FC5 .loop_alloc_handles←1← 8FD2 BNE
LDA ws_page ; Get current workspace page
8FC7 JSR byte_to_2bit_index ; Allocate FS handle page
8FCA BCS done_alloc_handles ; Allocation failed: finish init
8FCC LDA #&3f ; A=&3F: default handle permissions
8FCE STA (nfs_workspace),y ; Store handle permissions
8FD0 INC ws_page ; Advance to next page
8FD2 BNE loop_alloc_handles ; Continue allocating: loop
8FD4 .done_alloc_handles←1← 8FCA BCS
JSR restore_fs_context ; Restore FS context from saved state
8FD7 .alloc_post_restore_check←1← 8F65 BEQ
JSR read_cmos_byte_0 ; Read CMOS &00 (= station ID byte)
8FDA TYA ; Y (CMOS value) into A
8FDB BNE alloc_common_entry ; Non-zero: station ID valid -> alloc_common_entry
8FDD .alloc_error_overflow←1← 9007 BEQ
JSR print_inline ; Print 'Station number in CMOS RAM invalid...' warning
8FE0 EQUS "Badly configured station number", &07, &0D, &0D
9002 LDA #1 ; A=1: default station ID
9004 BRA alloc_store_station_id ; BRA to alloc_store_station_id with default
9006 .alloc_common_entry←1← 8FDB BNE
INY ; Check next byte (CMOS station ID hi?)
9007 BEQ alloc_error_overflow ; INY wrapped past 0 (station=&FF then INY=&00): report 'CMOS RAM invalid' and default to 1
9009 BRA alloc_store_station_id ; BRA to alloc_store_station_id (always)
900B .alloc_store_station_id←2← 9004 BRA← 9009 BRA
LDY #1 ; Y=1: net_rx_ptr offset for station-ID byte
900D STA (net_rx_ptr),y ; Store station ID into (net_rx_ptr)+1
900F LDX #&40 ; X=&40: econet_flags init value
9011 STX econet_flags ; Initialise econet_flags
9014 JSR cmd_net_fs ; Call cmd_net_fs to select NFS
9017 BEQ complete_nfs_init ; Z: selection succeeded
9019 LDA #&10 ; A=&10: bit 4 marker for fs_flags
901B ORA fs_flags ; Merge into the FS flags
901E STA fs_flags ; Store updated fs_flags
9021 .complete_nfs_init←1← 9017 BEQ
JSR init_adlc_and_vectors ; Initialise ADLC and FILEV/ARGSV/...vectors
9024 LDA #3 ; A=3: spool-ctrl byte 'init'
9026 JSR handle_spool_ctrl_byte ; Initialise *SPOOL handle in workspace
9029 JSR init_bridge_poll ; Send a bridge-discovery packet and poll
902C PHA ; Save current bridge byte
902D EOR hazel_fs_network ; With stored hazel_fs_network (network number)
9030 BNE verify_copy_station_id ; Different: take verify_copy_station_id path
9032 STA hazel_fs_network ; Same: store as new hazel_fs_network
9035 LDY #3 ; Y=3: net_rx_ptr offset 3
9037 STA (net_rx_ptr),y ; Store at (net_rx_ptr)+3
9039 .verify_copy_station_id←1← 9030 BNE
PLA ; Restore saved byte
903A LDY #3 ; Y=3: workspace offset
903C EOR (nfs_workspace),y ; Toggle the workspace flag at &21
903E BNE return_2 ; Mismatch: skip store
9040 STA (nfs_workspace),y ; Match: store at (nfs_workspace)+3
9042 .return_2←1← 903E 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)
9043 .init_adlc_and_vectors←2← 8BA7 JSR← 9021 JSR
JSR adlc_init ; Initialise ADLC hardware
9046 LDA #&a8 ; OSBYTE &A8: read ROM pointer table
9048 JSR osbyte_x0 ; Read ROM pointer table address
904B STX fs_error_ptr ; Store table pointer low
904D STY fs_crflag ; Store table pointer high
904F LDY #&36 ; Y=&36: NETV vector offset
9051 STY netv ; Set NETV address
9054 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
9056 .write_vector_entry←2← 8BAE JSR← 9068 BNE
LDA osbyte_a1,y ; Load vector address low byte
9059 STA (fs_error_ptr),y ; Store into extended vector table
905B INY ; Advance to high byte
905C LDA osbyte_a1,y ; Load vector address high byte
905F STA (fs_error_ptr),y ; Store into extended vector table
9061 INY ; Advance to ROM ID byte
9062 LDA romsel_copy ; Load current ROM slot number
9064 STA (fs_error_ptr),y ; Store ROM ID in extended vector
9066 INY ; Advance to next vector entry
9067 DEX ; Decrement vector counter
9068 BNE write_vector_entry ; More vectors to set: loop
906A 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)
906B .restore_fs_context←3← 8FD4 JSR← 9082 JSR← A6E6 JMP
LDY #9 ; Y=9: end of FS context block
906D .loop_restore_ctx←1← 9075 BNE
LDA hazel_minus_2,y ; Load FS context byte
9070 STA (net_rx_ptr),y ; Store into receive block
9072 DEY ; Decrement index
9073 CPY #1 ; Reached offset 1?
9075 BNE loop_restore_ctx ; No: continue copying
9077 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.
9078 .fscv_6_shutdown
BIT fs_flags ; FS currently selected?
907B BPL rts_fs_shutdown ; No (bit 7 clear): return
907D LDY #0 ; Y=0
907F JSR process_all_fcbs ; Close all FCBs (process_all_fcbs)
9082 JSR restore_fs_context ; Restore FS context to receive block
9085 LDY #&76 ; Y=&76: checksum range end
9087 LDA #0 ; A=0: checksum accumulator
9089 CLC ; Clear carry for addition
908A .loop_checksum_byte←1← 908E BPL
ADC hazel_fcb_addr_lo,y ; Add byte from the HAZEL &C2 FCB shadow
908D DEY ; Decrement index
908E BPL loop_checksum_byte ; Loop until all bytes summed
9090 LDY #&77 ; Y=&77: checksum storage offset
9092 BPL store_ws_byte ; Positive: store the workspace byte
9094 .loop_copy_to_ws←1← 909A BPL
LDA hazel_fcb_addr_lo,y ; Load byte from the HAZEL &C2 FCB shadow
9097 .store_ws_byte←1← 9092 BPL
STA (fs_ws_ptr),y ; Copy to FS workspace
9099 DEY ; Decrement index
909A BPL loop_copy_to_ws ; Loop until all bytes copied
909C LDA fs_flags ; Load FS flags
909F AND #&7f ; Clear bit 7 (FS no longer selected)
90A1 STA fs_flags ; Store updated flags
90A4 .rts_fs_shutdown←1← 907B 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)
90A5 .verify_ws_checksum←5← 9EA9 JSR← A055 JSR← A131 JSR← A172 JSR← B9CA JSR
PHP ; Save processor status
90A6 PHA ; Save A
90A7 PHY ; Save Y
90A8 LDY #&76 ; Y=&76: checksum range end
90AA LDA #0 ; A=0: checksum accumulator
90AC CLC ; Clear carry for addition
90AD .loop_sum_ws←1← 90B0 BPL
ADC (fs_ws_ptr),y ; Add byte from FS workspace
90AF DEY ; Decrement index
90B0 BPL loop_sum_ws ; Loop until all bytes summed
90B2 LDY #&77 ; Y=&77: checksum storage offset
90B4 CMP (fs_ws_ptr),y ; Compare with stored checksum
90B6 BNE error_net_checksum ; Mismatch: raise checksum error
90B8 PLY ; Restore Y
90B9 PLA ; Restore A
90BA PLP ; Restore processor status
90BB 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.

90BC .error_net_checksum←2← 8B7D JMP← 90B6 BNE
LDA #&aa ; Error number &AA
90BE JSR error_bad_inline ; Raise 'net checksum' error
90C1 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)
90CE .print_station_id←2← 8CCF JMP← 8D05 JSR
JSR print_inline ; Print 'Station ' inline string
90D1 EQUS "Econet Station " ; Print 'Econet Station ' via inline
90E0 LDY #1 ; Y=1: PB station-byte offset
90E2 LDA (net_rx_ptr),y ; Read RX[1] = station number
90E4 JSR print_num_no_leading ; Print as decimal (no leading zeros)
90E7 LDA #' ' ; Space character
90E9 BIT econet_control23_or_status2 ; Check ADLC status register 2
90EC BEQ done_print_newline ; Clock present: skip warning
90EE JSR print_inline ; Print ' No Clock' via inline
90F1 EQUS " No Clock"
90FA NOP ; String terminator
90FB .done_print_newline←1← 90EC BEQ
JSR osnewl ; Print newline
90FE 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.
90FF .cmd_syntax_strings←1Used as index base by← 8C2C LDA
.syn_opt_dir←1Used as index base by← 8C2C LDA
EQUS "(<dir>)" ; Syn 1: *Dir, *LCat, *LEx, *Wipe
9106 EQUB &00
9107 .syn_iam
EQUS "(<stn. id.>) <user id.> " ; Syn 2: *I Am (login)
911F EQUB &0D ; Line break
9120 EQUS "((:<CR>)<password>)" ; syntax help for *Pass / *I am
9133 EQUB &00
9134 .syn_object
EQUS "<object>" ; Syn 3: *Delete, *FS, *Remove
913C EQUB &00
913D EQUS "<filename> (<off"
914D EQUS "set" ; Store as string pointer low
9150 EQUB &3E, &20, &0D ; Store as string pointer high
9153 EQUS "(<address>))" ; Syn 4 continued: address clause
915F EQUB &00 ; Null terminator
9160 .syn_dir
EQUS "<dir>" ; Syn 5: *Lib
9165 EQUB &00
9166 EQUS "<dir> (<number>)"
9176 EQUB &00
9177 .syn_password
EQUS "(:<CR>) <password> " ; Syn 7: *Pass
918A EQUB &0D
918B EQUS "<new password>" ; Syn 7 continued: new password
9199 EQUB &00
919A EQUS "(<ps type>)|(<stn. id.>)" ; syntax help for *PS / *Pollps
91B2 EQUB &00
91B3 .syn_access
EQUS "<object> (L)(W)(R)(/(W)(R))" ; Syn 9: *Access
91CE EQUB &00 ; Null terminator
91CF .syn_rename
EQUS "<filename> <new filename>" ; Syn 10: *Rename
91E8 EQUB &00 ; Null terminator
91E9 .syn_opt_stn
EQUS "(<ps type>)" ; Syn 11: (station id. argument)
91F4 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.
91F5 .cmd_syntax_table←1Used as index base by← 8C27 LDA
EQUB syn_iam - syn_opt_dir - 2 ; Idx 0: 'opt_dir' (offset -2 variant for *Dir's INY-twice walker)
91F6 EQUB &FF ; Idx 1: &FF = no syntax string for this index
91F7 EQUB syn_iam - syn_opt_dir - 1 ; Idx 2: "(<stn.id.>) <user id.>..."
91F8 EQUB syn_object - syn_opt_dir - 1 ; Idx 3: "<object>"
91F9 EQUB &3D ; Idx 4: "<filename> (<offset>...)"
91FA EQUB &60 ; Idx 5: '<dir>' (offset 0x60 = syn_dir)
91FB EQUB &66 ; Idx 6: continued <dir> string region
91FC EQUB &77 ; Idx 7: "(:<CR>) <password>..."
91FD EQUB &9A ; Idx 8: "(<stn.id.>|<ps type>)"
91FE EQUB &B3 ; Syntax-table offset entry (into syn_opt_dir)
91FF EQUB &CF
9200 EQUB &A6
9201 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 (&8A7C) after the 'Bad ROM <slot>' message, and from two other diagnostic sites (&8E28, &9D3C).

On ExitA, X, Y, Ppreserved (print_char_no_spool brackets the call with full register save/restore via PHA/PHP/PLP/PLA)
9202 .print_newline_no_spool←6← 8AB4 JSR← 8E28 JSR← 9D3C JSR← B213 JSR← B25A JMP← B7E0 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, &9D2E, &9D5A, &B21F, &B2F9, &B321, &B77B.

On EntryAbyte to print as ASCII char (CR is translated by OSASCI)
9204 .print_char_no_spool←11← 9268 BRA← 92AD JSR← 9D2E JSR← 9D5A JMP← B1EC JSR← B252 JSR← B32C JSR← B354 JSR← B77B JSR← B79F JSR← B7B6 JSR
PHP ; Save caller's flags (V from caller is irrelevant — see &91FC)
9205 BIT always_set_v_byte ; Unconditionally sets V=1 (bit 6 of operand &FF)
9208 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 &8DE6.

On EntryAraw byte to print via OSWRCH
920A .print_byte_no_spool←2← 8DFE JSR← B797 JSR
PHP ; Alt entry: save caller's flags BEFORE forcing V=0
920B CLV ; Force V=0 -> OSWRCH path at &9229 (raw byte)
920C .save_regs_print_no_spool←1← 9208 BVS
PHX ; Save X
920D PHY ; Save Y
920E PHA ; Save A (the byte to print)
920F PHP ; Save inner P — V here picks OSASCI vs OSWRCH later
9210 LDA #osbyte_spool_file_handle ; OSBYTE 199 (read/write *SPOOL file handle)
9212 LDX #0 ; X=0: handle value to write
9214 LDY #0 ; Y=0: write mode (NEW = (OLD AND 0) EOR X = X = 0)
9216 JSR osbyte ; Closes spool; X returns OLD handle osbyte: spool file handle
9219 CPX #' ' ; OLD < ' '? (likely 0 = was already closed)
921B BCC do_print_no_spool ; Yes: leave spool closed for the print
921D CPX #'0' ; OLD >= '0'?
921F BCS do_print_no_spool ; Yes (>= &30): leave spool closed
9221 JSR osbyte ; OLD in [&20,&2F] (NFS handle range): re-open spool with X=OLD osbyte: spool file handle
9224 LDX #0 ; Clear X for the post-print restore
9226 .do_print_no_spool←2← 921B BCC← 921F BCS
PLP ; Restore inner P (V=1 OSASCI / V=0 OSWRCH)
9227 PLA ; Pull A (the byte)
9228 PHA ; Push it back so the final epilogue PLA still works
9229 BVC print_via_oswrch ; V=0 -> OSWRCH (raw); V=1 -> OSASCI (CR translation)
922B JSR osasci ; OSASCI: writes A, translating CR to CR/LF
922E BRA restore_spool_and_return ; Skip OSWRCH branch
9230 .print_via_oswrch←1← 9229 BVC
JSR oswrch ; OSWRCH: writes A as a raw byte
9233 .restore_spool_and_return←1← 922E BRA
LDA #osbyte_spool_file_handle ; OSBYTE 199 again to restore spool state
9235 LDY #&ff ; Y=&FF (read mode): NEW = OLD EOR X
9237 JSR osbyte ; X=0 -> no change; X=OLD -> writes OLD back osbyte: spool file handle
923A PLA ; Pull A (preserved across the call)
923B PLY ; Pull Y
923C PLX ; Pull X
923D PLP ; Pull caller's original flags
923E 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
923F .print_hex_byte←2← BDC0 JSR← BE68 JSR
PHA ; Save full byte
9240 LSR ; Shift high nybble to low
9241 LSR ; Continue shifting
9242 LSR ; Continue shifting
9243 LSR ; High nybble now in bits 0-3
9244 JSR print_hex_nybble ; Print high nybble as hex digit
9247 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)
9248 .print_hex_nybble←1← 9244 JSR
AND #&0f ; Mask to low nybble
924A CMP #&0a ; Digit >= &0A?
924C BCC add_ascii_base ; No: skip letter adjustment
924E ADC #6 ; Add 7 to get 'A'-'F' (6 + carry)
9250 .add_ascii_base←1← 924C BCC
ADC #'0' ; Add &30 for ASCII '0'-'9' or 'A'-'F'
9252 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
9255 .print_hex_byte_no_spool←2← 9D51 JSR← B1DC JSR
PHA ; Save full byte
9256 LSR ; Shift high nybble to low (LSR x4)
9257 LSR ; LSR / LSR / LSR -- shift hi nibble down to lo
9258 LSR ; (continued)
9259 LSR ; (continued)
925A JSR print_hex_nybble_no_spool ; Print high nybble as hex digit
925D 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)
925E .print_hex_nybble_no_spool←1← 925A JSR
AND #&0f ; Mask to low nybble
9260 CMP #&0a ; Digit >= &0A?
9262 BCC print_nybble_leading_zero ; No: skip letter adjustment
9264 ADC #6 ; Add 7 to get 'A'-'F' (6 + carry)
9266 .print_nybble_leading_zero←1← 9262 BCC
ADC #'0' ; Add &30 for ASCII '0'-'9' or 'A'-'F'
9268 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
926A .print_inline←34← 8AA3 JSR← 8C05 JSR← 8CB8 JSR← 8D59 JSR← 8FDD JSR← 90CE JSR← 90EE JSR← 95B4 JSR← 95C0 JSR← 95C7 JSR← 95CC JSR← 95D9 JSR← 964B JSR← 9657 JSR← 9670 JSR← B497 JSR← B4A3 JSR← B4BE JSR← B4C8 JSR← B4D3 JSR← B5A3 JSR← B611 JSR← B625 JSR← B654 JSR← B661 JSR← B678 JSR← B68A JSR← B6A0 JSR← B6AF JSR← BDDC JSR← BDF4 JSR← BE00 JSR← BE34 JSR← BE51 JSR
PLA ; Pop return address (low) — points to last byte of JSR
926B STA fs_error_ptr ; Store as fs_error_ptr (return-addr saved)
926D PLA ; Pop return address (high)
926E STA fs_crflag ; Store as fs_crflag (entry flag)
9270 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.

9272 .loop_next_char←1← 928D JMP
INC fs_error_ptr ; Advance pointer to next character
9274 BNE load_char ; Z clear: continue with this char
9276 INC fs_crflag ; Z set (CR): increment fs_crflag
9278 .load_char←1← 9274 BNE
LDA (fs_error_ptr),y ; Load next byte from inline string
927A BMI resume_caller ; Bit 7 set? Done — this byte is the next opcode
927C LDA fs_error_ptr ; Read fs_error_ptr (saved across OSASCI)
927E PHA ; Push it
927F LDA fs_crflag ; Read fs_crflag
9281 PHA ; Push it
9282 LDA (fs_error_ptr),y ; Reload character (pointer may have been clobbered)
9284 JSR osasci ; Print character via OSASCI
9287 PLA ; Pop saved fs_crflag
9288 STA fs_crflag ; Restore fs_crflag
928A PLA ; Pop saved fs_error_ptr
928B STA fs_error_ptr ; Restore fs_error_ptr
928D JMP loop_next_char ; Loop back
9290 .resume_caller←1← 927A 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), &B18B/&B195 (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
9293 .print_inline_no_spool←13← 9818 JSR← B18B JSR← B195 JSR← B1A3 JSR← B1AE JSR← B1CA JSR← B1DF JSR← B1F2 JSR← B201 JSR← B323 JSR← B787 JSR← B7A7 JSR← B7F4 JSR
PLA ; Pop return-addr low byte (-> string pointer low)
9294 STA fs_error_ptr ; Save in fs_error_ptr (the loop's pointer low)
9296 PLA ; Pop return-addr high byte
9297 STA fs_crflag ; Save in fs_crflag (the loop's pointer high)
9299 LDY #0 ; Y=0: indirect index for (fs_error_ptr),Y
929B .loop_print_inline_string←1← 92B6 BRA
INC fs_error_ptr ; Step pointer low byte to next char
929D BNE print_next_string_char ; No carry: skip high-byte INC
929F INC fs_crflag ; Page wrap: bump pointer high
92A1 .print_next_string_char←1← 929D BNE
LDA (fs_error_ptr),y ; Read next character from inline string
92A3 BMI print_char_terminator ; Bit 7 set: terminator -- this byte is the next opcode
92A5 LDA fs_error_ptr ; Save pointer low (print_char_no_spool may clobber)
92A7 PHA ; Push it
92A8 LDA fs_crflag ; Save pointer high
92AA PHA ; Push it
92AB LDA (fs_error_ptr),y ; Reload the character we're about to print
92AD JSR print_char_no_spool ; Print it via the *SPOOL-bypassing OSASCI wrapper
92B0 PLA ; Pop pointer high back
92B1 STA fs_crflag ; Restore
92B3 PLA ; Pop pointer low back
92B4 STA fs_error_ptr ; Restore
92B6 BRA loop_print_inline_string ; Always taken (BRA-style; A is non-zero from print)
92B8 .print_char_terminator←1← 92A3 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
92BB .parse_addr_arg←5← 8DC9 JSR← 8DD5 JSR← A3DD JSR← A3F2 JSR← B0E8 JSR
STZ fs_load_addr_2 ; Zero the accumulator (fs_load_addr_2)
92BD LDA (fs_crc_lo),y ; Read first command-line byte
92BF CMP #'&' ; Hex prefix '&'?
92C1 BNE next_dec_char ; No: try decimal path
92C3 INY ; Yes: skip the '&'
92C4 LDA (fs_crc_lo),y ; Read first hex digit
92C6 BCS check_digit_range ; Always taken (CMP #'&' set C if A>='&'); jump into the hex digit-range check
92C8 .next_hex_char←1← 92F7 BCC
INY ; Step to next character
92C9 LDA (fs_crc_lo),y ; Read next hex digit candidate
92CB CMP #'.' ; Dot? Net.station separator
92CD BEQ handle_dot_sep ; Yes: switch to station-parsing mode
92CF CMP #'!' ; Below '!' (CR/space)? End of argument
92D1 BCC done_parse_num ; Yes: number complete
92D3 .check_digit_range←1← 92C6 BCS
CMP #'0' ; Below '0'?
92D5 BCC skip_if_not_hex ; Yes: not a hex digit
92D7 CMP #':' ; Above '9'? (CMP #':')
92D9 BCC extract_digit_value ; No (it's '0'-'9'): straight to digit extraction
92DB AND #&5f ; Force uppercase via AND #&5F
92DD ADC #&b8 ; Map 'A'-'F' to &FA-&FF (ADC #&B8 with C from earlier CMP #':' which set C)
92DF BCS err_bad_hex ; Carry out of ADC: was below 'A' -- bad hex
92E1 CMP #&fa ; Below &FA? (digit > 'F' overflowed past)
92E3 .skip_if_not_hex←1← 92D5 BCC
BCC err_bad_hex ; Yes: bad hex (out of [&FA,&FF])
92E5 .extract_digit_value←1← 92D9 BCC
AND #&0f ; Mask to nibble
92E7 STA fs_load_addr_3 ; Stash digit value in fs_load_addr_3
92E9 LDA fs_load_addr_2 ; Load accumulator
92EB CMP #&10 ; Above 16? (would overflow when shifted left 4)
92ED BCS error_overflow ; Yes: overflow
92EF ASL ; Shift accumulator left 4 (multiply by 16)
92F0 ASL ; (shift 2)
92F1 ASL ; (shift 3)
92F2 ASL ; (shift 4)
92F3 ADC fs_load_addr_3 ; Add new nibble
92F5 STA fs_load_addr_2 ; Save updated accumulator
92F7 BCC next_hex_char ; No carry: continue (always taken since accumulator was checked < 16 above)
92F9 .next_dec_char←2← 92C1 BNE← 9323 BNE
LDA (fs_crc_lo),y ; Read next decimal-digit candidate
92FB CMP #'.' ; Dot? Net.station separator
92FD BEQ handle_dot_sep ; Yes: switch to station-parsing mode
92FF CMP #'!' ; Below '!' (CR/space)?
9301 BCC done_parse_num ; Yes: number complete
9303 JSR is_dec_digit_only ; Test for '0'-'9' and reject '&'/'.'
9306 BCC error_bad_number ; Not a decimal digit: bad number
9308 AND #&0f ; Mask to nibble
930A STA fs_load_addr_3 ; Stash digit
930C ASL fs_load_addr_2 ; Accumulator * 2
930E BCS error_overflow ; Overflowed: too big for byte
9310 LDA fs_load_addr_2 ; Reload doubled value
9312 ASL ; * 2 again (now * 4)
9313 BCS error_overflow ; Overflow check
9315 ASL ; * 2 again (now * 8)
9316 BCS error_overflow ; Overflow check
9318 ADC fs_load_addr_2 ; + accumulator (now * 8 + * 2 = * 10)
931A BCS error_overflow ; Overflow check
931C ADC fs_load_addr_3 ; + new digit
931E BCS error_overflow ; Overflow check
9320 STA fs_load_addr_2 ; Save * 10 + digit
9322 INY ; Step input cursor
9323 BNE next_dec_char ; Always taken (Y wraps at 256, never zero in practice)
9325 .done_parse_num←2← 92D1 BCC← 9301 BCC
LDA fs_work_4 ; Read mode flag
9327 BPL validate_station ; Bit 7 clear: in net.station mode -- validate result
9329 LDA fs_load_addr_2 ; Decimal-only mode: get result
932B BEQ error_bad_param ; Result is zero: bad parameter
932D RTS ; Return with parsed result in A (decimal-only path)
932E .validate_station←1← 9327 BPL
LDA fs_load_addr_2 ; Reload result
9330 CMP #&ff ; Station 255 is reserved (broadcast)
9332 BEQ err_bad_station_num ; Yes: bad station number
9334 LDA fs_load_addr_2 ; Reload result for the next test
9336 BNE return_parsed ; Non-zero: valid station, return
9338 LDA fs_work_4 ; Zero result: must have followed a dot to be valid
933A BEQ err_bad_station_num ; No dot was seen: bad station number
933C DEY ; Dot seen: peek the byte before current cursor
933D LDA (fs_crc_lo),y ; Read previous byte
933F INY ; Restore Y
9340 EOR #'.' ; Was previous char '.'?
9342 BNE err_bad_station_num ; No: bad station number
9344 .return_parsed←1← 9336 BNE
SEC ; All checks passed: C=1 marks 'parsed successfully'
9345 RTS ; Return
9346 .handle_dot_sep←2← 92CD BEQ← 92FD BEQ
LDA fs_work_4 ; Dot already seen?
9348 BNE error_bad_number ; Yes: 'Bad number' (multiple dots)
934A INC fs_work_4 ; Set dot-seen flag
934C LDA fs_load_addr_2 ; Get parsed network number (before dot)
934E CMP #&ff ; Network 255 is reserved
9350 BEQ error_bad_net_num ; Yes: 'Bad network number'
9352 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.

9353 .err_bad_hex←3← 92DF BCS← 92E3 BCC← BECF JMP
LDA #&f1 ; Error code &F1
9355 JSR error_bad_inline ; Raise 'Bad hex' error
9358 EQUS "hex", &00
935C .error_overflow←6← 92ED BCS← 930E BCS← 9313 BCS← 9316 BCS← 931A BCS← 931E BCS
BIT fs_work_4 ; Test fs_work_4 bit 7
935E BMI error_bad_param ; Bit 7 set: redirect to error_bad_param
9360 .err_bad_station_num←3← 9332 BEQ← 933A BEQ← 9342 BNE
LDA #&d0 ; A=&D0: 'Bad station' error code
9362 JSR error_bad_inline ; Raise via error_bad_inline (never returns)
9365 EQUS "station number", &00
9374 .error_bad_number←2← 9306 BCC← 9348 BNE
LDA #&f0 ; A=&F0: 'Bad number' error code
9376 JSR error_bad_inline ; Raise via error_bad_inline (never returns)
9379 EQUS "number", &00
9380 .error_bad_param←2← 932B BEQ← 935E BMI
LDA #&94 ; A=&94: 'Bad parameter' error code
9382 JSR error_bad_inline ; Raise via error_bad_inline (never returns)
9385 EQUS "parameter", &00
938F .error_bad_net_num←1← 9350 BEQ
LDA #&d1 ; A=&D1: 'Bad net number' error code
9391 JSR error_bad_inline ; Raise via error_bad_inline (never returns)
9394 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
939F .is_decimal_digit←2← 8DC4 JSR← B3FE JSR
CMP #'&' ; Hex prefix '&'?
93A1 BEQ rts_digit_test ; Yes: treat as digit-like (carry set on exit)
93A3 CMP #'.' ; Network/station separator '.'?
93A5 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
93A7 .is_dec_digit_only←1← 9303 JSR
CMP #':' ; Above '9'? (CMP #':')
93A9 BCS not_a_digit ; Yes: not a digit -- jump to clear-carry exit
93AB CMP #'0' ; Below '0'? (CMP sets carry if A >= '0')
93AD .rts_digit_test←2← 93A1 BEQ← 93A5 BEQ
RTS ; Carry now reflects '0'-'9' membership; return
93AE .not_a_digit←1← 93A9 BCS
CLC ; Out-of-range exit: clear carry to signal not-a-digit
93AF 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)
93B0 .get_access_bits←2← 9E0B JSR← 9E37 JSR
LDY #&0e ; Y=&0E: directory entry access byte offset
93B2 LDA (fs_options),y ; Read access byte through fs_options pointer
93B4 AND #&3f ; Mask to 6 protection bits (clears the unused top two)
93B6 LDX #4 ; X=4: encode-table column index for owner-access bits
93B8 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
93BA .get_prot_bits←2← 9D15 JSR← 9E54 JSR
AND #&1f ; Mask to 5 protection bits (low 5)
93BC LDX #&ff ; X=&FF; INX inside the loop bumps to 0 for column 0
93BE .begin_prot_encode←1← 93B8 BNE
STA fs_error_ptr ; Park source bits in fs_error_ptr -- the LSR target
93C0 LDA #0 ; A=0: accumulator for encoded result
93C2 .loop_encode_prot←1← 93CA BNE
INX ; Advance table column index
93C3 LSR fs_error_ptr ; Shift next source bit into carry
93C5 BCC skip_clear_prot ; Source bit was 0: skip the OR for this column
93C7 ORA prot_bit_encode_table,x ; Source bit was 1: OR in this column's encoded mask
93CA .skip_clear_prot←1← 93C5 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)
93CC 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
93CD .prot_bit_encode_table←1Used as index base by← 93C7 ORA
EQUB &50 ; prot src bit 0 -> out bits 6,4
93CE EQUB &20 ; prot src bit 1 -> out bit 5
93CF EQUB &05 ; prot src bit 2 -> out bits 2,0
93D0 EQUB &02 ; prot src bit 3 -> out bit 1
93D1 EQUB &88 ; prot src bit 4 -> out bits 7,3
93D2 EQUB &04 ; access src bit 0 -> out bit 2
93D3 EQUB &08 ; access src bit 1 -> out bit 3
93D4 EQUB &80 ; access src bit 2 -> out bit 7
93D5 EQUB &10 ; access src bit 3 -> out bit 4
93D6 EQUB &01 ; access src bit 4 -> out bit 0
93D7 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
93D8 .set_text_and_xfer_ptr←2← A443 JSR← B131 JSR
STX os_text_ptr ; Save text pointer low byte (where caller wants OS to scan from)
93DA 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
93DC .set_xfer_params←6← 8DBE JSR← 8E63 JSR← 9C20 JSR← A058 JSR← A175 JSR← B14B JSR
STA fs_last_byte_flag ; Stash transfer byte count (in A)
93DE STX fs_crc_lo ; Source pointer low byte
93E0 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
93E2 .set_options_ptr←2← 9EAE JSR← BD45 JSR
STX fs_options ; Options pointer low byte (parameter block base)
93E4 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).

93E6 .clear_escapable←1← 9B70 JMP
PHP ; Save flags so the LSR doesn't disturb caller's NZC
93E7 LSR need_release_tube ; Shift bit 0 of need_release_tube into carry, clearing the bit
93E9 PLP ; Restore caller's flags
93EA 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
93EB .cmp_5byte_handle←2← 9C83 JSR← 9D86 JSR
LDX #4 ; X=4: loop from offset 4 down to 1 (skips offset 0)
93ED .loop_cmp_handle←1← 93F4 BNE
LDA addr_work,x ; Load saved-handle byte from addr_work[X]
93EF EOR fs_load_addr_3,x ; EOR with parsed handle byte; Z set iff bytes match
93F1 BNE rts_cmp_handle ; Mismatch: bail out with Z clear
93F3 DEX ; Decrement to next byte
93F4 BNE loop_cmp_handle ; Loop while X != 0 (offset 0 is intentionally not compared)
93F6 .rts_cmp_handle←1← 93F1 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)
93F7 .fscv_7_read_handles
LDX #&20 ; X=&20: handle-table base offset
93F9 LDY #&2f ; Y=&2F: handle count + flag
93FB 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
93FC .set_conn_active←2← 9F31 JSR← A1AD JSR
PHP ; Save flags so the rest of the routine is transparent
93FD PHA ; Save A (the attribute byte we need to recover via stack)
93FE PHX ; Save X
93FF TSX ; Capture S into X to address stack from below
9400 LDA stack_page_2,x ; Re-read the original A from stack[X+2] (above PHX/PHA)
9403 JSR attr_to_chan_index ; Convert attribute byte to channel-table index
9406 BMI clear_channel_flag ; No matching channel: skip the flag set, just restore
9408 LDA #&40 ; A=&40: bit 6 = connection-active mask
940A ORA hazel_fcb_status,x ; OR with current status byte for this channel
940D STA hazel_fcb_status,x ; Write back the updated status
9410 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
9412 .clear_conn_active←2← 9FBC JSR← A1A8 JSR
PHP ; Save flags
9413 PHA ; Save A
9414 PHX ; Save X
9415 TSX ; Capture S into X for stack-relative reads
9416 LDA stack_page_2,x ; Re-read the attribute byte from stack[X+2]
9419 JSR attr_to_chan_index ; Convert attribute to channel index
941C BMI clear_channel_flag ; No matching channel: just restore
941E LDA #&bf ; A=&BF: bit 6 clear mask
9420 AND hazel_fcb_status,x ; Mask the FCB status flags
9423 STA hazel_fcb_status,x ; Write back the updated status
9426 .clear_channel_flag←3← 9406 BMI← 9410 BNE← 941C BMI
PLX ; Restore X (saved at PHX)
9427 PLA ; Restore A
9428 PLP ; Restore flags
9429 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)
942A .cmd_fs_operation
JSR copy_fs_cmd_name ; Copy command name 'Access'/'Delete'/'Info'/'Lib' to TX buffer
942D PHX ; Save X
942E JSR parse_quoted_arg ; Parse quoted filename argument from command line
9431 JSR parse_access_prefix ; Parse the access prefix (e.g. L,W,R) into a bitmask
9434 PLX ; Restore X
9435 JSR check_not_ampersand ; Reject '&' character in filename
9438 CMP #&0d ; End of line?
943A 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.

943C .error_bad_filename←3← 9450 BEQ← 9543 JMP← B2AC JMP
LDA #&cc ; Error number &CC
943E JSR error_bad_inline ; Raise 'Bad file name' error
9441 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)
944B .check_not_ampersand←2← 9435 JSR← 9453 JSR
LDA hazel_parse_buf ; Load first parsed character
944E CMP #'&' ; Is it '&'?
9450 BEQ error_bad_filename ; Yes: invalid filename
9452 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 &9435 (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
9453 .read_filename_char←3← 943A BNE← 9461 BRA← 9514 JMP
JSR check_not_ampersand ; Reject '&' in current char
9456 STA hazel_txcb_data,x ; Store character in TX buffer
9459 INX ; Advance buffer pointer
945A CMP #&0d ; End of line?
945C BEQ send_fs_request ; Yes: send request to file server
945E JSR strip_token_prefix ; Strip BASIC token prefix byte
9461 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.

9463 .send_fs_request←2← 945C BEQ← 953C JMP
LDY #0 ; Y=0: ensure offset starts from beginning of TX command buffer
9465 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
9468 .copy_fs_cmd_name←2← 942A JSR← 94CA JSR
PHY ; Save Y on entry
9469 .loop_scan_flag←1← 946D BPL
DEX ; Scan backwards in command table
946A LDA cmd_table_fs,x ; Load table byte
946D BPL loop_scan_flag ; Bit 7 clear: keep scanning
946F INX ; Point past flag byte to name start
9470 LDY #0 ; Y=0: TX buffer offset
9472 .loop_copy_name←1← 947C BNE
LDA cmd_table_fs,x ; Load command name character
9475 BMI append_space ; Bit 7 set: end of name
9477 STA hazel_txcb_data,y ; Store character in TX buffer
947A INX ; Advance table pointer
947B INY ; Advance buffer pointer
947C BNE loop_copy_name ; Continue copying name
947E .append_space←1← 9475 BMI
LDA #' ' ; Space separator
9480 STA hazel_txcb_data,y ; Append space after command name
9483 INY ; Advance buffer pointer
9484 TYA ; Transfer length to A
9485 TAX ; And to X (buffer position)
9486 PLY ; Restore Y
9487 .rts_copy_cmd_name←1← 94BC 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)
9488 .parse_quoted_arg←2← 942E JSR← 94D1 JSR
LDA #0 ; A=0: no quote mode
948A TAX ; Copy A to X
948B STA hazel_quote_mode ; Clear quote tracking flag
948E .loop_skip_spaces←1← 9495 BNE
LDA (fs_crc_lo),y ; Load char from command line
9490 CMP #' ' ; Space?
9492 BNE check_open_quote ; No: check for opening quote
9494 INY ; Skip leading space
9495 BNE loop_skip_spaces ; Continue skipping spaces
9497 .check_open_quote←1← 9492 BNE
CMP #'"' ; Double-quote character?
9499 BNE loop_copy_arg_char ; No: start reading filename
949B INY ; Skip opening quote
949C EOR hazel_quote_mode ; Toggle quote mode flag
949F STA hazel_quote_mode ; Store updated quote mode
94A2 .loop_copy_arg_char←2← 9499 BNE← 94B7 BNE
LDA (fs_crc_lo),y ; Load char from command line
94A4 CMP #'"' ; Double-quote?
94A6 BNE store_arg_char ; No: store character as-is
94A8 EOR hazel_quote_mode ; Toggle quote mode
94AB STA hazel_quote_mode ; Store updated quote mode
94AE LDA #' ' ; Replace closing quote with space
94B0 .store_arg_char←1← 94A6 BNE
STA hazel_parse_buf,x ; Store character in parse buffer
94B3 INY ; Advance command line pointer
94B4 INX ; Advance buffer pointer
94B5 CMP #&0d ; End of line?
94B7 BNE loop_copy_arg_char ; No: continue parsing
94B9 LDA hazel_quote_mode ; Check quote balance flag
94BC BEQ rts_copy_cmd_name ; Balanced: return OK
94BE LDA brk_ptr ; Unbalanced: use BRK ptr for error
94C0 JSR error_bad_inline ; Raise 'Bad string' error
94C3 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
94CA .cmd_rename
JSR copy_fs_cmd_name ; Copy 'Rename ' to TX buffer
94CD PHX ; Save X
94CE JSR mask_owner_access ; Clear owner-only access bits before parsing
94D1 JSR parse_quoted_arg ; Parse the quoted source filename
94D4 JSR parse_access_prefix ; Parse access prefix on the source filename
94D7 PLX ; Restore X
94D8 .loop_copy_rename←1← 94F6 BRA
LDA hazel_parse_buf ; Load next parsed character
94DB CMP #&0d ; End of line?
94DD BNE store_rename_char ; No: store character
94DF .error_bad_rename←1← 9512 BNE
LDA #&b0 ; Error number &B0
94E1 JSR error_bad_inline ; Raise 'Bad rename' error
94E4 EQUS "rename", &00 ; Add 5 for header size
94EB .store_rename_char←1← 94DD BNE
STA hazel_txcb_data,x ; Store character in TX buffer
94EE INX ; Advance buffer pointer
94EF CMP #' ' ; Space (name separator)?
94F1 BEQ skip_rename_spaces ; Yes: first name complete
94F3 JSR strip_token_prefix ; Strip BASIC token prefix byte
94F6 BRA loop_copy_rename ; BRA back to loop_copy_rename
94F8 .skip_rename_spaces←2← 94F1 BEQ← 9500 BEQ
JSR strip_token_prefix ; Strip token from next char
94FB LDA hazel_parse_buf ; Load next parsed character
94FE CMP #' ' ; Still a space?
9500 BEQ skip_rename_spaces ; Yes: skip multiple spaces
9502 LDA hazel_fs_lib_flags ; Save current FS options
9505 PHA ; Push them
9506 JSR mask_owner_access ; Reset access mask for second name
9509 PHX ; Save loop index across the access parse
950A JSR parse_access_prefix ; Parse access prefix on the second filename
950D PLX ; Restore loop index
950E PLA ; Restore original FS options
950F CMP hazel_fs_lib_flags ; Options changed (cross-FS)?
9512 BNE error_bad_rename ; Yes: error (can't rename across FS)
9514 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
9517 .cmd_dir
LDA (fs_crc_lo),y ; Get first char of argument
9519 CMP #'&' ; Is it '&' (FS selector prefix)?
951B BNE dir_pass_simple ; No: simple dir change
951D INY ; Skip '&'
951E LDA (fs_crc_lo),y ; Get char after '&'
9520 CMP #&0d ; End of line?
9522 BEQ setup_fs_root ; Yes: '&' alone (root directory)
9524 CMP #' ' ; Space?
9526 BNE check_fs_dot ; No: check for '.' separator
9528 .setup_fs_root←1← 9522 BEQ
LDY #&ff ; Y=&FF: pre-increment for loop
952A .loop_copy_fs_num←1← 9532 BNE
INY ; Advance index
952B LDA (fs_crc_lo),y ; Load char from command line
952D STA hazel_txcb_data,y ; Copy to TX buffer
9530 CMP #'&' ; Is it '&' (end of FS path)?
9532 BNE loop_copy_fs_num ; No: keep copying
9534 LDA #&0d ; Replace '&' with CR terminator
9536 STA hazel_txcb_data,y ; Store CR in buffer
9539 INY ; Point past CR
953A TYA ; Transfer length to A
953B TAX ; And to X (byte count)
953C JMP send_fs_request ; Send directory request to server
953F .check_fs_dot←1← 9526 BNE
CMP #'.' ; Is char after '&' a dot?
9541 BEQ parse_fs_dot_dir ; Yes: &FS.dir format
9543 JMP error_bad_filename ; No: invalid syntax
9546 .parse_fs_dot_dir←1← 9541 BEQ
INY ; Skip '.'
9547 STY fs_load_addr ; Save dir path start position
9549 LDA #4 ; FS command 4: examine directory
954B STA hazel_txcb_data ; Store in TX buffer
954E LDA hazel_fs_lib_flags ; Load FS flags
9551 ORA #&40 ; Set bit 6 (FS selection active)
9553 STA hazel_fs_lib_flags ; Store updated flags
9556 LDX #1 ; X=1: buffer offset
9558 JSR copy_arg_validated ; Copy FS number to buffer
955B LDY #&12 ; Y=&12: select FS command code
955D JSR save_net_tx_cb ; Send FS selection command
9560 LDA hazel_txcb_data ; Load reply status
9563 CMP #2 ; Status 2 (found)?
9565 BEQ dir_found_send ; Yes: proceed to dir change
9567 LDA #&d6 ; Error number &D6
9569 JSR error_inline_log ; Raise 'Not found' error
956C EQUS "Not found", &00 ; Store null terminator (A=0 from EOR) Get message length Go to error dispatch
9576 .dir_found_send←1← 9565 BEQ
LDA hazel_fs_context_copy ; Load current FS station byte
9579 STA hazel_txcb_data ; Store in TX buffer
957C LDX #1 ; X=1: buffer offset
957E LDY #7 ; Y=7: change directory command code
9580 JSR save_net_tx_cb ; Send directory change request
9583 LDX #1 ; X=1
9585 STX hazel_txcb_data ; Store start marker in buffer
9588 STX hazel_txcb_flag ; Store start marker in buffer+1
958B INX ; Non-zero: commit state and return
958C LDY fs_load_addr ; Restore dir path start position
958E JSR copy_arg_validated ; Copy directory path to buffer
9591 LDY #6 ; Y=6: set directory command code
9593 JSR save_net_tx_cb ; Send set directory command
9596 LDY hazel_txcb_data ; Load reply handle
9599 JMP fsreply_3_set_csd ; Select FS and return
959C .dir_pass_simple←1← 951B BNE
JMP check_urd_prefix ; Simple: pass command to FS
959F .print_fs_ps_help
LDA (os_text_ptr),y ; Read first command-line char at (os_text_ptr),Y
95A1 CMP #&0d ; Is it CR (no argument supplied)?
95A3 BNE dispatch_fs_ps_with_arg ; Non-CR: argument present -- exit via dispatch_fs_ps_with_arg (X=&A0)
95A5 JSR print_fs_station ; CR: print 'FS ' header
95A8 JSR print_dir_syntax ; Print '[<D>.]<D>\r'
95AB JSR print_station_low ; Print 'PS ' header
95AE JSR print_dir_syntax ; Print '[<D>.]<D>\r' again
95B1 JSR print_space_line ; Print final 'Space\rNoSpace\r' lines
95B4 JSR print_inline ; Print inline string
95B7 EQUS "No"
95B9 NOP ; NOP -- bit-7 terminator + resume opcode for the preceding inline string
95BA JSR print_space_line ; Print the 'Space' free-space label
95BD .bra_target_svc_return←1← 9616 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.

95C0 .print_station_low←2← 95AB JSR← 963B JSR
JSR print_inline ; Print 'P' prefix
95C3 EQUS "P"
95C4 CLV ; CLV -- bit-7 terminator + resume (V flag is irrelevant here, used as 1-byte resume opcode)
95C5 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.

95C7 .print_fs_station←2← 95A5 JSR← 9635 JSR
JSR print_inline ; Print 'F' prefix
95CA EQUS "F"
95CB NOP ; NOP -- bit-7 terminator; falls through into the shared 'S ' tail at &95CC
95CC .print_field_tail_s←1← 95C5 BVC
JSR print_inline ; Print 'S ' (S + 7 spaces) -- the shared 8-char field used by both 'FS' and 'PS' callers
95CF EQUS "S "
95D7 NOP ; Bit-7 terminator
95D8 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.

95D9 .print_dir_syntax←2← 95A8 JSR← 95AE JSR
JSR print_inline ; Print '[<D>.]<D>\r' (file-name syntax fragment, shared between *FS/*PS no-arg help and *Dir)
95DC EQUS "[<D>.]<D>", &0D
95E6 NOP ; Bit-7 terminator
95E7 RTS ; Return
95E8 .dispatch_fs_ps_with_arg←1← 95A3 BNE
LDX #&a5 ; X=&A5: index into svc4 dispatch table (no-arg path)
95EA 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 &A841, *PS at &A846) 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
95ED .set_fs_or_ps_cmos_station
LDA cmd_table_fs,x ; Read flag byte for matched cmd entry (syntax idx in bits 0..4)
95F0 AND #&3f ; Mask off end-marker (bit 7) and V-if-no-arg flag (bit 6)
95F2 TAX ; X = CMOS byte index (1=FS stn, 3=PS stn)
95F3 PHX ; Save CMOS index
95F4 PHY ; Save caller's command-line cursor
95F5 PHX ; Save CMOS index again (consumed by first PLX below)
95F6 JSR osbyte_a1 ; Read existing CMOS[idx] (current station)
95F9 STY fs_work_5 ; Default station if user gives no args
95FB PLX ; Recover CMOS index from stack
95FC INX ; X+=1: advance to network byte
95FD JSR osbyte_a1 ; Read existing CMOS[idx+1] (current network)
9600 STY fs_work_6 ; Default network if user gives no args
9602 PLY ; Restore command-line cursor
9603 JSR parse_fs_ps_args ; Parse '<net>.<stn>'; updates fs_work_5/6/7 if args present
9606 PLX ; Recover CMOS index from stack
9607 PHX ; Re-save CMOS index for second write
9608 LDY fs_work_5 ; Y = station (parsed or pre-read default)
960A JSR osbyte_a2 ; Write CMOS[idx] = station
960D PLX ; Recover CMOS index from stack
960E INX ; X+=1: advance to network byte
960F 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 &95BD. 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 &9616 → &8C89).

osbyte_a2 ends at &9618 (3 instructions, 8 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 &962C 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
9611 .osbyte_a2←3← 960A JSR← 962D BRA← A124 JSR
LDA #osbyte_write_cmos_ram ; A=&A2: write CMOS RAM byte via OSBYTE
9613 JSR osbyte ; Write CMOS RAM byte
9616 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.

9618 .cmd_space
LDX #&11 ; X=&11: CMOS RAM byte index
961A JSR osbyte_a1 ; Read CMOS &11 via osbyte_a1
961D TYA ; A = current CMOS &11 value
961E ORA #1 ; Set bit 0 in A
9620 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.

9622 .cmd_nospace
LDX #&11 ; X=&11: CMOS RAM byte index
9624 JSR osbyte_a1 ; Read CMOS &11 via osbyte_a1
9627 TYA ; A = current CMOS &11 value
9628 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.

962A .osbyte_a2_value_tya←1← 9620 BRA
TAY ; New CMOS value to Y
962B LDX #&11 ; X=&11: CMOS RAM byte index
962D 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.

962F .svc_29_status
LDA (os_text_ptr),y ; Read first command-line char
9631 CMP #&0d ; Is it CR (no argument)?
9633 BNE help_dispatch_setup ; Non-CR: parse the argument at help_dispatch_setup
9635 JSR print_fs_station ; Print 'FS ' header
9638 JSR print_fs_address ; Print FS network.station from CMOS &02/&01
963B JSR print_station_low ; Print 'PS ' header
963E JSR print_ps_address ; Print PS network.station from CMOS &04/&03
9641 LDX #&11 ; X=&11: CMOS RAM byte index
9643 JSR osbyte_a1 ; Read CMOS &11 (FS state)
9646 TYA ; A = CMOS &11
9647 AND #1 ; Mask bit 0 (FS-active flag)
9649 BNE parse_object_space_print ; Bit set: skip 'No ' prefix
964B JSR print_inline ; Print 'No ' prefix via inline
964E EQUS "No "
9651 NOP ; Bit-7 terminator + resume
9652 .parse_object_space_print←1← 9649 BNE
JSR print_space_line ; Print 'Space ' or similar via inline
9655 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.

9657 .print_space_line←3← 95B1 JSR← 95BA JSR← 9652 JSR
JSR print_inline ; Print inline string
965A EQUS "Space", &0D ; Bit-7 terminator + resume opcode
9660 NOP ; bit-7 terminator + resume opcode
9661 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.24 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.

9662 .print_ps_address←1← 963E JSR
LDX #4 ; X=4: CMOS RAM byte 4 (PS network number)
9664 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.

9666 .print_fs_address←1← 9638 JSR
LDX #2 ; X=2: CMOS RAM byte 2 (FS network number)
9668 .print_cmos_pair←1← 9664 BRA
PHX ; Save network-byte index across the first print
9669 JSR osbyte_a1 ; Read CMOS[X] (network number) via osbyte_a1
966C TYA ; A = CMOS network byte
966D JSR print_num_no_leading ; Print as decimal (no leading zeros)
9670 JSR print_inline ; Print '.' separator via inline
9673 EQUS "."
9674 PLX ; PLX terminator: restore X (network index)
9675 DEX ; X-1: the station byte sits just below the network byte
9676 .print_cmos_decimal_nl
JSR osbyte_a1 ; Read CMOS X via osbyte_a1
9679 TYA ; A = CMOS value
967A JSR print_num_no_leading ; Print as decimal
967D JSR osnewl ; Print newline
9680 .print_cmos_done←1← 9655 BRA
JMP svc_return_unclaimed ; JMP svc_return_unclaimed (release service call)
9683 .help_dispatch_setup←1← 9633 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.

9685 .dispatch_help_command
JMP svc4_dispatch_lookup ; JMP svc4_dispatch_lookup -- shared parser dispatch
9688 EQUS "!Help.*" ; '!Help.' prefix bytes (not used by the matcher; may be visible as a fallback help-message head)
968F EQUB &0D
9690 EQUS "Z,l"
9693 EQUB &0D, &50, &19
9696 EQUB &A5, &F2 ; Copy os_text_ptr lo to work_ae
9698 EQUB &85, &AE ; Store -> work_ae
969A EQUB &A5, &F3 ; Copy os_text_ptr hi
969C EQUB &85, &AF, &B1, &AE ; Store -> addr_work
96A0 EQUS "IO)_"
96A4 EQUB &D0, &09, &C8, &B1, &AE
96A9 EQUS "IN)_"
96AD EQUB &F0, &02, &7A, &60 ; Equal: continue checking pattern
96B1 EQUB &5A ; Match: save Y
96B2 EQUB &20, &73, ; Ensure NFS is selected (auto-select if &8B ; needed)
96B5 .match_char_process
EQUB &7A
96B6 .loop_skip_non_spaces
EQUB &C8 ; Advance Y to next char
96B7 EQUB &B1, &AE, &2C, ; Read text byte at (work_ae)+Y &67, &97
96BC EQUB &C9, &20, &90, &03, &F0, &F4, &B8 ; Is it space?
96C3 EQUB &8C, &05, ; Save Y as hazel_txcb_data (cmd buffer ptr) &C1
96C6 EQUB &8C, &06, &C1 ; Save Y as hazel_txcb_flag (cmd flag)
96C9 EQUB &A2, &01 ; X=1: index for template walk
96CB .loop_copy_command_suffix
EQUB &E8 ; Advance template index
96CC EQUB &BD, &86, ; Read template byte from &96 ; help_topic_template+X
96CF EQUB &9D, &05, &C1, &50, &07, &C9, &0D, &D0, &F3, &C8, &80, &17 ; Store at hazel_txcb_data+X
96DB EQUB &C9, &2E, &D0, ; Compare with '.' (template &EC ; terminator)
96DF .loop_copy_topic_name
EQUB &E8 ; Advance dest index
96E0 EQUB &B1, &AE ; Read topic char at (work_ae),Y
96E2 EQUB &C8 ; Advance source
96E3 .loop_store_topic_char
EQUB &9D, &05, &C1 ; Store at hazel_txcb_data+X
96E6 EQUB &C9, &0D ; CR? (end of name)
96E8 EQUB &F0, ; Yes: take start_help_file_load path (open file) &08
96EA EQUB &C9, &20 ; Space? (terminator)
96EC EQUB &D0, &F1 ; No: continue copying
96EE EQUB &A9, &0D ; A=&0D: replace space with CR
96F0 EQUB &80, &F1, &5A ; BRA back to store the CR
96F3 .start_help_file_load
EQUB &E8 ; Account for last char
96F4 EQUB &AD, &71, &C2 ; Read fs_lib_flags (hazel_fs_lib_flags)
96F7 EQUB &29, &3F ; Preserve low bits, clear high bits
96F9 EQUB &09, &80 ; Set bit 7 (load-pending flag)
96FB EQUB &8D, &71, &C2 ; Store back to fs_lib_flags
96FE EQUB &A9, &40 ; A=&40: load mode flag
9700 EQUB &85, &BD ; Store as fs_last_byte_flag
9702 EQUB &20, &14, &A0, &A8 ; Open the help-topic file
9706 EQUB &F0, &0D ; Y=0: open failed -> return
9708 .loop_print_help_byte
EQUB &20, &D7, &FF
970B EQUB &90, &16 ; C clear: byte read OK -> print it
970D EQUB &A9, &00, &20, &CE, &FF, &20, &E7, &FF, &7A, &88, &88, &C8, &B1, &AE, &C9, &20, &90, &90, &F0, &F7, &80, &9F, &24, &FF ; A=0: OSFIND close mode
9725 EQUB &10, &03, &4C, &5D, ; Bit 7 clear: not escaping, &BD, &20, &EE, &FF ; continue
972D EQUB &C9, &0D, &D0, &D7, &AE, &6A, &02 ; Compare with CR
9734 EQUB &D0, &D2, &20, ; Non-zero: paged mode pending -> &E7, &FF ; handle Escape
9739 EQUB &80, &CD ; BRA back to read next byte

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.

973B .init_txcb_bye←1← 97CC 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
973D .init_txcb_port←1← 9DCB JSR
JSR init_txcb ; Initialise TXCB from template
9740 STA txcb_port ; Set transmit port
9742 LDA #3 ; A=3: data start offset
9744 STA txcb_start ; Set TXCB start offset
9746 DEC txcb_ctrl ; Open receive: &80->&7F (bit 7 clear = awaiting reply)
9748 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)
9749 .init_txcb←5Referenced by← 8E2D JSR← 973D JSR← 97BB JSR← BCFB JSRUsed as index base by← AC75 LDA
PHA ; Save A
974A LDY #&0b ; Y=&0B: template size - 1
974C .loop_init_txcb←1← 975D BPL
LDA txcb_init_template,y ; Load byte from TXCB template
974F STA txcb_ctrl,y ; Store to TXCB workspace
9752 CPY #2 ; Index >= 2?
9754 BPL skip_txcb_dest ; Yes: skip dest station copy
9756 LDA hazel_fs_station,y ; Load dest station byte
9759 STA txcb_dest,y ; Store to TXCB destination
975C .skip_txcb_dest←1← 9754 BPL
DEY ; Decrement index
975D BPL loop_init_txcb ; More bytes: continue
975F PLA ; Restore A
9760 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.

9761 .txcb_init_template←1Used as index base by← 974C LDA
EQUB &80 ; Offset 0: txcb_ctrl = &80 (TX command)
9762 EQUB &99 ; Offset 1: txcb_port = &99 (FS command port)
9763 EQUB &00 ; Offset 2: txcb_dest lo placeholder (overwritten with hazel_fs_station[0])
9764 EQUB &00 ; Offset 3: txcb_dest hi placeholder (overwritten with hazel_fs_station[1])
9765 EQUB &00 ; Offset 4: txcb_start lo = 0
9766 EQUB &C1 ; Offset 5: txcb_start hi = &C1 (data buffer starts at &C100 in HAZEL)
9767 .always_set_v_byte←20← 8C8C BIT← 9205 BIT← 993B BIT← 9A68 BIT← 9E2C BIT← A016 BIT← A3CF BIT← A4CC BIT← A66E BIT← A699 BIT← A6D0 BIT← AE22 BIT← B35A BIT← B41C BIT← B59E BIT← B62E BIT← B6C6 BIT← B929 BIT← B967 BIT← BC52 BIT
EQUB &FF ; Offset 6: padding &FF; doubles as the always_set_v_byte BIT $abs target
9768 .bit_test_ff
EQUB &FF ; Offset 7: txcb_pos = &FF (also labelled bit_test_ff)
9769 EQUB &FF ; Offset 8: txcb_end lo = &FF
976A EQUB &C1 ; Offset 9: txcb_end hi = &C1 (buffer end &C1FF)
976B EQUB &FF ; Offset 10: extended-addr fill (&FF)
976C 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)
976D .send_request_nowrite←1← A245 JSR
PHA ; Save A
976E SEC ; Set carry (read-only mode)
976F 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)
9771 .send_request_write←2← 9C43 JSR← 9CF7 JSR
CLV ; Clear V
9772 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.

9774 .cmd_bye
LDY #0 ; Y=0: process_all_fcbs filter (0 = all FCBs)
9776 JSR process_all_fcbs ; Walk all 16 FCB slots, calling start_wipe_pass on each
9779 LDA #osbyte_close_spool_exec ; OSBYTE &77 = close *SPOOL and *EXEC files
977B JSR osbyte ; Close any open *SPOOL/*EXEC handles Close any SPOOLed or EXECed files
977E LDA #&40 ; A=&40: bit 6 of fs_flags = 'FS in active session'
9780 TRB fs_flags ; Clear bit 6: mark FS session inactive
9783 JSR close_all_net_chans ; Close every Econet client channel
9786 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
9788 .save_net_tx_cb←23← 8E54 JSR← 955D JSR← 9580 JSR← 9593 JSR← 9E48 JSR← 9F2B JSR← 9F3B JSR← 9FB3 JSR← A03E JSR← A0B7 JSR← A0EB JSR← A1BD JSR← A2A0 JSR← A35B JSR← A51F JSR← A547 JSR← B105 JMP← B183 JSR← B1C1 JSR← B230 JSR← B744 JSR← B7DB JSR← BCDA 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
9789 .save_net_tx_cb_vset←2← 9E2F JSR← A01C JSR
LDA hazel_fs_saved_station ; Read FS station from &C002 (saved from selection time)
978C STA hazel_txcb_station ; Copy into TX buffer at &C102 (dest station for header)
978F .txcb_copy_carry_clr←1← 9772 BVC
CLC ; Clear C: caller wants four-way handshake (not disconnect)
9790 .txcb_copy_carry_set←1← 976F BCS
PHP ; Save flags so we can keep V across the loop
9791 STY hazel_txcb_func_code ; Save Y -- the entry function code -- into TX[1]
9794 LDY #1 ; Y=1: copy 2 bytes (network/control) starting at index 1
9796 .loop_copy_vset_stn←1← 979D BPL
LDA hazel_fs_context_copy,y ; Read source byte at &C003+Y
9799 STA hazel_txcb_network,y ; Write to TX buffer at &C103+Y
979C DEY ; Step backwards
979D BPL loop_copy_vset_stn ; Loop while Y >= 0 (covers indices 1, 0)
979F BIT hazel_fs_lib_flags ; Test fs_lib_flags: bit 6 = use library, bit 7 = *-prefix-stripped
97A2 BVS use_lib_station ; V (bit 6) set: use the library station instead
97A4 BPL done_vset_station ; Neither bit set: leave the FS station copy intact
97A6 LDA hazel_fs_prefix_stn ; Bit 7 (FS-prefix) set: substitute the saved-prefix station from &C004
97A9 STA hazel_txcb_network ; Override TX[3]'s station byte
97AC BVC done_vset_station ; Always taken: V was clear when we entered (BVS at &97A4 didn't fire)
97AE .use_lib_station←1← 97A2 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)
97B1 STA hazel_txcb_network ; Override TX[3] with the library station byte
97B4 .done_vset_station←2← 97A4 BPL← 97AC 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)
97B5 .prep_send_tx_cb←1← A2F9 JSR
PHP ; Save flags so C survives the init_txcb call
97B6 LDA #&90 ; Reply port = &90 (FS reply port)
97B8 STA hazel_txcb_port ; Stash port in TXCB[0]
97BB JSR init_txcb ; Build the rest of the TXCB (control, dest stn/net, etc.)
97BE TXA ; Move TX-buffer end pointer (returned in X) into A
97BF ADC #5 ; Add 5 bytes of slack for trailing reply data
97C1 STA txcb_end ; Stash the resulting end-of-buffer offset
97C3 PLP ; Restore the original C flag from caller
97C4 BCS handle_disconnect ; C set: this is a disconnect; jump to handle_disconnect
97C6 PHP ; Save flags again across the actual TX (TX clobbers them)
97C7 JSR init_tx_ptr_and_send ; Send the four-way-handshake-initiated command packet
97CA 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
97CB .recv_and_process_reply←2← 9D0A JSR← A299 JSR
PHP ; Save flags so caller's V/C survive the receive
97CC JSR init_txcb_bye ; Set up open RX on port &90 for the FS reply (TXCB[0] = &90, ctrl = &7F)
97CF JSR wait_net_tx_ack ; Wait for the reply via the 3-level stack timer
97D2 PLP ; Restore caller's flags
97D3 .loop_next_reply←1← 97E7 BCC
INY ; Step Y to next reply byte
97D4 LDA (txcb_start),y ; Read reply byte at txcb_start+Y
97D6 TAX ; Stash for the dispatch tests below
97D7 BEQ rts_recv_reply ; Zero terminates: return
97D9 BVC process_reply_code ; V clear (caller's V): use code as-is
97DB ADC #&2a ; V set: shift the code by +&2A (extended-error mapping)
97DD .process_reply_code←1← 97D9 BVC
BNE store_reply_status ; Non-zero: dispatch as an error
97DF .rts_recv_reply←1← 97D7 BEQ
RTS ; Return
97E0 .handle_disconnect←1← 97C4 BCS
PLA ; Pull caller's pushed return state
97E1 LDX #&c0 ; X=&C0: 'remote disconnect' status
97E3 INY ; Step Y past the disconnect byte
97E4 JSR send_disconnect_reply ; Send disconnect notification to remote
97E7 BCC loop_next_reply ; C clear (success): continue scanning replies
97E9 .store_reply_status←1← 97DD BNE
STX hazel_fs_last_error ; Save the error code into &C009
97EC LDA hazel_fs_pending_state ; Read FS state byte at &C007
97EF PHP ; Save flags so we can branch later
97F0 BNE check_data_loss ; FS state non-zero: data-loss check needed
97F2 CPX #&bf ; Reply was &BF (special: not a real error)?
97F4 BNE build_error_block ; No: build error block
97F6 .check_data_loss←1← 97F0 BNE
LDA #&40 ; A=&40: 'channel-active' bitmask
97F8 PHA ; Push it onto the OR-accumulator
97F9 TRB fs_flags ; Clear the FS-active bit (we're losing the connection)
97FC LDX #&f0 ; X=&F0: scan from channel offset &F0 upwards
97FE .loop_scan_channels←1← 980C BMI
PLA ; Pull current OR accumulator
97FF ORA hazel_chan_status,x ; OR with channel status byte at &C1C8+X
9802 PHA ; Push back updated accumulator
9803 LDA hazel_chan_status,x ; Reload channel byte
9806 AND #&c0 ; Mask to top 2 bits (preserve TX/RX state)
9808 STA hazel_chan_status,x ; Write back trimmed status
980B INX ; Step channel index
980C BMI loop_scan_channels ; Loop while X bit 7 set (covers &F0..&FF)
980E STX hazel_fs_pending_state ; Clear the FS state byte (no longer active)
9811 JSR close_all_net_chans ; Force-close all client channels
9814 PLA ; Pull final OR accumulator
9815 ROR ; Bit 0 (was bit 6 of any &40 byte) -> C
9816 BCC scan_channel_store_reply ; Any channel was active: skip the warning
9818 JSR print_inline_no_spool ; No active channels were lost: print 'Data Lost' warning via inline string
981B EQUS "Data Lost", &0D
9825 .scan_channel_store_reply←1← 9816 BCC
LDX hazel_fs_last_error ; Reload error code from &C009
9828 PLP ; Restore saved flags (was bit 7 of fs_flags)
9829 BEQ build_error_block ; Z set (no error): build the error block anyway
982B PLA ; Pull caller's saved return state (3 bytes from PHP earlier)
982C PLA ; Unwind stacked byte
982D PLA ; Unwind stacked byte
982E RTS ; Return -- caller dispatched on a non-error reply
982F .build_error_block←2← 97F4 BNE← 9829 BEQ
LDY #1 ; Y=1: skip past the leading TXCB control byte
9831 CPX #&a8 ; Error code below &A8 (extended)?
9833 BCS setup_error_copy ; No (>= &A8): proceed to copy
9835 LDA #&a8 ; Yes: clamp to &A8 (truncate range)
9837 STA (txcb_start),y ; Write clamped code back into TXCB
9839 .setup_error_copy←1← 9833 BCS
LDY #&ff ; Y=&FF: INY in loop bumps to 0
983B .loop_copy_error←1← 9843 BNE
INY ; Step Y
983C LDA (txcb_start),y ; Read TXCB byte (error block content)
983E STA error_block,y ; Copy to BRK error block at &0100+Y
9841 EOR #&0d ; EOR with CR; Z set when we just copied the terminator
9843 BNE loop_copy_error ; Not yet at CR: continue copying
9845 STA error_block,y ; Write the CR terminator (Z still set so A=0; ensures cleanly terminated)
9848 DEY ; Step Y back so it points at the CR position
9849 TYA ; Move Y into A for the BRK
984A TAX ; Move Y into X (caller convention)
984B 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.

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

987C .lang_3_exec_0100
JSR commit_state_byte ; Commit the language-reply state byte
987F LDA #0 ; A=0: 'Bad' error code
9881 JSR error_inline_log ; Raise via error_inline_log (never returns)
9884 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)
988D .check_escape_and_classify←2← 8E07 JSR← 9B46 JSR
LDA escape_flag ; Read escape_flag
988F AND need_release_tube ; Mask with need_release_tube (escape-disable)
9891 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)
9893 .raise_escape_error←2← 98ED BMI← B808 JMP
LDA #osbyte_acknowledge_escape ; A=&7E: OSBYTE &7E = acknowledge Escape
9895 JSR osbyte ; Clear escape condition and perform escape effects
9898 LDA #6 ; A=6: error class for 'Escape'
989A 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.

989D .lang_4_validated
LDY #0 ; Y=0: status byte offset
989F LDA (net_rx_ptr),y ; Read RX status byte
98A1 BEQ init_remote_session ; Zero status: re-init the session
98A3 LDY #&80 ; Y=&80: session-ID byte offset in RX
98A5 LDA (net_rx_ptr),y ; Read remote session-ID
98A7 LDY #&0e ; Y=&0E: stored session-ID offset in workspace
98A9 CMP (nfs_workspace),y ; Compare with stored ID
98AB 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)
98AD .lang_0_insert_key
LDY #&82 ; Y=&82: keypress byte offset in RX
98AF LDA (net_rx_ptr),y ; Read remote keypress code
98B1 TAY ; Y = key code
98B2 LDX #0 ; X=0: keyboard buffer ID
98B4 JSR commit_state_byte ; Commit the language-reply state
98B7 LDA #osbyte_insert_input_buffer ; OSBYTE &99: insert byte into input buffer
98B9 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.

98BC .wait_net_tx_ack←6← 97CF JSR← 9C9D JSR← 9DD5 JSR← ACCA JMP← AF73 JSR← B02A JSR
LDA rx_wait_timeout ; Read the configurable rx-wait timeout (&0D6E, default &28 = ~22s on 2 MHz)
98BF PHA ; Push it as the outermost counter (read back via stack-X indexing later)
98C0 LDA econet_flags ; Read econet_flags so we can preserve it across the wait
98C3 PHA ; Push it (we'll temporarily set bit 7 to mark waiting)
98C4 LDA net_tx_ptr_hi ; Check whether net_tx_ptr_hi is non-zero (TX in flight?)
98C6 BNE init_poll_counters ; Yes: skip the flag-set; counters initialise either way
98C8 ORA #&80 ; TX idle: set bit 7 of econet_flags (signal RX-only wait)
98CA STA econet_flags ; Write the modified flags back
98CD .init_poll_counters←1← 98C6 BNE
LDA #0 ; A=0: initial value for inner+middle counters
98CF PHA ; Push it -- middle counter at stack[X+2]
98D0 PHA ; Push it again -- inner counter at stack[X+1]
98D1 TAY ; Y=0: indirect index for net_tx_ptr poll
98D2 TSX ; Capture S into X so we can address the stack counters
98D3 .loop_poll_tx←4← 98DA BNE← 98DF BNE← 98E4 BNE← 98F2 BNE
LDA (net_tx_ptr),y ; Read RX/TX flags through net_tx_ptr -- bit 7 set means complete
98D5 BMI done_poll_tx ; Bit 7 set: reply received, exit poll
98D7 DEC error_text,x ; Decrement inner counter at stack[X+1]
98DA BNE loop_poll_tx ; Inner not zero yet: poll again
98DC DEC stack_page_2,x ; Inner wrapped: decrement middle at stack[X+2]
98DF BNE loop_poll_tx ; Middle not zero: poll again
98E1 DEC stack_page_4,x ; Middle wrapped: decrement outer at stack[X+4] (the saved timeout value)
98E4 BNE loop_poll_tx ; Outer not zero: poll again
98E6 LDA rx_wait_timeout ; Reload the original timeout to test for timeout=0 mode
98E9 BNE done_poll_tx ; Configured timeout was non-zero: declare timeout
98EB LDA escape_flag ; Timeout=0 (poll forever): check escape flag
98ED BMI raise_escape_error ; Escape pressed: jump to escape handler at &9895
98EF INC stack_page_4,x ; Reset outer counter so we keep polling
98F2 BNE loop_poll_tx ; Always taken (INC's result is always non-zero here): back to inner
98F4 .done_poll_tx←2← 98D5 BMI← 98E9 BNE
PLA ; done_poll_tx: discard inner counter
98F5 PLA ; Discard middle counter
98F6 PLA ; Pull saved econet_flags
98F7 STA econet_flags ; Restore them (clearing bit 7 if we set it)
98FA PLA ; Pull saved rx_wait_timeout into A
98FB BEQ build_no_reply_error ; If timeout reached zero, raise 'No reply'
98FD .return_3←1← 9891 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
98FE .cond_save_error_code←6← 9914 JSR← 994D JSR← 9969 JSR← 9993 JSR← 99A5 JSR← 99BE JSR
BIT fs_flags ; Test bit 7 of fs_flags (FS-active flag)
9901 BPL rts_cond_save_err ; FS not active: skip the save
9903 STA hazel_fs_last_error ; FS active: store error code at &C009 (last-error byte)
9906 .rts_cond_save_err←1← 9901 BPL
RTS ; Return
9907 .build_no_reply_error←1← 98FB BEQ
LDX #8 ; X=8: net_error_lookup_data offset for 'No reply' message
9909 LDY net_error_lookup_data,x ; Y = message offset within the string table (&9AA4 base)
990C LDX #0 ; X=0: error-text buffer index
990E STX error_block ; Zero the &0100 length byte (length will be filled in later)
9911 LDA error_msg_table,y ; Read first message byte (the error code)
9914 JSR cond_save_error_code ; Conditionally save it as last-error
9917 .loop_copy_no_reply_msg←1← 9921 BNE
LDA error_msg_table,y ; Read next message byte
991A STA error_text,x ; Append to error-text buffer at &0101+X
991D BEQ done_no_reply_msg ; Null terminator: message done
991F INX ; Step buffer index
9920 INY ; Step source offset
9921 BNE loop_copy_no_reply_msg ; Loop while Y != 0 (Y wraps at 256, not expected)
9923 .done_no_reply_msg←1← 991D BEQ
JSR append_drv_dot_num ; Append ' on drive <num>' or similar context
9926 LDA #0 ; A=0: null terminator
9928 STA error_text,x ; Store at end of message
992B 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)
992E .fixup_reply_status_a←1← 9BB1 JMP
LDA (net_tx_ptr,x) ; Read FS reply status byte at (net_tx_ptr,X)
9930 CMP #'A' ; Status 'A'? (Acknowledge with no error)
9932 BNE skip_if_not_a ; Not 'A': pass through unchanged
9934 LDA #'B' ; Substitute 'B' for 'A' (handle ACK as a soft error)
9936 .skip_if_not_a←1← 9932 BNE
CLV ; Clear V to take the standard mask path
9937 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 (&9B6A, after a recv-and-classify path that already has X set).

On EntryXindirect index into net_tx_ptr
9939 .load_reply_and_classify←1← 9B6A 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
993B .classify_reply_error←2← 989A JMP← A0E3 JMP
BIT always_set_v_byte ; BIT $always_set_v_byte: force V=1 (extended-error path)
993E .mask_error_class←1← 9937 BVC
AND #7 ; Mask to 3 bits (error class 0..7)
9940 PHA ; Save error class on stack
9941 CMP #2 ; Class 2 = 'station-related' family?
9943 BNE build_simple_error ; No: build a simple one-line error
9945 PHP ; Class 2 yes: save flags so we can branch on V later
9946 TAX ; X = error class (=2)
9947 LDY net_error_lookup_data,x ; Y = lookup-table offset
994A LDA error_msg_table,y ; Read first message byte (error code)
994D JSR cond_save_error_code ; Conditionally save it
9950 LDX #0 ; X=0: text-buffer index
9952 STX error_block ; Zero length byte
9955 .loop_copy_station_msg←1← 995F BNE
LDA error_msg_table,y ; Read message byte
9958 STA error_text,x ; Append to buffer
995B BEQ done_station_msg ; Null terminator -- station message done
995D INY ; Advance Y
995E INX ; Advance X
995F BNE loop_copy_station_msg ; Loop until X wraps
9961 .done_station_msg←1← 995B BEQ
JSR append_drv_dot_num ; Append ' on drive <num>' suffix
9964 PLP ; Restore the saved class flags
9965 BVS suffix_not_listening ; V was set: use 'not listening' suffix
9967 LDA #&a4 ; A=&A4: 'station <n> not available' error code
9969 JSR cond_save_error_code ; Save the alternative error code
996C STA error_text ; Patch error-text buffer length byte
996F LDY #&0b ; Y=&0B: lookup index for the listening-station suffix
9971 BNE load_suffix_offset ; Always taken (Y is non-zero); jump to load_suffix_offset
9973 .suffix_not_listening←1← 9965 BVS
LDY #9 ; V was clear: 'not listening' suffix variant
9975 .load_suffix_offset←1← 9971 BNE
LDA net_error_lookup_data,y ; Read suffix offset from lookup
9978 TAY ; Y = suffix offset
9979 .loop_copy_suffix←1← 9983 BNE
LDA error_msg_table,y ; Read suffix byte
997C STA error_text,x ; Append
997F BEQ done_suffix ; Null: suffix done
9981 INY ; Step Y
9982 .suffix_copy_loop
INX ; Step X
9983 BNE loop_copy_suffix ; Loop while X != 0 (max 255 chars)
9985 .done_suffix←1← 997F BEQ
BEQ check_msg_terminator ; Always taken (Z still set from BEQ): final terminator check
9987 .build_simple_error←2← 8B68 JMP← 9943 BNE
TAX ; X = error class
9988 LDY net_error_lookup_data,x ; Y = lookup-table offset
998B LDX #0 ; X=0: buffer index
998D STX error_block ; Zero length
9990 LDA error_msg_table,y ; Read first message byte (error code)
9993 JSR cond_save_error_code ; Conditionally save it
9996 .loop_copy_error_msg←1← 99A0 BNE
LDA error_msg_table,y ; Read next message byte
9999 STA error_text,x ; Append to buffer
999C .check_msg_terminator←1← 9985 BEQ
BEQ check_net_error_code ; Null terminator -> dispatch
999E INY ; Step Y
999F INX ; Step X
99A0 .bad_str_anchor
BNE loop_copy_error_msg ; Loop while X != 0
99A2 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
99A5 .error_bad_inline←11← 90BE JSR← 9355 JSR← 9362 JSR← 9376 JSR← 9382 JSR← 9391 JSR← 943E JSR← 94C0 JSR← 94E1 JSR← A5B7 JSR← BF78 JSR
JSR cond_save_error_code ; Conditionally log error code to workspace
99A8 TAY ; Save error number in Y
99A9 PLA ; Pop return address (low) — points to last byte of JSR
99AA STA fs_load_addr ; Store return address low
99AC PLA ; Pop return address (high)
99AD STA fs_load_addr_hi ; Store return address high
99AF LDX #0 ; X=0: start of prefix string
99B1 .loop_copy_bad_prefix←1← 99BA BNE
INX ; Copy 'Bad ' prefix from lookup table
99B2 LDA bad_prefix_table,x ; Get next prefix character
99B5 STA error_text,x ; Store in error text buffer
99B8 CMP #' ' ; Is it space (end of 'Bad ')?
99BA BNE loop_copy_bad_prefix ; No: copy next prefix character
99BC 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
99BE .error_inline_log←12← 9569 JSR← 9881 JSR← A5CE JSR← AF9D JSR← AFB3 JSR← AFC9 JSR← B848 JSR← B8BE JSR← B90F JSR← BBA9 JSR← BBE3 JSR← BC2D 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)
99C1 .error_inline←4← A458 JSR← BD67 JSR← BF0B JSR← BFC1 JSR
TAY ; Save error number in Y
99C2 PLA ; Pop return address (low) — points to last byte of JSR
99C3 STA fs_load_addr ; Store return address low
99C5 PLA ; Pop return address (high)
99C6 STA fs_load_addr_hi ; Store return address high
99C8 LDX #0 ; X=0: error text index
99CA .write_error_num_and_str←1← 99BC BEQ
STY error_text ; Store error number in error block
99CD TYA ; Copy error number to A
99CE PHA ; Push error number on stack
99CF LDY #0 ; Y=0: inline string index
99D1 STY error_block ; Zero the BRK byte at &0100
99D4 .loop_copy_inline_str←1← 99DB BNE
INX ; Copy inline string into error block
99D5 INY ; Advance string index
99D6 LDA (fs_load_addr),y ; Read next byte from inline string
99D8 STA error_text,x ; Store byte in error block
99DB 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
99DD .check_net_error_code←4← 984B JMP← 992B JMP← 999C BEQ← BD32 JMP
JSR read_rx_attribute ; Read receive attribute byte
99E0 BNE handle_net_error ; Non-zero: network returned an error
99E2 PLA ; Pop saved error number
99E3 CMP #&de ; Was it &DE (file server error)?
99E5 BEQ append_error_number ; Yes: append error number and trigger BRK
99E7 .trigger_brk←1← 9A36 BEQ
JMP error_block ; Jump to BRK via error block
99EA .handle_net_error←1← 99E0 BNE
STA hazel_fs_error_code ; Store error code in workspace
99ED PHA ; Push error code
99EE TXA ; Save X (error text index)
99EF PHA ; Push X
99F0 JSR read_rx_attribute ; Read receive attribute byte
99F3 STA fs_load_addr ; Save to fs_load_addr as spool handle
99F5 LDA #0 ; A=0: clear error code in RX buffer
99F7 STA (net_rx_ptr),y ; Zero the error code byte in buffer
99F9 LDA #&c6 ; A=&C6: OSBYTE read spool handle
99FB JSR osbyte_x0 ; Read current spool file handle
99FE CPY fs_load_addr ; Compare Y result with saved handle
9A00 BEQ net_error_close_spool ; Match: close the spool file
9A02 CPX fs_load_addr ; Compare X result with saved handle
9A04 BNE done_close_files ; No match: skip spool close
9A06 PHA ; Push A (preserved)
9A07 LDA #&c6 ; A=&C6: disable spool with OSBYTE
9A09 BNE close_spool_exec ; ALWAYS branch to close spool
9A0B .net_error_close_spool←1← 9A00 BEQ
PHY ; Save Y
9A0C LDA #&c7 ; A=&C7: OSBYTE 'flush input buffer'
9A0E .close_spool_exec←1← 9A09 BNE
JSR osbyte_x0_y0 ; Tail-call OSBYTE with X=0/Y=0
9A11 PLY ; Restore Y
9A12 LDA #osfind_close ; A=0: close file
9A14 JSR osfind ; Close the spool/exec file osfind: close one or all files
9A17 .done_close_files←1← 9A04 BNE
PLA ; Pull saved X (error text index)
9A18 TAX ; Restore X
9A19 LDY #&0a ; Y=&0A: lookup index for 'on channel'
9A1B LDA net_error_lookup_data,y ; Load message offset from lookup table
9A1E TAY ; Transfer offset to Y
9A1F .loop_copy_channel_msg←1← 9A29 BNE
LDA error_msg_table,y ; Load error message byte
9A22 STA error_text,x ; Append to error text buffer
9A25 BEQ append_error_number ; Null terminator: done copying
9A27 INX ; Advance error text index
9A28 INY ; Advance message index
9A29 BNE loop_copy_channel_msg ; Loop until full message copied
9A2B .append_error_number←2← 99E5 BEQ← 9A25 BEQ
STX fs_load_addr_2 ; Save error text end position
9A2D PLA ; Pull saved error number
9A2E JSR append_space_and_num ; Append ' nnn' error number suffix
9A31 LDA #0 ; A=0: null terminator
9A33 STA stack_page_2,x ; Terminate error text string
9A36 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
9A38 .append_drv_dot_num←2← 9923 JSR← 9961 JSR
LDA #' ' ; A=' ': space separator
9A3A STA error_text,x ; Append space to error text
9A3D INX ; Advance error text index
9A3E STX fs_load_addr_2 ; Save position for number formatting
9A40 LDY #3 ; Y=3: offset to network number in TX CB
9A42 LDA (net_tx_ptr),y ; Load network number
9A44 BEQ append_station_num ; Zero: skip network part (local)
9A46 JSR append_decimal_num ; Append network number as decimal
9A49 LDX fs_load_addr_2 ; Reload error text position
9A4B LDA #'.' ; A='.': dot separator
9A4D STA error_text,x ; Append dot to error text
9A50 INC fs_load_addr_2 ; Advance past dot
9A52 .append_station_num←1← 9A44 BEQ
LDY #2 ; Y=2: offset to station number in TX CB
9A54 LDA (net_tx_ptr),y ; Load station number
9A56 JSR append_decimal_num ; Append station number as decimal
9A59 LDX fs_load_addr_2 ; Reload error text position
9A5B 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)
9A5C .append_space_and_num←2← 9A2E JSR← B89D JSR
TAY ; Save number in Y
9A5D LDA #' ' ; A=' ': space prefix
9A5F LDX fs_load_addr_2 ; Load current error text position
9A61 STA error_text,x ; Append space to error text
9A64 INC fs_load_addr_2 ; Advance position past space
9A66 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)
9A67 .append_decimal_num←2← 9A46 JSR← 9A56 JSR
TAY ; Save number in Y for division
9A68 BIT always_set_v_byte ; Set V: suppress leading zeros
9A6B LDA #&64 ; A=100: hundreds digit divisor
9A6D JSR append_decimal_digit ; Extract and append hundreds digit
9A70 LDA #&0a ; A=10: tens digit divisor
9A72 JSR append_decimal_digit ; Extract and append tens digit
9A75 LDA #1 ; A=1: units digit (remainder)
9A77 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
9A78 .append_decimal_digit←2← 9A6D JSR← 9A72 JSR
STA fs_load_addr_3 ; Store divisor
9A7A TYA ; Copy number to A for division
9A7B LDX #&2f ; X='0'-1: digit counter (ASCII offset)
9A7D PHP ; Save V flag (leading zero suppression)
9A7E SEC ; Set carry for subtraction
9A7F .loop_count_digit←1← 9A82 BCS
INX ; Increment digit counter
9A80 SBC fs_load_addr_3 ; Subtract divisor
9A82 BCS loop_count_digit ; Not negative yet: continue counting
9A84 ADC fs_load_addr_3 ; Add back divisor (restore remainder)
9A86 PLP ; Restore V flag
9A87 TAY ; Save remainder back to Y
9A88 TXA ; Digit counter to A (ASCII digit)
9A89 CMP #'0' ; Is digit '0'?
9A8B BNE store_digit ; Non-zero: always print
9A8D BVS rts_store_digit ; V set (suppress leading zeros): skip
9A8F .store_digit←1← 9A8B BNE
CLV ; Clear V: first non-zero digit seen
9A90 LDX fs_load_addr_2 ; Load current text position
9A92 STA error_text,x ; Store ASCII digit in error text
9A95 INC fs_load_addr_2 ; Advance text position
9A97 .rts_store_digit←1← 9A8D 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.

9A98 .net_error_lookup_data←5Used as index base by← 9909 LDY← 9947 LDY← 9975 LDA← 9988 LDY← 9A1B LDA
EQUB &00
9A99 EQUB msg_net_error - error_msg_table
9A9A EQUB msg_station - error_msg_table
9A9B EQUB msg_no_clock - error_msg_table
9A9C EQUB msg_escape - error_msg_table
9A9D EQUB msg_escape - error_msg_table
9A9E EQUB msg_escape - error_msg_table
9A9F EQUB msg_bad_option - error_msg_table
9AA0 EQUB msg_no_reply - error_msg_table
9AA1 EQUB msg_not_listening - error_msg_table
9AA2 EQUB msg_on_channel - error_msg_table
9AA3 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.

9AA4 .error_msg_table←8Used as index base by← 9911 LDA← 9917 LDA← 994A LDA← 9955 LDA← 9979 LDA← 9990 LDA← 9996 LDA← 9A1F LDA
EQUB &A0 ; BRK error code &A0 (first table entry)
9AA5 EQUS "Line jammed" ; err_line_jammed = &A0
9AB0 EQUB &00 ; Null terminator
9AB1 .msg_net_error
EQUB &A1 ; Error &A1: Net error
9AB2 EQUS "Net error" ; err_net_error = &A1
9ABB EQUB &00 ; Null terminator
9ABC .msg_station
EQUB &A2 ; Error &A2: Station
9ABD EQUS "Station"
9AC4 EQUB &00
9AC5 .msg_no_clock
EQUB &A3 ; BRK error code &A3
9AC6 EQUS "No clock"
9ACE EQUB &00 ; Null terminator
9ACF .msg_escape
EQUB &11 ; Error &11: Escape
9AD0 EQUS "Escape"
9AD6 EQUB &00
9AD7 .msg_bad_option
EQUB &CB ; Error &CB: Bad option
9AD8 EQUS "Bad option"
9AE2 EQUB &00 ; Null terminator + Error &A5: No reply from station
9AE3 .msg_no_reply
EQUB &A5 ; BRK error code &A5
9AE4 EQUS "No reply from station" ; err_no_reply = &A5 message body
9AF9 EQUB &00 ; Null terminator
9AFA .msg_not_listening
EQUS " not listening" ; Suffix string (offset &56 in lookup)
9B08 EQUB &00 ; Null terminator
9B09 .msg_on_channel
EQUS " on channel" ; Suffix: " on channel"
9B14 EQUB &00 ; Null terminator
9B15 .msg_not_present
EQUS " not present" ; Suffix: " not present"
9B21 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
9B22 .init_tx_ptr_and_send←2← 97C7 JSR← 9DC6 JSR
LDX #&c0 ; X=&C0: TX control block base (low)
9B24 STX net_tx_ptr ; Set TX pointer low
9B26 LDX #0 ; X=0: TX control block base (high)
9B28 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)
9B2A .send_net_packet←6← AD19 JMP← AD76 JSR← AF68 JSR← B008 JSR← B452 JSR← B602 JSR
LDA tx_retry_count ; Load retry count from workspace
9B2D BNE set_timeout ; Non-zero: use configured retry count
9B2F LDA #&ff ; A=&FF: default retry count (255)
9B31 .set_timeout←1← 9B2D BNE
LDY #&60 ; Y=&60: timeout value
9B33 PHA ; Push retry count
9B34 TYA ; A=&60: copy timeout to A
9B35 PHA ; Push timeout
9B36 LDX #0 ; X=0: TX pointer index
9B38 LDA (net_tx_ptr,x) ; Load first byte of TX control block
9B3A .start_tx_attempt←1← 9B5C BEQ
STA (net_tx_ptr,x) ; Restore control byte (overwritten by result code on retry)
9B3C PHA ; Push control byte
9B3D JSR poll_econet_data_continue_frame_status ; Poll ADLC until line idle
9B40 ASL ; Bit 6 (error flag) into N
9B41 BPL tx_success ; N=0 (bit 6 clear): success
9B43 ASL ; Shift away error flag, keep error type
9B44 BEQ tx_send_error ; Z=1 (no type bits): fatal; Z=0: retryable
9B46 JSR check_escape_and_classify ; Check for escape condition
9B49 PLA ; Pull control byte
9B4A TAX ; Restore to X
9B4B PLA ; Pull timeout
9B4C TAY ; Restore to Y
9B4D PLA ; Pull retry count
9B4E BEQ try_alternate_phase ; Zero retries remaining: try alternate
9B50 .loop_retry_tx←1← 9B67 BNE
SBC #1 ; Decrement retry counter
9B52 PHA ; Push updated retry count
9B53 TYA ; Copy timeout to A
9B54 PHA ; Push timeout for delay loop
9B55 TXA ; Copy control byte to A
9B56 .loop_tx_delay←2← 9B57 BNE← 9B5A BNE
DEX ; Inner delay: decrement X
9B57 BNE loop_tx_delay ; Loop until X=0
9B59 DEY ; Decrement outer counter Y
9B5A BNE loop_tx_delay ; Loop until Y=0
9B5C BEQ start_tx_attempt ; ALWAYS branch: retry transmission
9B5E .try_alternate_phase←1← 9B4E BEQ
CMP tx_retry_count ; Compare retry count with alternate
9B61 BNE tx_send_error ; Different: go to error handling
9B63 LDA #&80 ; A=&80: set escapable flag
9B65 STA need_release_tube ; Mark as escapable for second phase
9B67 BNE loop_retry_tx ; ALWAYS branch: retry with escapable
9B69 .tx_send_error←2← 9B44 BEQ← 9B61 BNE
TAX ; Result code to X
9B6A JMP load_reply_and_classify ; Jump to classify reply and return
9B6D .tx_success←1← 9B41 BPL
PLA ; Pull control byte
9B6E PLA ; Pull timeout
9B6F PLA ; Pull retry count
9B70 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.

9B73 .pass_txbuf_init_table←2Used as index base by← 9B89 LDX← 9BE3 LDX
EQUB &88 ; Offset 0: ctrl = &88 (immediate TX)
9B74 EQUB &00 ; Offset 1: port = &00 (immediate op)
9B75 EQUB &FD ; Offset 2: &FD skip (preserve dest stn)
9B76 EQUB &FD ; Offset 3: &FD skip (preserve dest net)
9B77 EQUB &3A ; Offset 4: buf start lo (&3A) -> &0D3A
9B78 EQUB &0D ; Offset 5: buf start hi (&0D) -> &0D3A
9B79 EQUB &FF ; Offset 6: extended-addr fill (&FF)
9B7A EQUB &FF ; Offset 7: extended-addr fill (&FF)
9B7B EQUB &3E ; Offset 8: buf end lo (&3E) -> &0D3E
9B7C EQUB &0D ; Offset 9: buf end hi (&0D) -> &0D3E
9B7D EQUB &FF ; Offset 10: extended-addr fill (&FF)
9B7E 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)
9B7F .init_tx_ptr_for_pass←1← 8E30 JSR
LDY #&c0 ; Y=&C0: TX control block base (low)
9B81 STY net_tx_ptr ; Set TX pointer low byte
9B83 LDY #0 ; Y=0: TX control block base (high)
9B85 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)
9B87 .setup_pass_txbuf←1← AF65 JSR
LDY #&0b ; Y=&0B: 12 bytes to process (0-11)
9B89 .loop_copy_template←1← 9B97 BPL
LDX pass_txbuf_init_table,y ; Load template byte for this offset
9B8C CPX #&fd ; Is it &FD (skip marker)?
9B8E BEQ skip_template_byte ; Yes: skip this offset, don't modify
9B90 LDA (net_tx_ptr),y ; Load existing TX buffer byte
9B92 PHA ; Save original value on stack
9B93 TXA ; Copy template value to A
9B94 STA (net_tx_ptr),y ; Store template value to TX buffer
9B96 .skip_template_byte←1← 9B8E BEQ
DEY ; Next offset (descending)
9B97 BPL loop_copy_template ; Loop until all 12 bytes processed
9B99 LDA peek_retry_count ; Load pass-through control value
9B9C PHA ; Push control value
9B9D TYA ; A=&FF (Y is &FF after loop)
9B9E PHA ; Push &FF as timeout
9B9F LDX #0 ; X=0: TX pointer index
9BA1 LDA (net_tx_ptr,x) ; Load control byte from TX CB
9BA3 .start_pass_tx←1← 9BDC BEQ
STA (net_tx_ptr,x) ; Write control byte to start TX
9BA5 PHA ; Save control byte on stack
9BA6 JSR poll_econet_data_continue_frame_status ; Poll ADLC until line idle
9BA9 ASL ; Shift result: check bit 6 (success)
9BAA BPL pass_tx_success ; Bit 6 clear: transmission complete
9BAC ASL ; Shift result: check bit 5 (fatal)
9BAD BNE restore_retry_state ; Non-zero (not fatal): retry
9BAF .done_pass_retries←1← 9BCE BEQ
LDX #0 ; X=0: clear error status
9BB1 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)
9BB4 .poll_econet_data_continue_frame_status←3← 9B3D JSR← 9BA6 JSR← 9BB7 BCC
ASL tx_complete_flag ; Shift ws_0d60 left to poll ADLC
9BB7 BCC poll_econet_data_continue_frame_status ; Bit not set: keep polling
9BB9 LDA net_tx_ptr ; Copy TX pointer low to NMI TX block
9BBB STA nmi_tx_block ; Store in NMI TX block low
9BBD LDA net_tx_ptr_hi ; Copy TX pointer high
9BBF STA nmi_tx_block_hi ; Store in NMI TX block high
9BC1 JSR tx_setup_from_txcb ; Begin Econet frame transmission
9BC4 .loop_poll_pass_tx←1← 9BC6 BMI
LDA (net_tx_ptr,x) ; Read TX status byte
9BC6 BMI loop_poll_pass_tx ; Bit 7 set: still transmitting
9BC8 RTS ; Return with result in A
9BC9 .restore_retry_state←1← 9BAD BNE
PLA ; Pull control byte
9BCA TAX ; Restore to X
9BCB PLA ; Pull timeout
9BCC TAY ; Restore to Y
9BCD PLA ; Pull retry count
9BCE BEQ done_pass_retries ; Zero retries: go to error handling
9BD0 SBC #1 ; Decrement retry counter
9BD2 PHA ; Push updated retry count
9BD3 TYA ; Copy timeout to A
9BD4 PHA ; Push timeout
9BD5 TXA ; Copy control byte to A
9BD6 .loop_pass_tx_delay←2← 9BD7 BNE← 9BDA BNE
DEX ; Inner delay loop: decrement X
9BD7 BNE loop_pass_tx_delay ; Loop until X=0
9BD9 DEY ; Decrement outer counter Y
9BDA BNE loop_pass_tx_delay ; Loop until Y=0
9BDC BEQ start_pass_tx ; ALWAYS branch: retry transmission
9BDE .pass_tx_success←1← 9BAA BPL
PLA ; Pull control byte (discard)
9BDF PLA ; Pull timeout (discard)
9BE0 PLA ; Pull retry count (discard)
9BE1 LDY #0 ; Y=0: start restoring from offset 0
9BE3 .loop_restore_txbuf←1← 9BF0 BNE
LDX pass_txbuf_init_table,y ; Load template byte for this offset
9BE6 CPX #&fd ; Is it &FD (skip marker)?
9BE8 BEQ skip_restore_byte ; Yes: don't restore this offset
9BEA PLA ; Pull original value from stack
9BEB STA (net_tx_ptr),y ; Restore original TX buffer byte
9BED .skip_restore_byte←1← 9BE8 BEQ
INY ; Next offset (ascending)
9BEE CPY #&0c ; Processed all 12 bytes?
9BF0 BNE loop_restore_txbuf ; No: continue restoring
9BF2 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)
9BF3 .load_text_ptr_and_parse←1← 9C23 JSR
LDY #1 ; Y=1: start at second byte of pointer
9BF5 .loop_copy_text_ptr←1← 9BFB BPL
LDA (fs_options),y ; Load pointer byte from FS options
9BF7 STA os_text_ptr,y ; Store in OS text pointer
9BFA DEY ; Decrement index
9BFB BPL loop_copy_text_ptr ; Loop until both bytes copied
9BFD 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
9BFE .gsread_to_buf←1← B25F JSR
LDX #&ff ; X=&FF: pre-increment for buffer index
9C00 CLC ; C=0: initialise for string input
9C01 JSR gsinit ; GSINIT: initialise string reading
9C04 BEQ terminate_buf ; Z set (empty string): store terminator
9C06 .loop_gsread_char←1← 9C0F BCC
JSR gsread ; GSREAD: read next character
9C09 BCS terminate_buf ; C set: end of string reached
9C0B INX ; Advance buffer index
9C0C STA hazel_parse_buf,x ; Store character in fs_filename_buf buffer
9C0F BCC loop_gsread_char ; ALWAYS branch: read next character
9C11 .terminate_buf←2← 9C04 BEQ← 9C09 BCS
INX ; Advance past last character
9C12 LDA #&0d ; A=CR: terminate filename
9C14 STA hazel_parse_buf,x ; Store CR terminator in buffer
9C17 LDA #<(hazel_parse_buf) ; Parse-buffer pointer (low)
9C19 STA fs_crc_lo ; Set command text pointer low
9C1B LDA #>(hazel_parse_buf) ; Parse-buffer pointer (high)
9C1D STA fs_crc_hi ; Set command text pointer high
9C1F 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)
9C20 .filev_handler
JSR set_xfer_params ; Set up transfer parameters
9C23 JSR load_text_ptr_and_parse ; Load text pointer and parse filename
9C26 JSR mask_owner_access ; Set owner-only access mask
9C29 JSR parse_access_prefix ; Parse access prefix from filename
9C2C LDA fs_last_byte_flag ; Load last byte flag
9C2E BPL check_display_type ; Positive (not last): display file info
9C30 CMP #&ff ; Is it &FF (last entry)?
9C32 BEQ copy_arg_and_enum ; Yes: copy arg and iterate
9C34 JMP return_with_last_flag ; Other value: return with flag
9C37 .copy_arg_and_enum←1← 9C32 BEQ
JSR copy_arg_to_buf_x0 ; Copy argument to buffer at X=0
9C3A 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
9C3C .do_fs_cmd_iteration←1← A629 JSR
LDA #&92 ; A=&92: FS port number
9C3E STA need_release_tube ; Set escapable flag to &92
9C40 STA hazel_txcb_station ; Store port number in TX buffer
9C43 JSR send_request_write ; Send request to file server
9C46 LDY #6 ; Y=6: offset to response cycle flag
9C48 LDA (fs_options),y ; Load cycle flag from FS options
9C4A BNE copy_ws_then_fsopts ; Non-zero: already initialised
9C4C JSR copy_fsopts_to_zp ; Copy FS options to zero page first
9C4F JSR copy_workspace_to_fsopts ; Then copy workspace to FS options
9C52 BCC setup_txcb_addrs ; Branch to continue (C clear from JSR)
9C54 .copy_ws_then_fsopts←1← 9C4A BNE
JSR copy_workspace_to_fsopts ; Copy workspace to FS options first
9C57 JSR copy_fsopts_to_zp ; Then copy FS options to zero page
9C5A .setup_txcb_addrs←1← 9C52 BCC
LDY #4 ; Y=4: loop counter
9C5C .loop_copy_addrs←1← 9C67 BNE
LDA fs_load_addr,x ; Load address byte from zero page
9C5E STA txcb_end,x ; Save to TXCB end pointer
9C60 ADC hazel_txcb_addr_lo,x ; Add offset from buffer
9C63 STA fs_work_4,x ; Store sum in fs_work area
9C65 INX ; Advance to next byte
9C66 DEY ; Decrement counter
9C67 BNE loop_copy_addrs ; Loop for all 4 bytes
9C69 SEC ; Set carry for subtraction
9C6A SBC hazel_txcb_addr_hi ; Subtract high offset
9C6D STA fs_work_7 ; Store result in fs_work_7
9C6F JSR format_filename_field ; Format filename for display
9C72 JSR send_txcb_swap_addrs ; Send TXCB and swap addresses
9C75 LDX #2 ; X=2: copy 3 offset bytes
9C77 .loop_copy_offsets←1← 9C7E BPL
LDA hazel_txcb_addr_hi,x ; Load offset byte from fs_file_len_3
9C7A STA hazel_txcb_data,x ; Store in fs_cmd_data for next iteration
9C7D DEX ; Decrement counter
9C7E BPL loop_copy_offsets ; Loop until all bytes copied
9C80 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)
9C83 .send_txcb_swap_addrs←2← 9C72 JSR← A283 JSR
JSR cmp_5byte_handle ; Compare 5-byte handle with current
9C86 BEQ rts_txcb_swap ; Match: no need to send, return
9C88 LDA #&92 ; A=&92: FS reply port number
9C8A STA txcb_port ; Set TXCB port
9C8C .loop_swap_and_send←1← 9CA8 BNE
LDX #3 ; X=3: copy 4 bytes
9C8E .loop_copy_start_end←1← 9C97 BPL
LDA txcb_end,x ; Load TXCB end pointer byte
9C90 STA txcb_start,x ; Store in TXCB start pointer
9C92 LDA fs_work_4,x ; Load new end address from fs_work
9C94 STA txcb_end,x ; Store in TXCB end pointer
9C96 DEX ; Decrement counter
9C97 BPL loop_copy_start_end ; Loop for all 4 bytes
9C99 LDA #&7f ; A=&7F: control byte for data transfer
9C9B STA txcb_ctrl ; Set TXCB control byte
9C9D JSR wait_net_tx_ack ; Wait for network TX acknowledgement
9CA0 LDY #3 ; Y=3: compare 4 bytes
9CA2 .loop_verify_addrs←1← 9CAB BPL
LDA txcb_end,y ; Load TXCB end byte
9CA5 EOR fs_work_4,y ; Compare with expected end address
9CA8 BNE loop_swap_and_send ; Mismatch: resend from start
9CAA DEY ; Decrement counter
9CAB BPL loop_verify_addrs ; Loop until all 4 bytes match
9CAD .rts_txcb_swap←1← 9C86 BEQ
RTS ; Return (all bytes match)
9CAE .check_display_type←1← 9C2E BPL
BEQ setup_dir_display ; Z set: directory entry display
9CB0 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
9CB3 .setup_dir_display←2← 9CAE BEQ← 9DE3 JMP
LDX #4 ; X=4: loop counter for 4 iterations
9CB5 LDY #&0e ; Y=&0E: FS options offset for addresses
9CB7 SEC ; Set carry for subtraction
9CB8 .loop_compute_diffs←1← 9CD2 BNE
LDA (fs_options),y ; Load address byte from FS options
9CBA STA port_ws_offset,y ; Save to workspace (port_ws_offset)
9CBD JSR retreat_y_by_4 ; Y -= 4 to point to paired offset
9CC0 SBC (fs_options),y ; Subtract paired value
9CC2 STA hazel_txcb_network,y ; Store difference in fs_cmd_csd buffer
9CC5 PHA ; Push difference
9CC6 LDA (fs_options),y ; Load paired value from FS options
9CC8 STA port_ws_offset,y ; Save to workspace
9CCB PLA ; Pull difference back
9CCC STA (fs_options),y ; Store in FS options for display
9CCE JSR skip_one_and_advance5 ; Advance Y by 5 for next field
9CD1 DEX ; Decrement loop counter
9CD2 BNE loop_compute_diffs ; Loop for all 4 address pairs
9CD4 LDY #9 ; Y=9: copy 9 bytes of options data
9CD6 .loop_copy_fs_options←1← 9CDC BNE
LDA (fs_options),y ; Load FS options byte
9CD8 STA hazel_txcb_network,y ; Store in fs_cmd_csd buffer
9CDB DEY ; Decrement index
9CDC BNE loop_copy_fs_options ; Loop until all 9 bytes copied
9CDE LDA #&91 ; A=&91: FS port for info request
9CE0 STA need_release_tube ; Set escapable flag
9CE2 STA hazel_txcb_station ; Store port in TX buffer
9CE5 STA fs_error_ptr ; Store in fs_error_ptr
9CE7 LDX #&0b ; X=&0B: copy argument at offset 11
9CE9 JSR copy_arg_to_buf ; Copy argument to TX buffer
9CEC LDY #1 ; Y=1: info sub-command
9CEE LDA fs_last_byte_flag ; Load last byte flag
9CF0 CMP #7 ; Is it 7 (catalogue info)?
9CF2 PHP ; Save comparison result
9CF3 BNE send_info_request ; Not 7: keep Y=1
9CF5 LDY #&1d ; Y=&1D: extended info command
9CF7 .send_info_request←1← 9CF3 BNE
JSR send_request_write ; Send request to file server
9CFA JSR format_filename_field ; Format filename for display
9CFD PLP ; Restore comparison flags
9CFE BNE setup_txcb_transfer ; Not catalogue info: show short format
9D00 LDX #0 ; X=0: start at first byte
9D02 BEQ store_result ; ALWAYS branch to store and display
9D04 .setup_txcb_transfer←1← 9CFE BNE
LDA hazel_txcb_data ; Load file handle from fs_cmd_data
9D07 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)
9D0A .recv_reply←1← 9C80 JMP
JSR recv_and_process_reply ; Receive and process reply
9D0D .store_result←1← 9D02 BEQ
STX hazel_txcb_result ; Store result byte in fs_reply_cmd
9D10 LDY #&0e ; Y=&0E: protection bits offset
9D12 LDA hazel_txcb_data ; Load access byte from fs_cmd_data
9D15 JSR get_prot_bits ; Extract protection bit flags
9D18 BEQ store_prot_byte ; Zero: use reply buffer data
9D1A .loop_copy_file_info←1← 9D22 BNE
LDA hazel_fs_reply_byte,y ; Load file info byte from fs_reply_data
9D1D .store_prot_byte←1← 9D18 BEQ
STA (fs_options),y ; Store in FS options at offset Y
9D1F INY ; Advance to next byte
9D20 CPY #&12 ; Y=&12: end of protection fields?
9D22 BNE loop_copy_file_info ; No: copy next byte
9D24 LDY hazel_fs_messages_flag ; Load display flag from fs_messages_flag
9D27 BEQ rts_advance_y ; Zero: skip display, return
9D29 LDY #&f4 ; Y=&F4: index into hazel_display_buf for filename
9D2B .loop_print_filename←1← 9D32 BNE
LDA hazel_display_buf_minusF4,y ; Load filename character from filename_buf
9D2E JSR print_char_no_spool ; Print character via OSASCI
9D31 INY ; Advance to next character
9D32 BNE loop_print_filename ; Printed all 12 characters?
9D34 LDY #5 ; Y=5: offset for access string
9D36 JSR print_5_hex_bytes ; Print 5 hex bytes (access info)
9D39 JSR print_load_exec_addrs ; Print load and exec addresses
9D3C JSR print_newline_no_spool ; Print newline
9D3F 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)
9D42 .print_load_exec_addrs←1← 9D39 JSR
LDY #9 ; Y=9: offset for exec address
9D44 JSR print_5_hex_bytes ; Print 5 hex bytes (exec address)
9D47 LDY #&0c ; Y=&0C: offset for length (3 bytes)
9D49 LDX #3 ; X=3: print 3 bytes only
9D4B 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)
9D4D .print_5_hex_bytes←2← 9D36 JSR← 9D44 JSR
LDX #4 ; X=4: print 5 bytes (4 to 0)
9D4F .loop_print_hex_byte←2← 9D4B BNE← 9D56 BNE
LDA (fs_options),y ; Load byte from FS options at offset Y
9D51 JSR print_hex_byte_no_spool ; Print as 2-digit hex
9D54 DEY ; Decrement byte offset
9D55 DEX ; Decrement byte count
9D56 BNE loop_print_hex_byte ; Loop until all bytes printed
9D58 LDA #' ' ; A=' ': space separator
9D5A 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
9D5D .copy_fsopts_to_zp←2← 9C4C JSR← 9C57 JSR
LDY #5 ; Y=5: copy 4 bytes (offsets 2-5)
9D5F .loop_copy_fsopts_byte←1← 9D67 BCS
LDA (fs_options),y ; Load byte from FS options
9D61 STA work_ae,y ; Store in zero page at work_ae+Y
9D64 DEY ; Decrement index
9D65 CPY #2 ; Below offset 2?
9D67 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
9D69 .skip_one_and_advance5←1← 9CCE 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
9D6A .advance_y_by_4←2← A260 JSR← B46B JSR
INY ; Y += 4
9D6B INY ; (continued)
9D6C INY ; (continued)
9D6D INY ; (continued)
9D6E .rts_advance_y←1← 9D27 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
9D6F .copy_workspace_to_fsopts←2← 9C4F JSR← 9C54 JSR
LDY #&0d ; Y=&0D: copy bytes from offset &0D down
9D71 TXA ; Transfer X to A
9D72 .loop_copy_ws_byte←1← 9D7A BCS
STA (fs_options),y ; Store byte in FS options at offset Y
9D74 LDA hazel_txcb_station,y ; Load next workspace byte from fs_cmd_urd+Y
9D77 DEY ; Decrement index
9D78 CPY #2 ; Below offset 2?
9D7A 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
9D7C .retreat_y_by_4←1← 9CBD 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
9D7D .retreat_y_by_3←2← 9DF9 JSR← A268 JSR
DEY ; Y -= 3
9D7E DEY ; (continued)
9D7F DEY ; (continued)
9D80 RTS ; Return
9D81 .discard_handle_match←2← 9D89 BEQ← 9DCF BCS
PLA ; Discard stacked value
9D82 LDY fs_block_offset ; Restore Y from fs_block_offset
9D84 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)
9D85 .check_and_setup_txcb←2← 9D07 JSR← A27E JSR
PHA ; Save port/sub-function on stack
9D86 JSR cmp_5byte_handle ; Compare 5-byte handle with current
9D89 BEQ discard_handle_match ; Match: discard port and return
9D8B .init_transfer_addrs←1← 9DD8 BNE
LDX #0 ; X=0: loop start
9D8D LDY #4 ; Y=4: copy 4 bytes
9D8F STX hazel_txcb_result ; Clear fs_reply_cmd (transfer size low)
9D92 STX hazel_exec_addr ; Clear fs_load_vector (transfer size high)
9D95 CLC ; Clear carry for addition
9D96 .loop_copy_addr_offset←1← 9DA3 BNE
LDA fs_load_addr,x ; Load address byte from zero page
9D98 STA txcb_start,x ; Store in TXCB start pointer
9D9A ADC hazel_txcb_flag,x ; Add offset from fs_func_code
9D9D STA txcb_end,x ; Store sum in TXCB end pointer
9D9F STA fs_load_addr,x ; Also update load address
9DA1 INX ; Advance to next byte
9DA2 DEY ; Decrement counter
9DA3 BNE loop_copy_addr_offset ; Loop for all 4 bytes
9DA5 BCS clamp_end_to_limit ; Carry set: overflow, use limit
9DA7 SEC ; Set carry for subtraction
9DA8 .loop_check_vs_limit←1← 9DB0 BNE
LDA fs_load_addr,y ; Load computed end address
9DAB SBC fs_work_4,y ; Subtract maximum from fs_work_4
9DAE INY ; Advance to next byte
9DAF DEX ; Decrement counter
9DB0 BNE loop_check_vs_limit ; Loop for all bytes
9DB2 BCC set_port_and_ctrl ; Below limit: keep computed end
9DB4 .clamp_end_to_limit←1← 9DA5 BCS
LDX #3 ; X=3: copy 4 bytes of limit
9DB6 .loop_copy_limit←1← 9DBB BPL
LDA fs_work_4,x ; Load limit from fs_work_4
9DB8 STA txcb_end,x ; Store as TXCB end
9DBA DEX ; Decrement counter
9DBB BPL loop_copy_limit ; Loop for all 4 bytes
9DBD .set_port_and_ctrl←1← 9DB2 BCC
PLA ; Pull port from stack
9DBE PHA ; Push back (keep for later)
9DBF PHP ; Save flags (carry = overflow state)
9DC0 STA txcb_port ; Set TXCB port number
9DC2 LDA #&80 ; A=&80: control byte for data request
9DC4 STA txcb_ctrl ; Set TXCB control byte
9DC6 JSR init_tx_ptr_and_send ; Init TX pointer and send packet
9DC9 LDA fs_error_ptr ; Load error pointer
9DCB JSR init_txcb_port ; Init TXCB port from error pointer
9DCE PLP ; Restore overflow flags
9DCF BCS discard_handle_match ; Carry set: discard and return
9DD1 LDA #&91 ; A=&91: FS reply port
9DD3 STA txcb_port ; Set TXCB port for reply
9DD5 JSR wait_net_tx_ack ; Wait for TX acknowledgement
9DD8 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
9DDA .dispatch_osword_op←1← 9CB0 JMP
STA hazel_txcb_data ; Store sub-operation code
9DDD CMP #7 ; Compare with 7
9DDF BCC dispatch_ops_1_to_6 ; Below 7: handle operations 1-6
9DE1 BNE skip_if_error ; Above 7: jump to handle via finalise
9DE3 JMP setup_dir_display ; Equal to 7: jump to directory display
9DE6 .dispatch_ops_1_to_6←1← 9DDF BCC
CMP #6 ; Compare with 6
9DE8 BEQ send_delete_request ; 6: delete file operation
9DEA CMP #5 ; Compare with 5
9DEC BEQ read_cat_info ; 5: read catalogue info
9DEE CMP #4 ; Compare with 4
9DF0 BEQ setup_write_access ; 4: write file attributes
9DF2 CMP #1 ; Compare with 1
9DF4 BEQ setup_save_access ; 1: read file info
9DF6 ASL ; Shift left twice: A*4
9DF7 ASL ; A*4
9DF8 TAY ; Copy to Y as index
9DF9 JSR retreat_y_by_3 ; Y -= 3 to get FS options offset
9DFC LDX #3 ; X=3: copy 4 bytes
9DFE .loop_copy_fsopts_4←1← 9E05 BPL
LDA (fs_options),y ; Load byte from FS options at offset Y
9E00 STA hazel_txcb_flag,x ; Store in fs_func_code buffer
9E03 DEY ; Decrement source offset
9E04 DEX ; Decrement byte count
9E05 BPL loop_copy_fsopts_4 ; Loop for all 4 bytes
9E07 LDX #5 ; X=5: copy arg to buffer at offset 5
9E09 BNE send_save_or_access ; ALWAYS branch to copy and send
9E0B .setup_save_access←1← 9DF4 BEQ
JSR get_access_bits ; Get access bits for file
9E0E STA hazel_txcb_access ; Store access byte in fs_file_attrs
9E11 LDY #9 ; Y=9: source offset in FS options
9E13 LDX #8 ; X=8: copy 8 bytes to buffer
9E15 .loop_copy_fsopts_8←1← 9E1C BNE
LDA (fs_options),y ; Load FS options byte
9E17 STA hazel_txcb_data,x ; Store in fs_cmd_data buffer
9E1A DEY ; Decrement source offset
9E1B DEX ; Decrement byte count
9E1C BNE loop_copy_fsopts_8 ; Loop for all 8 bytes
9E1E LDX #&0a ; X=&0A: buffer offset for argument
9E20 .send_save_or_access←2← 9E09 BNE← 9E3F BNE
JSR copy_arg_to_buf ; Copy argument to buffer
9E23 LDY #&13 ; Y=&13: OSWORD &13 (NFS operation)
9E25 BNE send_request_vset ; ALWAYS branch to send request
9E27 .send_delete_request←1← 9DE8 BEQ
JSR copy_arg_to_buf_x0 ; Copy argument to buffer at X=0
9E2A LDY #&14 ; Y=&14: delete file command
9E2C .send_request_vset←1← 9E25 BNE
BIT always_set_v_byte ; Set V flag (no directory check)
9E2F JSR save_net_tx_cb_vset ; Send request with V set
9E32 .skip_if_error←1← 9DE1 BNE
BCS done_osword_op ; Carry set: error, jump to finalise
9E34 JMP return_with_last_flag ; No error: return with last flag
9E37 .setup_write_access←1← 9DF0 BEQ
JSR get_access_bits ; Get access bits for file
9E3A STA hazel_txcb_flag ; Store in fs_func_code
9E3D LDX #2 ; X=2: buffer offset
9E3F BNE send_save_or_access ; ALWAYS branch to copy and send
9E41 .read_cat_info←1← 9DEC BEQ
LDX #1 ; X=1: buffer offset
9E43 JSR copy_arg_to_buf ; Copy argument to buffer
9E46 LDY #&12 ; Y=&12: open file command
9E48 JSR save_net_tx_cb ; Send open file request
9E4B LDA hazel_txcb_len ; Load reply handle from fs_obj_type
9E4E STX hazel_txcb_len ; Clear fs_obj_type
9E51 STX hazel_txcb_cycle ; Clear fs_len_clear
9E54 JSR get_prot_bits ; Get protection bits
9E57 LDX hazel_txcb_data ; Load file handle from fs_cmd_data
9E5A BEQ return_with_handle ; Zero: file not found, return
9E5C LDY #&0e ; Y=&0E: store access bits
9E5E STA (fs_options),y ; Store access byte in FS options
9E60 DEY ; Y=&0D
9E61 LDX #&0c ; X=&0C: copy 12 bytes of file info
9E63 .loop_copy_cat_info←1← 9E6A BNE
LDA hazel_txcb_data,x ; Load reply byte from fs_cmd_data+X
9E66 STA (fs_options),y ; Store in FS options at offset Y
9E68 DEY ; Decrement destination offset
9E69 DEX ; Decrement source counter
9E6A BNE loop_copy_cat_info ; Loop for all 12 bytes
9E6C INX ; X=1 (INX from 0)
9E6D INX ; X=2
9E6E LDY #&11 ; Y=&11: FS options offset
9E70 .loop_copy_ext_info←1← 9E77 BPL
LDA hazel_txcb_type,x ; Load extended info byte from fs_access_level
9E73 STA (fs_options),y ; Store in FS options
9E75 DEY ; Decrement destination offset
9E76 DEX ; Decrement source counter
9E77 BPL loop_copy_ext_info ; Loop until all copied
9E79 LDX hazel_txcb_data ; Reload file handle
9E7C .return_with_handle←1← 9E5A BEQ
TXA ; Transfer to A
9E7D .done_osword_op←1← 9E32 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
9E80 .format_filename_field←2← 9C6F JSR← 9CFA JSR
LDY #0 ; Y=0: start writing at filename_buf[0]
9E82 LDX hazel_txcb_network ; Load source offset from fs_cmd_csd
9E85 BNE copy_from_buf_entry ; Non-zero: copy from fs_cmd_data buffer
9E87 .loop_copy_cmdline_char←1← 9E91 BNE
LDA (fs_crc_lo),y ; Load character from command line
9E89 CMP #'!' ; Below '!' (control/space)?
9E8B BCC pad_with_spaces ; Yes: pad with spaces
9E8D STA hazel_display_buf,y ; Store printable character in filename_buf
9E90 INY ; Advance to next character
9E91 BNE loop_copy_cmdline_char ; Loop for more characters
9E93 .pad_with_spaces←2← 9E8B BCC← 9E9B BCC
LDA #' ' ; A=' ': space for padding
9E95 STA hazel_display_buf,y ; Store space in display buffer
9E98 INY ; Advance index
9E99 CPY #&0c ; Filled all 12 characters?
9E9B BCC pad_with_spaces ; No: pad more spaces
9E9D RTS ; Return with field formatted
9E9E .loop_copy_buf_char←1← 9EA6 BPL
INX ; Advance source and destination
9E9F INY ; Advance index
9EA0 .copy_from_buf_entry←1← 9E85 BNE
LDA hazel_txcb_data,x ; Load byte from fs_cmd_data buffer
9EA3 STA hazel_display_buf,y ; Store in filename_buf
9EA6 BPL loop_copy_buf_char ; Bit 7 clear: more characters
9EA8 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
9EA9 .argsv_handler
JSR verify_ws_checksum ; Verify workspace checksum
9EAC STA fs_last_byte_flag ; Store result as last byte flag
9EAE JSR set_options_ptr ; Set FS options pointer
9EB1 ORA #0 ; OR with 0 to set flags
9EB3 BPL dispatch_osfind_op ; Positive: handle sub-operations
9EB5 ASL ; Shift left to check bit 6
9EB6 BEQ validate_chan_close ; Zero (was &80): close channel
9EB8 JMP close_all_fcbs ; Other: process all FCBs first
9EBB .validate_chan_close←1← 9EB6 BEQ
TYA ; Transfer Y to A
9EBC CMP #' ' ; Compare with &20 (space)
9EBE BCS check_chan_range ; Above &20: check further
9EC0 .error_invalid_chan←1← 9EC5 BCS
JMP err_net_chan_invalid ; Below &20: invalid channel char
9EC3 .check_chan_range←1← 9EBE BCS
CMP #'0' ; Compare with '0'
9EC5 BCS error_invalid_chan ; Above '0': invalid channel char
9EC7 JSR process_all_fcbs ; Process all matching FCBs
9ECA TYA ; Transfer Y to A (FCB index)
9ECB PHA ; Push FCB index
9ECC TAX ; Copy to X
9ECD LDY #0 ; Y=0: clear counter
9ECF STY fs_last_byte_flag ; Clear last byte flag
9ED1 STY fs_block_offset ; Clear block offset
9ED3 .loop_copy_fcb_fields←1← 9EDE BNE
LDA hazel_fcb_addr_mid,x ; Load channel data from fcb_attr_or_count_mid+X
9ED6 STA (fs_options),y ; Store in FS options at Y
9ED8 JSR inx16 ; Advance X by 8 (next FCB field)
9EDB INY ; Advance destination index
9EDC CPY #4 ; Copied all 4 channel fields?
9EDE BNE loop_copy_fcb_fields ; No: copy next field
9EE0 PLA ; Pull saved FCB index
9EE1 STA fs_block_offset ; Restore to fs_block_offset
9EE3 .dispatch_osfind_op←1← 9EB3 BPL
CMP #5 ; Compare with 5
9EE5 BCS done_return_flag ; 5 or above: return with last flag
9EE7 CPY #0 ; Compare Y with 0
9EE9 BNE osfind_with_channel ; Non-zero: handle OSFIND with channel
9EEB JMP osfind_close_or_open ; Y=0 (close): jump to OSFIND open
9EEE .osfind_with_channel←1← 9EE9 BNE
PHA ; Push sub-function
9EEF PHX ; Save X (channel index) across the OSARGS body
9EF0 TYA ; Transfer Y to A
9EF1 PHA ; Push Y (channel char)
9EF2 JSR check_not_dir ; Check file is not a directory
9EF5 PLA ; Pull channel char
9EF6 JSR store_rx_attribute ; Store channel char as receive attribute
9EF9 LDA hazel_fcb_slot_attr,x ; Load FCB flag byte from fcb_net_or_port
9EFC STA hazel_txcb_data ; Store in fs_cmd_data
9EFF PLX ; Restore X
9F00 PLA ; Pull sub-function
9F01 LSR ; Shift right: check bit 0
9F02 BEQ osargs_ptr_dispatch ; Zero (OSFIND close): handle close
9F04 PHP ; Save flags (carry from LSR)
9F05 PHA ; Push sub-function
9F06 LDX fs_options ; Load FS options pointer low
9F08 LDY fs_block_offset ; Load block offset
9F0A JSR process_all_fcbs ; Process all matching FCBs
9F0D LDA hazel_fcb_addr_mid,y ; Load updated data from fcb_attr_or_count_mid
9F10 STA hazel_txcb_data ; Store in fs_cmd_data
9F13 PLA ; Pull sub-function
9F14 STA hazel_txcb_flag ; Store in fs_func_code
9F17 PLP ; Restore flags
9F18 TYA ; Transfer Y to A
9F19 PHA ; Push Y (offset)
9F1A BCC osargs_read_op ; Carry clear: read operation
9F1C LDY #3 ; Y=3: copy 4 bytes
9F1E .loop_copy_zp_to_buf←1← 9F25 BPL
LDA zp_work_3,x ; Load zero page data
9F20 STA hazel_txcb_count,y ; Store in fs_data_count buffer
9F23 DEX ; Decrement source
9F24 DEY ; Decrement counter
9F25 BPL loop_copy_zp_to_buf ; Loop for all 4 bytes
9F27 LDY #&0d ; Y=&0D: TX buffer size
9F29 LDX #5 ; X=5: argument offset
9F2B JSR save_net_tx_cb ; Send TX control block to server
9F2E STX fs_last_byte_flag ; Store X in last byte flag
9F30 PLA ; Pull saved offset
9F31 JSR set_conn_active ; Set connection active flag
9F34 .done_return_flag←1← 9EE5 BCS
JMP return_with_last_flag ; Return with last flag
9F37 .osargs_read_op←1← 9F1A BCC
LDY #&0c ; Y=&0C: TX buffer size (smaller)
9F39 LDX #2 ; X=2: argument offset
9F3B JSR save_net_tx_cb ; Send TX control block
9F3E STA fs_last_byte_flag ; Store A in last byte flag
9F40 LDX fs_options ; Load FS options pointer low
9F42 LDY #2 ; Y=2: zero page offset
9F44 STA zp_work_3,x ; Store A in zero page
9F46 .loop_copy_reply_to_zp←1← 9F4D BPL
LDA hazel_txcb_data,y ; Load buffer byte from fs_cmd_data+Y
9F49 STA zp_work_2,x ; Store in zero page at offset
9F4B DEX ; Decrement source X
9F4C DEY ; Decrement counter Y
9F4D BPL loop_copy_reply_to_zp ; Loop until all bytes copied
9F4F PLA ; Pull saved offset
9F50 JMP return_with_last_flag ; Return with last flag
9F53 .osargs_ptr_dispatch←1← 9F02 BEQ
BCS osargs_write_ptr ; Carry set: write file pointer
9F55 LDA fs_block_offset ; Load block offset
9F57 JSR attr_to_chan_index ; Convert attribute to channel index
9F5A LDY fs_options ; Load FS options pointer
9F5C LDA hazel_fcb_addr_lo,x ; Load FCB low byte from fcb_count_lo
9F5F STA zp_ptr_lo,y ; Store in zero page pointer low
9F62 LDA hazel_fcb_addr_mid,x ; Load FCB high byte from fcb_attr_or_count_mid
9F65 STA zp_ptr_hi,y ; Store in zero page pointer high
9F68 LDA hazel_fcb_addr_hi,x ; Load FCB extent from fcb_station_or_count_hi
9F6B STA zp_work_2,y ; Store in zero page work area
9F6E LDA #0 ; A=0: clear high byte
9F70 STA zp_work_3,y ; Store zero in work area high
9F73 BEQ return_with_last_flag ; ALWAYS branch to return with flag
9F75 .osargs_write_ptr←1← 9F53 BCS
STA hazel_txcb_flag ; Store write value in fs_func_code
9F78 TXA ; Transfer X to A
9F79 PHA ; Push X (zero page offset)
9F7A LDY #3 ; Y=3: copy 4 bytes
9F7C .loop_copy_ptr_to_buf←1← 9F83 BPL
LDA zp_work_3,x ; Load zero page data at offset
9F7E STA hazel_txcb_count,y ; Store in fs_data_count buffer
9F81 DEX ; Decrement source
9F82 DEY ; Decrement counter
9F83 BPL loop_copy_ptr_to_buf ; Loop for all 4 bytes
9F85 LDA fs_block_offset ; Load channel handle from fs_block_offset
9F87 JSR attr_to_chan_index ; Convert handle to FCB channel index in X
9F8A LDA hazel_txcb_size_hi ; Test high transfer-size byte first
9F8D BNE argsv_send_request ; Non-zero: pointer below extent, send request
9F8F LDA hazel_fcb_addr_hi,x ; Compare FCB pointer high byte...
9F92 CMP hazel_exec_addr ; ...against extent high byte
9F95 BCC argsv_send_request ; Pointer < extent: send request
9F97 BNE argsv_clamp_zero ; Pointer > extent: clamp path
9F99 LDA hazel_fcb_addr_mid,x ; Compare FCB pointer mid byte...
9F9C CMP hazel_txcb_result ; ...against extent mid byte
9F9F BCC argsv_send_request ; Pointer < extent: send request
9FA1 BNE argsv_clamp_zero ; Pointer > extent: clamp path
9FA3 LDA hazel_fcb_addr_lo,x ; Compare FCB pointer low byte...
9FA6 CMP hazel_txcb_count ; ...against extent low byte
9FA9 BCC argsv_send_request ; Pointer < extent: send request
9FAB .argsv_clamp_zero←2← 9F97 BNE← 9FA1 BNE
LDX #0 ; X=0: pointer at/beyond extent
9FAD BEQ argsv_store_result ; Branch to store result (always)
9FAF .argsv_send_request←4← 9F8D BNE← 9F95 BCC← 9F9F BCC← 9FA9 BCC
LDY #&0d ; Y=&0D: TX buffer size
9FB1 LDX #5 ; X=5: argument offset
9FB3 JSR save_net_tx_cb ; Send TX control block
9FB6 .argsv_store_result←1← 9FAD BEQ
STX fs_last_byte_flag ; Store X in last byte flag
9FB8 PLA ; Pull saved zero page offset
9FB9 TAY ; Transfer to Y
9FBA LDA fs_block_offset ; Load block offset (attribute)
9FBC JSR clear_conn_active ; Clear connection active flag
9FBF JSR attr_to_chan_index ; Convert attribute to channel index
9FC2 LDA zp_ptr_lo,y ; Load zero page pointer low
9FC5 STA hazel_fcb_addr_lo,x ; Store back to FCB fcb_count_lo
9FC8 LDA zp_ptr_hi,y ; Load zero page pointer high
9FCB STA hazel_fcb_addr_mid,x ; Store back to FCB fcb_attr_or_count_mid
9FCE LDA zp_work_2,y ; Load zero page work byte
9FD1 STA hazel_fcb_addr_hi,x ; Store back to FCB fcb_station_or_count_hi
9FD4 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)
9FD7 .close_all_fcbs←1← 9EB8 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 (&9FD4) reaches it via JSR.

On ExitAfs_last_byte_flag
Xfs_options (restored by finalise_and_return)
Yfs_block_offset (restored by finalise_and_return)
9FDA .return_with_last_flag←12← 9C34 JMP← 9D3F JMP← 9E34 JMP← 9F34 JMP← 9F50 JMP← 9F73 BEQ← 9FD4 JMP← A0C2 JMP← A185 JMP← A64F JMP← A655 JMP← A70F 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
9FDC .finalise_and_return←7← 9E7D JMP← 9FF1 BNE← 9FF8 BPL← A08E JMP← A1ED JMP← A54F JMP← A5AD JSR
PHA ; Push result on stack
9FDD LDA #0 ; A=0: clear error flag
9FDF JSR store_rx_attribute ; Clear receive attribute (A=0)
9FE2 PLA ; Pull result back
9FE3 LDX fs_options ; Restore X from FS options pointer
9FE5 LDY fs_block_offset ; Restore Y from block offset
9FE7 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)
9FE8 .osfind_close_or_open←1← 9EEB JMP
CMP #2 ; Compare with 2 (open for output)
9FEA BCS done_file_open ; 2 or above: handle file open
9FEC TAY ; Transfer to Y (Y=0 or 1)
9FED BNE done_file_open ; Non-zero (1 = read pointer): copy data
9FEF LDA #5 ; A=5: return code for close-all
9FF1 BNE finalise_and_return ; ALWAYS branch to finalise
9FF3 .done_file_open←2← 9FEA BCS← 9FED 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 (&A33D).

On ExitA0
C0 (LSR of 0)
9FF5 .clear_result←2← 9FFC BNE← A33D JMP
LDA #0 ; A=0: clear result
9FF7 .shift_and_finalise←1← 9FF3 BEQ
LSR ; Shift right (always positive)
9FF8 BPL finalise_and_return ; Positive: jump to finalise
9FFA .alloc_fcb_for_open←1← A068 BEQ
AND #&3f ; Mask to 6-bit access value
9FFC BNE clear_result ; Non-zero: clear A and finalise
9FFE TXA ; Transfer X to A (options pointer)
9FFF JSR alloc_fcb_or_error ; Allocate FCB slot or raise error
A002 EOR #&80 ; Toggle bit 7
A004 ASL ; Shift left: build open mode
A005 STA hazel_txcb_data ; Store open mode in fs_cmd_data
A008 ROL ; Rotate to complete mode byte
A009 STA hazel_txcb_flag ; Store in fs_func_code
A00C JSR parse_cmd_arg_y0 ; Parse command argument (Y=0)
A00F LDX #2 ; X=2: buffer offset
A011 JSR copy_arg_to_buf ; Copy argument to TX buffer
fall through ↓
A014 .send_open_file_request
LDY #6 ; Y=6: open file command
A016 BIT always_set_v_byte ; Set V flag (skip directory check)
A019 SEC ; Set carry
A01A ROR need_release_tube ; Rotate carry into escapable flag bit 7
A01C JSR save_net_tx_cb_vset ; Send open request with V set
A01F BCS done_osfind ; Carry set (error): jump to finalise
A021 LDA #&ff ; A=&FF: mark as newly opened
A023 JSR store_rx_attribute ; Store &FF as receive attribute
A026 LDA hazel_txcb_data ; Load handle from fs_cmd_data
A029 PHA ; Push handle
A02A LDA #4 ; A=4: file info sub-command
A02C STA hazel_txcb_data ; Store sub-command
A02F LDX #1 ; X=1: shift filename
A031 .loop_shift_filename←1← A03A BNE
LDA hazel_txcb_flag,x ; Load filename byte from fs_func_code+X
A034 STA hazel_txcb_data,x ; Shift down to fs_cmd_data+X
A037 INX ; Advance source index
A038 CMP #&0d ; Is it CR (end of filename)?
A03A BNE loop_shift_filename ; No: continue shifting
A03C LDY #&12 ; Y=&12: file info request
A03E JSR save_net_tx_cb ; Send file info request
A041 LDA fs_last_byte_flag ; Load last byte flag
A043 AND #&bf ; Clear bit 6 (read/write bits)
A045 ORA hazel_txcb_data ; OR with reply access byte
A048 ORA #1 ; Set bit 0 (file is open)
A04A TAY ; Transfer to Y (access flags)
A04B AND #2 ; Check bit 1 (write access)
A04D BEQ check_open_mode ; No write access: check read-only
A04F PLA ; Pull handle from stack
A050 JSR alloc_fcb_slot ; Allocate FCB slot for channel
A053 BNE store_fcb_flags ; Non-zero: FCB allocated, store flags
A055 .findv_handler←1← 8EAD JMP
JSR verify_ws_checksum ; Verify workspace checksum
A058 JSR set_xfer_params ; Set up transfer parameters
A05B TAX ; Transfer A to X
A05C JSR mask_owner_access ; Set owner-only access mask
A05F TXA ; Transfer X back to A
A060 BEQ close_all_channels ; Zero: close file, process FCBs
A062 JSR save_ptr_to_os_text ; Save text pointer for OS
A065 LDY hazel_cur_dir_handle ; Load current directory handle
A068 BEQ alloc_fcb_for_open ; Zero: allocate new FCB
A06A TYA ; Transfer Y to A
A06B LDX #0 ; X=0: clear directory handle
A06D STX hazel_cur_dir_handle ; Store zero (clear handle)
A070 BEQ done_osfind ; ALWAYS branch to finalise
A072 .check_open_mode←1← A04D BEQ
LDA hazel_txcb_flag ; Load access/open mode byte
A075 ROR ; Rotate right: check bit 0
A076 BCS alloc_fcb_with_flags ; Carry set (bit 0): check read permission
A078 ROR ; Rotate right: check bit 1
A079 BCC alloc_fcb_with_flags ; Carry clear (no write): skip
A07B BIT hazel_txcb_count ; Test bit 7 of fs_data_count (lock flag)
A07E BPL alloc_fcb_with_flags ; Not locked: skip
A080 TYA ; Transfer Y to A (flags)
A081 ORA #&20 ; Set bit 5 (locked file flag)
A083 TAY ; Transfer back to Y
A084 .alloc_fcb_with_flags←3← A076 BCS← A079 BCC← A07E BPL
PLA ; Pull handle from stack
A085 JSR alloc_fcb_slot ; Allocate FCB slot for channel
A088 .store_fcb_flags←1← A053 BNE
TAX ; Transfer to X
A089 TYA ; Transfer Y to A (flags)
A08A STA hazel_fcb_state_byte,x ; Store flags in FCB table fcb_flags
A08D TXA ; Transfer X back to A (handle)
A08E .done_osfind←2← A01F BCS← A070 BEQ
JMP finalise_and_return ; Jump to finalise and return
A091 .close_all_channels←1← A060 BEQ
JSR process_all_fcbs ; Process all matching FCBs
A094 TYA ; Transfer Y to A
A095 BNE close_specific_chan ; Non-zero channel: close specific
A097 LDA fs_options ; Load FS options pointer low
A099 PHA ; Push (save for restore)
A09A LDA #osbyte_close_spool_exec ; A=&77: OSBYTE close spool/exec files
A09C JSR osbyte ; Close any *SPOOL and *EXEC files Close any SPOOLed or EXECed files
A09F PLA ; Pull saved options pointer
A0A0 STA fs_options ; Restore FS options pointer
A0A2 LDA #0 ; A=0: clear flags
A0A4 STA fs_last_byte_flag ; Save to fs_work_5
A0A6 STA fs_block_offset ; Load current FS station low
A0A8 BEQ send_close_request ; ALWAYS branch to send close request Save to fs_work_6
A0AA .close_specific_chan←1← A095 BNE
JSR check_chan_char ; Validate channel character
A0AD LDA hazel_fcb_slot_attr,x ; Is it CR (no argument)?
A0B0 .send_close_request←1← A0A8 BEQ
STA hazel_txcb_data ; Store as fs_cmd_data (file handle)
A0B3 LDX #1 ; X=1: argument size
A0B5 LDY #7 ; Y=7: close file command
A0B7 JSR save_net_tx_cb ; Send close file request
A0BA LDY fs_block_offset ; Parameter block low
A0BC BNE clear_single_fcb ; Parameter block high
A0BE CLV ; Clear V flag
A0BF JSR scan_fcb_flags ; Scan and clear all FCB flags
A0C2 .done_close←4← A0CD BEQ← A0DF BCC← A0F3 BPL← A127 BRA
JMP return_with_last_flag ; Return with last flag
A0C5 .clear_single_fcb←1← A0BC BNE
LDA #0 ; A=0: clear FCB entry
A0C7 STA hazel_fcb_addr_mid,y ; Clear hazel_fcb_addr_mid for slot Y
A0CA STA hazel_fcb_state_byte,y ; Clear hazel_fcb_state_byte for slot Y
A0CD 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)
A0CF .fscv_0_opt_entry
BEQ store_display_flag ; A=0 (init sub-code): jump to store_display_flag
A0D1 CPX #4 ; Non-zero A: X==4? (read OSARGS args)
A0D3 BNE osargs_dispatch ; X != 4: take normal OSARGS dispatch
A0D5 CPY #4 ; X==4 path: Y < 4?
A0D7 BCC send_osargs_request ; Yes: send OSARGS request via TXCB
A0D9 .osargs_dispatch←1← A0D3 BNE
DEX ; X-- (osargs_dispatch entry): step sub-code down
A0DA BNE osargs_store_ptr_lo ; X != 1: take store-ptr-lo path
A0DC .store_display_flag←1← A0CF BEQ
STY hazel_fs_messages_flag ; Store Y as hazel_fs_messages_flag (display control)
A0DF BCC done_close ; Tail-branch to done_close
A0E1 .error_osargs←2← A0F7 BCS← A103 BCS
LDA #7 ; A=7: error code (out-of-range OSARGS sub-code)
A0E3 JMP classify_reply_error ; Raise BRK error
A0E6 .send_osargs_request←1← A0D7 BCC
STY hazel_txcb_data ; Store Y as TXCB data byte (OSARGS payload)
A0E9 LDY #&16 ; Y=&16: TXCB function code (OSARGS request)
A0EB JSR save_net_tx_cb ; Send OSARGS request via TX control block
A0EE LDY fs_block_offset ; Reload Y from fs_block_offset
A0F0 STY hazel_fs_flags ; Update hazel_fs_flags from OSARGS reply
A0F3 BPL done_close ; No error (positive): tail to done_close
A0F5 .osargs_store_ptr_lo←1← A0DA BNE
CPX #8 ; X >= 8?
A0F7 BCS error_osargs ; Yes: out-of-range OSARGS sub-code
A0F9 CPX #4 ; X == 4?
A0FB BEQ osargs_check_length ; Yes: take fast read path (osargs_check_length)
A0FD CPY #4 ; Y < 4?
A0FF BCC osopt_check_cmos_protect ; Yes: take CMOS-protect path
A101 .osargs_check_length←1← A0FB BEQ
CPY #2 ; Y >= 2?
A103 BCS error_osargs ; Yes: argument out of range
A105 .osopt_check_cmos_protect←1← A0FF BCC
PHY ; Save Y
A106 PHX ; Save sub-code across the CMOS read
A107 LDX #&11 ; X=&11: CMOS RAM byte index
A109 JSR osbyte_a1 ; Read CMOS &11 (Econet status) -> Y
A10C PLX ; Restore sub-code
A10D TYA ; Read CMOS &11 result to A
A10E AND cmos_opt_mask_table,x ; Mask CMOS &11 with cmos_opt_mask_table[X]
A111 PLY ; Restore Y
A112 PHA ; Push CMOS value
A113 LDA la125,x ; Load shift count from cmos_attr_table[X]
A116 TAX ; Value to X
A117 TYA ; Caller's Y back to A as the value to shift
A118 .loop_extract_attr_bits←1← A11A BNE
ASL ; Shift CMOS bits
A119 DEX ; Count down shift iterations
A11A BNE loop_extract_attr_bits ; Loop until X reaches 0
A11C STA fs_load_addr ; Stash shifted value in fs_load_addr scratch
A11E PLA ; Pop saved value
A11F ORA fs_load_addr ; OR with the CMOS-masked value
A121 TAY ; Copy A to Y
A122 LDX #&11 ; X=&11: target CMOS byte for write-back
A124 .osopt_cmos_writeback_jsr
JSR osbyte_a2 ; Write CMOS RAM byte (Y) to byte index (X)
A127 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.

A129 .cmos_opt_mask_table←1Used as index base by← A10E AND
EQUB &01 ; Idx 0: AND mask = &01 (extract CMOS &11 bit 0)
A12A EQUB &02 ; Idx 1: AND mask = &02 (extract CMOS &11 bit 1)
A12B EQUB &04 ; Idx 2: AND mask = &04 (extract CMOS &11 bit 2)
A12C EQUB &06 ; Idx 3: AND mask = &06 (extract CMOS &11 bits 1,2)
A12D EQUB &FD ; Idx 4: AND mask = &FD (clear CMOS &11 bit 1)
A12E EQUB &F3 ; Idx 5: AND mask = &F3 (clear CMOS &11 bits 2,3)
A12F EQUB &CF ; Idx 6: AND mask = &CF (clear CMOS &11 bits 4,5)
A130 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
A131 .fscv_1_eof
JSR verify_ws_checksum ; Verify workspace checksum
A134 PHA ; Push checksum-verify result -- preserve it across the FCB lookups below
A135 LDA fs_block_offset ; Load block offset
A137 PHA ; Push block offset
A138 STX hazel_chan_attr ; Store X in cur_chan_attr
A13B JSR find_matching_fcb ; Find matching FCB entry
A13E BEQ mark_not_found ; Zero: no match found
A140 LDA hazel_fcb_addr_lo,y ; Load FCB low byte from fcb_count_lo
A143 CMP hazel_fcb_offset_save,x ; Compare with stored offset fcb_buf_offset
A146 BCC mark_not_found ; FCB lo-byte below stored offset -> not the matching FCB; mark_not_found
A148 LDX #&ff ; X=&FF: mark as found (all bits set)
A14A BMI restore_and_return ; ALWAYS branch (negative)
A14C .mark_not_found←2← A13E BEQ← A146 BCC
LDX #0 ; X=0: mark as not found
A14E .restore_and_return←1← A14A BMI
PLA ; Restore block offset from stack
A14F TAY ; Generate 'Syntax' error
A150 PLA ; Restore result from stack
A151 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)
A152 .update_addr_from_offset9←1← A28B JSR
LDY #9 ; Y=9: FS options offset for high address
A154 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
A157 .update_addr_from_offset1←1← A384 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
A159 .add_workspace_to_fsopts←1← A154 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
A15A .adjust_fsopts_4bytes←2← A291 JSR← A390 JSR
LDX #&fc ; X=&FC: loop counter (-4 to -1)
A15C .loop_adjust_byte←1← A16F BNE
LDA (fs_options),y ; Load FS options byte at offset Y
A15E BIT fs_load_addr_2 ; Test fs_load_addr_2 bit 7 (add/subtract)
A160 BMI subtract_ws_byte ; Push high byte
A162 ADC hazel_fs_opts_addend,x ; Add workspace byte to FS options
A165 JMP store_adjusted_byte ; RTS dispatches to command handler
A168 .subtract_ws_byte←1← A160 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 &A165).

On EntryAbyte to store
Ycurrent FS-options index
Xremaining-byte counter
A16B .store_adjusted_byte←1← A165 JMP
STA (fs_options),y ; Store result back to FS options
A16D INY ; Advance to next byte
A16E INX ; Advance counter
A16F BNE loop_adjust_byte ; Loop until 4 bytes processed
A171 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)
A172 .gbpbv_handler
JSR verify_ws_checksum ; Verify workspace checksum
A175 JSR set_xfer_params ; Set up transfer parameters
A178 PHA ; Push transfer type on stack
A179 JSR mask_owner_access ; Set owner-only access mask
A17C PLA ; Pull transfer type
A17D TAX ; Transfer to X
A17E BEQ skip_if_out_of_range ; Zero: no valid operation, return
A180 DEX ; Decrement (convert 1-based to 0-based)
A181 CPX #8 ; Compare with 8 (max operation)
A183 BCC valid_osgbpb_op ; Below 8: valid operation
A185 .skip_if_out_of_range←1← A17E BEQ
JMP return_with_last_flag ; Out of range: return with flag
A188 .valid_osgbpb_op←1← A183 BCC
TXA ; Transfer operation code to A
A189 LDY #0 ; Y=0: buffer offset
A18B PHA ; Push operation code
A18C CMP #4 ; Compare with 4 (write operations)
A18E BCC load_chan_handle ; Below 4: read operation
A190 JMP write_block_entry ; 4 or above: write data block
A193 .load_chan_handle←1← A18E BCC
LDA (fs_options),y ; Load channel handle from FS options
A195 PHA ; Push handle
A196 JSR check_not_dir ; Check file is not a directory
A199 PLY ; Restore Y
A19A JSR process_all_fcbs ; Send TX control block to server
A19D PLA ; Pull operation code
A19E JSR setup_transfer_workspace ; Set up transfer workspace
A1A1 PHP ; Save flags (carry from setup)
A1A2 LDY #0 ; Y=0: index for channel handle
A1A4 LDA (fs_options),y ; Load channel handle from FS options
A1A6 BCS set_write_active ; Carry set (write): set active
A1A8 JSR clear_conn_active ; Read: clear connection active
A1AB BPL setup_gbpb_request ; Branch to continue (always positive)
A1AD .set_write_active←1← A1A6 BCS
JSR set_conn_active ; Write: set connection active
A1B0 .setup_gbpb_request←1← A1AB BPL
STY hazel_txcb_flag ; Clear fs_func_code (Y=0)
A1B3 JSR lookup_cat_slot_data ; Look up channel slot data
A1B6 STA hazel_txcb_data ; Store flag byte in fs_cmd_data
A1B9 LDY #&0c ; Y=&0C: TX buffer size (short)
A1BB LDX #2 ; X=2: argument count
A1BD JSR save_net_tx_cb ; Send TX control block
A1C0 JSR lookup_cat_entry_0 ; Look up channel entry at Y=0
A1C3 LDY #9 ; Y=9: FS options offset for position
A1C5 LDA hazel_txcb_data ; Load new position low from fs_cmd_data
A1C8 STA hazel_fcb_addr_lo,x ; Update FCB low byte in fcb_count_lo
A1CB STA (fs_options),y ; Store in FS options at Y=9
A1CD INY ; Y=&0A
A1CE LDA hazel_txcb_flag ; Load new position high from fs_func_code
A1D1 STA hazel_fcb_addr_mid,x ; Update FCB high byte in fcb_attr_or_count_mid
A1D4 STA (fs_options),y ; Store in FS options at Y=&0A
A1D6 INY ; Y=&0B
A1D7 LDA hazel_txcb_count ; Load new extent from fs_data_count
A1DA STA hazel_fcb_addr_hi,x ; Update FCB extent in fcb_station_or_count_hi
A1DD STA (fs_options),y ; Store in FS options at Y=&0B
A1DF LDA #0 ; A=0: clear high byte of extent
A1E1 INY ; Y=&0C
A1E2 STA (fs_options),y ; Store zero in FS options at Y=&0C
A1E4 PLP ; Restore flags
A1E5 BCC return_success ; Carry clear: skip last-byte check
A1E7 LDA fs_last_byte_flag ; Load last-byte-of-transfer flag
A1E9 CMP #3 ; Is transfer still pending (flag=3)?
A1EB .return_success←1← A1E5 BCC
LDA #0 ; A=0: success
A1ED 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
A1F0 .lookup_cat_entry_0←2← A1C0 JSR← A1FC JSR
LDY #0 ; Y=0: offset for channel handle
A1F2 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
A1F4 .lookup_cat_slot_data←1← A1B3 JSR
JSR lookup_chan_by_char ; Look up channel by character
A1F7 LDA hazel_fcb_slot_attr,x ; Load slot-attribute byte from hazel_fcb_slot_attr,X
A1FA 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
A1FB .setup_transfer_workspace←2← A19E JSR← BD48 JMP
PHA ; Push operation code on stack
A1FC JSR lookup_cat_entry_0 ; Look up channel entry at Y=0
A1FF STA hazel_txcb_data ; Store flag byte in fs_cmd_data
A202 PHX ; Save X (channel index)
A203 LDY #&0b ; Y=&0B: source offset in FS options
A205 LDX #6 ; X=6: copy 6 bytes
A207 .loop_copy_opts_to_buf←1← A213 BNE
LDA (fs_options),y ; Load FS options byte
A209 STA hazel_txcb_flag,x ; Store in fs_func_code buffer
A20C DEY ; Decrement source index
A20D CPY #8 ; Skip offset 8?
A20F BNE skip_struct_hole ; No: continue copy
A211 DEY ; Skip offset 8 (hole in structure)
A212 .skip_struct_hole←1← A20F BNE
DEX ; Decrement destination counter
A213 BNE loop_copy_opts_to_buf ; Loop until all 6 bytes copied
A215 PLY ; Restore Y (channel index)
A216 PLA ; Pull operation code
A217 LSR ; Shift right: check bit 0 (direction)
A218 PHA ; Push updated code
A219 BCC store_direction_flag ; Carry clear: OSBGET (read)
A21B LDA hazel_fcb_addr_lo,y ; Copy FCB pointer low byte...
A21E STA hazel_txcb_size_hi ; ...into TX control block size low
A221 LDA hazel_fcb_addr_mid,y ; Copy FCB pointer mid byte...
A224 STA hazel_txcb_tx_status ; ...into TX control block size mid
A227 LDA hazel_fcb_addr_hi,y ; Copy FCB pointer high byte...
A22A STA hazel_txcb_osword_flag ; ...into TX control block size high
A22D .store_direction_flag←1← A219 BCC
STX hazel_txcb_flag ; Store direction flag in fs_func_code
A230 LDY #&0b ; Y=&0B: TX buffer size
A232 LDX #&91 ; X=&91: port for OSBGET
A234 PLA ; Pull operation code
A235 PHA ; Push back (keep on stack)
A236 BEQ store_port_and_send ; Zero (OSBGET): keep port &91
A238 LDX #&92 ; X=&92: port for OSBPUT
A23A DEY ; Y=&0A: adjusted buffer size
A23B .store_port_and_send←1← A236 BEQ
STX hazel_txcb_station ; Store port in fs_cmd_urd
A23E STX fs_error_ptr ; Store port in fs_error_ptr
A240 LDX #8 ; X=8: argument count
A242 LDA hazel_txcb_data ; Load file handle from fs_cmd_data
A245 JSR send_request_nowrite ; Send request (no write data)
A248 LDX #0 ; X=0: index
A24A LDA (fs_options,x) ; Load channel handle from FS options
A24C TAX ; Transfer to X as index
A24D LDA hazel_fcb_state_byte,x ; Load FCB flags from fcb_flags
A250 EOR #1 ; Toggle bit 0 (transfer direction)
A252 STA hazel_fcb_state_byte,x ; Store updated flags
A255 CLC ; Clear carry for addition
A256 LDX #4 ; X=4: process 4 address bytes
A258 .loop_setup_addr_bytes←1← A26C BNE
LDA (fs_options),y ; Load FS options address byte
A25A STA addr_work,y ; Store in zero page address area
A25D STA txcb_pos,y ; Store in TXCB position
A260 JSR advance_y_by_4 ; Advance Y by 4
A263 ADC (fs_options),y ; Add offset from FS options
A265 STA addr_work,y ; Store computed end address
A268 JSR retreat_y_by_3 ; Retreat Y by 3 for next pair
A26B DEX ; Decrement byte counter
A26C BNE loop_setup_addr_bytes ; Loop for all 4 address bytes
A26E INX ; X=1 (INX from 0)
A26F .loop_copy_offset←1← A276 BPL
LDA hazel_txcb_network,x ; Load offset from fs_cmd_csd
A272 STA hazel_txcb_flag,x ; Copy to fs_func_code
A275 DEX ; Decrement counter
A276 BPL loop_copy_offset ; Loop until both bytes copied
A278 PLA ; Pull operation code
A279 BNE send_with_swap ; Non-zero (OSBPUT): swap addresses
A27B LDA hazel_txcb_station ; Load port from fs_cmd_urd
A27E JSR check_and_setup_txcb ; Check and set up TXCB
A281 BCS recv_and_update ; Carry set: skip swap
A283 .send_with_swap←1← A279 BNE
JSR send_txcb_swap_addrs ; Send TXCB and swap start/end addresses
A286 .recv_and_update←1← A281 BCS
JSR recv_reply_preserve_flags ; Receive and process reply
A289 STX fs_load_addr_2 ; Store result in fs_load_addr_2
A28B JSR update_addr_from_offset9 ; Update addresses from offset 9
A28E DEC fs_load_addr_2 ; Decrement fs_load_addr_2
A290 SEC ; Set carry for subtraction
A291 JSR adjust_fsopts_4bytes ; Adjust FS options by 4 bytes
A294 ASL hazel_txcb_data ; Shift fs_cmd_data left (update status)
A297 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)
A298 .recv_reply_preserve_flags←1← A286 JSR
PHP ; Save flags before reply processing
A299 JSR recv_and_process_reply ; Process server reply
A29C PLP ; Restore flags after reply processing
A29D 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.

A29E .send_osbput_data←1← A2CE BEQ
LDY #&15 ; Y=&15: TX buffer size for OSBPUT data
A2A0 JSR save_net_tx_cb ; Send TX control block
A2A3 LDA hazel_fs_flags ; Load display flag from hazel_fs_flags
A2A6 STA hazel_txcb_byte_16 ; Store in hazel_txcb_byte_16
A2A9 STX fs_load_addr ; Clear fs_load_addr (X=0)
A2AB STX fs_load_addr_hi ; Clear fs_load_addr_hi
A2AD LDA #&12 ; A=&12: byte count for data block
A2AF STA fs_load_addr_2 ; Store in fs_load_addr_2
A2B1 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)
A2B3 .write_block_entry←1← A190 JMP
LDY #4 ; Y=4: offset for station comparison
A2B5 LDA tube_present ; Load stored station from tube_present
A2B8 BEQ store_station_result ; Zero: skip station check
A2BA CMP (fs_options),y ; Compare with FS options station
A2BC BNE store_station_result ; Mismatch: skip subtraction
A2BE DEY ; Y=3
A2BF SBC (fs_options),y ; Subtract FS options value
A2C1 .store_station_result←2← A2B8 BEQ← A2BC BNE
STA svc_state ; Store result in svc_state
A2C3 .loop_copy_opts_to_ws←1← A2C9 BNE
LDA (fs_options),y ; Load FS options byte at Y
A2C5 STA fs_last_byte_flag,y ; Store in workspace at fs_last_byte_flag+Y
A2C8 DEY ; Decrement index
A2C9 BNE loop_copy_opts_to_ws ; Loop until all bytes copied
A2CB PLA ; Pull operation code
A2CC AND #3 ; Mask to 2-bit sub-operation
A2CE BEQ send_osbput_data ; Zero: send OSBPUT data
A2D0 LSR ; Shift right: check bit 0
A2D1 BEQ handle_cat_update ; Zero (bit 0 clear): handle read
A2D3 BCS update_cat_position ; Carry set: handle catalogue update
A2D5 .handle_cat_update←1← A2D1 BEQ
TAY ; Transfer to Y (Y=0)
A2D6 LDA hazel_fs_context_copy,y ; Load data byte from fs_csd_handle
A2D9 STA hazel_txcb_network ; Store in fs_cmd_csd
A2DC LDA hazel_fs_prefix_stn ; Load high data byte from fs_lib_handle
A2DF STA hazel_txcb_lib ; Store in fs_cmd_lib
A2E2 LDA hazel_fs_saved_station ; Load port from fs_urd_handle
A2E5 STA hazel_txcb_station ; Store in fs_cmd_urd
A2E8 LDX #&12 ; X=&12: buffer size marker
A2EA STX hazel_txcb_func_code ; Store in fs_cmd_y_param
A2ED LDA #&0d ; A=&0D: count value
A2EF STA hazel_txcb_flag ; Store in fs_func_code
A2F2 STA fs_load_addr_2 ; Store in fs_load_addr_2
A2F4 LSR ; Shift right (A=6)
A2F5 STA hazel_txcb_data ; Store in fs_cmd_data
A2F8 CLC ; Clear carry for addition
A2F9 JSR prep_send_tx_cb ; Prepare and send TX control block
A2FC STX fs_load_addr_hi ; Store X in fs_load_addr_hi (X=0)
A2FE INX ; X=1 (after INX)
A2FF 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
A301 .write_data_block←2← A2B1 BNE← A379 JSR
LDA svc_state ; Load svc_state (tube flag)
A303 BNE tube_write_setup ; Non-zero: write via tube
A305 LDX fs_load_addr ; Load source index from fs_load_addr
A307 LDY fs_load_addr_hi ; Load destination index from fs_load_addr_hi
A309 .loop_copy_to_host←1← A312 BNE
LDA hazel_txcb_data,x ; Load data byte from fs_cmd_data buffer
A30C STA (fs_crc_lo),y ; Store to destination via fs_crc pointer
A30E INX ; Advance source index
A30F INY ; Advance destination index
A310 DEC fs_load_addr_2 ; Decrement byte counter
A312 BNE loop_copy_to_host ; Loop until all bytes transferred
A314 BEQ tail_update_catalogue ; X=&10: scan 16 slots (15 to 0)
A316 .tube_write_setup←1← A303 BNE
JSR tube_claim_c3 ; Clear V
A319 LDA #1 ; A=1: tube transfer type (write)
A31B LDX fs_options ; Load destination low from fs_options
A31D LDY fs_block_offset ; No match: try next
A31F INX ; Load slot status byte
A320 BNE set_tube_addr ; No wrap: skip high increment
A322 INY ; Test bit 2 (PS active flag)?
A323 .set_tube_addr←1← A320 BNE
JSR tube_addr_data_dispatch ; Set up tube transfer address
A326 LDX fs_load_addr ; Transfer Y to A
A328 .loop_write_to_tube←1← A336 BNE
LDA hazel_txcb_data,x ; Load data byte from buffer
A32B STA tube_data_register_3 ; Write to tube data register 3 Store Y to fs_urd_handle
A32E INX ; Advance source index
A32F LDY #6 ; Y=6: tube write delay
A331 .loop_tube_delay←1← A332 BNE
DEY ; Delay loop: decrement Y
A332 BNE loop_tube_delay ; Transfer Y to A
A334 DEC fs_load_addr_2 ; Decrement byte counter
A336 BNE loop_write_to_tube ; Store allocation result
A338 LDA #&83 ; A=&83: release tube claim
A33A 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 &A38D (the no-change path). Never returns directly (clear_result loads A=0 and tail-falls into finalise_and_return).

A33D .tail_update_catalogue←2← A314 BEQ← A3A1 JMP
JMP clear_result ; Jump to clear A and finalise return
A340 .update_cat_position←1← A2D3 BCS
LDY #9 ; Y=9: offset for position byte
A342 LDA (fs_options),y ; Try next slot
A344 STA hazel_txcb_flag ; Store in fs_func_code
A347 LDY #5 ; Y=5: offset for extent byte
A349 LDA (fs_options),y ; Load extent byte from FS options
A34B STA hazel_txcb_count ; Store in fs_data_count
A34E LDX #&0d ; X=&0D: byte count
A350 STX hazel_txcb_result ; Store in fs_reply_cmd
A353 LDY #2 ; Y=2: command sub-type
A355 STY fs_load_addr ; Set V (found match)
A357 STY hazel_txcb_data ; Store in fs_cmd_data Store Y to fs_csd_handle
A35A INY ; Y=3: TX buffer command byte
A35B JSR save_net_tx_cb ; V set: found, skip allocation
A35E STX fs_load_addr_hi ; Allocate FCB slot
A360 LDA hazel_txcb_flag ; Load data offset from fs_func_code
A363 STA (fs_options,x) ; Store as first byte of FS options
A365 LDA hazel_txcb_data ; Load data count from fs_cmd_data
A368 LDY #9 ; Y=9: position offset in FS options
A36A ADC (fs_options),y ; Add to current position
A36C STA (fs_options),y ; Store updated position
A36E LDA txcb_end ; Load TXCB end byte
A370 SBC #7 ; Subtract 7 (header overhead)
A372 STA hazel_txcb_flag ; Store remaining data size
A375 STA fs_load_addr_2 ; Store in fs_load_addr_2 (byte count)
A377 BEQ clear_buf_after_write ; Zero bytes: skip write
A379 JSR write_data_block ; Write data block to host/tube
A37C .clear_buf_after_write←1← A377 BEQ
LDX #2 ; X=2: clear 3 bytes (indices 0-2)
A37E .loop_clear_buf←1← A382 BPL
STA hazel_txcb_count,x ; Clear fs_data_count+X
A381 DEX ; Decrement index
A382 BPL loop_clear_buf ; Loop until all cleared
A384 JSR update_addr_from_offset1 ; Update addresses from offset 1
A387 SEC ; Set carry for subtraction
A388 DEC fs_load_addr_2 ; Decrement fs_load_addr_2
A38A LDA hazel_txcb_data ; Load data count from fs_cmd_data
A38D STA hazel_txcb_flag ; Copy to fs_func_code
A390 JSR adjust_fsopts_4bytes ; Adjust FS options by 4 bytes (subtract)
A393 LDX #3 ; X=3: check 4 bytes
A395 LDY #5 ; Y=5: starting offset
A397 SEC ; Set carry for comparison
A398 .loop_check_remaining←1← A39E BPL
LDA (fs_options),y ; Load FS options byte
A39A BNE done_write_block ; Non-zero: more data remaining
A39C INY ; Advance to next byte
A39D DEX ; Decrement counter
A39E BPL loop_check_remaining ; Loop until all bytes checked
A3A0 CLC ; All zero: clear carry (transfer complete)
A3A1 .done_write_block←1← A39A 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)
A3A4 .tube_claim_c3←3← A316 JSR← A3A9 BCC← A63B JSR
LDA #&c3 ; A=&C3: tube claim protocol
A3A6 JSR tube_addr_data_dispatch ; Dispatch tube address/data claim
A3A9 BCC tube_claim_c3 ; Carry clear: claim failed, retry
A3AB 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
A3AC .cmd_fs
LDA hazel_fs_station ; Read current FS station from workspace
A3AF STA fs_work_5 ; Save in fs_work_5 (so 'no-arg' path can print it)
A3B1 LDA hazel_fs_network ; Read current FS network
A3B4 STA fs_work_6 ; Save in fs_work_6
A3B6 LDA (fs_crc_lo),y ; Look at the first command-line byte
A3B8 CMP #&0d ; Is it CR (no argument)?
A3BA BEQ print_current_fs ; Yes: print the current FS address
A3BC JSR parse_fs_ps_args ; Parse 'net.station' arg into fs_work_5/6
A3BF LDA #1 ; A=1: OSWORD &13 sub-function 1 = set file server station
A3C1 STA fs_work_4 ; Store sub-function in PB[0]
A3C3 LDA #osword_nfs_info ; A=&13: OSWORD &13
A3C5 LDX #<(fs_work_4) ; X = lo of PB pointer (fs_work_4 = &B4)
A3C7 LDY #>(fs_work_4) ; Y = hi of PB pointer (=0, since fs_work_4 is in zero page)
A3C9 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)
A3CC .print_current_fs←1← A3BA 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)
A3CF .print_fs_info_newline←1← B4AF JSR
BIT always_set_v_byte ; Set V so print_station_addr suppresses the leading '0.' when the network number is zero
A3D2 JSR print_station_addr ; Print the station/network address
A3D5 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)
A3D8 .parse_fs_ps_args←3← 9603 JSR← A3BC JSR← B408 JSR
PHX ; Save caller's X (command-line offset cursor)
A3D9 LDA #0 ; A=0: clear the dot-seen flag for parse_addr_arg
A3DB STA fs_work_4 ; Store cleared dot-seen flag
A3DD JSR parse_addr_arg ; Parse first number (network or standalone station)
A3E0 BCS skip_if_no_station ; C set: parse_addr_arg saw an empty argument -- skip station storage
A3E2 STA fs_work_7 ; Save the network number in fs_work_7
A3E4 PHY ; Save Y (current command-line cursor) for after the bridge poll
A3E5 JSR init_bridge_poll ; Populate the bridge routing table -- returns local network number in A
A3E8 EOR fs_load_addr_2 ; EOR with parsed network: Z set iff parse matched local
A3EA BEQ store_station_lo ; Match: keep A=0 to mark local network
A3EC LDA fs_load_addr_2 ; Mismatch: A = parsed network number
A3EE .store_station_lo←1← A3EA BEQ
STA fs_work_6 ; Store network number into fs_work_6 (the canonical form: 0=local, non-zero=remote)
A3F0 PLY ; Restore Y
A3F1 INY ; Step Y past the dot separator
A3F2 JSR parse_addr_arg ; Parse station number after the dot
A3F5 .skip_if_no_station←1← A3E0 BCS
BEQ no_station_loop ; C set: no station after dot -- leave fs_work_5 alone
A3F7 STA fs_work_5 ; Store parsed station in fs_work_5
A3F9 .no_station_loop←1← A3F5 BEQ
PLX ; Restore caller's X
A3FA 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)
A3FB .get_pb_ptr_as_index←2← A419 JSR← A429 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)
A3FD .byte_to_2bit_index←4← 8FC7 JSR← A95D JSR← A976 JSR← B501 JSR
ASL ; Multiply A by 2
A3FE ASL ; Multiply A by 2 again -- A is now A_orig * 4
A3FF PHA ; Stash A_orig * 4 on the stack
A400 ASL ; Multiply A by 2 -- A is now A_orig * 8 (C = bit 7 of A_orig*4)
A401 TSX ; Capture S so we can read the just-pushed value
A402 PHP ; Save the C flag from the third ASL
A403 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
A406 ROR ; Halve the result, putting the new C as bit 7
A407 PLP ; Restore the saved C (from the third ASL)
A408 ASL ; ASL doubles the halved value (effectively undoes the ROR's divide while reusing C)
A409 TAY ; Y = A_orig * 12 (the 12-byte-aligned index)
A40A PLA ; Recover A_orig * 4 (left on the stack at &A3FF)
A40B CMP #&48 ; Above &48 (i.e. A_orig * 4 >= 72, A_orig >= 18)?
A40D BCC rts_2bit_index ; No: keep computed Y
A40F LDY #0 ; Yes: clamp Y to 0 (out of range)
A411 TYA ; Mirror Y -> A so callers can test Z
A412 .rts_2bit_index←1← A40D 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
A413 .net_1_read_handle
LDY #&6f ; Y=&6F: net_rx_ptr offset for the 'inline' handle byte
A415 LDA (net_rx_ptr),y ; Read handle byte directly from RX buffer
A417 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].

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

A429 .net_3_close_handle
JSR get_pb_ptr_as_index ; Convert PB pointer to workspace table offset
A42C BCC mark_ws_uninit ; Out of range: mark as uninitialised
A42E ROR fs_flags ; Shift bit 0 of fs_flags into C (save state)
A431 LDA osword_pb_ptr ; Read PB[0] (the handle to close)
A433 ROL ; Shift bit 7 of A into C
A434 ROL fs_flags ; Restore C into bit 0 of fs_flags
A437 RTS ; Return; the close action is dispatched elsewhere based on the saved C state
A438 .mark_ws_uninit←1← A42C BCC
ROR econet_flags ; Save bit 0 of econet_flags
A43B LDA #'?' ; A='?': uninitialised marker
A43D STA (nfs_workspace),y ; Write '?' to workspace[Y] (the slot is now free)
A43F ROL econet_flags ; Restore bit 0 of econet_flags
A442 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).

A443 .fscv_3_star_cmd
JSR set_text_and_xfer_ptr ; Set text/transfer pointers from FS context
A446 LDY #&ff ; Y=&FF -- mark spool/Tube state inactive
A448 STY fs_spool_handle ; Store fs_spool_handle = &FF
A44A STY need_release_tube ; Store need_release_tube = &FF
A44C INY ; Advance index
A44D LDX #&35 ; X=&35: NFS-commands sub-table offset
A44F JSR match_fs_cmd ; Match against the NFS sub-table
A452 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).

A454 .cmd_fs_reentry←1← 8C73 JMP
BVC dispatch_fs_cmd ; V clear: re-enter dispatch_fs_cmd
A456 .error_syntax
LDA #&dc ; Error code &DC
A458 JSR error_inline ; Raise 'Syntax' error
A45B EQUS "Syntax", &00
A462 .dispatch_fs_cmd←2← A452 BCS← A454 BVC
LDA #0 ; A=0: clear svc_state
A464 STA svc_state ; Store -> svc_state
A466 LDA cmd_dispatch_hi_table,x ; Load dispatch hi byte from cmd_dispatch_hi_table+X
A469 PHA ; Push hi for RTS dispatch
A46A LDA cmd_dispatch_lo_table,x ; Load dispatch lo byte from cmd_dispatch_lo_table+X
A46D PHA ; Push lo for RTS dispatch
A46E 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
A46F .match_fs_cmd←3← 8C6E JSR← 8C9C JSR← A44F JSR
TYA ; Save command-line offset Y on stack
A470 PHA ; Push for save/restore
A471 .restart_table_scan←1← A497 BNE
PLA ; Reload saved Y (peek without popping)
A472 PHA ; Push it back to keep on stack
A473 TAY ; Y = saved command-line offset
A474 LDA cmd_table_fs,x ; First char of current entry name
A477 BMI check_char_type ; Bit 7 set already: end of table
A479 .loop_match_char←1← A486 BNE
LDA cmd_table_fs,x ; Next char from table
A47C BMI check_separator ; Bit 7 set: name fully matched
A47E EOR (fs_crc_lo),y ; XOR against the stored command character
A480 AND #&df ; Mask off case bit (5)
A482 BNE skip_entry_chars ; Mismatch (after case mask): skip entry
A484 INY ; Advance command-line offset
A485 INX ; Advance table offset
A486 BNE loop_match_char ; ALWAYS branch: continue matching
A488 .skip_entry_chars←2← A482 BNE← A48C BPL
INX ; Skip remaining name chars
A489 LDA cmd_table_fs,x ; Load next table byte
A48C BPL skip_entry_chars ; Bit 7 clear: continue skipping
A48E LDA (fs_crc_lo),y ; Char on command line at current Y
A490 CMP #'.' ; Is it . (abbreviation)?
A492 BEQ skip_dot_and_spaces ; Yes: accept abbreviated match
A494 .loop_skip_to_next←1← A4A9 BNE
INX ; Skip 3-byte handler trailer (flag, lo, hi)
A495 INX ; (continued)
A496 INX ; (continued)
A497 BNE restart_table_scan ; ALWAYS branch: try next entry
A499 .check_separator←1← A47C BMI
TYA ; Save matched-name length on stack
A49A PHA ; Push for stack-based comparison
A49B LDA (fs_crc_lo),y ; Char on command line just past name
A49D LDY #9 ; Y=9: separator-table size - 1
A49F .loop_check_sep_table←1← A4A5 BPL
CMP sep_table_data,y ; Compare with separator
A4A2 BEQ separator_char_table ; Match: valid command boundary
A4A4 DEY ; Try next separator
A4A5 BPL loop_check_sep_table ; Loop through 10 separators
A4A7 PLA ; Restore matched-name length
A4A8 TAY ; A = matched offset, save in Y
A4A9 BNE loop_skip_to_next ; ALWAYS branch: try next entry
A4AB .sep_table_data←1Used as index base by← A49F CMP
JSR separator_parse_dispatch ; Dispatch helper (sep_table_data path)
A4AE BIT parse_separator_flag ; Check separator flag (zp_0026)
A4B0 ROL ; Rotate result bit into carry
A4B1 DEC ; Decrement match counter
A4B2 RTI ; Effective unconditional jump
A4B3 EQUB &0D ; CR (carriage return)
A4B4 .separator_char_table←1← A4A2 BEQ
PLA ; Restore matched-name length
A4B5 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 &A4A8 (alt-entry that also accepts dots). Single caller (the BNE retry at &A4A9).

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

A4F8 .fscv_2_star_run←1← A4F3 BVS
JSR save_ptr_to_os_text ; Save text pointer (for GSREAD-driven parsing)
A4FB JSR mask_owner_access ; Reset fs_lib_flags low bits to 5-bit access mask
A4FE ORA #2 ; Set bit 1 of A (mark *RUN-style invocation)
A500 STA hazel_fs_lib_flags ; Update hazel_fs_lib_flags with the result
A503 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).

A505 .cmd_run_via_urd←1← 8E4D JMP
JSR save_ptr_to_os_text ; Save current OS text pointer
A508 JSR mask_owner_access ; Mask access bits
A50B AND #&fd ; Clear bit 1 of mask
A50D STA hazel_fs_lib_flags ; Save into fs_lib_flags
A510 .cmd_run_load_mask←1← A503 BNE
JSR parse_cmd_arg_y0 ; Begin parsing the *RUN argument
A513 .open_file_for_run←1← A58A BNE
LDX #1 ; X=1: TX-buffer write index for argument
A515 JSR copy_arg_to_buf ; Copy argument to TX buffer
A518 LDA #2 ; A=2: open-input mode for OSFIND
A51A STA hazel_txcb_data ; Next byte down
A51D LDY #&12 ; Y=&12: cmd code for *RUN
A51F JSR save_net_tx_cb ; Send the request and wait for reply
A522 LDA hazel_txcb_data ; Read reply status from TX[5]
A525 CMP #1 ; Compare with 1 (not-found)
A527 BNE try_library_path ; Loop until all 6 restored
A529 LDX #3 ; Return from svc_8_osword
A52B .loop_check_handles←1← A534 BPL
INC hazel_txcb_size_hi,x ; Increment handle byte
A52E BEQ alloc_run_fcb ; Load handler address low byte
A530 JMP check_exec_addr ; Non-zero: handle valid, execute
A533 .alloc_run_fcb←1← A52E BEQ
DEX ; Decrement X (post-find adjustment)
A534 BPL loop_check_handles ; Loop while X >= 0 (scan all 4 handle slots)
A536 JSR alloc_fcb_or_error ; RTS dispatches to pushed handler
A539 LDX #1 ; X=1: target offset for the *RUN-channel command
A53B STX hazel_txcb_data ; Store X to hazel_txcb_data (cmd byte)
A53E STX hazel_txcb_flag ; Store X to hazel_txcb_flag (cmd flag)
A541 INX ; X=2
A542 JSR copy_arg_to_buf ; Copy filename arg into TX buffer
A545 LDY #6 ; Test station active flag
A547 JSR save_net_tx_cb ; Send re-open request
A54A BCS done_run_dispatch ; C set: error from save_net_tx_cb -- abort *RUN
A54C JMP alloc_run_channel ; Yes: handle clock read
A54F .done_run_dispatch←1← A54A BCS
JMP finalise_and_return ; Jump to finalise and return
A552 .try_library_path←1← A527 BNE
LDA hazel_parse_buf ; Return
A555 CMP #&24 ; Y=&10: length of TXCB to save
A557 BEQ error_bad_command ; Save current TX control block
A559 LDA hazel_fs_lib_flags ; Load library flag byte
A55C BMI library_tried ; Bit 7 set: library already tried
A55E ROL ; Shift bit 7 into carry
A55F ROL ; Rotate mask bit into carry
A560 BMI restore_filename ; Store BCD seconds
A562 BCS error_bad_command ; Carry set: bad command
A564 LDX #&ff ; X=&FF -- start scan from end
A566 .loop_find_name_end←1← A56C BNE
INX ; Convert binary to BCD
A567 LDA hazel_parse_buf,x ; Load filename byte
A56A CMP #&0d ; Compare with CR (terminator)
A56C BNE loop_find_name_end ; Load hours from clock workspace
A56E .loop_shift_name_right←1← A575 BPL
LDA hazel_parse_buf,x ; Shift filename right by 8 bytes
A571 STA hazel_rtc_buffer,x ; Store shifted byte
A574 DEX ; Decrement scan index
A575 BPL loop_shift_name_right ; Clear hours high position
A577 LDX #7 ; Store zero
A579 .loop_copy_lib_prefix←1← A580 BPL
LDA library_dir_prefix,x ; Copy 'Library.' prefix
A57C STA hazel_parse_buf,x ; Store prefix byte
A57F DEX ; Decrement scan index
A580 BPL loop_copy_lib_prefix ; Loop until prefix copied
A582 LDA hazel_fs_lib_flags ; Load library flag
A585 ORA #&60 ; Mark byte as 'argument'
A587 STA hazel_fs_lib_flags ; Restore day+month byte
A58A .retry_with_library←1← A5A1 BNE
BNE open_file_for_run ; Retry file open with library path
A58C .restore_filename←1← A560 BMI
LDX #&ff ; X=&FF -- restart scan from end
A58E .loop_restore_name←1← A597 BNE
INX ; Store BCD month
A58F LDA hazel_rtc_buffer,x ; Load backup byte
A592 STA hazel_parse_buf,x ; Shift high nibble down
A595 EOR #&0d ; 4th shift: isolate high nibble
A597 BNE loop_restore_name ; No: continue restoring
A599 JSR mask_owner_access ; Set owner-only access mask
A59C ORA #&80 ; Mark caller's flags
A59E STA hazel_fs_lib_flags ; Copy 7 bytes (Y=6 down to 0)
A5A1 BNE retry_with_library ; No match: retry via the library directory
A5A3 .library_tried←1← A55C BMI
JSR mask_owner_access ; Store to parameter block
A5A6 LDA #2 ; Loop for all 7 bytes
A5A8 BIT hazel_fs_lib_flags ; Test hazel_fs_lib_flags bits 6 / 7
A5AB BNE error_bad_command ; Either bit set: this is an invalid command path
A5AD JSR finalise_and_return ; Otherwise finalise and return
A5B0 LDA #&0b ; A=&0B: FSCV reason 11 (filing-system change)
A5B2 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.

A5B5 .error_bad_command←4← A4F5 JMP← A557 BEQ← A562 BCS← A5AB BNE
LDA #&fe ; Error code &FE
A5B7 JSR error_bad_inline ; Raise 'Bad command' error
A5BA 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
A5C2 .check_exec_addr←1← A530 JMP
LDX #3 ; X=3: check 4 execution bytes
A5C4 .loop_check_exec_bytes←1← A5CA BNE
INC hazel_txcb_flag,x ; Increment execution address byte
A5C7 BNE run_copy_arg_to_buf ; Low byte = &6F
A5C9 DEX ; Set osword_flag
A5CA BNE loop_check_exec_bytes ; Loop until all checked
A5CC LDA #&93 ; A=&93: error code 'Bad command'
A5CE JSR error_inline_log ; Generate 'No!' error
A5D1 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)
A5D7 .alloc_run_channel←1← A54C JMP
LDA hazel_txcb_data ; Set workspace pointer high
A5DA JSR alloc_fcb_slot ; Allocate FCB slot
A5DD TAY ; A = parsed character
A5DE LDA #0 ; Y=OSWORD flag (slot specifier)
A5E0 STA hazel_fcb_status,x ; Clear status in channel table
A5E3 STY hazel_cur_dir_handle ; A=3: start searching from slot 3
A5E6 LDY #3 ; Y=3: skip past 3-byte FS header
A5E8 JMP boot_cmd_oscli ; C set: slot invalid, store result
A5EB .library_dir_prefix←1Used as index base by← A579 LDA
EQUS "Library." ; Continue shift
A5F3 .run_copy_arg_to_buf←1← A5C7 BNE
JSR copy_arg_to_buf_x0 ; Copy parsed arg to TX buffer with X=0
A5F6 LDY #0 ; Y=0
A5F8 CLC ; For the loop entry
A5F9 JSR gsinit ; Transfer found slot to A
A5FC .loop_read_gs_string←1← A5FF BCC
JSR gsread ; Store slot number to PB byte 0
A5FF BCC loop_read_gs_string ; Always (BCC after CLC) loop back
A601 DEY ; C set: slot invalid, store result
A602 .loop_skip_trailing←1← A607 BEQ
INY ; Advance Y past trailing space
A603 LDA (os_text_ptr),y ; Y=Y-1: adjust workspace offset
A605 CMP #' ' ; Is it space?
A607 BEQ loop_skip_trailing ; Yes: skip it
A609 EOR #&0d ; Test for CR (terminator)
A60B CLC ; Clear C for arithmetic
A60C TYA ; Compare Y with OSWORD flag
A60D ADC os_text_ptr ; Add to text pointer low
A60F STA fs_crc_lo ; Store low byte of (os_text_ptr + Y) -> fs_crc_lo (repurposed as a generic pointer)
A611 LDA os_text_ptr_hi ; Load os_text_ptr_hi for the high-byte add
A613 ADC #0 ; Add carry from low add (no extra increment)
A615 STA fs_crc_hi ; Store result high byte -> fs_crc_hi
A617 JSR save_text_ptr ; Save text pointer for later
A61A LDX #&c0 ; X=&C0: pointer-to-options high byte
A61C STX fs_block_offset ; Y=1: workspace flag offset
A61E LDA #&0e ; Store pending marker to workspace
A620 STA fs_options ; Store as fs_options
A622 STA hazel_retry_counter ; Increment retry counter
A625 LDX #&4a ; X=&4A: FS command table offset
A627 LDY #5 ; Store result A to PB via Y
A629 JSR do_fs_cmd_iteration ; Rotate Econet flags back (restore state)
A62C LDA tube_present ; Return from OSWORD 11 handler
A62F BEQ dispatch_via_vector ; Store to ws_ptr_lo
A631 AND hazel_txcb_tx_status ; Y=&7F: last byte of RX buffer
A634 AND hazel_txcb_osword_flag ; Mask the OSWORD flag byte
A637 CMP #&ff ; All &FF?
A639 BEQ dispatch_via_vector ; X-1: adjust count
A63B JSR tube_claim_c3 ; Claim tube for data transfer
A63E LDX #9 ; X=9: parameter count
A640 LDY #&c1 ; Y=&C1: high byte of TX buffer pointer
A642 LDA #4 ; A=4: option byte for *RUN
A644 JMP tube_addr_data_dispatch ; Relocated execute path
A647 .dispatch_via_vector←2← A62F BEQ← A639 BEQ
LDA #1 ; A=1: dispatch flag
A649 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 &9599).

On ExitAfs_last_byte_flag (loaded by return_with_last_flag)
A64C .fsreply_3_set_csd←1← 9599 JMP
JSR find_station_bit3 ; Find station-bit-3 entry
A64F 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.

A652 .fsreply_5_set_lib
JSR flip_set_station_boot ; Record library station in station table
A655 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
A658 .find_station_bit2←1← A6FD JSR
LDX #&10 ; X=&10: scan 16 entries
A65A CLV ; Clear V (no-match marker)
A65B .loop_search_stn_bit2←2← A661 BNE← A668 BEQ
DEX ; Step to previous entry
A65C BMI done_search_bit2 ; Below 0: scan complete
A65E JSR match_station_net ; Compare entry X's stn/net with caller's
A661 BNE loop_search_stn_bit2 ; No match: continue
A663 LDA hazel_fcb_status,x ; Match: read entry's flag byte at hazel_fcb_status+X
A666 AND #4 ; Mask bit 2
A668 BEQ loop_search_stn_bit2 ; Bit 2 clear: keep scanning
A66A TYA ; Bit 2 set: A = matched entry index (Y)
A66B STA hazel_fcb_slot_attr,x ; Store Y at hazel_fcb_slot_attr+X (link entry to slot)
A66E BIT always_set_v_byte ; BIT always_set_v_byte: V <- 1 (match found)
A671 .done_search_bit2←1← A65C BMI
STY hazel_fs_saved_station ; Save Y at hazel_fs_saved_station (matched entry index)
A674 BVS set_flags_bit2 ; V set: skip new-slot alloc
A676 TYA ; A = caller's index
A677 JSR alloc_fcb_slot ; Allocate a fresh FCB slot
A67A STA hazel_fcb_slot_1 ; Save FCB slot index at hazel_fcb_slot_1
A67D BEQ jmp_restore_fs_ctx ; Z set: alloc failed -> restore FS context
A67F .set_flags_bit2←1← A674 BVS
LDA #&26 ; A=&26: workspace flag for bit 2 search
A681 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
A683 .find_station_bit3←3← A64C JSR← A6B5 JSR← A703 JSR
LDX #&10 ; X=&10: scan 16 entries
A685 CLV ; Clear V (no-match marker)
A686 .loop_search_stn_bit3←2← A68C BNE← A693 BEQ
DEX ; Step to previous entry
A687 BMI done_search_bit3 ; Below 0: scan complete
A689 JSR match_station_net ; Compare entry's stn/net with caller's
A68C BNE loop_search_stn_bit3 ; No match: continue
A68E LDA hazel_fcb_status,x ; Match: read entry's flag byte at hazel_fcb_status+X
A691 AND #8 ; Mask bit 3
A693 BEQ loop_search_stn_bit3 ; Bit 3 clear: keep scanning
A695 TYA ; Bit 3 set: A = matched entry index (Y)
A696 STA hazel_fcb_slot_attr,x ; Store Y at hazel_fcb_slot_attr+X (link entry to slot)
A699 BIT always_set_v_byte ; BIT always_set_v_byte: V <- 1 (match found)
A69C .done_search_bit3←1← A687 BMI
STY hazel_fs_context_copy ; Save Y at hazel_fs_context_copy (matched entry index)
A69F BVS set_flags_bit3 ; V set: skip new-slot alloc
A6A1 TYA ; A = caller's index
A6A2 JSR alloc_fcb_slot ; Allocate a fresh FCB slot
A6A5 STA hazel_fcb_slot_2 ; Save FCB slot index at hazel_fcb_slot_2
A6A8 BEQ jmp_restore_fs_ctx ; Z set: alloc failed -> restore FS context
A6AA .set_flags_bit3←1← A69F BVS
LDA #&2a ; A=&2A: workspace flag for bit 3 search
A6AC 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
A6AE .cmd_flip
LDA hazel_fs_context_copy ; Load current CSD handle
A6B1 PHA ; Save CSD handle
A6B2 LDY hazel_fs_prefix_stn ; Load library handle into Y
A6B5 JSR find_station_bit3 ; Install library as new CSD
A6B8 PLA ; Restore original CSD handle
A6B9 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
A6BA .flip_set_station_boot←2← A652 JSR← A709 JSR
LDX #&10 ; X=&10: max 16 station entries
A6BC CLV ; Clear V (no match found yet)
A6BD .loop_search_stn_boot←2← A6C3 BNE← A6CA BEQ
DEX ; Decrement station index
A6BE BMI done_search_boot ; All searched: exit loop
A6C0 JSR match_station_net ; Check if station[X] matches
A6C3 BNE loop_search_stn_boot ; No match: try next station
A6C5 LDA hazel_fcb_status,x ; Load station flags byte
A6C8 AND #&10 ; Test bit 4 (active flag)
A6CA BEQ loop_search_stn_boot ; Not active: try next station
A6CC TYA ; Transfer boot type to A
A6CD STA hazel_fcb_slot_attr,x ; Store boot setting for station
A6D0 BIT always_set_v_byte ; Set V flag (station match found)
A6D3 .done_search_boot←1← A6BE BMI
STY hazel_fs_prefix_stn ; Store boot type
A6D6 BVS set_flags_boot ; V set (matched): skip allocation
A6D8 TYA ; Boot type to A
A6D9 JSR alloc_fcb_slot ; Allocate FCB slot for new entry
A6DC STA hazel_fcb_slot_3 ; Store allocation result
A6DF BEQ jmp_restore_fs_ctx ; Zero: allocation failed, exit
A6E1 .set_flags_boot←1← A6D6 BVS
LDA #&32 ; A=&32: station flags (active+boot)
A6E3 .store_stn_flags_restore←2← A681 BNE← A6AC BNE
STA hazel_fcb_status,x ; Store station flags
A6E6 .jmp_restore_fs_ctx←3← A67D BEQ← A6A8 BEQ← A6DF 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 &A730, 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.

A6E9 .fsreply_1_boot
JSR close_all_net_chans ; Close all network channels
A6EC LDA #&40 ; A=&40: bit-6 mask for fs_flags (boot-pending flag)
A6EE TSB fs_flags ; Set boot-pending bit on fs_flags (TSB = test-and-set)
A6F1 SEC ; C=1: signal boot-pending to fsreply_2_copy_handles (its BCS at &A6F9 takes the boot path)
A6F2 LDA hazel_txcb_result ; Load boot-type byte from FS reply (hazel_txcb_result)
A6F5 STA hazel_fs_flags ; Store boot type as hazel_fs_flags (consumed later by boot_select_cmd)
A6F8 PHA ; Push boot-type byte (popped later by boot_persist_fs_maybe at &A730)
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 &A6F9 is not taken and the routine exits via JMP return_with_last_flag without ever reaching the PLA at &A730. 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 &A730 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)
A6F9 .fsreply_2_copy_handles
PHP ; Save processor status
A6FA LDY hazel_txcb_data ; Load station number from reply
A6FD JSR find_station_bit2 ; Find station entry with bit 2
A700 LDY hazel_txcb_flag ; Load network number from reply
A703 JSR find_station_bit3 ; Find station entry with bit 3
A706 LDY hazel_txcb_count ; Load boot type from reply
A709 JSR flip_set_station_boot ; Set boot config for station
A70C PLP ; Restore processor status
A70D BCS boot_try_findlib ; Carry set: proceed with boot
A70F 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.

A712 .findlib_oscli_cmd
EQUS "-Net-FindLib"
A71E 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).

A71F .boot_try_findlib←1← A70D BCS
LDX #&11 ; X=&11: CMOS RAM byte index
A721 JSR osbyte_a1 ; Read CMOS &11 via osbyte_a1
A724 TYA ; Result to A
A725 AND #2 ; Mask bit 1 (auto-CLI flag)
A727 BEQ boot_persist_fs_maybe ; Bit clear: skip auto-CLI
A729 LDX #<(findlib_oscli_cmd) ; -NET-FindLib command pointer (low)
A72B LDY #>(findlib_oscli_cmd) ; -NET-FindLib command pointer (high)
A72D JSR oscli ; OSCLI '-NET-FindLib': dispatch to NFS via FSCV,3 (bypass service-4 broadcast)
A730 .boot_persist_fs_maybe←1← A727 BEQ
PLA ; Pop saved A
A731 CMP #2 ; Compare with 2
A733 BCC check_auto_boot_flag ; Below: skip making FS permanent
A735 LDA #osbyte_make_fs_permanent ; Boot type >= 2 (NFS-resident !Boot): A=&6D to commit NFS as default FS
A737 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.

A73A .check_auto_boot_flag←1← A733 BCC
LDA hazel_fs_lib_flags ; Load config flags
A73D TAX ; Save copy in X
A73E AND #4 ; Test bit 2 (auto-boot flag)
A740 PHP ; Save bit 2 test result
A741 TXA ; Restore full flags
A742 AND #&fb ; Clear bit 2 (consume flag)
A744 STA hazel_fs_lib_flags ; Store cleared flags
A747 PLP ; Restore bit 2 test result
A748 BNE boot_select_cmd ; Auto-boot flag was set: skip CTRL check, dispatch boot via boot_select_cmd
A74A LDA #osbyte_scan_keyboard ; OSBYTE &79: scan keyboard
A74C LDX #(255 - inkey_key_ctrl) EOR 128 ; X = CTRL key scan code (negative-X INKEY form for OSBYTE &79)
A74E JSR osbyte ; Test for ctrl key pressed
A751 TXA ; Copy X to A
A752 BPL boot_select_cmd ; CTRL not pressed: proceed to boot
A754 .boot_cancel_rts←1← A776 BEQ
RTS ; Cancel boot, return (CTRL held, or boot type 0 via BEQ at &A762)
A755 .boot_cmd_load_str
EQUS "L.-Net" ; Boot cmd '*LOAD -NET-!Boot' (load !Boot via NFS, bypassing service-4 broadcast — see boot_try_findlib)
A75B .boot_cmd_lo_table
EQUS "-!Boot"
A761 EQUB &0D ; CR terminator
A762 .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)
A76E EQUB &0D
A76F .la76f←1Used as index base by← A778 LDX
EQUS "nU\b"

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 &A748 (auto-boot flag was set, skip CTRL check) and the BPL at &A73E (CTRL not pressed, proceed to boot).

A773 .boot_select_cmd←2← A748 BNE← A752 BPL
LDY hazel_fs_flags ; Y = boot-type byte from FS reply (0..3)
A776 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)
A778 .boot_cmd_oscli←1← A5E8 JMP
LDX la76f,y ; Load boot-command low byte from boot_cmd_lo_table[Y]
A77B LDY #&a7 ; Y=&A7: high byte (boot strings live in &A7xx)
A77D 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.

A780 .cmd_table_fs←11Used as index base by← 8BFD LDA← 8C0C LDA← 8C14 LDA← 8C21 LDA← 946A LDA← 9472 LDA← 95ED LDA← A474 LDA← A479 LDA← A489 LDA← A4C0 LDA
EQUS "Net" ; Econet HW check + select NFS
A783 EQUB &80 ; no syn
A784 EQUW cmd_net_check_hw - 1
A786 EQUS "Pollps" ; syn 8: (<stn. id.>|<ps type>)
A78C EQUB &8C ; syn &8
A78D EQUW cmd_pollps - 1
A78F EQUS "Prot" ; toggle CMOS protection bit
A793 EQUB &80 ; no syn
A794 EQUW cmd_prot - 1
A796 EQUS "PS" ; syn 8: (<stn. id.>|<ps type>)
A798 EQUB &88 ; syn &8
A799 EQUW cmd_ps - 1
A79B EQUS "Roff" ; printer offline
A79F EQUB &80 ; no syn
A7A0 EQUW cmd_roff - 1
A7A2 EQUS "Unprot" ; toggle CMOS protection bit
A7A8 EQUB &80 ; no syn
A7A9 EQUW cmd_unprot - 1
A7AB EQUS "Wdump" ; syn 4 -- *DUMP alias
A7B0 EQUB &C4 ; syn &4, V if no arg
A7B1 EQUW cmd_dump - 1
A7B3 EQUB &80 ; Sub-table 1 end (walker reads &80 -> stop)
A7B4 EQUB &80 ; Padding (alignment before sub-table 2)
A7B5 .cmd_table_nfs
EQUS "Access" ; syn 9: <obj> (L)(W)(R)...
A7BB EQUB &C9 ; syn &9, V if no arg
A7BC EQUW cmd_fs_operation - 1
A7BE EQUS "Bye" ; log off FS
A7C1 EQUB &80 ; no syn
A7C2 EQUW cmd_bye - 1
A7C4 EQUS "Cdir" ; syn 6 -- create directory
A7C8 EQUB &C6 ; syn &6, V if no arg
A7C9 EQUW cmd_cdir - 1
A7CB EQUS "Dir" ; syn 1: (<dir>)
A7CE EQUB &81 ; syn &1
A7CF EQUW cmd_dir - 1
A7D1 EQUS "Flip" ; swap fs/private workspace
A7D5 EQUB &80 ; no syn
A7D6 EQUW cmd_flip - 1
A7D8 EQUS "FS" ; syn &B -- file-server selection
A7DA EQUB &8B ; syn &B
A7DB EQUW cmd_fs - 1
A7DD .cmd_table_nfs_iam←1Used as index base by← 8DE0 LDA
EQUS "I am" ; syn 2: (<stn>) <user>...
A7E1 EQUB &C2 ; syn &2, V if no arg
A7E2 EQUW cmd_iam_save_ctx - 1
A7E4 EQUS "Lcat" ; syn 1: (<dir>) -- *CAT of library
A7E8 EQUB &81 ; syn &1
A7E9 EQUW cmd_lcat - 1
A7EB EQUS "Lex" ; syn 1: (<dir>) -- *EX of library
A7EE EQUB &81 ; syn &1
A7EF EQUW cmd_lex - 1
A7F1 EQUS "Lib" ; syn 5: <dir> -- set library
A7F4 EQUB &C5 ; syn &5, V if no arg
A7F5 EQUW cmd_fs_operation - 1
A7F7 EQUS "Pass" ; syn 7: <pass> ...
A7FB EQUB &C7 ; syn &7, V if no arg
A7FC EQUW cmd_pass - 1
A7FE EQUS "Rename" ; syn &A: <old> <new>
A804 EQUB &CA ; syn &A, V if no arg
A805 EQUW cmd_rename - 1
A807 EQUS "Wipe" ; syn 1: (<dir>) -- delete with confirm
A80B EQUB &81 ; syn &1
A80C EQUW cmd_wipe - 1
A80E EQUB &80, &44, ; &80 sub-table separator; the &8E44 word is &8E, &4F, ; the FS-command sub-table default handler &6E, &80, ; (&8E45-1); the following &4F &6E &80 &00 &00, &00 ; &00 (ASCII 'On' + markers) is a 5-byte ; record new in this build
A816 EQUS "Net" ; *HELP NET
A819 EQUB &80 ; no syn
A81A EQUW help_net - 1
A81C EQUS "Utils" ; *HELP UTILS
A821 EQUB &80 ; no syn
A822 EQUW help_utils - 1
A824 EQUB &80 ; Sub-table 3 end (walker reads &80 -> stop)
A825 .cmd_table_syntax_help
EQUS "FS" ; FS not selected
A827 EQUB &C1 ; syn &1, V if no arg
A828 EQUW set_fs_or_ps_cmos_station - 1
A82A EQUS "PS" ; PS not selected
A82C EQUB &C3 ; syn &3, V if no arg
A82D EQUW set_fs_or_ps_cmos_station - 1
A82F EQUS "NoSpace" ; caller &9623
A836 EQUB &80 ; no syn
A837 EQUW &9621 ; NoSpace dispatch target (&9621)
A839 EQUS "Space" ; caller &9619
A83E EQUB &80 ; no syn
A83F EQUW &9617 ; Space dispatch target (&9617)
A841 EQUB &80 ; Sub-tables 4/5 separator
A842 EQUS "FS" ; caller &9670
A844 EQUB &81 ; syn &1
A845 EQUW print_fs_address - 1
A847 EQUS "PS" ; caller &965F
A849 EQUB &83 ; syn &3
A84A EQUW print_ps_address - 1
A84C EQUS "Space" ; caller &9641
A851 EQUB &80 ; no syn
A852 EQUW &9640 ; Space dispatch target (&9640)

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
A854 .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
A856 EQUB &A5, ; OSWORD setup state (13 bytes -- constants and &EF ; offsets used by svc_8_osword)
A858 EQUB &E9, &0D ; A = OSWORD - &0E (CLC+SBC = -&0E)
A85A EQUB &30, &2D ; Below &0E: not ours, return
A85C EQUB &C9, &07 ; Index >= 7? (OSWORD > &14)
A85E EQUB &B0, &29 ; Above &14: not ours, return
A860 EQUB &AA ; X=OSWORD handler index (0-6)
A861 EQUB &A0, &06 ; Y=6: save 6 workspace bytes
A863 .loop_save_osword_ws←1← A86E BNE
LDA svc_state,y ; Read svc_state[Y] (frame slot)
A866 PHA ; Save on stack
A867 LDA tx_imm_idx_base,y ; Load OSWORD parameter byte
A86A STA svc_state,y ; Copy parameter to workspace
A86D DEY ; Next slot
A86E .osword_store_svc_state←1← A854 BRA
BNE loop_save_osword_ws ; Loop until Y wraps
A870 JSR osword_setup_handler ; Set up dispatch and save state
A873 LDY #&fa ; Y=&FA: restore 6 workspace bytes
A875 .loop_restore_osword_ws←1← A87A BNE
PLA ; Restore saved workspace byte
A876 STA nmi_buf_idx_base,y ; Store to osword_flag workspace
A879 INY ; Next byte
A87A BNE loop_restore_osword_ws ; Loop until all 6 restored
A87C 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)
A87D .osword_setup_handler←1← A870 JSR
LDA la891,x ; X = OSWORD index (0-6)
A880 PHA ; Push for stack frame manipulation
A881 LDA la88a,x ; Load handler address low byte
A884 PHA ; Push again
A885 LDA (ws_ptr_hi),y ; Copy 3 bytes (Y=2,1,0)
A887 STY svc_state ; Load from osword_flag workspace
A889 .rts_osword_setup
RTS ; RTS dispatches to pushed handler
A88A .la88a←1Used as index base by← A881 LDA
EQUB &97, &88, &2F, &4C, &A4, &B9, &66, &A8, &A8, &A9, &A9, &A9, &A9
A897 EQUB &AC ; Load template source pointer
A898 EQUS "H s"
A89B EQUB &8B, &68, &3A, &F0, &10, &C9, &03, &F0, &0C, &1A
A8A5 EQUB &F0, &60 ; Equal: take save_txcb_and_convert path
A8A7 EQUB &C9, &03 ; Restore A (OSWORD sub-code)
A8A9 EQUB &F0, &5C ; Equal: take save_txcb_done path
A8AB EQUB &A9, &08 ; Other sub-codes: set state = 8
A8AD EQUB &85, &A9 ; Store service state
A8AF .rts_osword_0e
EQUB &60 ; Return
A8B0 .save_txcb_and_convert
EQUB &A2, &00 ; X=0: start of TX control block
A8B2 EQUB &A0, &10 ; Y=&10: length of TXCB to save
A8B4 EQUB &20, &88, &97 ; Save current TX control block
A8B7 EQUB &AD, &09, &C1 ; Load seconds from clock workspace
A8BA EQUB &20, &21, &A9 ; Convert binary to BCD
A8BD EQUB &8D, &0B, &C1 ; Store BCD seconds
A8C0 EQUB &AD, &08, &C1 ; Load minutes from clock workspace
A8C3 EQUB &20, &21, &A9 ; Convert binary to BCD
A8C6 EQUB &8D, &0A, &C1 ; Store BCD minutes
A8C9 EQUB &AD, &07, &C1 ; Load hours from clock workspace
A8CC EQUB &20, &21, &A9 ; Convert binary to BCD
A8CF EQUB &8D, &09, &C1 ; Store BCD hours
A8D2 EQUB &A9, &00 ; Clear hours high position
A8D4 EQUB &8D, &08, &C1 ; Store zero
A8D7 EQUB &AD, &06, &C1 ; Load day+month byte
A8DA EQUB &48 ; Save for later high nibble extract
A8DB EQUB &AD, &05, &C1 ; Load day value
A8DE EQUB &20, &21, &A9 ; Convert day to BCD
A8E1 EQUB &8D, &07, &C1 ; Store BCD day
A8E4 EQUB &68 ; Restore day+month byte
A8E5 EQUB &48 ; Push current A
A8E6 EQUB &29, &0F ; Mask low nibble (month low bits)
A8E8 EQUB &20, &21, &A9 ; Convert to BCD
A8EB EQUB &8D, &06, &C1 ; Store BCD month
A8EE EQUB &68 ; Pop saved value
A8EF EQUB &4A ; Shift high nibble down
A8F0 EQUB &4A ; Divide by 4
A8F1 EQUB &4A ; (continued)
A8F2 EQUB &4A, &18 ; 4th shift: isolate high nibble
A8F4 EQUB &69, &51 ; Add &51 (offset base)
A8F6 EQUB &20, &21, &A9 ; Convert year to BCD
A8F9 EQUB &8D, &05, &C1 ; Store BCD year
A8FC EQUB &A0, &06 ; Copy 7 bytes (Y=6 down to 0)
A8FE .loop_copy_bcd_to_pb
EQUB &B9, &05, &C1 ; Load BCD byte from workspace
A901 EQUB &91, &AC ; Store to parameter block
A903 EQUB &88 ; Next byte down
A904 EQUB &10, &F8 ; Loop for all 7 bytes
A906 EQUB &60 ; Return
A907 .save_txcb_done
EQUB &20, &B0, &A8 ; Convert TXCB date/time bytes to BCD
A90A EQUB &A0, &07 ; Y=7: copy 8 bytes (Y=7 down to 0)
A90C .loop_copy_pbytes_to_ws
EQUB &B9, &04, ; Load BCD byte from TXCB area &C1 ; (hazel_txcb_lib + Y)
A90F EQUB &91, &AC ; Store to PB[Y]
A911 EQUB &88 ; Decrement Y (advance backwards)
A912 EQUB &D0, &F8 ; Loop until Y wraps
A914 EQUB &A9, ; A=2: PB[0] parameter for OSWORD &0E &02 ; (seconds-since-midnight format)
A916 EQUB &91, &AC ; Store parameter at PB[0]
A918 EQUB &A9, &0E ; A=&0E: OSWORD &0E (read CMOS RTC)
A91A EQUB &A6, &AC ; X = PB pointer low
A91C EQUB &A4, &AD, &4C, ; Y = PB pointer high (via table_idx &F1, &FF ; scratch)

Convert binary byte to BCD

Uses decimal mode (SED) with a count-up loop: starts at BCD 0 and adds 1 in decimal mode for each decrement of the binary input. Saves and restores the processor flags to avoid leaving decimal mode active. Called 6 times by save_txcb_and_convert for clock date/time conversion.

On EntryAbinary value (0-99)
On ExitABCD equivalent
A921 .bin_to_bcd
PHP ; Save caller flags (D may be in any state)
A922 TAX ; Save A across decimal-mode arithmetic
A923 BEQ done_bcd_convert ; Zero: result is 0, skip loop
A925 SED ; Enter decimal mode
A926 LDA #0 ; Start BCD result at 0
A928 .loop_bcd_add←1← A92C BNE
CLC ; Clear carry for BCD add
A929 ADC #1 ; Add 1 in decimal mode
A92B DEX ; Count down binary value
A92C BNE loop_bcd_add ; Loop until zero
A92E .done_bcd_convert←1← A923 BEQ
PLP ; Restore caller flags (incl. D)
A92F RTS ; Return with BCD result in A

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)
A930 .osword_10_handler
ASL tx_complete_flag ; ASL tx_complete_flag: old bit 7 -> C
A933 TYA ; A = Y (saved index)
A934 BCS setup_ws_rx_ptrs ; C=1 (TX idle): start new transmission
A936 STA (ws_ptr_hi),y ; C=0 (TX busy): write status byte back to PB
A938 RTS ; Return (TX still in progress)
A939 .setup_ws_rx_ptrs←1← A934 BCS
LDA net_rx_ptr_hi ; Read net_rx_ptr_hi
A93B STA ws_ptr_lo ; Copy to ws_ptr_lo
A93D STA nmi_tx_block_hi ; Also set as NMI TX block high
A93F LDA #&6f ; Low byte = &6F
A941 STA osword_flag ; Set osword_flag
A943 STA nmi_tx_block ; Set NMI TX block low
A945 LDX #&0f ; X=&0F: byte count for copy
A947 JSR copy_pb_byte_to_ws ; Copy data and begin transmission
A94A 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)
A94D .osword_11_handler
LDX nfs_workspace_hi ; Load NFS workspace page high byte
A94F STX ws_ptr_lo ; Set workspace pointer high
A951 STY osword_flag ; Set workspace pointer low from Y
A953 ROR econet_flags ; Rotate Econet flags (save interrupt state)
A956 TAY ; Y=OSWORD flag (slot specifier)
A957 STA work_ae ; Store OSWORD flag
A959 BNE use_specified_slot ; Non-zero: use specified slot
A95B LDA #3 ; A=3: start searching from slot 3
A95D .loop_find_rx_slot←1← A96F BNE
JSR byte_to_2bit_index ; Convert slot to 2-bit workspace index
A960 BCS store_rx_result ; C set: slot invalid, store result
A962 LSR ; Divide by 2
A963 LSR ; Continue shift
A964 TAX ; Index to X
A965 LDA (osword_flag),y ; Load workspace byte at offset
A967 BEQ store_rx_result ; Zero: slot empty, store result
A969 CMP #'?' ; Compare with &3F ('?' marker)
A96B BEQ store_rx_slot_found ; Match: slot found for receive
A96D INX ; Step to next slot
A96E TXA ; Transfer back to A
A96F BNE loop_find_rx_slot ; Loop back (A != 0)
A971 .store_rx_slot_found←1← A96B BEQ
TXA ; Found slot index
A972 LDX #0 ; X=0: index for indirect store
A974 STA (ws_ptr_hi,x) ; Store slot number to PB byte 0
A976 .use_specified_slot←1← A959 BNE
JSR byte_to_2bit_index ; Convert specified slot to workspace index
A979 BCS store_rx_result ; C set: slot invalid, store result
A97B DEY ; Back up scan
A97C STY osword_flag ; Update workspace pointer low
A97E LDA #&c0 ; A=&C0: slot active marker
A980 LDY #1 ; Y=1: result-byte offset
A982 LDX #&0b ; X=&0B: byte count for PB copy
A984 CPY work_ae ; Compare Y with OSWORD flag
A986 ADC (osword_flag),y ; Add workspace byte (check slot state)
A988 BEQ copy_pb_and_mark ; Zero: slot ready, copy PB and mark
A98A BMI increment_and_retry ; Negative: slot busy, increment and retry
A98C .loop_copy_slot_data←1← A99C BNE
CLC ; For the ADC chain
A98D .copy_pb_and_mark←1← A988 BEQ
JSR copy_pb_byte_to_ws ; Copy PB byte to workspace slot
A990 BCS osword_11_done ; C set: copy done, finish
A992 LDA #'?' ; A=&3F: mark slot as pending ('?')
A994 LDY #1 ; Y=1: workspace flag offset
A996 STA (osword_flag),y ; Store pending marker to workspace
A998 BNE osword_11_done ; Branch to OSWORD-11 done
A99A .increment_and_retry←1← A98A BMI
ADC #1 ; Increment retry counter
A99C BNE loop_copy_slot_data ; Non-zero: retry copy loop
A99E DEY ; Decrement Y (adjust offset)
A99F .store_rx_result←3← A960 BCS← A967 BEQ← A979 BCS
STA (ws_ptr_hi),y ; Store result A to PB via Y
A9A1 .osword_11_done←2← A990 BCS← A998 BNE
ROL econet_flags ; Rotate Econet flags back (restore state)
A9A4 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)
A9A5 .osword_12_handler
LDA net_rx_ptr_hi ; Set workspace from RX ptr high
A9A7 STA ws_ptr_lo ; Store to ws_ptr_lo
A9A9 LDY #&7f ; Y=&7F: last byte of RX buffer
A9AB LDA (net_rx_ptr),y ; Load port/count from RX buffer
A9AD INY ; Y=&80: set workspace pointer
A9AE STY osword_flag ; Store as osword_flag
A9B0 TAX ; X = port/count value
A9B1 DEX ; X-1: adjust count
A9B2 LDY #0 ; Y=0 for copy
A9B4 JSR copy_pb_byte_to_ws ; Copy workspace data
A9B7 JMP commit_state_byte ; Update state and return
A9BA .osword_13_dispatch
TAX ; X = sub-code
A9BB CMP #&13 ; Sub-code < &13?
A9BD BCS rts_osword_13 ; Out of range: return
A9BF LDA osword_13_dispatch_hi,x ; Read dispatch hi from osword_13_dispatch_hi+X
A9C2 PHA ; Push hi for RTS dispatch
A9C3 LDA osword_13_dispatch_lo,x ; Read dispatch lo from osword_13_dispatch_lo+X
A9C6 PHA ; Push lo for RTS dispatch
A9C7 .rts_osword_13←1← A9BD BCS
RTS ; RTS -> dispatched OSWORD &13 sub-handler

OSWORD &13 dispatch low-byte table (18 entries)

Low-byte half of the OSWORD &13 sub-reason PHA/PHA/RTS dispatch, read as LDA &A9C8,X; paired with osword_13_dispatch_hi.

A9C8 .osword_13_dispatch_lo←1Used as index base by← A9C3 LDA
EQUB <(osword_13_read_station - 1) ; sub &00: osword_13_read_ station (read FS station)
A9C9 EQUB <(osword_13_set_station - 1) ; sub &01: osword_13_set_ station (set FS station)
A9CA EQUB <(osword_13_read_ws_pair - 1) ; sub &02: osword_13_read_ws_ pair (read workspace pair)
A9CB EQUB <(osword_13_write_ws_pair - 1) ; sub &03: osword_13_write_ws_ pair (write workspace pair)
A9CC EQUB <(osword_13_read_prot - 1) ; sub &04: osword_13_read_prot (read protection mask)
A9CD EQUB <(osword_13_write_prot - 1) ; sub &05: osword_13_write_prot (write protection mask)
A9CE EQUB <(osword_13_read_handles - 1) ; sub &06: osword_13_read_ handles (read transfer handles)
A9CF EQUB <(osword_13_set_handles - 1) ; sub &07: osword_13_set_ handles (set transfer handles)
A9D0 EQUB <(osword_13_read_rx_flag - 1) ; sub &08: osword_13_read_rx_ flag (read RX flag)
A9D1 EQUB <(osword_13_read_rx_port - 1) ; sub &09: osword_13_read_rx_ port (read RX port)
A9D2 EQUB <(osword_13_read_error - 1) ; sub &0A: osword_13_read_error (read last error)
A9D3 EQUB <(osword_13_read_context - 1) ; sub &0B: osword_13_read_ context (read context)
A9D4 EQUB <(osword_13_read_csd - 1) ; sub &0C: osword_13_read_csd (read CSD)
A9D5 EQUB <(osword_13_write_csd - 1) ; sub &0D: osword_13_write_csd (write CSD)
A9D6 EQUB <(osword_13_read_free_bufs - 1) ; sub &0E: osword_13_read_free_ bufs (read free buffers)
A9D7 EQUB <(osword_13_read_ctx_3 - 1) ; sub &0F: osword_13_read_ctx_3 (read context byte 3)
A9D8 EQUB <(osword_13_write_ctx_3 - 1) ; sub &10: osword_13_write_ctx_ 3 (write context byte 3)
A9D9 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).

A9DA .osword_13_dispatch_hi←1Used as index base by← A9BF LDA
EQUB >(osword_13_read_station - 1) ; sub &00: osword_13_read_ station
A9DB EQUB >(osword_13_set_station - 1) ; sub &01: osword_13_set_ station
A9DC EQUB >(osword_13_read_ws_pair - 1) ; sub &02: osword_13_read_ws_ pair
A9DD EQUB >(osword_13_write_ws_pair - 1) ; sub &03: osword_13_write_ws_ pair
A9DE EQUB >(osword_13_read_prot - 1) ; sub &04: osword_13_read_prot
A9DF EQUB >(osword_13_write_prot - 1) ; sub &05: osword_13_write_prot
A9E0 EQUB >(osword_13_read_handles - 1) ; sub &06: osword_13_read_ handles
A9E1 EQUB >(osword_13_set_handles - 1) ; sub &07: osword_13_set_ handles
A9E2 EQUB >(osword_13_read_rx_flag - 1) ; sub &08: osword_13_read_rx_ flag
A9E3 EQUB >(osword_13_read_rx_port - 1) ; sub &09: osword_13_read_rx_ port
A9E4 EQUB >(osword_13_read_error - 1) ; sub &0A: osword_13_read_error
A9E5 EQUB >(osword_13_read_context - 1) ; sub &0B: osword_13_read_ context
A9E6 EQUB >(osword_13_read_csd - 1) ; sub &0C: osword_13_read_csd
A9E7 EQUB >(osword_13_write_csd - 1) ; sub &0D: osword_13_write_csd
A9E8 EQUB >(osword_13_read_free_bufs - 1) ; sub &0E: osword_13_read_free_ bufs
A9E9 EQUB >(osword_13_read_ctx_3 - 1) ; sub &0F: osword_13_read_ctx_3
A9EA EQUB >(osword_13_write_ctx_3 - 1) ; sub &10: osword_13_write_ctx_ 3
A9EB 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.

A9EC .osword_13_read_station
JSR ensure_fs_selected ; Ensure NFS is currently the selected FS
A9EF .read_station_bytes
LDY #2 ; Y=2: copy 2 bytes
A9F1 .loop_copy_station←1← A9F7 BNE
LDA hazel_minus_1,y ; Load station byte
A9F4 STA (osword_pb_ptr),y ; Store to PB[Y]
A9F6 DEY ; Step back
A9F7 BNE loop_copy_station ; Loop for bytes 2..1
A9F9 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 &A9DA 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.

A9FA .osword_13_set_station
JSR ensure_fs_selected ; Ensure NFS is currently the selected FS
A9FD .osword_13_set_station_body
LDY #0 ; Y=0 for process_all_fcbs
A9FF JSR process_all_fcbs ; Close all open FCBs
AA02 LDY #2 ; Y=2: copy 2 bytes
AA04 .loop_store_station←1← AA0A BNE
LDA (osword_pb_ptr),y ; Load new station byte from PB
AA06 STA hazel_minus_1,y ; Store to fs_server_base
AA09 DEY ; Step back to previous byte
AA0A BNE loop_store_station ; Loop for bytes 2..1
AA0C JSR clear_if_station_match ; Clear handles if station matches
AA0F LDA #&0e ; A=&0E: bits 1..3 (FS-state mask)
AA11 TSB fs_flags ; Set fs_flags bits 1..3
AA14 LDA #&40 ; A=&40: FS-active flag bit
AA16 TRB fs_flags ; Clear FS-active flag (bit 6)
AA19 LDX #&0f ; X=&0F: scan all 16 FCB slots (X = 15 down to 0)
AA1B .scan_fcb_entry←1← AA83 BPL
LDA hazel_fcb_status,x ; Load FCB flags
AA1E TAY ; Save flags in Y
AA1F AND #2 ; Test bit 1 (FCB allocated?)
AA21 BEQ next_fcb_entry ; No: skip to next entry
AA23 TYA ; Entry index to A
AA24 AND #&df ; Mask bit 5
AA26 STA hazel_fcb_status,x ; Store updated flags
AA29 TAY ; Save in Y
AA2A JSR match_station_net ; Does FCB match new station?
AA2D BNE next_fcb_entry ; No match: skip to next
AA2F CLC ; Clear carry
AA30 TYA ; Restore flags
AA31 AND #4 ; Test bit 2 (handle 1 active?)
AA33 BEQ check_handle_2 ; No: check handle 2
AA35 TYA ; Restore flags
AA36 ORA #&20 ; Set bit 5 (handle reassigned)
AA38 TAY ; Copy A to Y
AA39 LDA hazel_fcb_slot_attr,x ; Get FCB high byte
AA3C STA hazel_fs_saved_station ; Store as handle 1 station
AA3F TXA ; FCB index
AA40 ADC #&20 ; Add &20 for FCB table offset
AA42 STA hazel_fcb_slot_1 ; Store as handle 1 FCB index
AA45 LDA #2 ; A=2: fs_flags bit 1 mask
AA47 TRB fs_flags ; Clear fs_flags bit 1
AA4A .check_handle_2←1← AA33 BEQ
TYA ; Y still holds the saved FCB status -- TYA so we can re-test bit 3 (handle-2 active flag)
AA4B AND #8 ; Test bit 3 (handle 2 active?)
AA4D BEQ check_handle_3 ; No: check handle 3
AA4F TYA ; Copy Y to A
AA50 ORA #&20 ; Set bit 5
AA52 TAY ; Copy A to Y
AA53 LDA hazel_fcb_slot_attr,x ; Get FCB high byte
AA56 STA hazel_fs_context_copy ; Store as handle 2 station
AA59 TXA ; FCB index
AA5A ADC #&20 ; Add &20 for FCB table offset
AA5C STA hazel_fcb_slot_2 ; Store as handle 2 FCB index
AA5F LDA #4 ; A=4: fs_flags bit 2 mask
AA61 TRB fs_flags ; Clear fs_flags bit 2
AA64 .check_handle_3←1← AA4D BEQ
TYA ; Y still holds the saved FCB status -- TYA so we can re-test bit 4 (handle-3 active flag)
AA65 AND #&10 ; Test bit 4 (handle 3 active?)
AA67 BEQ store_updated_status ; No: store final flags
AA69 TYA ; Restore flags
AA6A ORA #&20 ; Set bit 5
AA6C TAY ; Save updated flags
AA6D LDA hazel_fcb_slot_attr,x ; Get FCB high byte
AA70 STA hazel_fs_prefix_stn ; Store as handle 3 station
AA73 TXA ; FCB index
AA74 ADC #&20 ; Add &20 for FCB table offset
AA76 STA hazel_fcb_slot_3 ; Store as handle 3 FCB index
AA79 LDA #8 ; A=8: fs_flags bit 3 (FS-error pending)
AA7B TRB fs_flags ; Clear FS-error-pending flag
AA7E .store_updated_status←1← AA67 BEQ
TYA ; A = Y for store
AA7F STA hazel_fcb_status,x ; Store updated status into hazel_fcb_status[X]
AA82 .next_fcb_entry←2← AA21 BEQ← AA2D BNE
DEX ; Decrement entry counter
AA83 BPL scan_fcb_entry ; Loop while X >= 0 (scan all FCBs)
AA85 LDA #&0e ; A=&0E: status flag value
AA87 BIT fs_flags ; Test fs_flags bits 1..3
AA8A BNE return_4 ; Non-zero: skip the FS-active set
AA8C LDA #&40 ; A=&40: FS-active flag bit
AA8E TSB fs_flags ; Set FS-active flag (bit 6 of fs_flags)
AA91 .return_4←1← AA8A 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.

AA92 .osword_13_read_csd
CLC ; WS-to-PB direction (read)
AA93 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.

AA95 .osword_13_write_csd
SEC ; C=1: PB-to-workspace direction
AA96 .setup_csd_copy←1← AA93 BCC
LDA #&17 ; Workspace offset &17
AA98 STA osword_flag ; Save A as osword_flag (counter)
AA9A LDA net_rx_ptr_hi ; Page from RX pointer high byte
AA9C STA ws_ptr_lo ; Set ws_ptr_hi
AA9E LDY #1 ; Y=1: first PB data byte
AAA0 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
AAA2 .copy_pb_byte_to_ws←5← A947 JSR← A98D JSR← A9B4 JSR← AAAE BPL← AABB BCC
BCC copy_ws_byte_to_pb ; C=0: skip PB-to-WS copy
AAA4 LDA (ws_ptr_hi),y ; C=1: load from parameter block
AAA6 STA (osword_flag),y ; Store to workspace
AAA8 .copy_ws_byte_to_pb←1← AAA2 BCC
LDA (osword_flag),y ; Load from workspace
AAAA STA (ws_ptr_hi),y ; Store to parameter block
AAAC INY ; Next byte
AAAD DEX ; Count down
AAAE BPL copy_pb_byte_to_ws ; Loop for all bytes
AAB0 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.

AAB1 .osword_13_read_ws_pair
LDA nfs_workspace_hi ; Load workspace page high byte
AAB3 STA ws_ptr_lo ; Set ws_ptr_hi
AAB5 INY ; Y=1
AAB6 TYA ; A = current byte index
AAB7 STA osword_flag ; Set ws_ptr_lo = 1
AAB9 TAX ; X=1: copy 2 bytes
AABA CLC ; WS-to-PB direction
AABB 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.

AABD .osword_13_write_ws_pair
INY ; Y=1: first PB data byte
AABE LDA (ws_ptr_hi),y ; Load PB[1]
AAC0 INY ; Next byte
AAC1 STA (nfs_workspace),y ; Store to (nfs_workspace)+2
AAC3 LDA (ws_ptr_hi),y ; Load PB[2]
AAC5 INY ; Y=3
AAC6 STA (nfs_workspace),y ; Store to (nfs_workspace)+3
AAC8 JSR init_bridge_poll ; Reinitialise bridge routing
AACB EOR (nfs_workspace),y ; Compare result with workspace
AACD BNE rts_write_ws_pair ; Different: leave unchanged
AACF STA (nfs_workspace),y ; Same: clear workspace byte
AAD1 .rts_write_ws_pair←1← AACD BNE
RTS ; Return

OSWORD &13 sub 4: read protection mask

Returns the current protection mask (prot_status) in PB[1].

AAD2 .osword_13_read_prot
LDA prot_status ; Load protection mask
AAD5 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.

AAD8 .osword_13_write_prot
INY ; Y=1: PB data offset
AAD9 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 &8FA6 (where A is 0 or &FF based on FS-options bit 6) and cmd_prot at &B702 (the *Prot path). A 2-store-and-return convenience to keep both call sites flat.

On EntryAvalue to mirror into both workspace bytes
AADB .set_ws_pair_0d68_0d69←2← 8FC2 JSR← B702 JSR
STA prot_status ; Mirror A into prot_status (ACR-format byte)
AADE STA prot_status_save ; Mirror A into prot_status_save (IER-format byte)
AAE1 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.

AAE2 .osword_13_read_handles
JSR ensure_fs_selected ; Ensure NFS is currently the selected FS
AAE5 LDY #3 ; Y=3: copy 3 bytes
AAE7 .loop_copy_handles←1← AAED BNE
LDA hazel_fs_lib_flags,y ; Load handle byte
AAEA STA (ws_ptr_hi),y ; Store to PB[Y]
AAEC DEY ; Previous byte
AAED BNE loop_copy_handles ; Loop for bytes 3..1
AAEF 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.

AAF0 .osword_13_set_handles
JSR ensure_fs_selected ; Ensure NFS is currently the selected FS
AAF3 .start_set_handles
LDY #1 ; Y=1: first handle in PB
AAF5 .validate_handle←1← AB35 BNE
LDA (ws_ptr_hi),y ; Load handle value from PB[Y]
AAF7 CMP #&20 ; Must be >= &20
AAF9 BCC handle_invalid ; Below range: invalid
AAFB CMP #&30 ; Must be < &30
AAFD BCS handle_invalid ; Above range: invalid
AAFF TAX ; X = handle value
AB00 LDA hazel_fcb_addr_mid,x ; Load fcb_attr_or_count_mid[handle]
AB03 BNE check_handle_alloc ; Non-zero: FCB exists
AB05 .handle_invalid←3← AAF9 BCC← AAFD BCS← AB11 BEQ
LDA #0 ; A=0: invalid-handle marker
AB07 TAX ; Copy A to X
AB08 STA (ws_ptr_hi,x) ; Clear PB[0] status
AB0A BEQ next_handle_slot ; Skip to next handle
AB0C .check_handle_alloc←1← AB03 BNE
LDA hazel_fcb_state_byte,x ; Load fcb_flags[handle] flags
AB0F AND #2 ; Test bit 1 (allocated?)
AB11 BEQ handle_invalid ; Not allocated: invalid
AB13 TXA ; X = handle value
AB14 STA hazel_fs_lib_flags,y ; Store handle to fs_lib_flags+Y
AB17 LDA hazel_fcb_addr_mid,x ; Load station from fcb_attr_or_count_mid
AB1A STA hazel_fs_network,y ; Store station to fs_server_net+Y
AB1D CPY #1 ; Is this handle 1 (Y=1)?
AB1F BNE assign_handle_2 ; No: check handle 2
AB21 TYA ; Save Y for processing
AB22 PHA ; Push Y
AB23 LDY #4 ; Bit mask &04 for handle 1
AB25 JSR update_fcb_flag_bits ; Update flags across all FCBs
AB28 PLA ; Pop saved Y
AB29 TAY ; Back to Y
AB2A LDA hazel_fcb_state_byte,x ; Reload fcb_flags flags
AB2D ORA #&24 ; Set bits 2+5 (active+updated)
AB2F STA hazel_fcb_state_byte,x ; Store updated flags
AB32 .next_handle_slot←3← AB0A BEQ← AB4E BNE← AB61 BNE
INY ; Next handle slot
AB33 CPY #4 ; Compare with 4
AB35 BNE validate_handle ; No: process next handle
AB37 DEY ; Y=3 for return
AB38 RTS ; Return
AB39 .assign_handle_2←1← AB1F BNE
CPY #2 ; Is this handle 2 (Y=2)?
AB3B BNE assign_handle_3 ; No: must be handle 3
AB3D TYA ; Save current Y
AB3E PHA ; Push Y
AB3F LDY #8 ; Y=8 (handle-bit shift index)
AB41 JSR update_fcb_flag_bits ; Update flags across all FCBs
AB44 PLA ; Restore Y
AB45 TAY ; Back to Y
AB46 LDA hazel_fcb_state_byte,x ; Reload fcb_flags flags
AB49 ORA #&28 ; Set bits 3 and 5
AB4B STA hazel_fcb_state_byte,x ; Store updated flags
AB4E BNE next_handle_slot ; Next handle slot
AB50 .assign_handle_3←1← AB3B BNE
TYA ; Handle 3: save Y
AB51 PHA ; Push for save/restore
AB52 LDY #&10 ; Bit mask &10 for handle 3
AB54 JSR update_fcb_flag_bits ; Update flags across all FCBs
AB57 PLA ; Pop saved value
AB58 TAY ; Back to Y
AB59 LDA hazel_fcb_state_byte,x ; Reload fcb_flags flags
AB5C ORA #&30 ; Set bits 4+5 (active+updated)
AB5E STA hazel_fcb_state_byte,x ; Store updated flags
AB61 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)
AB63 .update_fcb_flag_bits←3← AB25 JSR← AB41 JSR← AB54 JSR
TXA ; A = caller X
AB64 PHA ; Push X
AB65 LDX #&0f ; X=&0F: scan all 16 FCB slots
AB67 .loop_scan_fcb_flags←1← AB83 BPL
LDA hazel_fcb_status,x ; Load FCB flags
AB6A ROL ; Shift bits 6-7 into bits 7-0
AB6B ROL ; Shift bit into carry for test
AB6C BPL next_flag_entry ; Bit 6 clear: skip entry
AB6E TYA ; Restore Y (bit mask)
AB6F AND hazel_fcb_status,x ; Test mask bits against flags
AB72 BEQ no_flag_match ; Zero: no matching bits
AB74 TYA ; Matching: restore Y
AB75 ORA #&20 ; Set bit 5 (updated)
AB77 BNE clear_flag_bits ; Skip clear path
AB79 .no_flag_match←1← AB72 BEQ
TYA ; No match: restore Y
AB7A .clear_flag_bits←1← AB77 BNE
EOR #&ff ; Invert all bits
AB7C AND hazel_fcb_status,x ; Clear tested bits in flags
AB7F STA hazel_fcb_status,x ; Store updated flags
AB82 .next_flag_entry←1← AB6C BPL
DEX ; Decrement FCB index
AB83 BPL loop_scan_fcb_flags ; Loop for all 16 entries
AB85 PLA ; Restore original X
AB86 TAX ; Back to X
AB87 RTS ; Return

OSWORD &13 sub 8: read RX control block flag

Returns byte 1 of the current RX control block in PB[1].

AB88 .osword_13_read_rx_flag
LDY #1 ; Y=1: PB[1] = RX flag location
AB8A LDA (net_rx_ptr),y ; Load (net_rx_ptr)+1
AB8C LDY #0 ; Y=0
AB8E 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].

AB91 .osword_13_read_rx_port
LDY #&7f ; Y=&7F: port byte offset
AB93 LDA (net_rx_ptr),y ; Load (net_rx_ptr)+&7F
AB95 LDY #1 ; Y=1
AB97 STA (ws_ptr_hi),y ; Store to PB[1]
AB99 INY ; Advance index
AB9A LDA #&80 ; A=&80
AB9C STA (ws_ptr_hi),y ; Store &80 to PB[2]
AB9E 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.

AB9F .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
ABA2 .store_a_to_pb_1←4← AAD5 JMP← AB8E JMP← ABA9 BPL← ABB1 BCS
INY ; Y=1: parameter block offset 1
ABA3 STA (ws_ptr_hi),y ; Store result to PB[1]
ABA5 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.

ABA6 .osword_13_read_context
LDA hazel_fs_error_code ; Load context byte
ABA9 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.

ABAB .osword_13_read_free_bufs
LDA #&6f ; Total buffers = &6F
ABAD SEC ; PB-to-WS direction (write)
ABAE SBC spool_buf_idx ; Free = &6F - spool_buf_idx
ABB1 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.

ABB3 .osword_13_read_ctx_3←1← ABBB BNE
INY ; Next ctx byte
ABB4 LDA fs_flags,y ; Return
ABB7 STA (ws_ptr_hi),y ; Store to PB[Y]
ABB9 CPY #3 ; Done 3 bytes?
ABBB BNE osword_13_read_ctx_3 ; No: loop
ABBD 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.

ABBE .osword_13_write_ctx_3←1← ABC6 BNE
INY ; Next byte offset
ABBF LDA (ws_ptr_hi),y ; Load PB[Y]
ABC1 STA fs_flags,y ; Store to tx_retry_count[Y]
ABC4 CPY #3 ; Done 3 bytes?
ABC6 BNE osword_13_write_ctx_3 ; No: loop
ABC8 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.

ABC9 .osword_13_bridge_query
JSR init_bridge_poll ; Poll for bridge
ABCC LDY #0 ; Y=0
ABCE LDA bridge_status ; Load bridge status
ABD1 CMP #&ff ; Is it &FF (no bridge)?
ABD3 BNE bridge_found ; No: bridge found
ABD5 TYA ; Copy Y to A
ABD6 STA (ws_ptr_hi),y ; PB[0] = 0 (no bridge)
ABD8 INY ; Advance index
ABD9 BNE store_bridge_station ; Branch to store the bridge station
ABDB .bridge_found←1← ABD3 BNE
INY ; Y=1
ABDC STA (ws_ptr_hi),y ; PB[1] = bridge status
ABDE INY ; Advance Y
ABDF INY ; Y=3
ABE0 LDA (ws_ptr_hi),y ; Load PB[3] (caller value)
ABE2 BEQ use_default_station ; Zero: use default station
ABE4 .compare_bridge_status
EOR bridge_status ; Compare with bridge status
ABE7 BNE rts_bridge_query ; Non-zero: take return path
ABE9 BEQ store_bridge_station ; Same: confirm station
ABEB .use_default_station←1← ABE2 BEQ
LDA hazel_fs_network ; Load default from fs_server_net
ABEE .store_bridge_station←2← ABD9 BNE← ABE9 BEQ
STA (ws_ptr_hi),y ; Store to PB[3]
ABF0 .rts_bridge_query←1← ABE7 BNE
RTS ; Return
ABF1 .bridge_txcb_init_table←1Used as index base by← AC1E LDA
EQUB &82 ; TX 0: ctrl = &82 (immediate mode)
ABF2 EQUB &9C ; TX 1: port = &9C (bridge discovery)
ABF3 EQUB &FF ; TX 2: dest station = &FF (broadcast)
ABF4 EQUB &FF ; TX 3: dest network = &FF (all nets)
ABF5 EQUS "BRIDGE" ; TX 4-9: immediate data payload
ABFB EQUB &9C ; TX init data byte &9C
ABFC EQUB &00 ; TX 11: &00 (terminator)
ABFD .bridge_rxcb_init_data
EQUB &7F ; RX 0: ctrl = &7F (receive)
ABFE EQUB &9C ; RX 1: port = &9C (bridge discovery)
ABFF EQUB &00 ; RX 2: station = &00 (any)
AC00 EQUB &00, &72, &0D ; RX 3: network = &00 (any)
AC03 EQUB &FF ; RX 6: extended addr fill (&FF)
AC04 EQUB &FF, &74 ; RX 7: extended addr fill (&FF)
AC06 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
AC09 .init_bridge_poll←5← 8E39 JSR← 9029 JSR← A3E5 JSR← AAC8 JSR← ABC9 JSR
LDA bridge_status ; Check bridge status
AC0C CMP #&ff ; Is it &FF (uninitialised)?
AC0E BNE rts_bridge_poll ; No: bridge already active, return
AC10 TYA ; Save Y
AC11 PHA ; Preserve Y on stack
AC12 LDY #&18 ; Y=&18: workspace offset for init
AC14 LDX #&0b ; X=&0B: 12 bytes to copy
AC16 ROR econet_flags ; Rotate econet_flags right (save flag)
AC19 .loop_copy_bridge_init←1← AC25 BPL
LDA labe5,y ; Load init data byte
AC1C STA (nfs_workspace),y ; Store to workspace
AC1E LDA bridge_txcb_init_table,x ; Load TXCB template byte
AC21 STA txcb_ctrl,x ; Store to TX control block
AC23 INY ; Next workspace byte
AC24 DEX ; Next template byte
AC25 BPL loop_copy_bridge_init ; Loop for all 12 bytes
AC27 STX bridge_status ; Store X (-1) as bridge counter
AC2A ROL econet_flags ; Restore econet_flags flag
AC2D .loop_wait_ws_status←2← AC30 BCC← AC55 BPL
ASL tx_complete_flag ; Shift ws_0d60 left (check status)
AC30 BCC loop_wait_ws_status ; C=0: status clear, retry
AC32 LDA #&82 ; Control byte &82 for TX
AC34 STA txcb_ctrl ; Set in TX control block
AC36 LDA #&c0 ; Data block at &00C0
AC38 STA nmi_tx_block ; Set NMI TX block low
AC3A LDA #0 ; High byte = 0 (page 0)
AC3C STA nmi_tx_block_hi ; Set NMI TX block high
AC3E JSR tx_setup_from_txcb ; Begin Econet transmission
AC41 .loop_wait_tx_done←1← AC43 BMI
BIT txcb_ctrl ; Test TX control block bit 7
AC43 BMI loop_wait_tx_done ; Negative: TX still in progress
AC45 PHX ; Push X (saved across delay)
AC46 LDA #osbyte_vsync ; A=&13: OSBYTE 'wait for VSYNC'
AC48 JSR osbyte ; Wait for vertical sync
AC4B PLX ; Restore caller's X
AC4C LDY #&18 ; Y=&18: status-byte offset
AC4E LDA (nfs_workspace),y ; Load bridge response
AC50 BMI bridge_responded ; Negative: bridge responded
AC52 JSR inx16 ; Advance retry counter by 8
AC55 BPL loop_wait_ws_status ; Positive: retry poll loop
AC57 .bridge_responded←1← AC50 BMI
LDA #&3f ; Set response to &3F (OK)
AC59 STA (nfs_workspace),y ; Store to workspace
AC5B PLA ; Restore saved Y
AC5C TAY ; Result byte to Y
AC5D LDA bridge_status ; Load bridge status
AC60 TAX ; X = bridge status
AC61 EOR #&ff ; Invert (presence -> absence)
AC63 BEQ rts_bridge_poll ; Status was &FF: return (no bridge)
AC65 TXA ; Return bridge station in A
AC66 .rts_bridge_poll←2← AC0E BNE← AC63 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)
AC67 .osword_14_handler
CMP #1 ; Compare sub-code with 1
AC69 BCS handle_tx_request ; Sub-code >= 1: handle TX request
AC6B PHA ; Save state
AC6C JSR ensure_fs_selected ; Ensure NFS is currently the selected FS
AC6F PLA ; Pop saved A from the stack frame
AC70 LDY #&23 ; Y=&23: workspace offset for params
AC72 JSR mask_owner_access ; Set owner access mask
AC75 .loop_copy_txcb_init←1← AC82 BNE
LDA init_txcb,y ; Load TXCB init byte
AC78 BNE store_txcb_init_byte ; Non-zero: use template value
AC7A LDA hazel_minus_1a,y ; Zero: use workspace default value
AC7D .store_txcb_init_byte←1← AC78 BNE
STA (nfs_workspace),y ; Store to workspace
AC7F DEY ; Next byte down
AC80 CPY #&17 ; Until Y reaches &17
AC82 BNE loop_copy_txcb_init ; Loop for all bytes
AC84 INY ; Next byte
AC85 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.

AC87 .store_osword_pb_ptr
LDY #&1c ; Y=&1C: workspace offset for PB pointer
AC89 LDA ws_ptr_hi ; Load PB page number
AC8B ADC #1 ; PB starts at next page boundary (+1)
AC8D JSR store_ptr_at_ws_y ; Store PB start pointer at ws[&1C]
AC90 LDY #1 ; Y=1: PB byte 1 (transfer length)
AC92 LDA (ws_ptr_hi),y ; Load transfer length from PB
AC94 LDY #&20 ; Y=&20: TXCB offset
AC96 ADC ws_ptr_hi ; Add PB base for buffer end address
AC98 JSR store_ptr_at_ws_y ; Store PB pointer to workspace
AC9B LDY #2 ; Y=2: parameter offset
AC9D LDA #&90 ; Control byte &90
AC9F STA need_release_tube ; Set escapable flag
ACA1 STA (ws_ptr_hi),y ; Store control byte to PB
ACA3 INY ; Advance index
ACA4 INY ; Advance index
ACA5 .loop_copy_ws_to_pb←1← ACAD BNE
LDA hazel_minus_2,y ; Load workspace data
ACA8 STA (ws_ptr_hi),y ; Store to parameter block
ACAA INY ; Next byte
ACAB CPY #7 ; Until Y reaches 7
ACAD BNE loop_copy_ws_to_pb ; Loop for 3 bytes (Y=4,5,6)
ACAF LDA nfs_workspace_hi ; Read nfs_workspace_hi
ACB1 STA net_tx_ptr_hi ; Store to net_tx_ptr_hi
ACB3 JSR enable_irq_and_poll ; Enable interrupts
ACB6 LDY #&20 ; Y=&20: workspace offset
ACB8 LDA #&ff ; Set to &FF (pending)
ACBA STA (nfs_workspace),y ; Mark send pending in workspace
ACBC INY ; Advance index
ACBD STA (nfs_workspace),y ; Also mark offset &21
ACBF LDY #&19 ; Y=&19: control offset
ACC1 LDA #&90 ; Control byte &90
ACC3 STA (nfs_workspace),y ; Store to workspace
ACC5 DEY ; Y=&18: RX control offset
ACC6 LDA #&7f ; Control byte &7F
ACC8 STA (nfs_workspace),y ; Store RX control
ACCA 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
ACCD .store_ptr_at_ws_y←2← AC8D JSR← AC98 JSR
STA (nfs_workspace),y ; Store address low byte at ws[Y]
ACCF INY ; Advance to high byte offset
ACD0 LDA table_idx ; Load high byte base (table_idx)
ACD2 ADC #0 ; Add carry for page crossing
ACD4 STA (nfs_workspace),y ; Store address high byte at ws[Y+1]
ACD6 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
ACD7 .handle_tx_request←1← AC69 BCS
PHP ; Save processor flags
ACD8 LDY #1 ; Y=1: workspace offset
ACDA LDA (ws_ptr_hi),y ; Load station number from PB
ACDC TAX ; X = station number
ACDD INY ; Advance index
ACDE LDA (ws_ptr_hi),y ; Load network number from PB
ACE0 INY ; Y=3: workspace start offset
ACE1 STY osword_flag ; Store Y as ws_ptr_lo
ACE3 LDY #&72 ; Y=&72: workspace offset for dest
ACE5 STA (net_rx_ptr),y ; Store network to workspace
ACE7 DEY ; Y=&71
ACE8 TXA ; A = station (from X)
ACE9 STA (net_rx_ptr),y ; Store station to workspace
ACEB PLP ; Restore flags from PHP
ACEC BNE handle_burst_xfer ; Non-zero sub-code: handle burst
ACEE .loop_send_pb_chars←1← AD0A BNE
LDY osword_flag ; Load current offset
ACF0 INC osword_flag ; Advance offset for next byte
ACF2 LDA (ws_ptr_hi),y ; Load next char from PB
ACF4 BEQ return_5 ; Zero: end of data, return
ACF6 LDY #&7d ; Y=&7D: workspace pointer offset
ACF8 STA (net_rx_ptr),y ; Store char to RX buffer
ACFA PHA ; Save char for later test
ACFB JSR init_ws_copy_wide ; Init workspace copy for wide xfer
ACFE SEC ; Set carry
ACFF ROR need_release_tube ; Set bit 7: Tube needs release
AD01 JSR enable_irq_and_poll ; Enable IRQ and send packet
AD04 .loop_bridge_tx_delay←1← AD05 BNE
DEX ; Delay countdown
AD05 BNE loop_bridge_tx_delay ; Loop while X != 0
AD07 PLA ; Restore char
AD08 EOR #&0d ; Test if char was CR (&0D)
AD0A BNE loop_send_pb_chars ; Loop while not CR
AD0C .return_5←1← ACF4 BEQ
RTS ; CR sent: return

OSWORD &14 burst-transfer path: extend buffer end and TX

Reached from handle_tx_request's BNE at &ACEC. 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)
AD0D .handle_burst_xfer←1← ACEC BNE
JSR init_ws_copy_wide ; Init workspace for wide copy
AD10 LDY #&7b ; Y=&7B: end-byte offset
AD12 LDA (net_rx_ptr),y ; Load buffer size
AD14 ADC #3 ; Add 3 (end-of-buffer adjust)
AD16 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)
AD18 .enable_irq_and_poll←2← ACB3 JSR← AD01 JSR
CLI ; Re-enable IRQs
AD19 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
AD1C .netv_handler
PHP ; Save processor flags
AD1D PHA ; Save A
AD1E TXA ; Save X
AD1F PHA ; Push X
AD20 TYA ; Save Y
AD21 PHA ; Push Y
AD22 TSX ; Get stack pointer
AD23 LDA stack_page_3,x ; Read OSWORD number from stack
AD26 CMP #9 ; OSWORD >= 9?
AD28 BCS restore_regs_return ; Yes: out of range, restore + return
AD2A TAX ; X = OSWORD number
AD2B JSR push_osword_handler_addr ; Push handler address for dispatch
AD2E .restore_regs_return←1← AD28 BCS
PLA ; Restore Y
AD2F TAY ; Back to Y
AD30 PLA ; Restore X
AD31 TAX ; Back to X
AD32 PLA ; Restore A
AD33 PLP ; Restore flags
AD34 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)
AD35 .push_osword_handler_addr←1← AD2B JSR
LDA netv_dispatch_hi,x ; Load handler high byte from hi-table column X
AD38 PHA ; Push for the eventual RTS dispatch
AD39 LDA netv_dispatch_lo,x ; Load handler low byte from lo-table column X
AD3C PHA ; Push lo so RTS pulls (lo, hi)+1 -> handler entry
AD3D LDA osbyte_a_copy ; Reload original OSWORD number into A for the handler
AD3F RTS ; RTS jumps to handler with A=OSWORD number

NETV reason-code dispatch low-byte table (9 entries)

Low-byte half of the NETV reason-code PHA/PHA/RTS dispatch, read as LDA &AD40,X; paired with netv_dispatch_hi.

AD40 .netv_dispatch_lo←1Used as index base by← AD39 LDA
EQUB <(dispatch_rts - 1) ; reason &00: dispatch_rts (no-op (RTS only))
AD41 EQUB <(netv_print_data - 1) ; reason &01: netv_print_data (NETV reason 1: print data)
AD42 EQUB <(netv_print_data - 1) ; reason &02: netv_print_data (NETV reason 2: print data (alias))
AD43 EQUB <(netv_print_data - 1) ; reason &03: netv_print_data (NETV reason 3: print data (alias))
AD44 EQUB <(osword_4_handler - 1) ; reason &04: osword_4_handler (NETV reason 4: OSWORD &04)
AD45 EQUB <(netv_spool_check - 1) ; reason &05: netv_spool_check (NETV reason 5: spool check)
AD46 EQUB <(dispatch_rts - 1) ; reason &06: dispatch_rts (no-op (RTS only))
AD47 EQUB <(netv_claim_release - 1) ; reason &07: netv_claim_release (NETV reason 7: claim/release)
AD48 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).

AD49 .netv_dispatch_hi←1Used as index base by← AD35 LDA
EQUB >(dispatch_rts - 1) ; reason &00: dispatch_rts
AD4A EQUB >(netv_print_data - 1) ; reason &01: netv_print_data
AD4B EQUB >(netv_print_data - 1) ; reason &02: netv_print_data
AD4C EQUB >(netv_print_data - 1) ; reason &03: netv_print_data
AD4D EQUB >(osword_4_handler - 1) ; reason &04: osword_4_handler
AD4E EQUB >(netv_spool_check - 1) ; reason &05: netv_spool_check
AD4F EQUB >(dispatch_rts - 1) ; reason &06: dispatch_rts
AD50 EQUB >(netv_claim_release - 1) ; reason &07: netv_claim_release
AD51 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)
AD52 .osword_4_handler
TSX ; Read the MOS stack frame holding caller flags
AD53 ROR stack_page_6,x ; Shift carry out of caller P (stack[&106+X])
AD56 ASL stack_page_6,x ; Carry is now cleared in caller P
AD59 TYA ; A = original Y
AD5A LDY #&da ; Y=&DA: workspace osword-4 result offset
AD5C STA (nfs_workspace),y ; Store Y at (nfs_workspace)+&DA
AD5E 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)
AD60 .tx_econet_abort←3← 8B23 JSR← ADB3 JSR← AE19 JSR
LDY #&d9 ; Y=&D9: workspace offset for the abort code byte
AD62 STA (nfs_workspace),y ; Store the abort code (passed in A) at workspace[&D9]
AD64 LDA #&80 ; A=&80: control = immediate-operation flag
AD66 LDY #&0c ; Y=&0C: TXCB control-byte offset
AD68 STA (nfs_workspace),y ; Set TXCB[&0C] = &80 (immediate / abort)
AD6A LDA net_tx_ptr ; Save current net_tx_ptr low (we'll repoint TX at the abort packet)
AD6C PHA ; Push it for restore on exit
AD6D LDA net_tx_ptr_hi ; Save net_tx_ptr high too
AD6F PHA ; Push it
AD70 STY net_tx_ptr ; TX low = &0C (abort packet starts at workspace[&0C])
AD72 LDX nfs_workspace_hi ; Get nfs_workspace high byte
AD74 STX net_tx_ptr_hi ; TX high = workspace page (so net_tx_ptr now points at the abort packet in workspace)
AD76 JSR send_net_packet ; Send the abort packet via the standard TX path
AD79 LDA #&3f ; A=&3F: TXCB status = abort-complete sentinel
AD7B STA (net_tx_ptr,x) ; Write status via (net_tx_ptr,X) -- mark TX done
AD7D PLA ; Pull saved net_tx_ptr high
AD7E STA net_tx_ptr_hi ; Restore
AD80 PLA ; Pull saved net_tx_ptr low
AD81 STA net_tx_ptr ; Restore -- caller's TX state intact
AD83 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)
AD84 .netv_claim_release
LDY osword_pb_ptr_hi ; Y = OSWORD parameter-block pointer high byte (used as an 'unrecognised' sentinel below)
AD86 CMP #&81 ; Code &81? (compatibility shortcut for one specific claim type)
AD88 BEQ process_match_result ; Yes: skip table scan, use match-result with Y already set non-zero
AD8A LDY #1 ; Y=1: state 2 marker
AD8C LDX #&0a ; X=&0A: scan first 11 entries (table indices 0..&0A)
AD8E JSR match_rx_code ; Look up A in the claim code table
AD91 BEQ process_match_result ; Match: handle as state 2
AD93 DEY ; DEY: Y=0 (state 3 marker, two DEYs from 1)
AD94 DEY ; Y=-1: flag second range
AD95 LDX #&11 ; X=&11: scan all 18 entries (state 3 also accepts the extended range)
AD97 JSR match_rx_code ; Look up A again with extended range
AD9A BEQ process_match_result ; Match: handle as state 3
AD9C INY ; Y=1 again (no match found, will return below)
AD9D .process_match_result←3← AD88 BEQ← AD91 BEQ← AD9A BEQ
LDX #2 ; X=2: default state code passed to tx_econet_abort
AD9F TYA ; Move match marker (Y) into A for the BEQ test
ADA0 BEQ rts_claim_release ; Y=0 (no match): return without action
ADA2 PHP ; Save flags so we can branch later on Y's sign
ADA3 BPL save_tube_state ; Y > 0 (state 2): skip the X bump
ADA5 INX ; State 3: X=3 (different abort code)
ADA6 .save_tube_state←1← ADA3 BPL
LDY #&dc ; Y=&DC: workspace offset for tube state bytes
ADA8 .loop_save_tube_bytes←1← ADB0 BPL
LDA tube_claimed_id,y ; Read tube_claimed_id,Y
ADAB STA (nfs_workspace),y ; Save in workspace[&DC..]
ADAD DEY ; Step backwards
ADAE CPY #&da ; Done at &DA?
ADB0 BPL loop_save_tube_bytes ; Loop while Y > &DA (saves &DA, &DB, &DC -- 3 bytes)
ADB2 TXA ; Move state code (2 or 3) into A for the abort
ADB3 JSR tx_econet_abort ; Send abort with the state code
ADB6 PLP ; Restore the saved flags (Y's sign)
ADB7 BPL rts_claim_release ; Y was positive (state 2): just return
ADB9 LDA #&7f ; A=&7F: 'pending response' control value
ADBB LDY #&0c ; Y=&0C: TXCB control offset
ADBD STA (nfs_workspace),y ; Mark TXCB as pending
ADBF .loop_poll_ws_status←1← ADC1 BPL
LDA (nfs_workspace),y ; Read TXCB status byte
ADC1 BPL loop_poll_ws_status ; Bit 7 still clear: keep polling for response
ADC3 TSX ; Capture S so we can patch the caller's stack frame
ADC4 LDY #&dd ; Y=&DD: highest workspace offset for the response copy
ADC6 LDA (nfs_workspace),y ; Read first response byte (workspace[&DD])
ADC8 ORA #&44 ; Set bit 6 and bit 2
ADCA BNE store_stack_byte ; Always taken (after ORA result is non-zero); store into stack[&106+X] then walk down
ADCC .loop_restore_stack←1← ADD5 BNE
DEY ; Step Y down
ADCD DEX ; Step X down (stack offset)
ADCE LDA (nfs_workspace),y ; Read next workspace byte
ADD0 .store_stack_byte←1← ADCA BNE
STA stack_page_6,x ; Patch caller's stack frame at &106+X
ADD3 CPY #&da ; Reached &DA (lower workspace bound)?
ADD5 BNE loop_restore_stack ; No: keep restoring
ADD7 .rts_claim_release←2← ADA0 BEQ← ADB7 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
ADD8 .match_rx_code←3← AD8E JSR← AD97 JSR← ADDE BPL
CMP osword_claim_codes,x ; Compare A with table entry at index X
ADDB BEQ rts_match_rx_code ; Match: return with Z set
ADDD DEX ; Step to next earlier table entry
ADDE BPL match_rx_code ; Loop while X >= 0 (table walked top-down)
ADE0 .rts_match_rx_code←2← ADDB BEQ← ADFB 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.

ADE1 .osword_claim_codes←1Used as index base by← ADD8 CMP
EQUB &04 ; Range 1+2: OSWORD &04
ADE2 EQUB &09 ; Range 1+2: OSWORD &09
ADE3 EQUB &0A ; Range 1+2: OSWORD &0A
ADE4 EQUB &14 ; Range 1+2: OSWORD &14
ADE5 EQUB &15 ; Range 1+2: OSWORD &15
ADE6 EQUB &9A ; OSWORD &9A
ADE7 EQUB &9B ; Range 1+2: OSWORD &9B
ADE8 EQUB &E1 ; Range 1+2: OSWORD &E1
ADE9 EQUB &E2 ; OSWORD &E2
ADEA EQUB &E3 ; Range 1+2: OSWORD &E3
ADEB EQUB &E4 ; Range 1+2: OSWORD &E4
ADEC EQUB &0B ; OSWORD &0B
ADED EQUB &0C ; Range 2 only: OSWORD &0C
ADEE EQUB &0F ; Range 2 only: OSWORD &0F
ADEF EQUB &79 ; Range 2 only: OSWORD &79
ADF0 EQUB &7A ; OSWORD &7A
ADF1 EQUB &86 ; OSWORD &86
ADF2 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)
ADF3 .osword_8_handler
LDY #&0e ; Y=&0E: scan 15 bytes (offsets 14..0) of the PB
ADF5 CMP #7 ; Is the OSWORD number 7?
ADF7 BEQ copy_pb_to_ws ; Yes: handle as either 7 or 8 -- both copy PB to ws
ADF9 CMP #8 ; Is the OSWORD number 8?
ADFB BNE rts_match_rx_code ; Neither 7 nor 8: return early (other OSWORDs handled elsewhere)
ADFD .copy_pb_to_ws←1← ADF7 BEQ
LDX #&db ; X=&DB: workspace offset for the PB copy
ADFF STX nfs_workspace ; Temporarily reuse nfs_workspace as the destination low byte (high byte already points at the workspace page)
AE01 .loop_copy_pb_to_ws←1← AE06 BPL
LDA (osword_pb_ptr),y ; Read PB[Y]
AE03 STA (nfs_workspace),y ; Write to (nfs_workspace),Y -- effectively writes to workspace[&DB+Y]
AE05 DEY ; Step backwards through the 15 bytes
AE06 BPL loop_copy_pb_to_ws ; Loop while Y >= 0
AE08 INY ; Bring Y back to 0 for the next single-byte write
AE09 DEC nfs_workspace ; Decrement nfs_workspace low byte: now points at workspace[&DA] (one before the copied region)
AE0B LDA osbyte_a_copy ; Read original OSWORD number from osbyte_a_copy
AE0D STA (nfs_workspace),y ; Store at workspace[&DA] -- so the abort packet header carries the OSWORD number
AE0F STY nfs_workspace ; Restore nfs_workspace to its proper low byte (Y=0)
AE11 LDY #&14 ; Y=&14: TXCB control offset
AE13 LDA #&e9 ; A=&E9: status code for OSWORD-passthrough abort
AE15 STA (nfs_workspace),y ; Store status at TXCB[&14]
AE17 LDA #1 ; A=1: abort code for tx_econet_abort
AE19 JSR tx_econet_abort ; Send the abort packet
AE1C 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)
AE1E .init_ws_copy_wide←2← ACFB JSR← AD0D JSR
LDX #&0d ; X=&0D: 14 template bytes to process
AE20 LDY #&7c ; Y=&7C: workspace destination offset for wide variant
AE22 BIT always_set_v_byte ; BIT &FF unconditionally sets V (the always_set_v_byte trick)
AE25 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
AE27 .init_ws_copy_narrow←1← 9870 JSR
LDY #&17 ; Y=&17: workspace destination offset for narrow variant
AE29 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
AE2B .ws_copy_vclr_entry←1← AEEC JSR
CLV ; Clear V: narrow mode (writes via nfs_workspace pointer)
AE2C .loop_copy_ws_template←2← AE25 BVS← AE4D BPL
LDA ws_txcb_template_data,x ; Read next template byte
AE2F CMP #&fe ; &FE: end-of-template marker?
AE31 BEQ done_ws_template_copy ; Yes: finalise and return
AE33 CMP #&fd ; &FD: skip-this-offset marker?
AE35 BEQ advance_template_idx ; Yes: advance index without storing
AE37 CMP #&fc ; &FC: substitute-workspace-page- pointer marker?
AE39 BNE select_store_target ; No special marker: store this byte verbatim
AE3B LDA net_rx_ptr_hi ; Wide path: page pointer is net_rx_ptr's high byte
AE3D BVS store_tx_ptr_hi ; V=1 (wide): keep the rx_ptr high byte
AE3F LDA nfs_workspace_hi ; V=0 (narrow): use nfs_workspace high byte instead
AE41 .store_tx_ptr_hi←1← AE3D BVS
STA net_tx_ptr_hi ; Stash whichever page byte we picked into net_tx_ptr_hi
AE43 .select_store_target←1← AE39 BNE
BVS store_via_rx_ptr ; V=1 (wide): store via net_rx_ptr,Y
AE45 STA (nfs_workspace),y ; V=0 (narrow): store via nfs_workspace,Y
AE47 BVC advance_template_idx ; Always branch: V is still clear here
AE49 .store_via_rx_ptr←1← AE43 BVS
STA (net_rx_ptr),y ; Wide-mode store via net_rx_ptr
AE4B .advance_template_idx←2← AE35 BEQ← AE47 BVC
DEY ; Step Y down (workspace offset)
AE4C DEX ; Step X down (template index)
AE4D BPL loop_copy_ws_template ; Loop while X >= 0
AE4F .done_ws_template_copy←1← AE31 BEQ
INY ; Bump Y back to first written offset
AE50 STY net_tx_ptr ; Save it as net_tx_ptr low for the caller
AE52 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.

AE53 .ws_txcb_template_data←1Used as index base by← AE2C LDA
EQUB &85 ; Wide &6F: ctrl=&85
AE54 EQUB &00
AE55 EQUB &FD ; Wide &71: skip (dest station)
AE56 EQUB &FD
AE57 EQUB &7D
AE58 EQUB &FC ; Wide &74: buf start hi=page ptr
AE59 EQUB &FF ; Wide &75: buf start ext lo
AE5A EQUB &FF ; Wide &76: buf start ext hi
AE5B EQUB &7E ; Wide &77: buf end lo=&7E
AE5C EQUB &FC
AE5D EQUB &FF
AE5E EQUB &FF ; Wide &7A: buf end ext hi
AE5F EQUB &00 ; Wide &7B: zero
AE60 EQUB &00 ; Wide &7C: zero
AE61 EQUB &FE ; Narrow stop (&FE terminator)
AE62 EQUB &80
AE63 EQUB &93
AE64 EQUB &FD ; Narrow &0E: skip (dest station)
AE65 EQUB &FD
AE66 EQUB &D9
AE67 EQUB &FC ; Narrow &11: buf start hi=page ptr
AE68 EQUB &FF ; Narrow &12: buf start ext lo
AE69 EQUB &FF ; Narrow &13: buf start ext hi
AE6A EQUB &DE ; Narrow &14: buf end lo=&DE
AE6B EQUB &FC
AE6C EQUB &FF
AE6D EQUB &FF ; Narrow &17: buf end ext hi
AE6E EQUB &FE ; Spool stop (&FE terminator)
AE6F EQUB &D1
AE70 EQUB &FD
AE71 EQUB &FD ; Spool &03: skip (dest network)
AE72 EQUB &22
AE73 EQUB &FD
AE74 EQUB &FF ; Spool &06: buf start ext lo
AE75 EQUB &FF ; Spool &07: buf start ext hi
AE76 EQUB &FD ; Spool &08: skip (buf end lo)
AE77 EQUB &FD
AE78 EQUB &FF
AE79 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)
AE7A .netv_spool_check
DEX ; Step counter
AE7B CPX osword_pb_ptr ; Match osword_pb_ptr?
AE7D BNE rts_spool_reset ; No: return (not our PB)
AE7F LDA vdu_status ; Load spool state byte
AE81 ROR ; Shift bit 0 into C
AE82 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
AE84 .reset_spool_buf_state←2← 8FAF JSR← AED5 JMP
LDA #&22 ; Buffer start offset = &21
AE86 STA spool_buf_idx ; Store as buffer pointer
AE89 LDA #&41 ; Control state &41
AE8B STA ws_0d6a ; Store as spool control state
AE8E .rts_spool_reset←4← AE7D BNE← AE82 BCS← AE91 BNE← AEA5 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
AE8F .netv_print_data
CPY #4 ; Check Y == 4
AE91 BNE rts_spool_reset ; Non-zero: nothing to print, return
AE93 TXA ; A = X (control byte)
AE94 DEX ; Step counter back
AE95 BNE handle_spool_ctrl_byte ; Non-zero: handle spool ctrl byte
AE97 TSX ; Read MOS stack frame
AE98 ORA stack_page_6,x ; OR with stack value
AE9B STA stack_page_6,x ; Store back to stack
AE9E .loop_drain_printer_buf←2← AEAD BCC← AEB2 BCC
LDA #osbyte_read_buffer ; OSBYTE &91: read buffer
AEA0 LDX #buffer_printer ; X=3: printer buffer
AEA2 JSR osbyte ; Read character from buffer Read character from buffer X
AEA5 BCS rts_spool_reset ; C set: return path
AEA7 TYA ; A = extracted character
AEA8 JSR append_byte_to_rxbuf ; Add byte to RX buffer
AEAB CPY #&6e ; Buffer past &6E limit?
AEAD BCC loop_drain_printer_buf ; No: read more from buffer
AEAF JSR process_spool_data ; Print accumulated spool data
AEB2 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
AEB4 .append_byte_to_rxbuf←3← AEA8 JSR← AECF JSR← AF85 JSR
LDY spool_buf_idx ; Y = spool_buf_idx
AEB7 STA (net_rx_ptr),y ; Store A at (net_rx_ptr)+Y
AEB9 INC spool_buf_idx ; Advance spool_buf_idx
AEBC 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
AEBD .handle_spool_ctrl_byte←2← 9026 JSR← AE95 BNE
ROR ; Rotate bit 0 into carry
AEBE BCC check_spool_state ; C clear: take check_spool_state path
AEC0 LDA ws_0d6a ; Load spool control state Equal: take fill path
AEC3 PHA ; Save state byte
AEC4 ROR ; Rotate bit 0 into carry
AEC5 PLA ; Restore state
AEC6 BCS done_spool_ctrl ; C=1: already started, reset
AEC8 ORA #3 ; Set bits 0-1 (active + pending)
AECA STA ws_0d6a ; Store updated state Stop: process_spool_data and return
AECD LDA #3 ; A=3: spool-data result code
AECF JSR append_byte_to_rxbuf ; Append result to RX buffer
AED2 JSR process_spool_data ; Process the accumulated spool data
AED5 .done_spool_ctrl←1← AEC6 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)
AED8 .process_spool_data←4← AEAF JSR← AED2 JSR← AF1B BCC← AF88 JSR
LDY #8 ; Y=8: buf_start_lo TXCB offset
AEDA LDA spool_buf_idx ; Load current spool-buffer index
AEDD STA (nfs_workspace),y ; Store at workspace+8 (buf_start_lo)
AEDF LDA net_rx_ptr_hi ; Load RX page (= net_rx_ptr_hi)
AEE1 INY ; Advance index
AEE2 STA (nfs_workspace),y ; Store at workspace+9 (buf_start_hi)
AEE4 LDY #5 ; Y=5: alt buf_start_hi offset
AEE6 STA (nfs_workspace),y ; Store at workspace+5 (also buf-start hi)
AEE8 LDY #&0b ; Y=&0B: TXCB offset for following copy
AEEA LDX #&26 ; X=&26: template offset for vclr region
AEEC JSR ws_copy_vclr_entry ; Copy 12-byte ws-template region (V-clear)
AEEF DEY ; Step back to offset &0A
AEF0 LDA ws_0d6a ; Read shadow ACR (ws_0d6a)
AEF3 PHA ; Save state
AEF4 ROL ; Shift bit 7 into C
AEF5 PLA ; Restore state
AEF6 EOR #&80 ; Toggle bit 7
AEF8 STA ws_0d6a ; Store updated shadow back to ws_0d6a
AEFB ROL ; Shift bit 0 into bit 1
AEFC STA (nfs_workspace),y ; Store at workspace+&0A
AEFE LDA vdu_status ; Read vdu_status
AF00 PHA ; Push for later restore
AF01 AND #&fe ; Clear bit 0 of vdu_status
AF03 STA vdu_status ; Store updated
AF05 LDY #&22 ; Y=&22: spool_buf_idx reset value
AF07 STY spool_buf_idx ; Reset spool_buf_idx
AF0A LDA #0 ; A=0
AF0C TAX ; X=0
AF0D LDY nfs_workspace_hi ; Y = workspace high page
AF0F CLI ; Re-enable IRQs (NMI window over)
AF10 JSR send_disconnect_reply ; Send disconnect reply
AF13 PLA ; Restore vdu_status
AF14 STA vdu_status ; Restore vdu_status
AF16 RTS ; Return
AF17 .check_spool_state←1← AEBE BCC
LDA ws_0d6a ; Read shadow ACR
AF1A ROR ; Shift bit 0 into C
AF1B BCC process_spool_data ; C clear: re-process spool data
AF1D LDA vdu_status ; Read vdu_status
AF1F PHA ; Push for restore
AF20 AND #&fe ; Clear bit 0 of vdu_status
AF22 STA vdu_status ; Store updated
AF24 LDA #&14 ; A=&14: TX command byte
AF26 .start_spool_retry←1← AF96 BNE
PHA ; Save TX command
AF27 LDX #&0b ; X=&0B: tx_econet_txcb_template offset
AF29 LDY #&2d ; Y=&2D: dest TXCB offset
AF2B .loop_copy_spool_tx←1← AF32 BPL
LDA tx_econet_txcb_template,x ; Read template byte at tx_econet_txcb_template+X
AF2E STA (net_rx_ptr),y ; Store at (net_rx_ptr)+Y
AF30 DEY ; Decrement Y
AF31 DEX ; Decrement X
AF32 BPL loop_copy_spool_tx ; Loop until X wraps below 0
AF34 STX need_release_tube ; Store X (= &FF) as need_release_tube
AF36 LDY #2 ; Y=2: workspace offset for source
AF38 LDA (nfs_workspace),y ; Read (nfs_workspace)+2
AF3A PHA ; Save station
AF3B INY ; Y=3
AF3C LDA (nfs_workspace),y ; Read (nfs_workspace)+3
AF3E LDY #&25 ; Y=&25: dest offset in TXCB
AF40 STA (net_rx_ptr),y ; Store at (net_rx_ptr)+Y
AF42 DEY ; Y=&23
AF43 PLA ; Restore station
AF44 STA (net_rx_ptr),y ; Store at (net_rx_ptr)+Y
AF46 LDX #&0b ; X=&0B: rx_palette_txcb_template offset
AF48 LDY #&0b ; Y=&0B: dest offset in workspace
AF4A .loop_copy_spool_rx←1← AF5B BPL
LDA rx_palette_txcb_template,x ; Read template byte at rx_palette_txcb_template+X
AF4D CMP #&fd ; Compare with &FD (skip-byte marker)
AF4F BEQ advance_spool_rx_idx ; Equal: skip this byte
AF51 CMP #&fc ; Compare with &FC (page-ptr marker)
AF53 BNE store_spool_rx_byte ; Not &FC: store as-is
AF55 LDA net_rx_ptr_hi ; &FC: substitute net_rx_ptr_hi
AF57 .store_spool_rx_byte←1← AF53 BNE
STA (nfs_workspace),y ; Store at (nfs_workspace)+Y
AF59 .advance_spool_rx_idx←1← AF4F BEQ
DEY ; Next dest
AF5A DEX ; Next source
AF5B BPL loop_copy_spool_rx ; Loop until X wraps
AF5D LDA #&22 ; A=&22: TXCB control byte
AF5F STA net_tx_ptr ; Store at net_tx_ptr lo
AF61 LDA net_rx_ptr_hi ; Read net_rx_ptr_hi
AF63 STA net_tx_ptr_hi ; Store as net_tx_ptr hi
AF65 JSR setup_pass_txbuf ; Set up the pass-through TX buffer
AF68 JSR send_net_packet ; Send the TX packet
AF6B LDA #0 ; A=0: clear net_tx_ptr lo
AF6D STA net_tx_ptr ; Store -> net_tx_ptr lo
AF6F LDA nfs_workspace_hi ; Read nfs_workspace_hi
AF71 STA net_tx_ptr_hi ; Store -> net_tx_ptr hi
AF73 JSR wait_net_tx_ack ; Wait for TX ack
AF76 LDY #&2e ; Y=&2E: spool result-byte offset
AF78 LDA (net_rx_ptr),y ; Read result via (net_rx_ptr)+Y
AF7A TAX ; Copy A to X
AF7B AND #7 ; Mask the low 3 bits
AF7D BNE spool_pop_cmd ; Other: take retry path
AF7F .spool_tx_succeeded
PLA ; Discard saved TX cmd
AF80 PLA ; Restore vdu_status
AF81 STA vdu_status ; Restore vdu_status
AF83 LDA #0 ; A=0: success-return code
AF85 JSR append_byte_to_rxbuf ; Append byte to RX buffer
AF88 JSR process_spool_data ; Recurse: process_spool_data
AF8B LDA ws_0d6a ; Read shadow ACR
AF8E AND #&f0 ; Mask high nibble
AF90 STA ws_0d6a ; Store updated shadow
AF93 RTS ; Return
AF94 .spool_pop_cmd←1← AF7D BNE
PLA ; Pop saved TX cmd
AF95 DEC ; Decrement
AF96 BNE start_spool_retry ; Non-zero: retry from start_spool_retry
AF98 DEX ; Decrement counter
AF99 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.

AF9B .err_printer_busy←1← B3E6 JMP
LDA #&a6 ; A=&A6: 'Printer busy' error code
AF9D JSR error_inline_log ; Raise via error_inline_log (never returns)
AFA0 EQUS "Printer busy", &00
AFAD .check_err_code_5←1← AF99 BNE
CMP #5 ; Error code = 5?
AFAF BNE err_printer_jammed ; Not 1: take printer_busy_msg path
AFB1 LDA #&ab ; A=&AB: 'Printer off line' error code
AFB3 JSR error_inline_log ; Raise via error_inline_log (never returns)
AFB6 EQUS "Printer off line", &00
AFC7 .err_printer_jammed←1← AFAF BNE
LDA #&a7 ; A=&A7: 'Printer jammed' error code
AFC9 JSR error_inline_log ; Raise via error_inline_log (never returns)
AFCC 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
AFDB .send_disconnect_reply←3← 97E4 JSR← AF10 JSR← BD12 JSR
STX net_tx_ptr ; X = caller's TX-ptr low byte
AFDD STY net_tx_ptr_hi ; Y = caller's TX-ptr high byte
AFDF PHA ; Save A (the disconnect status to send)
AFE0 ORA #0 ; Test if A=0 (broadcast disconnect)
AFE2 BEQ send_disconnect_status ; Yes: skip the per-station scan
AFE4 LDX #&ff ; X=&FF: scan counter -- INX in loop bumps to 0
AFE6 TAY ; Y=A: status code (also used as station-table key)
AFE7 .loop_scan_disconnect←2← AFF0 BNE← AFFA BNE
TYA ; Restore status into A for the compare
AFE8 INX ; Step station-table index
AFE9 CMP hazel_fcb_slot_attr,x ; Compare with table[X] at &C230 (per-station status)
AFEC BEQ verify_stn_match ; Match: verify station address still matches
AFEE CPX #&0f ; Reached end of 16-slot table?
AFF0 BNE loop_scan_disconnect ; No: keep scanning
AFF2 LDA #0 ; All slots tested, no match: A=0
AFF4 BEQ send_disconnect_status ; Always taken: jump to send-status
AFF6 .verify_stn_match←1← AFEC BEQ
TAY ; Y = matching index
AFF7 JSR match_station_net ; Verify station/network at this slot still matches caller
AFFA BNE loop_scan_disconnect ; Mismatch: station moved, keep scanning
AFFC LDA hazel_fcb_status,x ; Read connection-active flag at &C260+X
AFFF AND #1 ; Mask to bit 0 (active flag)
B001 .send_disconnect_status←2← AFE2 BEQ← AFF4 BEQ
LDY #0 ; Y=0: TX[0] = control byte
B003 ORA (net_tx_ptr),y ; OR active-flag bit into the status
B005 PHA ; Save the combined status
B006 STA (net_tx_ptr),y ; Write it to TX[0]
B008 JSR send_net_packet ; Send the disconnect packet via four-way handshake
B00B LDA #&ff ; A=&FF: sentinel
B00D LDY #8 ; Y=8: TX[8] / TX[9] = packet trailer markers
B00F STA (net_tx_ptr),y ; Write &FF at TX[8]
B011 INY ; Step Y
B012 STA (net_tx_ptr),y ; Write &FF at TX[9]
B014 PLA ; Pull the saved status
B015 TAX ; Move into X for the test
B016 LDY #&d1 ; Y=&D1: control byte for ack-mode TXCB[1]
B018 PLA ; Pull caller's original A again (was double-saved)
B019 PHA ; Push it back
B01A BEQ store_tx_ctrl_byte ; A=0: skip the override
B01C LDY #&90 ; Non-zero: use Y=&90 (FS reply port instead)
B01E .store_tx_ctrl_byte←1← B01A BEQ
TYA ; Move chosen control/port into A
B01F LDY #1 ; Y=1: TX[1] is the port byte
B021 STA (net_tx_ptr),y ; Write to TX[1]
B023 TXA ; Move saved status into A
B024 DEY ; Y=0: TX[0] for ack poll
B025 PHA ; Push the status (we'll EOR with reply below)
B026 .loop_wait_disc_tx_ack←1← B032 BCS
LDA #&7f ; A=&7F: marker pattern
B028 STA (net_tx_ptr),y ; Write to TX[0]
B02A JSR wait_net_tx_ack ; Wait for the TX/RX flip
B02D PLA ; Pull saved status (peek without consuming)
B02E PHA ; Push it back
B02F EOR (net_tx_ptr),y ; EOR with TX[0]: zero iff reply matches saved
B031 ROR ; Rotate result; C set if bit 0 differs
B032 BCS loop_wait_disc_tx_ack ; C set: keep waiting
B034 PLA ; Discard saved status
B035 PLA ; Discard caller's saved A
B036 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.

B037 .tx_econet_txcb_template←1Used as index base by← AF2B LDA
EQUB &80 ; ctrl=&80 (standard TX)
B038 EQUB &9F ; port=&9F
B039 EQUB &00 ; dest station=&00 (filled later)
B03A EQUB &00 ; dest network=&00 (filled later)
B03B EQUB &B7 ; buf start lo
B03C EQUB &8E ; buf start hi (&8E); start = &8EB7
B03D EQUB &FF ; buf start ext lo=&FF
B03E EQUB &FF ; buf start ext hi=&FF
B03F EQUB &BF
B040 EQUB &8E ; buf end hi (&8E); end = &8EA7
B041 EQUB &FF ; buf end ext lo=&FF
B042 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.

B043 .rx_palette_txcb_template←1Used as index base by← AF4A LDA
EQUB &7F ; ctrl=&7F (RX listen)
B044 EQUB &9E ; port=&9E
B045 EQUB &FD
B046 EQUB &FD
B047 EQUB &2E
B048 EQUB &FC ; buf start hi=page ptr (&FC)
B049 EQUB &FF ; buf start ext lo=&FF
B04A EQUB &FF ; buf start ext hi=&FF
B04B EQUB &31
B04C EQUB &FC
B04D EQUB &FF ; buf end ext lo=&FF
B04E 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.

B04F .lang_2_save_palette_vdu
LDA osword_flag ; Read osword_flag (preserved across the dispatch)
B051 PHA ; Save state byte
B052 LDA #&e9 ; A=&E9: workspace start lo for palette save
B054 STA nfs_workspace ; Store as nfs_workspace lo
B056 LDY #0 ; Y=0
B058 STY osword_flag ; Reset osword_flag = 0
B05A LDA vdu_screen_mode ; Read vdu_screen_mode (MOS state byte)
B05D STA (nfs_workspace),y ; Store at (nfs_workspace)+0
B05F INC nfs_workspace ; Advance nfs_workspace lo
B061 LDA vdu_display_start_hi ; Read vdu_display_start_hi (next MOS byte)
B064 PHA ; Save another byte
B065 TYA ; A=0 for first palette entry
B066 .loop_read_palette←1← B085 BNE
STA (nfs_workspace),y ; Store at (nfs_workspace)
B068 LDX nfs_workspace ; Read updated nfs_workspace lo
B06A LDY nfs_workspace_hi ; Read nfs_workspace hi
B06C LDA #osword_read_palette ; A=&0B: OSWORD &0B = read palette entry
B06E JSR osword ; Read palette entry Read palette
B071 PLA ; Restore inner saved
B072 LDY #0 ; Y=0
B074 STA (nfs_workspace),y ; Store palette result at workspace
B076 INY ; Y=1: physical colour offset
B077 LDA (nfs_workspace),y ; Re-read palette result
B079 PHA ; Save for next iteration
B07A LDX nfs_workspace ; Read updated workspace lo
B07C INC nfs_workspace ; Advance workspace
B07E INC osword_flag ; Increment osword_flag (palette index)
B080 DEY ; Y=0
B081 LDA osword_flag ; Read updated osword_flag
B083 CPX #&f9 ; Compare with &F9 (last palette entry)
B085 BNE loop_read_palette ; Not done: loop
B087 PLA ; Restore outer saved
B088 STY osword_flag ; Reset osword_flag = 0 after palette loop
B08A INC nfs_workspace ; Advance workspace
B08C JSR serialise_palette_entry ; Serialise the next palette entry
B08F INC nfs_workspace ; Advance workspace
B091 PLA ; Restore final saved
B092 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
B094 .commit_state_byte←4← 9854 JMP← 987C JSR← 98B4 JSR← A9B7 JMP
LDA prot_status_save ; Read saved copy of prot_status from prot_status_save
B097 STA prot_status ; Store back to prot_status
B09A 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)
B09B .serialise_palette_entry←1← B08C JSR
LDA vdu_mode ; Read vdu_mode (current palette index)
B09E TAX ; Copy A to X
B09F ORA #&40 ; Mark as palette entry
B0A1 STA (nfs_workspace),y ; Store at (nfs_workspace)+Y
B0A3 INC nfs_workspace ; Advance workspace
B0A5 TYA ; A = current Y (= 0)
B0A6 STA (nfs_workspace),y ; Store 0 at (nfs_workspace)+Y
B0A8 LDA read_osbyte_table,x ; Read lookup byte from read_osbyte_table+X
B0AB LDX #0 ; X=0: indexed-indirect mode
B0AD INC nfs_workspace ; Advance workspace
B0AF STA (nfs_workspace,x) ; Store at (nfs_workspace,X)
B0B1 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)
B0B4 .read_osbyte_to_ws_x0←1← B0B1 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
B0B6 .read_osbyte_to_ws
LDY osword_flag ; Y = osword_flag (OSBYTE-table index)
B0B8 INC osword_flag ; Increment osword_flag for next call
B0BA INC nfs_workspace ; Advance nfs_workspace
B0BC LDA read_osbyte_return,y ; Load OSBYTE number from read_osbyte_return+Y
B0BF LDY #&ff ; Y=&FF -- OSBYTE arg (read mode)
B0C1 JSR osbyte ; Issue OSBYTE
B0C4 TXA ; Result to A
B0C5 LDX #0 ; X=0: indexed-indirect mode
B0C7 STA (nfs_workspace,x) ; Store at (nfs_workspace,X)
B0C9 RTS ; Return
B0CA .read_osbyte_return←1Used as index base by← B0BC LDA
EQUB &C2, &C3
B0CC .read_osbyte_table←1Used as index base by← B0A8 LDA
EQUS "000@XX`"
B0D3 .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
B0D6 JSR mask_owner_access ; Set owner-only access mask
B0D9 JSR skip_to_next_arg ; Skip to optional size argument
B0DC CMP #&0d ; End of line?
B0DE BNE parse_cdir_size ; No: parse size argument
B0E0 LDX #2 ; Default allocation size index = 2
B0E2 BNE done_cdir_size ; Branch when the *CDIR size is complete
B0E4 .parse_cdir_size←1← B0DE BNE
LDA #&ff ; A=&FF: mark as decimal parse
B0E6 STA fs_work_4 ; Store decimal parse flag
B0E8 JSR parse_addr_arg ; Parse numeric size argument
B0EB LDX #&1b ; X=&1B: top of 26-entry size table
B0ED .loop_find_alloc_size←1← B0F1 BCC
DEX ; Try next lower index
B0EE CMP cdir_size_thresholds,x ; Compare size with threshold
B0F1 BCC loop_find_alloc_size ; A < threshold: keep searching
B0F3 .done_cdir_size←1← B0E2 BNE
STX hazel_txcb_data ; Store allocation size index
B0F6 PLA ; Restore command line offset
B0F7 TAY ; Transfer to Y
B0F8 JSR save_ptr_to_os_text ; Save text pointer for filename parse
B0FB JSR parse_filename_arg ; Parse directory name argument
B0FE LDX #1 ; X=1: one argument to copy
B100 JSR copy_arg_to_buf ; Copy directory name to TX buffer
B103 LDY #&1b ; Y=&1B: *CDir FS command code
B105 .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.

B108 .cdir_alloc_size_table
EQUB &00 ; Index 1: threshold 0 (catch-all)
B109 EQUB &0A ; Index 2: threshold 10 (default)
B10A EQUB &14 ; Index 3: threshold 20
B10B EQUB &1D ; Index 4: threshold 29
B10C EQUB &27 ; Index 5: threshold 39
B10D EQUB &31 ; Index 6: threshold 49
B10E EQUB &3B ; Index 7: threshold 59
B10F EQUB &45 ; Index 8: threshold 69
B110 EQUB &4F ; Index 9: threshold 79
B111 EQUB &58 ; Index 10: threshold 88
B112 EQUB &62 ; Index 11: threshold 98
B113 EQUB &6C ; Index 12: threshold 108
B114 EQUB &76 ; Index 13: threshold 118
B115 EQUB &80 ; Index 14: threshold 128
B116 EQUB &8A ; Index 15: threshold 138
B117 EQUB &94 ; Index 16: threshold 148
B118 EQUB &9D ; Index 17: threshold 157
B119 EQUB &A7 ; Index 18: threshold 167
B11A EQUB &B1 ; Index 19: threshold 177
B11B EQUB &BB ; Index 20: threshold 187
B11C EQUB &C5 ; Index 21: threshold 197
B11D EQUB &CF ; Index 22: threshold 207
B11E EQUB &D8 ; Index 23: threshold 216
B11F EQUB &E2 ; Index 24: threshold 226
B120 EQUB &EC ; Index 25: threshold 236
B121 .cdir_size_done←1Used as index base by← 8FA0 LDA
EQUB &F6 ; Index 26: threshold &F6 (246) -- last cdir-size threshold; doubles as cdir_size_done[0] (unread by init loop)
B122 EQUB &FF ; cdir_size_done[1] = &FF -> tx_retry_count (retry counter init)
B123 EQUB &28 ; cdir_size_done[2] = &28 -> rx_wait_timeout (40 retries)
B124 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)
B125 .cmd_lcat
ROR hazel_fs_lib_flags ; Rotate carry into lib flag bit 7
B128 SEC ; Set carry (= library directory)
B129 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)
B12B .cmd_lex
ROR hazel_fs_lib_flags ; Rotate carry into lib flag bit 7
B12E SEC ; Set carry (= library directory)
B12F BCS ex_set_lib_flag ; C set: set the library flag
B131 .ps_scan_resume
JSR set_text_and_xfer_ptr ; Set OS text pointer and FS-options transfer ptr
B134 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
B136 .cmd_ex
ROR hazel_fs_lib_flags ; Rotate carry into lib flag bit 7
B139 CLC ; Clear carry (= current directory)
B13A .ex_set_lib_flag←1← B12F BCS
ROL hazel_fs_lib_flags ; Rotate carry back, clearing bit 7
B13D LDA #&ff ; A=&FF: initial column counter
B13F STA fs_spool_handle ; Store column counter
B141 LDA #1 ; One entry per line (Ex format)
B143 STA fs_work_7 ; Store entries per page
B145 LDA #3 ; FS command code 3: Examine
B147 STA fs_work_5 ; Store command code
B149 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.

B14B .fscv_5_cat
JSR set_xfer_params ; Set transfer parameters
B14E LDY #0 ; Y=0: start from entry 0
B150 ROR hazel_fs_lib_flags ; Rotate carry into lib flag
B153 CLC ; Clear carry (= current directory)
B154 .cat_set_lib_flag←1← B129 BCS
ROL hazel_fs_lib_flags ; Rotate carry back, clearing bit 7
B157 LDA #3 ; Three entries per column (Cat)
B159 STA fs_spool_handle ; Store column counter
B15B STA fs_work_7 ; Store entries per page
B15D LDA #&0b ; FS command code &0B: Catalogue
B15F STA fs_work_5 ; Store command code
B161 .setup_ex_request←1← B149 BNE
JSR save_ptr_to_os_text ; Save text pointer
B164 LDA #&ff ; A=&FF: enable escape checking
B166 STA need_release_tube ; Set escapable flag
B168 LDA #6 ; Command code 6
B16A STA hazel_txcb_data ; Store in TX buffer
B16D JSR parse_filename_arg ; Parse directory argument
B170 LDX #1 ; X=1: offset in buffer
B172 JSR copy_arg_to_buf ; Copy argument to TX buffer
B175 LDA hazel_fs_lib_flags ; Get library/FS flags
B178 LSR ; Shift bit 0 to carry
B179 BCC store_owner_flags ; Bit 0 clear: skip
B17B ORA #&40 ; Set bit 6 (owner access flag)
B17D .store_owner_flags←1← B179 BCC
ROL ; Rotate back
B17E STA hazel_fs_lib_flags ; Store modified flags
B181 LDY #&12 ; Y=&12: FS command for examine
B183 JSR save_net_tx_cb ; Send request to file server
B186 LDX #3 ; X=3: offset to directory title
B188 JSR print_10_chars ; Print directory title (10 chars)
B18B JSR print_inline_no_spool ; Print '('
B18E EQUS "("
B18F LDA hazel_txcb_objtype ; Load FS object-type code from hazel_txcb_objtype (file/dir/etc)
B192 JSR print_dec_3dig_no_spool ; Print a 3-digit decimal (no spool)
B195 JSR print_inline_no_spool ; Print ') ' to close the type-code field
B198 EQUS ") "
B19E LDY hazel_txcb_type ; Read hazel_txcb_type (FS reply opcode)
B1A1 BNE print_public_label ; Non-zero (private library): take the public-label branch
B1A3 JSR print_inline_no_spool ; Print 'Owner' + CR
B1A6 EQUS "Owner", &0D
B1AC BNE cat_after_label_print ; Non-zero: branch to cat_after_label_print
B1AE .print_public_label←1← B1A1 BNE
JSR print_inline_no_spool ; Print 'Public' + CR
B1B1 EQUS "Public", &0D
B1B8 .cat_after_label_print←1← B1AC BNE
LDA hazel_fs_lib_flags ; Read hazel_fs_lib_flags
B1BB PHA ; Push for stack-based saves
B1BC JSR mask_owner_access ; Mask owner access bits
B1BF LDY #&15 ; Y=&15: FS command for dir info
B1C1 JSR save_net_tx_cb ; Send request to file server
B1C4 INX ; Advance X past header
B1C5 LDY #&10 ; Y=&10: print 16 chars
B1C7 JSR print_chars_from_buf ; Print file entry
B1CA JSR print_inline_no_spool ; Print ' Option '
B1CD EQUS " Option "
B1D8 LDA hazel_fs_flags ; Read hazel_fs_flags
B1DB TAX ; Transfer to X for table lookup
B1DC JSR print_hex_byte_no_spool ; Print option as hex
B1DF JSR print_inline_no_spool ; Print ' ('
B1E2 EQUS " ("
B1E4 LDY option_str_offset_data,x ; Look up option-string offset for index X
B1E7 .loop_print_option←1← B1F0 BNE
LDA option_offset_table,y ; Look up option byte at the resolved offset
B1EA BMI print_dir_header ; Bit 7 of A set (negative): print directory header
B1EC JSR print_char_no_spool ; Print char (no spool)
B1EF INY ; Advance Y
B1F0 BNE loop_print_option ; Loop until Y wraps
B1F2 .print_dir_header←1← B1EA BMI
JSR print_inline_no_spool ; Print ')\rDir. ' header for the directory listing
B1F5 EQUS ")", &0D, "Dir. "
B1FC LDX #&11 ; X=&11: filename offset in TX buffer
B1FE JSR print_10_chars ; Print 10-char filename
B201 JSR print_inline_no_spool ; Print inline 'attr-bits' fragment
B204 EQUS " Lib. " ; label for *Ex output
B20E LDX #&1b ; X=&1B: extension offset in TX buffer
B210 JSR print_10_chars ; Print 10-char extension
B213 JSR print_newline_no_spool ; Print newline
B216 PLA ; Pop saved counter
B217 STA hazel_fs_lib_flags ; Store as fs_lib_flags
B21A .setup_ex_pagination←1← B24B BNE
STY hazel_txcb_flag ; Save Y as hazel_txcb_flag (next-entry index)
B21D STY fs_work_4 ; Save Y as fs_work_4
B21F LDX fs_work_5 ; Load fs_work_5 (page count)
B221 STX hazel_txcb_count ; Store at hazel_txcb_count
B224 LDX fs_work_7 ; Load fs_work_7
B226 STX hazel_txcb_data ; Store at hazel_txcb_data
B229 LDX #3 ; X=3: TX[3] is start of arg buffer
B22B JSR copy_arg_to_buf ; Copy filename arg
B22E LDY #3 ; Y=3: cmd code 3 (catalog)
B230 JSR save_net_tx_cb ; Send TX request
B233 INX ; X advances entry counter
B234 LDA hazel_txcb_data ; Read reply status
B237 BEQ jmp_osnewl ; Z: empty reply -> exit cat
B239 PHA ; Push reply status
B23A .loop_scan_entry_data←1← B23E BPL
INY ; Advance Y
B23B LDA hazel_txcb_data,y ; Read entry byte from hazel_txcb_data+Y
B23E BPL loop_scan_entry_data ; Bit 7 clear: keep scanning
B240 STA hazel_txcb_lib,y ; Store with high-bit clear at hazel_txcb_lib+Y
B243 JSR ex_print_col_sep ; Print column separator
B246 PLA ; Pop saved status
B247 CLC ; Clear carry for the ADC below
B248 ADC fs_work_4 ; Add fs_work_4 (page accumulator)
B24A TAY ; New index
B24B 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
B24D .print_10_chars←3← B188 JSR← B1FE JSR← B210 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
B24F .print_chars_from_buf←2← B1C7 JSR← B257 BNE
LDA hazel_txcb_data,x ; Read next character from reply buffer at offset X
B252 JSR print_char_no_spool ; Print via OSASCI, bypassing the *SPOOL file
B255 INX ; Step buffer offset
B256 DEY ; Step character counter
B257 BNE print_chars_from_buf ; Loop until Y=0
B259 RTS ; Return; X points just past the last printed byte
B25A .jmp_osnewl←1← B237 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
B25D .parse_cmd_arg_y0←3← A00C JSR← A510 JSR← B39A 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
B25F .parse_filename_arg←3← B0FB JSR← B16D JSR← B726 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.

B262 .parse_access_prefix←4← 9431 JSR← 94D4 JSR← 950A JSR← 9C29 JSR
LDA hazel_parse_buf ; Read first parsed-buffer character (the candidate prefix)
B265 EOR #'&' ; EOR with '&'; Z set iff the byte was '&'
B267 BNE check_colon_prefix ; Not '&': try ':' (and '#') instead
B269 LDA hazel_fs_lib_flags ; Read fs_lib_flags
B26C ORA #&40 ; Set bit 6 (URD-relative resolution flag)
B26E STA hazel_fs_lib_flags ; Write back updated flags
B271 JSR strip_token_prefix ; Strip the '&' from the buffer (shift left + trim)
B274 DEX ; Step caller's X back to account for the consumed character
B275 LDA hazel_parse_buf ; Re-read the (now first) buffer byte after the strip
B278 EOR #'.' ; EOR with '.'; Z set iff '&.' pair (URD root)
B27A BNE check_hash_prefix ; Not '&.': just '&' alone -- check for trailing '#'
B27C LDA hazel_parse_buf_1 ; It was '&.': peek the byte after the dot
B27F EOR #&0d ; EOR with CR; Z set iff '&.<CR>' (illegal: dot needs a name to follow)
B281 BEQ error_bad_prefix ; '&.<CR>' is invalid: raise 'Bad filename'
B283 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
B284 .strip_token_prefix←5← 945E JSR← 94F3 JSR← 94F8 JSR← B271 JSR← B2C6 JSR
TXA ; Save caller's X (TX buffer offset)
B285 PHA ; Push it
B286 LDX #&ff ; X=&FF: INX in loop bumps to 0 for first byte
B288 .loop_shift_str_left←1← B291 BNE
INX ; Step to next byte position
B289 LDA hazel_parse_buf_1,x ; Read byte X+1 (the next character)
B28C STA hazel_parse_buf,x ; Store it back at byte X (shifting left by one)
B28F EOR #&0d ; EOR with CR; Z set if we just shifted the terminator
B291 BNE loop_shift_str_left ; More to shift: continue
B293 TXA ; X is now the buffer length (excluding CR)
B294 BEQ done_strip_prefix ; Empty after shift: skip trim, restore X, return
B296 .loop_trim_trailing←1← B2A3 BNE
LDA hazel_parse_buf_m1,x ; Read last buffer byte (X-1 because we count from 0)
B299 EOR #' ' ; EOR with space; Z set iff it's a trailing space
B29B BNE done_strip_prefix ; Not a space: trim done, restore X, return
B29D LDA #&0d ; It is a space: replace with CR (truncate the string)
B29F STA hazel_parse_buf_m1,x ; Store CR at the now-trimmed position
B2A2 DEX ; Step backwards
B2A3 BNE loop_trim_trailing ; Loop while X > 0
B2A5 .done_strip_prefix←2← B294 BEQ← B29B BNE
PLA ; Restore caller's TX buffer offset
B2A6 TAX ; Transfer back to X
B2A7 .rts_strip_prefix←3← B2AA BEQ← B2B1 BNE← B2BC BNE
RTS ; Return
B2A8 .check_hash_prefix←1← B27A BNE
EOR #'#' ; Test for '#' prefix (3 ^ &23 = 0)
B2AA BEQ rts_strip_prefix ; Equal: '#' was the prefix, return
B2AC .error_bad_prefix←2← B281 BEQ← B2E2 BEQ
JMP error_bad_filename ; Other: not a recognised prefix -> error
B2AF .check_colon_prefix←1← B267 BNE
EOR #&1c ; Test for ':' (&3F ^ &1C)
B2B1 BNE rts_strip_prefix ; Different: caller had no prefix, return
B2B3 LDA hazel_parse_buf_2 ; ':' confirmed -- read next char from parse buffer
B2B6 EOR #'.' ; Test for '.' (path separator)
B2B8 BEQ set_fs_select_flag ; Equal: ':.' qualified prefix
B2BA EOR #'#' ; Test for '#'
B2BC BNE rts_strip_prefix ; Other: no recognised tail prefix, return
B2BE .set_fs_select_flag←1← B2B8 BEQ
LDA hazel_fs_lib_flags ; Recognised: load fs_lib_flags
B2C1 ORA #&40 ; Set bit 6 (FS-select pending)
B2C3 STA hazel_fs_lib_flags ; Store updated fs_lib_flags
B2C6 JSR strip_token_prefix ; Recurse to strip the trailing component
B2C9 DEX ; Decrement X (consume processed char)
B2CA RTS ; Return
B2CB .option_str_offset_data←1Used as index base by← B1E4 LDY
EQUB &00 ; Data: option string offset table
B2CC EQUS "/<c"
B2CF .option_offset_table←1Used as index base by← B1E7 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
B2D2 .copy_arg_to_buf_x0←5← 8DED JSR← 8E50 JSR← 9C37 JSR← 9E27 JSR← A5F3 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
B2D4 .copy_arg_to_buf←11← 9CE9 JSR← 9E20 JSR← 9E43 JSR← A011 JSR← A515 JSR← A542 JSR← B100 JSR← B172 JSR← B22B JSR← B39F JSR← B73B 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
B2D6 .copy_arg_validated←3← 8DE8 JSR← 9558 JSR← 958E JSR
SEC ; Set C: this entry validates against '&'
B2D7 .loop_copy_char←1← B2EA BNE
LDA (fs_crc_lo),y ; Read next source byte through fs_crc_lo pointer
B2D9 STA hazel_txcb_data,x ; Store into TX buffer at offset X
B2DC BCC advance_positions ; Validation off (C clear): just advance positions
B2DE CMP #'!' ; Test against '!' to bias the EOR comparison
B2E0 EOR #'&' ; EOR with '&'; Z set iff source byte was '&'
B2E2 BEQ error_bad_prefix ; '&' inside the argument is illegal: raise 'Bad filename'
B2E4 .restore_after_check
EOR #'&' ; Restore A by undoing the EOR (so the loop terminator test below sees the original byte)
B2E6 .advance_positions←1← B2DC BCC
INX ; Advance TX buffer offset
B2E7 INY ; Advance command-line offset
B2E8 EOR #&0d ; EOR with CR; Z set iff we just stored the terminator
B2EA BNE loop_copy_char ; More to copy: continue
B2EC .loop_trim_trailing_spaces←1← B2F9 BNE
LDA hazel_txcb_network,x ; Look at the byte just before the CR we stopped on
B2EF EOR #' ' ; EOR with space; Z set iff that byte was a trailing space
B2F1 BNE done_trim_spaces ; Not a space: trim done
B2F3 DEX ; Step back over the space
B2F4 LDA #&0d ; A=&0D: replace the trailing space with CR
B2F6 STA hazel_txcb_lib,x ; Store CR at the now-truncated end
B2F9 BNE loop_trim_trailing_spaces ; Always taken (A=&0D from LDA #&0D so Z is clear); look at the next byte back
B2FB .done_trim_spaces←1← B2F1 BNE
LDA #0 ; All trailing spaces consumed (or none present)
B2FD .rts_copy_arg←1← B313 BMI
RTS ; Return
B2FE 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
B302 .mask_owner_access←15← 8E6B JSR← 94CE JSR← 9506 JSR← 9C26 JSR← A05C JSR← A179 JSR← A4FB JSR← A508 JSR← A599 JSR← A5A3 JSR← AC72 JSR← B0D6 JSR← B1BC JSR← B38A JSR← B71C JSR
LDA hazel_fs_lib_flags ; Read fs_lib_flags (&C271)
B305 AND #&1f ; Keep only the 5-bit owner access mask
B307 STA hazel_fs_lib_flags ; Store back, clearing FS-selection and other high bits
B30A RTS ; Return
B30B EQUS "Run"
B30E .ex_init_scan_x0
LDX #0 ; X=0: scan from start of TX entry
B310 .loop_scan_entries←1← B330 BNE
LDA hazel_txcb_data,x ; Read entry byte at hazel_txcb_data+X
B313 BMI rts_copy_arg ; Bit 7 set: end-of-entries -> return
B315 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.

B317 .ex_print_col_sep←1← B243 JSR
LDY fs_spool_handle ; Read fs_spool_handle (also column counter in *Cat mode)
B319 BMI col_sep_eol_check ; Negative: *Ex mode (one-per-line) -- skip column logic, just print newline
B31B INY ; Bump column counter
B31C TYA ; Get the new value into A
B31D AND #3 ; Wrap to 0..3 (4 columns per row)
B31F STA fs_spool_handle ; Save the new column index
B321 BEQ col_sep_eol_check ; Wrapped to 0: end of row, print newline
B323 JSR print_inline_no_spool ; Mid-row: print 2-space column separator via inline
B326 EQUS " "
B328 BNE col_sep_print_char ; Non-zero: take col_sep_print_char tail
B32A .col_sep_eol_check←2← B319 BMI← B321 BEQ
LDA #&0d ; A=&0D: CR character
B32C .print_col_cr←1← B315 BNE
JSR print_char_no_spool ; Print CR (no spool)
B32F .col_sep_print_char←1← B328 BNE
INX ; Next entry
B330 BNE loop_scan_entries ; Loop until X wraps
B332 EOR zp_0078 ; XOR with zp_0078
B334 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
B336 .print_dec_3dig_no_spool←1← B192 JSR
TAY ; Y = value to convert (digits read off via successive divisions)
B337 LDA #&64 ; Divisor for hundreds digit
B339 JSR print_dec_digit_no_spool ; Print hundreds digit
B33C LDA #&0a ; Divisor for tens digit
B33E JSR print_dec_digit_no_spool ; Print tens digit
B341 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
B343 .print_dec_digit_no_spool←2← B339 JSR← B33E JSR
STA fs_error_ptr ; Stash divisor in fs_error_ptr (the SBC target below)
B345 TYA ; Convert remaining value to A
B346 LDX #&2f ; X = '0'-1: digit counter, INX in the loop steps to '0' first
B348 SEC ; Set carry
B349 .loop_divide_decimal_digit←1← B34C BCS
INX ; Step quotient digit
B34A SBC fs_error_ptr ; Subtract divisor
B34C BCS loop_divide_decimal_digit ; No underflow: keep dividing
B34E ADC fs_error_ptr ; Underflow: add divisor back to recover the remainder
B350 TAY ; Remainder -> Y, ready for the next digit
B351 TXA ; Move digit ('0'-'9') from X into A for printing
B352 LDX fs_error_ptr ; Save divisor in X across the print (print_char_no_spool preserves X is not guaranteed)
B354 JSR print_char_no_spool ; Print the digit, bypassing *SPOOL
B357 STX fs_error_ptr ; Restore divisor from X
B359 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)
B35A .print_num_no_leading←4← 8AB1 JSR← 90E4 JSR← 966D JSR← 967A 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
B35D .print_decimal_3dig←2← B596 JSR← B5AD JMP
TAY ; Transfer value to Y (remainder)
B35E LDA #&64 ; A=100: hundreds divisor
B360 JSR print_decimal_digit ; Print hundreds digit
B363 LDA #&0a ; A=10: tens divisor
B365 JSR print_decimal_digit ; Print tens digit
B368 CLV ; Clear V (always print units)
B369 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
B36B .print_decimal_digit←2← B360 JSR← B365 JSR
STA fs_error_ptr ; Store divisor
B36D TYA ; Get remaining value
B36E LDX #&2f ; X='0'-1: digit counter
B370 SEC ; Set carry for subtraction
B371 PHP ; Save V flag for leading zero check
B372 .loop_divide_digit←1← B375 BCS
INX ; Count quotient digit
B373 SBC fs_error_ptr ; Subtract divisor
B375 BCS loop_divide_digit ; No underflow: continue dividing
B377 ADC fs_error_ptr ; Add back divisor (get remainder)
B379 TAY ; Remainder to Y for next digit
B37A TXA ; Digit character to A
B37B PLP ; Restore V flag
B37C BVC print_nonzero_digit ; V clear: always print digit
B37E CMP #'0' ; V set: is digit '0'?
B380 BEQ rts_print_digit ; Yes: suppress leading zero
B382 .print_nonzero_digit←1← B37C BVC
LDX fs_error_ptr ; Save divisor across OSASCI call
B384 JSR osasci ; Print the digit
B387 STX fs_error_ptr ; Restore divisor
B389 .rts_print_digit←1← B380 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
B38A .cmd_info_dispatch
JSR mask_owner_access ; Clear owner-only access bits before checking the URD
B38D LDA #'i' ; A=&69: 'i' character (info prefix)
B38F STA hazel_txcb_data ; Store 'i' as start of FS command name in the TX buffer
B392 LDA #'.' ; A='.': abbreviation terminator
B394 STA hazel_txcb_flag ; Store '.' as command-name terminator
B397 JSR save_ptr_to_os_text ; Save the command-line pointer for the dispatcher
B39A JSR parse_cmd_arg_y0 ; Parse the *Info argument from the command line
B39D LDX #2 ; X=2: TX-buffer offset to copy the arg into (after 'i.')
B39F JSR copy_arg_to_buf ; Append parsed argument to the TX command buffer
B3A2 TAY ; A = next index
B3A3 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)
B3A6 .save_ptr_to_os_text←9← A062 JSR← A4F8 JSR← A505 JSR← B0F8 JSR← B161 JSR← B397 JSR← B42F JSR← B5DE JSR← B723 JSR
PHA ; Save A
B3A7 LDA fs_crc_lo ; Copy text pointer low byte
B3A9 STA os_text_ptr ; To OS text pointer low
B3AB LDA fs_crc_hi ; Copy text pointer high byte
B3AD STA os_text_ptr_hi ; To OS text pointer high
B3AF PLA ; Restore A
B3B0 RTS ; Return
B3B1 .loop_advance_char←1← B3BC 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
B3B2 .skip_to_next_arg←1← B0D9 JSR
LDA (fs_crc_lo),y ; Load char from command line
B3B4 CMP #' ' ; Space?
B3B6 BEQ loop_skip_space_chars ; Yes: skip trailing spaces
B3B8 CMP #&0d ; CR (end of line)?
B3BA BEQ rts_skip_arg ; Yes: return (at end)
B3BC BNE loop_advance_char ; Loop to the next character
B3BE .loop_skip_space_chars←2← B3B6 BEQ← B3C3 BEQ
INY ; Advance past space
B3BF LDA (fs_crc_lo),y ; Load next character
B3C1 CMP #' ' ; Still a space?
B3C3 BEQ loop_skip_space_chars ; Yes: skip multiple spaces
B3C5 .rts_skip_arg←1← B3BA 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)
B3C6 .save_ptr_to_spool_buf←2← B3EC JSR← B5C9 JSR
PHA ; Save A
B3C7 LDA fs_crc_lo ; Copy text pointer low byte
B3C9 STA fs_options ; To spool buffer pointer low
B3CB LDA fs_crc_hi ; Copy text pointer high byte
B3CD STA fs_block_offset ; To spool buffer pointer high
B3CF PLA ; Restore A
B3D0 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)
B3D1 .init_spool_drive←2← B3E9 JSR← B5BE JSR
TYA ; Save Y
B3D2 PHA ; Push it
B3D3 JSR get_ws_page ; Get workspace page number
B3D6 STA addr_work ; Store as spool drive page high
B3D8 PLA ; Restore Y
B3D9 TAY ; Transfer to Y
B3DA LDA #0 ; A=0
B3DC STA work_ae ; Clear spool drive page low
B3DE 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
B3DF .cmd_ps
LDA #1 ; A=1: check printer ready
B3E1 BIT ws_0d6a ; Test printer server workspace flag
B3E4 BNE done_ps_available ; Non-zero: printer available
B3E6 JMP err_printer_busy ; Printer not available: error
B3E9 .done_ps_available←1← B3E4 BNE
JSR init_spool_drive ; Initialise spool drive
B3EC JSR save_ptr_to_spool_buf ; Save pointer to spool buffer
B3EF PHY ; Save Y
B3F0 LDY #&21 ; Y=&21: workspace flag offset
B3F2 LDA #0 ; A=0
B3F4 STA (nfs_workspace),y ; Clear the workspace flag at &21
B3F6 PLY ; Restore Y
B3F7 LDA (fs_options),y ; Read fs_options[Y]
B3F9 CMP #&0d ; End of command line?
B3FB BEQ no_ps_name_given ; Yes: no argument given
B3FD CLV ; Clear V (= explicit PS name given)
B3FE JSR is_decimal_digit ; Is first char a decimal digit?
B401 BCC save_ps_cmd_ptr ; C clear: save ptr and continue
B403 PHY ; Save Y
B404 JSR load_ps_server_addr ; Load PS server address
B407 PLY ; Restore Y
B408 JSR parse_fs_ps_args ; Parse FS/PS arguments
B40B 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)
B40E .copy_ps_data_y1c←1← 8F77 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 (&8EB7). 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
B410 .copy_ps_data
LDX #&f8 ; X=&F8: walks 0..7 via wraparound (loads from ps_template_base+&F8 = ps_template_data &8EB7)
B412 .loop_copy_ps_tmpl←1← B419 BNE
LDA l8dbf,x ; Read template byte from ps_template_data + (X-&F8)
B415 STA (net_rx_ptr),y ; Store into RX buffer at offset Y
B417 INY ; Step destination
B418 INX ; Step source -- wraps from &FF to &00 to terminate
B419 BNE loop_copy_ps_tmpl ; Loop while X != 0 (8 iterations: &F8..&FF)
B41B RTS ; Return
B41C .no_ps_name_given←1← B3FB BEQ
BIT always_set_v_byte ; Set V (= no explicit PS name)
B41F .save_ps_cmd_ptr←1← B401 BCC
STY ws_ptr_hi ; Save Y at ws_ptr_hi
B421 BVS done_ps_name_parse ; V set: skip PS name parsing
B423 LDX #6 ; Max 6 characters for PS name
B425 LDY #&18 ; Buffer offset &1C for PS name
B427 LDA #' ' ; Space character
B429 .loop_pad_ps_name←1← B42D BNE
STA (net_rx_ptr),y ; Fill buffer with space
B42B INY ; Advance Y past padding
B42C DEX ; Count down
B42D BNE loop_pad_ps_name ; Loop while Y wraps
B42F JSR save_ptr_to_os_text ; Save text pointer
B432 LDY ws_ptr_hi ; Restore Y from ws_ptr_hi
B434 JSR gsinit ; Initialise string reading
B437 BEQ done_ps_name_parse ; Empty string: skip to send
B439 LDX #6 ; X=6: scan up to 6 PS slots
B43B STY ws_ptr_hi ; Save updated string pointer
B43D LDY #&18 ; Buffer offset for PS name
B43F STY table_idx ; Save buffer position
B441 .loop_read_ps_char←1← B44D BNE
LDY ws_ptr_hi ; Restore string pointer
B443 JSR gsread ; Read next character
B446 STY ws_ptr_hi ; Save updated pointer
B448 BCS done_ps_name_parse ; C set: end of slots
B44A JSR store_char_uppercase ; Store char uppercased in buffer
B44D BNE loop_read_ps_char ; Loop for more characters
B44F .done_ps_name_parse←3← B421 BVS← B437 BEQ← B448 BCS
JSR reverse_ps_name_to_tx ; Copy reversed PS name to TX
B452 JSR send_net_packet ; Send PS status request
B455 JSR pop_requeue_ps_scan ; Pop and requeue PS scan
B458 JSR load_ps_server_addr ; Load PS server address
B45B LDA #0 ; A=0
B45D TAX ; Copy A to X
B45E LDY #&20 ; Offset &24 in buffer
B460 STA (net_rx_ptr),y ; Clear PS status byte
B462 .loop_pop_ps_slot←1← B48C BNE
PLA ; Pop saved slot index
B463 BEQ done_ps_scan ; Zero: all slots done
B465 PHA ; Push it back (for retry)
B466 TAY ; Transfer to Y
B467 LDA (nfs_workspace),y ; Read slot status
B469 BPL done_ps_slot_mark ; Bit 7 clear: slot inactive
B46B JSR advance_y_by_4 ; Advance Y by 4 (next slot)
B46E LDA (nfs_workspace),y ; Read ws byte at (nfs_workspace)+Y
B470 STA work_ae ; Save as work_ae lo
B472 LDA (work_ae,x) ; Read indirect via (work_ae,X)
B474 AND #7 ; Mask the low 3 bits
B476 BNE done_ps_slot_mark ; Other than 3: skip slot mark
B478 .read_ps_station_addr
DEY ; Back up to network byte
B479 LDA (nfs_workspace),y ; Read network byte
B47B STA fs_work_6 ; Save as fs_work_6
B47D DEY ; Back up to station byte
B47E LDA (nfs_workspace),y ; Read station byte
B480 STA fs_work_5 ; Save as fs_work_5
B482 LDY #&20 ; Y=&20: PS marker offset
B484 STA (net_rx_ptr),y ; Store station to (net_rx_ptr)+&20
B486 .done_ps_slot_mark←2← B469 BPL← B476 BNE
PLA ; Pop saved slot index
B487 TAY ; Transfer to Y
B488 LDA #'?' ; A=&3F: 'processed' marker
B48A STA (nfs_workspace),y ; Mark slot as processed
B48C BNE loop_pop_ps_slot ; Loop to pop the next PS slot
B48E .done_ps_scan←1← B463 BEQ
JSR print_printer_server_is ; Print 'Printer server is ' fragment
B491 LDY #&20 ; Y=&20: marker offset
B493 LDA (net_rx_ptr),y ; Read marker byte
B495 BNE print_ps_now ; Non-zero: print 'now <stn>'
B497 JSR print_inline ; Print 'still ' fragment
B49A EQUS "still "
B4A0 CLV ; Bit-7 terminator (next opcode)
B4A1 BVC ps_print_info_newline ; V clear: branch onward
B4A3 .print_ps_now←1← B495 BNE
JSR print_inline ; Print 'now ' fragment
B4A6 EQUS "now "
B4AA LDY #&21 ; Y=&21: workspace flag offset
B4AC TYA ; Copy Y to A
B4AD STA (nfs_workspace),y ; Store the workspace flag at &21
B4AF .ps_print_info_newline←1← B4A1 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).

B4B2 .store_ps_station←1← B40B JMP
LDY #2 ; Y=2: workspace offset for stored station
B4B4 LDA fs_work_5 ; Load station number
B4B6 STA (nfs_workspace),y ; Store at (nfs_workspace)+2
B4B8 INY ; Advance index
B4B9 LDA fs_work_6 ; Load network number
B4BB STA (nfs_workspace),y ; Store at (nfs_workspace)+3
B4BD 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)
B4BE .print_file_server_is←1← A3CC JSR
JSR print_inline ; Print 'File' via inline string
B4C1 EQUS "File"
B4C5 CLV ; Clear V so the BVC below is taken
B4C6 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)
B4C8 .print_printer_server_is←2← B48E JSR← B608 JSR
JSR print_inline ; Print 'Printer' via inline string
B4CB EQUS "Printer"
B4D2 NOP ; NOP -- bit-7 terminator + harmless resume opcode
B4D3 .print_server_is_suffix←1← B4C6 BVC
JSR print_inline ; Print ' server is ' via inline string
B4D6 EQUS " server is " ; fragment for 'File/Printer server is ...' messages
B4E1 NOP ; NOP -- bit-7 terminator + harmless resume opcode
B4E2 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
B4E3 .load_ps_server_addr←3← B404 JSR← B458 JSR← B62B JSR
LDY #2 ; Y=2: workspace offset of PS station byte
B4E5 LDA (nfs_workspace),y ; Read station byte
B4E7 STA fs_work_5 ; Stash in fs_work_5 (PS station)
B4E9 INY ; Y=3: workspace offset of PS network byte
B4EA LDA (nfs_workspace),y ; Read network byte
B4EC STA fs_work_6 ; Stash in fs_work_6 (PS network)
B4EE 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
B4EF .pop_requeue_ps_scan←2← B455 JSR← B605 JSR
PLA ; Pull saved upper byte of ws_ptr_lo+osword_flag pair
B4F0 STA osword_flag ; Save into osword_flag
B4F2 PLA ; Pull lower byte
B4F3 STA ws_ptr_lo ; Save into ws_ptr_lo
B4F5 LDA #0 ; Push 0 -- placeholder, will be the stacked return marker
B4F7 PHA ; Push it
B4F8 LDA #&84 ; ws_ptr_hi base = &84 (start of PS slot table area)
B4FA STA ws_ptr_hi ; Save base
B4FC LSR econet_flags ; Shift bit 0 of econet_flags into C (saved scan state)
B4FF LDA #3 ; A=3: PS slot index counter
B501 .loop_scan_ps_slots←1← B513 BNE
JSR byte_to_2bit_index ; Convert slot index to 12-byte-aligned table offset
B504 BCS done_ps_slot_scan ; Out of range (clamped to 0): all slots scanned
B506 LSR ; A /= 2 (shift down)
B507 LSR ; A /= 2 again (now slot index * 4 / 4 = slot index)
B508 TAX ; X = slot index
B509 LDA (nfs_workspace),y ; Read slot's status byte at workspace[Y]
B50B BEQ done_ps_slot_scan ; Slot empty (0): scan done
B50D CMP #'?' ; Slot is '?' (uninitialised marker)?
B50F 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 &B4FF in the PS slot scanner).

On EntryXcurrent slot index
B511 .skip_next_ps_slot←1← B53A JMP
INX ; Step slot index
B512 TXA ; Move to A for next iteration
B513 BNE loop_scan_ps_slots ; Loop while X != 0 (wraps when all slots done)
B515 .reinit_ps_slot←1← B50F BEQ
TYA ; Save Y (slot table offset)
B516 PHA ; Push it
B517 LDA #&7f ; A=&7F: slot status 'busy/active'
B519 STA (nfs_workspace),y ; Mark slot active
B51B INY ; Step Y to control byte
B51C LDA #&9e ; A=&9E: control byte (PS-init pattern)
B51E STA (nfs_workspace),y ; Store control byte
B520 LDA #0 ; A=0: zero-fill the next two bytes
B522 JSR write_two_bytes_inc_y ; Write two zeros, advance Y
B525 LDA ws_ptr_hi ; Read current ws_ptr_hi
B527 STA (nfs_workspace),y ; Store as buffer-link low byte
B529 CLC ; Clear C ready for the +3
B52A PHP ; Save flags so the ADC's C doesn't leak
B52B ADC #3 ; Bump ws_ptr_hi by 3 (next slot's base)
B52D PLP ; Restore flags
B52E STA ws_ptr_hi ; Save updated ws_ptr_hi
B530 JSR write_ps_slot_byte_ff ; Write buffer page + two &FF sentinels
B533 LDA ws_ptr_hi ; Read ws_ptr_hi (now updated)
B535 STA (nfs_workspace),y ; Store as second-link byte
B537 .write_ps_slot_hi_link
JSR write_ps_slot_byte_ff ; Write another buffer page + two &FF sentinels
B53A JMP skip_next_ps_slot ; Continue scanning slots
B53D .done_ps_slot_scan←2← B504 BCS← B50B BEQ
ASL econet_flags ; Restore bit 0 of econet_flags via ASL (recovers from the LSR at &B4C1)
B540 LDA ws_ptr_lo ; Pull saved ws_ptr_lo
B542 PHA ; Push it back (the caller's return-resume sequence)
B543 LDA osword_flag ; Pull saved osword_flag
B545 PHA ; Push it back
B546 LDA #&0a ; A=&0A: outer counter
B548 TAY ; Y=&0A: inner counter
B549 TAX ; X=&0A: middle counter
B54A STA fs_work_4 ; Save outer in fs_work_4
B54C .loop_ps_delay←3← B54D BNE← B550 BNE← B554 BNE
DEY ; Decrement inner counter
B54D BNE loop_ps_delay ; Inner not zero: keep delaying
B54F DEX ; Decrement middle
B550 BNE loop_ps_delay ; Middle not zero: refresh inner and continue
B552 DEC fs_work_4 ; Decrement outer in fs_work_4
B554 BNE loop_ps_delay ; Outer not zero: another full sweep (~1000 cycles)
B556 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)
B557 .write_ps_slot_byte_ff←2← B530 JSR← B537 JSR
INY ; Step Y to next workspace slot byte
B558 LDA addr_work ; Load buffer page byte from addr_work
B55A STA (nfs_workspace),y ; Write at offset Y
B55C 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
B55E .write_two_bytes_inc_y←1← B522 JSR
INY ; Step Y to next destination
B55F STA (nfs_workspace),y ; Write A at workspace offset Y
B561 INY ; Step Y again
B562 STA (nfs_workspace),y ; Write A at the next offset (two consecutive copies)
B564 INY ; Final INY leaves Y pointing past the second write
B565 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
B566 .reverse_ps_name_to_tx←2← B44F JSR← B5C1 JSR
LDY #&18 ; Y=&18: source offset (start of PS name in RX buffer)
B568 .loop_push_ps_name←1← B56E BNE
LDA (net_rx_ptr),y ; Read RX byte at offset Y
B56A PHA ; Push it (the stack reverses the order)
B56B INY ; Step source
B56C CPY #&20 ; Reached &20 (one past the 8-byte name)?
B56E BNE loop_push_ps_name ; No: continue pushing
B570 LDY #&17 ; Y=&17: destination offset for the reversed name
B572 .loop_pop_ps_name←1← B578 BNE
PLA ; Pull next pushed byte (LIFO -> reversed order)
B573 STA (net_rx_ptr),y ; Store at destination offset Y
B575 DEY ; Step destination back
B576 CPY #&0f ; Reached &0F (one before the destination range)?
B578 BNE loop_pop_ps_name ; No: continue popping
B57A LDA net_rx_ptr_hi ; Copy net_rx_ptr_hi as the TX page (TX shares the same page as RX for this packet)
B57C STA net_tx_ptr_hi ; Set net_tx_ptr_hi
B57E LDA #&0c ; TX low byte = &0C: skip past the TX header to where the reversed name lives
B580 STA net_tx_ptr ; Set net_tx_ptr lo
B582 LDY #3 ; Y=3: copy 4-byte TX header (offsets 3..0)
B584 .loop_copy_tx_hdr←1← B58A BPL
LDA ps_tx_header_template,y ; Read template byte
B587 STA (net_tx_ptr),y ; Write to TX buffer at offset Y
B589 DEY ; Step backwards
B58A BPL loop_copy_tx_hdr ; Loop while Y >= 0
B58C 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.

B58D .ps_tx_header_template←1Used as index base by← B584 LDA
EQUB &80 ; Control byte &80 (immediate TX)
B58E EQUB &9F ; Port &9F (printer server)
B58F EQUB &FF ; Station &FF (any)
B590 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)
B591 .print_station_addr←4← A3D2 JSR← B631 JSR← B651 JSR← B6C9 JSR
PHP ; Save caller's V (controls leading-zero padding via the BVS at &B566)
B592 LDA fs_work_6 ; Read network number (fs_work_6)
B594 BEQ skip_if_local_net ; Network 0 means local: skip the 'NN.' prefix
B596 JSR print_decimal_3dig ; Network non-zero: print as 3-digit decimal
B599 LDA #'.' ; A='.': separator between network and station
B59B JSR osasci ; Print the dot
B59E BIT always_set_v_byte ; Set V so the next BVS branches over the padding (we just printed digits, no padding needed)
B5A1 .skip_if_local_net←1← B594 BEQ
BVS local_net_prefix ; V set: skip leading-space padding
B5A3 JSR print_inline ; V clear (caller wanted padding): print 4 leading spaces via inline string
B5A6 EQUS " "
B5AA .local_net_prefix←1← B5A1 BVS
LDA fs_work_5 ; Read station number (fs_work_5)
B5AC PLP ; Restore caller's V (so print_decimal_3dig honours its own leading-zero suppression)
B5AD 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.

B5B0 .ps_slot_txcb_template
EQUB &80 ; Offset 0: txcb_ctrl = &80 (standard)
B5B1 EQUB &9F ; Offset 1: txcb_port = &9F (PS port)
B5B2 EQUB &00 ; Offset 2: dest station (placeholder, &00)
B5B3 EQUB &00 ; Offset 3: dest network (placeholder, &00)
B5B4 EQUB &10 ; Offset 4: buf1 start lo = &10
B5B5 EQUB &00 ; Offset 5: buf1 start hi (page from net_rx_ptr)
B5B6 EQUB &FF ; Offset 6: buf1 end lo placeholder = &FF
B5B7 EQUB &FF ; Offset 7: buf1 end hi placeholder = &FF
B5B8 EQUB &18 ; Offset 8: buf2 start lo = &18
B5B9 EQUB &00 ; Offset 9: buf2 start hi (page from net_rx_ptr)
B5BA EQUB &FF ; Offset 10: buf2 end lo placeholder = &FF
B5BB 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
B5BC .cmd_pollps
STY ws_ptr_hi ; Save command line pointer high
B5BE JSR init_spool_drive ; Initialise spool/print drive
B5C1 JSR reverse_ps_name_to_tx ; Copy PS name to TX buffer
B5C4 JSR init_ps_slot_from_rx ; Init PS slot from RX data
B5C7 LDY ws_ptr_hi ; Restore command line pointer
B5C9 JSR save_ptr_to_spool_buf ; Save pointer to spool buffer
B5CC LDA (fs_options),y ; Get first argument character
B5CE CMP #&0d ; End of command line?
B5D0 BEQ done_poll_name_parse ; Yes: no argument given
B5D2 LDX #6 ; Max 6 characters for PS name
B5D4 LDY #&10 ; Buffer offset for PS name
B5D6 LDA #' ' ; Space character
B5D8 .loop_pad_poll_name←1← B5DC BNE
STA (net_rx_ptr),y ; Fill buffer position with space
B5DA INY ; Next position
B5DB DEX ; Count down
B5DC BNE loop_pad_poll_name ; Loop until 6 spaces filled
B5DE JSR save_ptr_to_os_text ; Save pointer to OS text
B5E1 LDY ws_ptr_hi ; Restore command line pointer
B5E3 JSR gsinit ; Initialise string reading
B5E6 BEQ done_poll_name_parse ; Empty string: skip to send
B5E8 LDX #6 ; Max 6 characters
B5EA STY ws_ptr_hi ; Save updated string pointer
B5EC LDY #&10 ; Buffer offset for PS name
B5EE STY table_idx ; Save buffer position
B5F0 .loop_read_poll_char←1← B5FC BNE
LDY ws_ptr_hi ; Restore string pointer
B5F2 JSR gsread ; Read next char from string
B5F5 STY ws_ptr_hi ; Save updated string pointer
B5F7 BCS done_poll_name_parse ; End of string: go to send
B5F9 JSR store_char_uppercase ; Store char uppercased in buffer
B5FC BNE loop_read_poll_char ; Loop if more chars to copy
B5FE .done_poll_name_parse←3← B5D0 BEQ← B5E6 BEQ← B5F7 BCS
LDA #&80 ; Enable escape checking
B600 STA need_release_tube ; Set escapable flag
B602 JSR send_net_packet ; Send the poll request packet
B605 JSR pop_requeue_ps_scan ; Pop and requeue PS scan
B608 JSR print_printer_server_is ; Print 'Printer server '
B60B LDY #&21 ; Y=&21: PS-entry flag offset in workspace
B60D LDA (nfs_workspace),y ; Load PS-entry flag
B60F BEQ poll_load_server ; Zero: slot empty, skip display
B611 JSR print_inline ; Print ' "'
B614 EQUS &22
B615 LDY #&18 ; Y=&18: name field offset in RX buffer
B617 .loop_print_poll_name←1← B623 BNE
LDA (net_rx_ptr),y ; Get character from name field
B619 CMP #' ' ; Is it a space?
B61B BEQ done_poll_name_print ; Yes: end of name
B61D JSR osasci ; Print character of PS name
B620 INY ; Next character
B621 CPY #&1e ; Past end of name field?
B623 BNE loop_print_poll_name ; No: continue printing name
B625 .done_poll_name_print←1← B61B BEQ
JSR print_inline ; Print '"' + CR
B628 EQUS &22, " "
B62A NOP ; Bit-7 terminator from preceding stringhi
B62B .poll_load_server←1← B60F BEQ
JSR load_ps_server_addr ; Load this PS server's address for display
B62E BIT always_set_v_byte ; Set V (always) via always_set_v_byte
B631 JSR print_station_addr ; Print the server station address
B634 JSR osnewl ; Print newline
B637 .loop_next_poll_slot←1← B69D BNE
PLA ; Pop saved slot index
B638 BEQ return_6 ; Zero: all slots done, return
B63A PHA ; Save slot offset
B63B TAY ; Transfer to Y
B63C LDA (nfs_workspace),y ; Read slot status byte
B63E BPL poll_mark_slot ; Bit 7 clear: slot inactive
B640 INY ; Advance to station number
B641 INY ; Offset+2 in slot
B642 LDA (nfs_workspace),y ; Read station number low
B644 STA fs_work_5 ; Store station low
B646 INY ; Next byte (offset+3)
B647 LDA (nfs_workspace),y ; Read network number
B649 STA fs_work_6 ; Store network number
B64B INY ; Next byte (offset+4)
B64C LDA (nfs_workspace),y ; Read status page pointer
B64E STA work_ae ; Store pointer low
B650 CLV ; Clear V flag
B651 JSR print_station_addr ; Print station address (V=0)
B654 JSR print_inline ; Print ' is '
B657 EQUS " is "
B65B LDX #0 ; X=0: indexed-indirect access mode
B65D LDA (work_ae,x) ; Read printer status byte
B65F BNE poll_test_status ; Non-zero: not ready
B661 JSR print_inline ; Print 'ready'
B664 EQUS "ready"
B669 CLV ; Ensure V clear so next BVC always taken
B66A BVC poll_entry_done ; Status ready printed: branch to end-of-entry (always)
B66C .poll_test_status←1← B65F BNE
ASL ; Shift status byte left to test its flag bits...
B66D ASL ; ...
B66E ASL ; ...
B66F ASL ; ...(4 shifts move bits 4-7 into C/N)
B670 BCS poll_print_jammed ; C set: status jammed
B672 BMI poll_print_offline ; N set: status off line
B674 CMP #&60 ; Status = 2?
B676 BNE poll_check_busy ; No: check for busy
B678 .poll_print_offline←1← B672 BMI
JSR print_inline ; Print 'jammed'
B67B EQUS "off line"
B683 CLV ; Clear V
B684 BVC poll_entry_done ; Off-line printed: branch to end-of-entry (always)
B686 .poll_check_busy←1← B676 BNE
CMP #&10 ; Status = 1?
B688 BEQ poll_print_busy ; Status = 1: print at cb6a0
B68A .poll_print_jammed←1← B670 BCS
JSR print_inline ; Print 'jammed'
B68D EQUS "jammed"
B693 NOP ; bit-7 terminator + resume opcode
B694 .poll_entry_done←4← B66A BVC← B684 BVC← B6AD BEQ← B6CD BVC
JSR osnewl ; Print newline after the status line
B697 .poll_mark_slot←1← B63E BPL
PLA ; Pull saved slot index
B698 TAY ; Y = slot index
B699 LDA #'?' ; &3F: 'slot processed' marker
B69B STA (nfs_workspace),y ; Store the &3F marker in the workspace slot
B69D BNE loop_next_poll_slot ; Not 1 or 2: default to jammed
B69F .return_6←1← B638 BEQ
RTS ; Return
B6A0 .poll_print_busy←1← B688 BEQ
JSR print_inline ; Print 'busy'
B6A3 EQUS "busy"
B6A7 INC work_ae ; Advance work_ae to next status byte (lo)
B6A9 LDA (work_ae,x) ; Read client station number
B6AB STA fs_work_5 ; Store station low
B6AD BEQ poll_entry_done ; Zero: no client info, skip
B6AF JSR print_inline ; Print ' with station '
B6B2 EQUS " with station "
B6C0 INC work_ae ; Advance work_ae to next status byte (lo)
B6C2 LDA (work_ae,x) ; Read network number byte via (work_ae,X)
B6C4 STA fs_work_6 ; Store network number
B6C6 BIT always_set_v_byte ; Set V flag
B6C9 JSR print_station_addr ; Print client station address
B6CC CLV ; Clear V for the unconditional branch
B6CD 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
B6CF .init_ps_slot_from_rx←1← B5C4 JSR
LDY #&78 ; Start at offset &78
B6D1 .loop_copy_slot_tmpl←1← B6E3 BNE
LDA lb538,y ; Load template byte
B6D4 CPY #&7d ; At offset &7D?
B6D6 BEQ subst_rx_page_byte ; Yes: substitute RX page
B6D8 CPY #&81 ; At offset &81?
B6DA BNE store_slot_tmpl_byte ; No: use template byte
B6DC .subst_rx_page_byte←1← B6D6 BEQ
LDA net_rx_ptr_hi ; Use RX buffer page instead
B6DE .store_slot_tmpl_byte←1← B6DA BNE
STA (work_ae),y ; Store byte in slot buffer
B6E0 INY ; Next offset
B6E1 CPY #&84 ; Past end of slot (&84)?
B6E3 BNE loop_copy_slot_tmpl ; No: continue copying
B6E5 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
B6E6 .store_char_uppercase←2← B44A JSR← B5F9 JSR
LDY table_idx ; Y = current buffer position
B6E8 AND #&7f ; Strip high bit
B6EA CMP #'a' ; Is it lowercase 'a' or above?
B6EC BCC done_uppercase_store ; Below 'a': not lowercase
B6EE CMP #'{' ; Above 'z'?
B6F0 BCS done_uppercase_store ; Yes: not lowercase
B6F2 AND #&5f ; Convert to uppercase
B6F4 .done_uppercase_store←2← B6EC BCC← B6F0 BCS
STA (net_rx_ptr),y ; Store in RX buffer
B6F6 INY ; Next buffer position
B6F7 STY table_idx ; Update buffer position
B6F9 DEX ; Decrement character count
B6FA 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)
B6FB .cmd_prot
LDA #&ff ; Load &FF (protect)
B6FD 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)
B6FF .cmd_unprot
LDA #0 ; Load &00 (unprotect)
B701 .unprot_clear←1← B6FD BNE
PHP ; Save Z flag (1 = unprot, 0 = prot) for later
B702 JSR set_ws_pair_0d68_0d69 ; Mirror A into prot_status / prot_status_save pair
B705 LDX #&11 ; X=&11: CMOS offset for Econet flags
B707 JSR osbyte_a1 ; OSBYTE &A1 reads CMOS byte &11 -> Y
B70A TYA ; A = current CMOS byte
B70B PLP ; Restore the saved Z flag
B70C BEQ unprot_check ; Z=1: unprot path
B70E ORA #&40 ; Set bit 6 (protection on)
B710 BNE unprot_apply ; ALWAYS branch to write-back
B712 .unprot_check←1← B70C BEQ
AND #&bf ; Clear bit 6 (protection off)
B714 .unprot_apply←1← B710 BNE
TAY ; Y = new flag byte
B715 LDA #osbyte_write_cmos_ram ; OSBYTE &A2: write CMOS byte
B717 .loop_match_prot_attr
LDX #&11 ; X=&11: CMOS offset for Econet flags
B719 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
B71C .cmd_wipe
JSR mask_owner_access ; Reset access flags before parsing the new argument
B71F LDA #0 ; A=0: clear the file-iteration counter
B721 STA fs_work_5 ; Store iteration counter (steps to next file each loop)
B723 JSR save_ptr_to_os_text ; Save text pointer for re-reading the wildcard each iteration
B726 JSR parse_filename_arg ; Parse the wildcard filename into the &C030 buffer
B729 INX ; Step X past the CR terminator (so X = filename length+1)
B72A 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).

B72C .request_next_wipe←1← B768 JMP
LDA #1 ; FS function code byte 0 = 1 (examine)
B72E STA hazel_txcb_data ; TXCB[5] = 1: 'examine directory entry'
B731 STA hazel_txcb_count ; TXCB[7] = 1: ditto for the second buffer slot
B734 LDX fs_work_5 ; Load current iteration index
B736 STX hazel_txcb_flag ; TXCB[6] = iteration index (which directory entry)
B739 LDX #3 ; X=3: copy starting at TX[3] (after the FS header bytes)
B73B JSR copy_arg_to_buf ; Copy the parsed filename into the TX buffer
B73E LDY #3 ; Y=3: FS function code 'Examine'
B740 LDA #&80 ; A=&80: set bit 7 of need_release_tube to flag long-lived TX
B742 STA need_release_tube ; Store flag
B744 JSR save_net_tx_cb ; Send the examine request and wait for reply
B747 LDA hazel_txcb_data ; Read FS reply byte 0 (status code)
B74A BNE check_wipe_attr ; Non-zero status: process the response
B74C LDA #osbyte_flush_buffer_class ; OSBYTE &0F: flush input buffer class
B74E LDX #1 ; X=1: flush keyboard buffer
B750 JSR osbyte ; Flush keyboard buffer (clear pending Y/N keypress) Flush selected class of buffer
B753 LDA #osbyte_scan_keyboard_from_16 ; OSBYTE &7A: scan keyboard from key 16 (clear keypress queue)
B755 JSR osbyte ; Run the scan Scan keyboard from key 16
B758 LDY #0 ; Y=0: no key
B75A LDA #osbyte_write_keys_pressed ; OSBYTE &78: write keys-pressed state
B75C JMP osbyte ; Tail-call OSBYTE: clean up and return Write all keys pressed information
B75F .check_wipe_attr←1← B74A BNE
LDA hazel_txcb_end ; Read attribute byte from FS reply (TXCB[&2F])
B762 .loop_check_if_locked←1← B772 BNE
CMP #'L' ; Is it 'L' (locked)?
B764 BNE check_wipe_dir ; Not locked: check for directory
B766 .skip_wipe_locked←1← B7E3 JMP
INC fs_work_5 ; Locked: skip this file, advance to next
B768 JMP request_next_wipe ; Loop back to request the next directory entry
B76B .check_wipe_dir←1← B764 BNE
CMP #'D' ; Is it 'D' (directory)?
B76D BNE show_wipe_prompt ; Not a directory: prompt the user
B76F LDA hazel_examine_attr ; Directory: check second attribute byte (size)
B772 BNE loop_check_if_locked ; Loop back to attribute test (re-checks if non-empty)
B774 .show_wipe_prompt←1← B76D BNE
LDX #1 ; X=1: scan name starting at TX[1]
B776 LDY fs_work_6 ; Y = end-of-buffer offset (saved earlier in fs_work_6)
B778 .loop_copy_wipe_name←1← B785 BNE
LDA hazel_txcb_flag,x ; Read filename byte from TX[6+X]
B77B JSR print_char_no_spool ; Print via *SPOOL-bypassing OSASCI
B77E STA hazel_parse_buf,y ; Also store into the parse buffer for later use
B781 INY ; Step parse-buffer offset
B782 INX ; Step TX-buffer offset
B783 CPX #&0c ; Reached &0C (12 chars)?
B785 BNE loop_copy_wipe_name ; No: continue copying
B787 JSR print_inline_no_spool ; Print '(?/' prompt prefix and read response
B78A EQUS "(?/" ; Inline string '(?/' is read by the hook above
B78D NOP ; NOP -- bit-7 terminator + resume opcode for the '(?/' stringhi
B78E JSR prompt_yn ; Print 'Y/N) ' via prompt_yn (reads keypress)
B791 CMP #'?' ; Was the keypress '?' (help)?
B793 BNE check_wipe_response ; Not '?': process Y/N response
B795 LDA #&0d ; '?': print CR before help text
B797 JSR print_byte_no_spool ; Print CR character
B79A LDX #2 ; X=2: start of name in TX[2]
B79C .loop_print_wipe_info←1← B7A5 BNE
LDA hazel_txcb_data,x ; Read name byte from TX[5+X] (FS reply)
B79F JSR print_char_no_spool ; Print name char (no spool)
B7A2 INX ; Advance index
B7A3 CPX #&3e ; End of TX[5+X] name field at offset &3E?
B7A5 BNE loop_print_wipe_info ; No: continue printing
B7A7 JSR print_inline_no_spool ; Print 'Wipe? ' help suffix via inline string
B7AA EQUS " ("
B7AC NOP ; Bit-7 terminator + resume
B7AD JSR prompt_yn ; Re-prompt user with prompt_yn
B7B0 .check_wipe_response←1← B793 BNE
AND #&df ; Mask to upper-case ('A'..'Z' map to themselves)
B7B2 CMP #'Y' ; Was the response 'Y'?
B7B4 BNE skip_wipe_to_next ; No: skip this entry, advance to next
B7B6 JSR print_char_no_spool ; Yes: echo the keypress
B7B9 LDX #0 ; X=0: start scanning the parse-buffer name
B7BB LDA hazel_parse_buf,x ; Read first parse-buffer byte at hazel_parse_buf
B7BE CMP #&0d ; Is it CR (no path component)?
B7C0 BEQ use_wipe_leaf_name ; Yes: use leaf-name only path at &B7E6
B7C2 .loop_build_wipe_cmd←1← B7D7 BNE
LDA hazel_parse_buf,x ; Read parse-buffer byte at hazel_parse_buf+X
B7C5 CMP #&0d ; Is it CR (end of name)?
B7C7 BNE skip_if_not_space ; No: check for space separator
B7C9 LDA #'.' ; CR: substitute '.' so the dir prefix terminates with a separator
B7CB .skip_if_not_space←1← B7C7 BNE
CMP #' ' ; Is it space?
B7CD BNE store_wipe_tx_char ; No: store byte as-is
B7CF .set_wipe_cr_end←1← B7F2 BEQ
LDA #&0d ; Yes: substitute CR (end-of-cmd)
B7D1 .store_wipe_tx_char←1← B7CD BNE
STA hazel_txcb_data,x ; Store byte into TX[5+X] (delete-command buffer)
B7D4 INX ; Advance index
B7D5 CMP #&0d ; Was that byte CR (just stored)?
B7D7 BNE loop_build_wipe_cmd ; No: continue copying
B7D9 LDY #&14 ; Y=&14: FS function code &14 = delete
B7DB JSR save_net_tx_cb ; Send the delete request and wait for reply
B7DE DEC fs_work_5 ; Decrement iteration counter so we re-examine the now-shifted-up slot
B7E0 .skip_wipe_to_next←1← B7B4 BNE
JSR print_newline_no_spool ; Print newline before next entry
B7E3 JMP skip_wipe_locked ; Loop back to skip_wipe_locked (= request next entry)
B7E6 .use_wipe_leaf_name←1← B7C0 BEQ
DEX ; DEX: pre-decrement before the INX in the loop
B7E7 .loop_copy_wipe_leaf←1← B7F0 BNE
INX ; Advance index
B7E8 LDA hazel_parse_buf_1,x ; Read parse-buffer byte at hazel_parse_buf_1+X (skip CR at hazel_parse_buf)
B7EB STA hazel_txcb_data,x ; Store into TX[5+X] (delete-command buffer)
B7EE CMP #' ' ; Reached space (end-of-leaf)?
B7F0 BNE loop_copy_wipe_leaf ; No: continue copying
B7F2 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)
B7F4 .prompt_yn←2← B78E JSR← B7AD JSR
JSR print_inline_no_spool ; Print 'Y/N) ' via the inline-string helper
B7F7 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)
B7FC .flush_and_read_char
LDA #osbyte_flush_buffer_class ; OSBYTE &0F: flush buffer class
B7FE LDX #1 ; X=1: flush input buffers
B800 JSR osbyte ; Flush keyboard buffer before read Flush selected class of buffer
B803 JSR osrdch ; Read character from input stream
B806 BCC return_7 ; C clear: character read OK
B808 JMP raise_escape_error ; Escape pressed: raise error
B80B .return_7←1← B806 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
B80C .init_channel_table←1← 8BC5 JSR
LDA #0 ; A=0: clear value
B80E TAY ; Y=0: start index
B80F .loop_clear_chan_table←1← B813 BNE
STA hazel_fcb_addr_lo,y ; Clear channel table entry
B812 INY ; Next entry
B813 BNE loop_clear_chan_table ; Loop until all 256 bytes cleared
B815 LDY #&0b ; Offset &0F in receive buffer
B817 LDA (net_rx_ptr),y ; Get number of available channels
B819 SEC ; Prepare subtraction
B81A SBC #&0c ; Subtract 'Z' to get negative count
B81C TAY ; Y = negative channel count (index)
B81D LDA #&40 ; Channel marker &40 (available)
B81F .loop_mark_chan_avail←1← B825 BPL
STA hazel_fcb_addr_lo,y ; Mark channel slot as available
B822 DEY ; Previous channel slot
B823 CPY #&b8 ; Reached start of channel range?
B825 BPL loop_mark_chan_avail ; No: continue marking channels
B827 INY ; Point to first channel slot
B828 LDA #&c0 ; Active channel marker &C0
B82A STA hazel_fcb_addr_lo,y ; Mark first channel as active
B82D 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
B82E .attr_to_chan_index←6← 9403 JSR← 9419 JSR← 9F57 JSR← 9F87 JSR← 9FBF JSR← BB0C JSR
PHP ; Save flags
B82F SEC ; Prepare subtraction
B830 SBC #&20 ; Subtract &20 to get table index
B832 BMI error_chan_out_of_range ; Negative: out of valid range
B834 CMP #&10 ; Above maximum channel index &0F?
B836 BCC return_chan_index ; In range: valid index
B838 .error_chan_out_of_range←1← B832 BMI
LDA #&ff ; Out of range: return &FF (invalid)
B83A .return_chan_index←1← B836 BCC
PLP ; Restore flags
B83B TAX ; X = channel index (or &FF)
B83C 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
B83D .check_chan_char←2← A0AA JSR← B8B5 JSR
CMP #' ' ; Below space?
B83F BCC err_net_chan_invalid ; Yes: invalid channel character
B841 CMP #'0' ; Below '0'?
B843 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 &B81D 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)
B845 .err_net_chan_invalid←2← 9EC0 JMP← B83F BCC
PHA ; Save channel character
B846 .error_chan_not_found←1← B878 BEQ
LDA #&de ; Error code &DE
B848 .err_net_chan_not_found
JSR error_inline_log ; Generate 'Net channel' error
B84B EQUS "Net channel", &00
B857 JSR false_ref_6f6e ; Error string continuation (unreachable)
B85A STZ tx_buffer_scratch,x ; Clear tx_buffer_scratch+X scratch
B85C EQUS "on this file server"
B86F 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
B870 .lookup_chan_by_char←2← A1F4 JSR← B843 BCC
PHA ; Save channel character
B871 SEC ; Prepare subtraction
B872 SBC #&20 ; Convert char to table index
B874 TAX ; X = channel table index
B875 LDA hazel_fcb_slot_attr,x ; Look up network number for channel
B878 BEQ error_chan_not_found ; Zero: channel not found, raise error
B87A JSR match_station_net ; Check station/network matches current
B87D BNE error_chan_not_here ; No match: build detailed error msg
B87F PLA ; Discard saved channel character
B880 LDA hazel_fcb_status,x ; Load channel status flags
B883 RTS ; Return; A = channel flags
B884 .error_chan_not_here←1← B87D BNE
LDA #&de ; Error code &DE
B886 STA error_text ; Store error code in error block
B889 LDA #0 ; BRK opcode
B88B STA error_block ; Store BRK at start of error block
B88E TAX ; X=0: copy index
B88F .loop_copy_chan_err_str←1← B896 BNE
INX ; Advance copy position
B890 LDA net_chan_err_strings,x ; Load 'Net channel' string byte
B893 STA error_text,x ; Copy to error text
B896 BNE loop_copy_chan_err_str ; Continue until NUL terminator
B898 STX fs_load_addr_2 ; Save end-of-string position
B89A STX fs_work_4 ; Save for suffix append
B89C PLA ; Retrieve channel character
B89D JSR append_space_and_num ; Append ' N' (channel number)
B8A0 LDY fs_work_4 ; Load 'Net channel' end position
B8A2 .loop_append_err_suffix←1← B8AA BNE
INY ; Skip past NUL to suffix string
B8A3 INX ; Advance destination position
B8A4 LDA net_chan_err_strings,y ; Load ' not on this...' suffix byte
B8A7 STA error_text,x ; Append to error message
B8AA BNE loop_append_err_suffix ; Continue until NUL
B8AC 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
B8AF .store_result_check_dir←2← BB9D JSR← BC23 JSR
LDA hazel_chan_attr ; Load current channel attribute
B8B2 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)
B8B5 .check_not_dir←2← 9EF2 JSR← A196 JSR
JSR check_chan_char ; Validate and look up channel
B8B8 AND #2 ; Test directory flag (bit 1)
B8BA BEQ return_8 ; Not a directory: return OK
B8BC LDA #&a8 ; Error code &A8
B8BE JSR error_inline_log ; Generate 'Is a dir.' error
B8C1 EQUS "Is a directory", &00
B8D0 .return_8←1← B8BA 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
B8D1 .alloc_fcb_slot←7← A050 JSR← A085 JSR← A5DA JSR← A677 JSR← A6A2 JSR← A6D9 JSR← B908 JSR
PHA ; Save channel attribute
B8D2 LDX #&20 ; Start scanning from FCB slot &20
B8D4 .loop_scan_fcb_slots←1← B8DC BNE
LDA hazel_fcb_addr_mid,x ; Load FCB station byte
B8D7 BEQ done_found_free_slot ; Zero: slot is free, use it
B8D9 INX ; Try next slot
B8DA CPX #&30 ; Past last FCB slot &2F?
B8DC BNE loop_scan_fcb_slots ; No: check next slot
B8DE PLA ; No free slot: discard saved attribute
B8DF LDA #0 ; A=0: return failure (Z set)
B8E1 RTS ; Return
B8E2 .done_found_free_slot←1← B8D7 BEQ
PLA ; Restore channel attribute
B8E3 STA hazel_fcb_addr_mid,x ; Store attribute in FCB slot
B8E6 LDA #0 ; A=0: clear value
B8E8 STA hazel_fcb_addr_lo_minus20,x ; Clear FCB transfer count low
B8EB STA hazel_fcb_addr_mid_minus20,x ; Clear FCB transfer count mid
B8EE STA hazel_fcb_addr_lo,x ; Clear FCB transfer count high
B8F1 LDA hazel_fs_station ; Load current station number
B8F4 STA hazel_fcb_addr_hi,x ; Store station in FCB
B8F7 LDA hazel_fs_network ; Load current network number
B8FA STA hazel_fcb_slot_attr,x ; Store network in FCB
B8FD TXA ; Get FCB slot index
B8FE PHA ; Save slot index
B8FF SEC ; Prepare subtraction
B900 SBC #&20 ; Convert slot to channel index (0-&0F)
B902 TAX ; X = channel index
B903 PLA ; Restore A = FCB slot index
B904 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
B905 .alloc_fcb_or_error←2← 9FFF JSR← A536 JSR
PHA ; Save argument
B906 LDA #0 ; A=0: allocate any available slot
B908 JSR alloc_fcb_slot ; Try to allocate an FCB slot
B90B BNE return_alloc_success ; Success: slot allocated
B90D LDA #&c0 ; Error code &C0
B90F JSR error_inline_log ; Generate 'No more FCBs' error
B912 EQUS "Too many open files", &00
B926 .return_alloc_success←1← B90B BNE
PLA ; Restore argument
B927 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
B928 .close_all_net_chans←3← 9783 JSR← 9811 JSR← A6E9 JSR
CLC ; C=0: close all matching channels
B929 .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)
B92C .scan_fcb_flags←1← A0BF JSR
LDX #&10 ; Start from FCB slot &10
B92E .loop_scan_fcb_down←4← B93A BVC← B944 BVS← B949 BNE← B953 BEQ
DEX ; Previous FCB slot
B92F BPL skip_if_slots_done ; More slots to check
B931 RTS ; All FCB slots processed, return
B932 .skip_if_slots_done←1← B92F BPL
LDA hazel_fcb_status,x ; Load channel flags for this slot
B935 TAY ; Save flags in Y
B936 AND #2 ; Test active flag (bit 1)
B938 BEQ done_check_station ; Not active: check station match
B93A BVC loop_scan_fcb_down ; V clear (close all): next slot
B93C BCC done_check_station ; C clear: check station match
B93E TYA ; Restore original flags
B93F AND #&df ; Clear write-pending flag (bit 5)
B941 STA hazel_fcb_status,x ; Update channel flags
B944 BVS loop_scan_fcb_down ; Next slot (V always set here)
B946 .done_check_station←2← B938 BEQ← B93C BCC
JSR match_station_net ; Check if channel belongs to station
B949 BNE loop_scan_fcb_down ; No match: skip to next slot
B94B LDA #0 ; A=0: clear channel
B94D STA hazel_fcb_status,x ; Clear channel flags (close it)
B950 STA hazel_fcb_slot_attr,x ; Clear network number
B953 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
B955 .match_station_net←7← A65E JSR← A689 JSR← A6C0 JSR← AA2A JSR← AFF7 JSR← B87A JSR← B946 JSR
LDA hazel_fcb_state_byte,x ; Load FCB station number
B958 EOR hazel_fs_station ; Compare with current station
B95B BNE rts_match_stn ; Different: Z=0, no match
B95D LDA hazel_fcb_network,x ; Load FCB network number
B960 EOR hazel_fs_network ; Compare with current network
B963 .rts_match_stn←1← B95B 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)
B964 .find_open_fcb←2← BA63 JSR← BB51 JSR
LDX hazel_cur_fcb_index ; Load current FCB index
B967 BIT always_set_v_byte ; Set V flag (first pass marker)
B96A .loop_find_fcb←4← B979 BVC← B97F BPL← B99C BVS← B9A3 BNE
INX ; Next FCB slot
B96B CPX #&10 ; Past end of table (&10)?
B96D BNE skip_if_no_wrap ; No: continue checking
B96F LDX #0 ; Wrap around to slot 0
B971 .skip_if_no_wrap←1← B96D BNE
CPX hazel_cur_fcb_index ; Back to starting slot?
B974 BNE done_check_fcb_status ; No: check this slot
B976 BVC loop_scan_empty_fcb ; V clear (second pass): scan empties
B978 CLV ; Clear V for second pass
B979 BVC loop_find_fcb ; Continue scanning
B97B .done_check_fcb_status←1← B974 BNE
LDA hazel_fcb_flags,x ; Load FCB status flags
B97E ROL ; Shift bit 7 (in-use) into carry
B97F BPL loop_find_fcb ; Not in use: skip
B981 AND #4 ; Test bit 2 (modified flag)
B983 BNE skip_if_modified_fcb ; Modified: check further conditions
B985 .done_select_fcb←1← B9A5 BEQ
DEX ; Adjust for following INX
B986 .loop_scan_empty_fcb←2← B976 BVC← B991 BPL
INX ; Next FCB slot
B987 CPX #&10 ; Past end of table?
B989 BNE done_test_empty_slot ; No: continue
B98B LDX #0 ; Wrap around to slot 0
B98D .done_test_empty_slot←1← B989 BNE
LDA hazel_fcb_flags,x ; Load FCB status flags
B990 ROL ; Shift bit 7 into carry
B991 BPL loop_scan_empty_fcb ; Not in use: continue scanning
B993 SEC ; Set carry
B994 ROR ; Restore original flags
B995 STA hazel_fcb_flags,x ; Save flags back (mark as found)
B998 LDX hazel_cur_fcb_index ; Restore original FCB index
B99B RTS ; Return with found slot in X
B99C .skip_if_modified_fcb←1← B983 BNE
BVS loop_find_fcb ; V set (first pass): skip modified
B99E LDA hazel_fcb_flags,x ; Load FCB status flags
B9A1 AND #&20 ; Test bit 5 (offset pending)
B9A3 BNE loop_find_fcb ; Bit 5 set: skip this slot
B9A5 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)
B9A7 .init_wipe_counters←2← B9EA JSR← BA95 JSR
LDY #1 ; Initial pass count = 1
B9A9 STY hazel_pass_counter ; Store pass counter
B9AC DEY ; Y=0
B9AD STY hazel_byte_counter_lo ; Clear byte counter low
B9B0 STY hazel_offset_counter ; Clear offset counter
B9B3 STY hazel_transfer_flag ; Clear transfer flag
B9B6 TYA ; A=0
B9B7 LDX #2 ; Clear 3 counter bytes
B9B9 .loop_clear_counters←1← B9BD BPL
STA hazel_xfer_init_zeros,x ; Clear counter byte
B9BC DEX ; Next byte
B9BD BPL loop_clear_counters ; Loop for indices 2, 1, 0
B9BF STX hazel_sentinel_cd ; Store &FF as sentinel in xfer_sentinel_1
B9C2 STX hazel_sentinel_ce ; Store &FF as sentinel in xfer_sentinel_2
B9C5 LDX #&ca ; X=&CA: workspace offset
B9C7 LDY #&c2 ; Y=&C2: HAZEL workspace page &C2
B9C9 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
B9CA .start_wipe_pass←2← BA5A JSR← BB84 JSR
JSR verify_ws_checksum ; Verify workspace checksum integrity
B9CD STX hazel_cur_fcb_index ; Save current FCB index
B9D0 LDA hazel_fcb_flags,x ; Load FCB status flags
B9D3 ROR ; Shift bit 0 (active) into carry
B9D4 BCC done_clear_fcb_active ; Not active: clear status and return
B9D6 LDA hazel_station_lo ; Save current station low to stack
B9D9 PHA ; Push station low
B9DA LDA hazel_station_hi ; Save current station high
B9DD PHA ; Push station high
B9DE LDA hazel_fcb_station_lo,x ; Load FCB station low
B9E1 STA hazel_station_lo ; Set as working station low
B9E4 LDA hazel_fcb_station_hi,x ; Load FCB station high
B9E7 STA hazel_station_hi ; Set as working station high
B9EA JSR init_wipe_counters ; Reset transfer counters
B9ED DEC hazel_offset_counter ; Set offset to &FF (no data yet)
B9F0 DEC hazel_pass_counter ; Set pass counter to 0 (flush mode)
B9F3 LDX hazel_cur_fcb_index ; Reload FCB index
B9F6 TXA ; Transfer to A
B9F7 CLC ; Prepare addition
B9F8 ADC #&c3 ; Add &11 for buffer page offset
B9FA STA hazel_buf_addr_hi ; Store buffer address high byte
B9FD LDA hazel_fcb_flags,x ; Load FCB status flags
BA00 AND #&20 ; Test bit 5 (has saved offset)
BA02 BEQ done_restore_offset ; No offset: skip restore
BA04 LDA hazel_fcb_offset_save,x ; Load saved byte offset
BA07 STA hazel_offset_counter ; Restore offset counter
BA0A .done_restore_offset←1← BA02 BEQ
LDA hazel_fcb_attr_ref,x ; Load FCB attribute reference
BA0D STA hazel_chan_ref ; Store as current reference
BA10 TAX ; Transfer to X
BA11 JSR read_rx_attribute ; Read saved receive attribute
BA14 PHA ; Push to stack
BA15 TXA ; Restore attribute to A
BA16 STA (net_rx_ptr),y ; Set attribute in receive buffer
BA18 LDX #&ca ; X=&CA: workspace offset
BA1A LDY #&c2 ; Y=&C2: HAZEL workspace page &C2
BA1C LDA #0 ; A=0: standard transfer mode
BA1E JSR send_and_receive ; Send data and receive response
BA21 LDX hazel_cur_fcb_index ; Reload FCB index
BA24 PLA ; Restore saved receive attribute
BA25 JSR store_rx_attribute ; Restore receive attribute
BA28 PLA ; Restore station high
BA29 STA hazel_station_hi ; Store station high
BA2C PLA ; Restore station low
BA2D STA hazel_station_lo ; Store station low
BA30 .done_clear_fcb_active←1← B9D4 BCC
LDA #&dc ; Mask &DC: clear bits 0, 1, 5
BA32 AND hazel_fcb_flags,x ; Clear active and offset flags
BA35 STA hazel_fcb_flags,x ; Update FCB status
BA38 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)
BA39 .save_fcb_context←2← BAFC JSR← BCB5 JSR
LDX #&0c ; Copy 13 bytes (indices 0 to &0C)
BA3B .loop_save_tx_context←1← BA45 BPL
LDA hazel_txcb_port,x ; Load TX buffer byte
BA3E STA hazel_ctx_buffer,x ; Save to context buffer at &10D9
BA41 LDA fs_load_addr,x ; Load workspace byte from fs_load_addr
BA43 PHA ; Save to stack
BA44 DEX ; Next byte down
BA45 BPL loop_save_tx_context ; Loop for all 13 bytes
BA47 CPY #0 ; Y=0? (no FCB to process)
BA49 BNE done_save_context ; Non-zero: scan and process FCBs
BA4B JMP loop_restore_workspace ; Y=0: skip to restore workspace
BA4E .done_save_context←1← BA49 BNE
PHP ; Save flags
BA4F LDX #&ff ; X=&FF: start scanning from -1
BA51 .loop_find_pending_fcb←2← BA55 BPL← BA58 BPL
INX ; Next FCB slot
BA52 LDA hazel_fcb_flags,x ; Load FCB status flags
BA55 BPL loop_find_pending_fcb ; Bit 7 clear: not pending, skip
BA57 ASL ; Shift bit 6 to bit 7
BA58 BPL loop_find_pending_fcb ; Bit 6 clear: skip
BA5A JSR start_wipe_pass ; Flush this FCB's pending data
BA5D LDA #&40 ; Pending marker &40
BA5F STA hazel_fcb_flags,x ; Mark FCB as pending-only
BA62 PHP ; Save flags
BA63 JSR find_open_fcb ; Find next available FCB slot
BA66 PLP ; Restore flags
BA67 LDA hazel_chan_attr ; Load current channel attribute
BA6A STA hazel_chan_ref ; Store as current reference
BA6D PHA ; Save attribute
BA6E SEC ; Prepare attribute-to-channel conversion
BA6F SBC #&20 ; Convert attribute (&20+) to channel index
BA71 TAY ; Y = attribute index
BA72 LDA hazel_fcb_slot_attr,y ; Load station for this attribute
BA75 STA hazel_txcb_data ; Store station in TX buffer
BA78 PLA ; Restore attribute
BA79 STA hazel_fcb_attr_ref,x ; Store attribute in FCB slot
BA7C LDA hazel_station_lo ; Load working station low
BA7F STA hazel_fcb_station_lo,x ; Store in TX buffer
BA82 LDA hazel_station_hi ; Load working station high
BA85 STA hazel_fcb_station_hi,x ; Store in TX buffer
BA88 TXA ; Get FCB slot index
BA89 CLC ; Prepare addition
BA8A ADC #&c3 ; Add &11 for buffer page offset
BA8C STA hazel_buf_addr_hi ; Store buffer address high byte
BA8F PLP ; Restore flags
BA90 BVC done_init_wipe ; V clear: skip directory request
BA92 JSR flush_fcb_if_stn_known ; Command byte = 0
BA95 .done_init_wipe←1← BA90 BVC
JSR init_wipe_counters ; Reset transfer counters
BA98 JSR read_rx_attribute ; Read saved receive attribute
BA9B PHA ; Function code &0D
BA9C LDA hazel_chan_ref ; Load current reference
BA9F STA (net_rx_ptr),y ; Set in receive buffer
BAA1 LDY #&c2 ; Y=&C2: HAZEL workspace page &C2
BAA3 LDA #2 ; A=2: transfer mode 2
BAA5 JSR send_and_receive ; Send and receive data
BAA8 PLA ; Restore receive attribute
BAA9 JSR store_rx_attribute ; Restore receive attribute
BAAC LDX hazel_cur_fcb_index ; Reload FCB index
BAAF LDA hazel_pass_counter ; Load pass counter
BAB2 BNE done_calc_offset ; Non-zero: data received, calc offset
BAB4 LDA hazel_offset_counter ; Load offset counter
BAB7 BEQ done_set_fcb_active ; Zero: no data received at all
BAB9 .done_calc_offset←1← BAB2 BNE
LDA hazel_offset_counter ; Load offset counter
BABC EOR #&ff ; Negate (ones complement)
BABE CLC ; Clear carry for add
BABF ADC #1 ; Complete twos complement negation
BAC1 STA hazel_fcb_offset_save,x ; Store negated offset in FCB
BAC4 LDA #&20 ; Set bit 5 (has saved offset)
BAC6 ORA hazel_fcb_flags,x ; Add to FCB flags
BAC9 STA hazel_fcb_flags,x ; Update FCB status
BACC LDA hazel_buf_addr_hi ; Load buffer address high byte
BACF STA fs_load_addr_3 ; Set pointer high byte
BAD1 LDA #0 ; A=0: pointer low byte and clear val
BAD3 STA fs_load_addr_2 ; Set pointer low byte
BAD5 LDY hazel_fcb_offset_save,x ; Load negated offset (start of clear)
BAD8 .loop_clear_buffer←1← BADB BNE
STA (fs_load_addr_2),y ; Clear buffer byte
BADA INY ; Next byte
BADB BNE loop_clear_buffer ; Loop until page boundary
BADD .done_set_fcb_active←1← BAB7 BEQ
LDA #2 ; Set bit 1 (active flag)
BADF ORA hazel_fcb_flags,x ; Add active flag to status
BAE2 STA hazel_fcb_flags,x ; Update FCB status
BAE5 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.

BAE7 .loop_restore_workspace←2← BA4B JMP← BAEE BNE
PLA ; Restore workspace byte from stack
BAE8 STA fs_load_addr,y ; Store to fs_load_addr workspace
BAEB INY ; Next byte
BAEC CPY #&0d ; Restored all 13 bytes?
BAEE 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.

BAF0 .restore_catalog_entry←1← BCE5 JSR
LDY #&0c ; Copy 13 bytes (indices 0 to &0C)
BAF2 .loop_restore_tx_buf←1← BAF9 BPL
LDA hazel_ctx_buffer,y ; Load saved catalog byte from &10D9
BAF5 STA hazel_txcb_port,y ; Restore to TX buffer
BAF8 DEY ; Next byte down
BAF9 BPL loop_restore_tx_buf ; Loop for all bytes
BAFB 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 &BAEB). Used once the first scan past slot &0F has failed and the search needs to restart from slot 0 with the saved context restored.

BAFC .loop_save_before_match←1← BB1B 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
BAFF .find_matching_fcb←3← A13B JSR← BBB8 JSR← BC55 JSR
LDX #&ff ; X=&FF: start scanning from -1
BB01 .loop_reload_attr←2← BB3D BNE← BB45 BNE
LDY hazel_chan_attr ; Load channel attribute to match
BB04 .loop_next_fcb_slot←2← BB23 BEQ← BB29 BNE
INX ; Next FCB slot
BB05 CPX #&10 ; Past end of table (&10)?
BB07 BNE done_test_fcb_active ; No: check this slot
BB09 LDA hazel_chan_attr ; Load channel attribute
BB0C JSR attr_to_chan_index ; Convert to channel index
BB0F LDA hazel_fcb_addr_hi,x ; Load station for this channel
BB12 STA hazel_station_hi ; Store as match target station high
BB15 LDA hazel_fcb_addr_mid,x ; Load port for this channel
BB18 STA hazel_station_lo ; Store as match target station low
BB1B JMP loop_save_before_match ; Save context and rescan from start
BB1E .done_test_fcb_active←1← BB07 BNE
LDA hazel_fcb_flags,x ; Load FCB status flags
BB21 AND #2 ; Test active flag (bit 1)
BB23 BEQ loop_next_fcb_slot ; Not active: skip to next
BB25 TYA ; Get attribute to match
BB26 CMP hazel_fcb_attr_ref,x ; Compare with FCB attribute ref
BB29 BNE loop_next_fcb_slot ; No attribute match: skip
BB2B STX hazel_cur_fcb_index ; Save matching FCB index
BB2E PHP ; Save flags from attribute compare
BB2F SEC ; Prepare subtraction
BB30 SBC #&20 ; Convert attribute to channel index
BB32 PLP ; Restore flags from attribute compare
BB33 TAY ; Y = channel index
BB34 LDX hazel_cur_fcb_index ; Reload FCB index
BB37 LDA hazel_fcb_addr_mid,y ; Load channel station byte
BB3A CMP hazel_fcb_station_lo,x ; Compare with FCB station
BB3D BNE loop_reload_attr ; Station mismatch: try next
BB3F LDA hazel_fcb_addr_hi,y ; Load channel network byte
BB42 CMP hazel_fcb_station_hi,x ; Compare with FCB network
BB45 BNE loop_reload_attr ; Network mismatch: try next
BB47 LDA hazel_fcb_flags,x ; Load FCB flags
BB4A BPL return_test_offset ; Bit 7 clear: no pending flush
BB4C AND #&7f ; Clear pending flag (bit 7)
BB4E STA hazel_fcb_flags,x ; Update FCB status
BB51 JSR find_open_fcb ; Find new open FCB slot
BB54 LDA hazel_fcb_flags,x ; Reload FCB flags
BB57 .return_test_offset←1← BB4A BPL
AND #&20 ; Test bit 5 (has offset data)
BB59 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
BB5A .inc_fcb_byte_count←2← BBFD JSR← BC95 JSR
INC hazel_fcb_addr_lo,x ; Increment byte count low
BB5D BNE rts_inc_fcb_count ; No overflow: done
BB5F INC hazel_fcb_addr_mid,x ; Increment byte count mid
BB62 BNE rts_inc_fcb_count ; No overflow: done
BB64 INC hazel_fcb_addr_hi,x ; Increment byte count high
BB67 .rts_inc_fcb_count←2← BB5D BNE← BB62 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)
BB68 .process_all_fcbs←9← 8DB5 JSR← 907F JSR← 9776 JSR← 9EC7 JSR← 9F0A JSR← 9FD7 JSR← A091 JSR← A19A JSR← A9FF JSR
PHX ; Save X on entry
BB69 PHY ; Save Y across the body
BB6A LDX #&f7 ; X=&F7: save 9 workspace bytes (&F7..&FF)
BB6C .loop_save_fcb_workspace←1← BB71 BMI
LDA fcb_workspace_idx_base,x ; Load workspace byte
BB6F PHA ; Push fs_options
BB70 INX ; Next byte
BB71 BMI loop_save_fcb_workspace ; X<0: more bytes to save
BB73 LDX #&0f ; Start from FCB slot &0F
BB75 STX hazel_cur_fcb_index ; Store as current FCB index
BB78 .loop_process_fcb←1← BB8B BPL
LDX hazel_cur_fcb_index ; Load current FCB index
BB7B TYA ; Get filter attribute
BB7C BEQ done_flush_fcb ; Zero: process all FCBs
BB7E CMP hazel_fcb_attr_ref,x ; Compare with FCB attribute ref
BB81 BNE done_advance_fcb ; No match: skip this FCB
BB83 .done_flush_fcb←1← BB7C BEQ
PHA ; Save filter attribute
BB84 JSR start_wipe_pass ; Flush pending data for this FCB
BB87 PLY ; Restore Y
BB88 .done_advance_fcb←1← BB81 BNE
DEC hazel_cur_fcb_index ; Previous FCB index
BB8B BPL loop_process_fcb ; More slots: continue loop
BB8D LDX #8 ; X=8: restore 9 workspace bytes
BB8F .loop_restore_fcb_ws←1← BB93 BPL
PLA ; Restore fs_block_offset
BB90 STA fs_work_4,x ; Restore workspace byte
BB92 DEX ; Next byte down
BB93 BPL loop_restore_fcb_ws ; More bytes: continue restoring
BB95 PLY ; Restore Y
BB96 PLX ; Restore X
BB97 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
BB98 .bgetv_handler
STY hazel_chan_attr ; Save channel attribute
BB9B TXA ; Save caller's X
BB9C PHA ; Push X
BB9D JSR store_result_check_dir ; Store result and check not directory
BBA0 LDA hazel_fcb_status,x ; Load channel flags
BBA3 AND #&20 ; Test write-only flag (bit 5)
BBA5 BEQ done_read_fcb_byte ; Not write-only: proceed with read
BBA7 LDA #&d4 ; Error code &D4
BBA9 JSR error_inline_log ; Generate 'Write only' error
BBAC EQUS "Write only", &00
BBB7 .done_read_fcb_byte←1← BBA5 BEQ
CLV ; Clear V (first-pass matching)
BBB8 JSR find_matching_fcb ; Find FCB matching this channel
BBBB BEQ done_load_from_buf ; No offset: read byte from buffer
BBBD LDA hazel_fcb_addr_lo,y ; Load byte count for matching FCB
BBC0 CMP hazel_fcb_offset_save,x ; Compare with buffer offset limit
BBC3 BCC done_load_from_buf ; Below offset: data available
BBC5 LDA hazel_fcb_status,y ; Load channel flags for FCB
BBC8 TAX ; Transfer to X for testing
BBC9 AND #&40 ; Test bit 6 (EOF already signalled)
BBCB BNE error_end_of_file ; EOF already set: raise error
BBCD TXA ; Restore flags
BBCE ORA #&40 ; Set EOF flag (bit 6)
BBD0 STA hazel_fcb_status,y ; Update channel flags with EOF
BBD3 LDA #0 ; A=0: clear receive attribute
BBD5 JSR store_rx_attribute ; Clear receive attribute (A=0)
BBD8 PLA ; Restore caller's X
BBD9 TAX ; X restored
BBDA LDA #&fe ; A=&FE: EOF marker byte
BBDC LDY hazel_chan_attr ; Restore channel attribute
BBDF SEC ; C=1: end of file
BBE0 RTS ; Return
BBE1 .error_end_of_file←1← BBCB BNE
LDA #&df ; Error code &DF
BBE3 JSR error_inline_log ; Generate 'End of file' error
BBE6 EQUS "End of file", &00
BBF2 .done_load_from_buf←2← BBBB BEQ← BBC3 BCC
LDA hazel_fcb_addr_lo,y ; Load current byte count (= offset)
BBF5 PHA ; Save byte count
BBF6 TYA ; Get FCB slot index
BBF7 TAX ; X = FCB slot for byte count inc
BBF8 LDA #0 ; A=0: clear receive attribute
BBFA JSR store_rx_attribute ; Clear receive attribute (A=0)
BBFD JSR inc_fcb_byte_count ; Increment byte count for this FCB
BC00 PLA ; Restore byte count (= buffer offset)
BC01 TAY ; Y = offset into data buffer
BC02 LDA hazel_cur_fcb_index ; Load current FCB index
BC05 CLC ; Prepare addition
BC06 ADC #&c3 ; Add &11 for buffer page offset
BC08 STA fs_load_addr_3 ; Set pointer high byte
BC0A LDA #0 ; A=0: pointer low byte
BC0C STA fs_load_addr_2 ; Set pointer low byte
BC0E PLA ; Restore caller's X
BC0F TAX ; X restored
BC10 LDA (fs_load_addr_2),y ; Read data byte from buffer
BC12 LDY hazel_chan_attr ; Restore channel attribute
BC15 CLC ; C=0: byte read successfully
BC16 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
BC17 .bputv_handler
STY hazel_chan_attr ; Save channel attribute
BC1A PHA ; Save data byte
BC1B TAY ; Y = data byte
BC1C TXA ; Save caller's X
BC1D PHA ; Push X
BC1E TYA ; Restore data byte to A
BC1F PHA ; Push data byte for later
BC20 STA hazel_saved_byte ; Save data byte in workspace
BC23 JSR store_result_check_dir ; Store result and check not directory
BC26 LDA hazel_fcb_status,x ; Load channel flags
BC29 BMI done_test_write_flag ; Bit 7 set: channel open, proceed
BC2B LDA #&c1 ; Error &C1: Not open for update
BC2D JSR error_inline_log ; Raise error with inline string
BC30 EQUS "Not open for update", &00
BC44 .done_test_write_flag←1← BC29 BMI
AND #&20 ; Test write flag (bit 5)
BC46 BEQ done_find_write_fcb ; Not write-capable: use buffer path
BC48 LDY hazel_fcb_slot_attr,x ; Load reply port for this channel
BC4B PLA ; Restore data byte
BC4C JSR send_wipe_request ; Send byte directly to server
BC4F JMP done_inc_byte_count ; Update byte count and return
BC52 .done_find_write_fcb←1← BC46 BEQ
BIT always_set_v_byte ; Set V flag (alternate match mode)
BC55 JSR find_matching_fcb ; Find matching FCB for channel
BC58 LDA hazel_fcb_addr_lo,y ; Load byte count for FCB
BC5B CMP #&ff ; Buffer full (&FF bytes)?
BC5D BNE done_check_buf_offset ; No: store byte in buffer
BC5F JSR flush_fcb_with_init ; Save X
BC62 .done_check_buf_offset←1← BC5D BNE
CMP hazel_fcb_offset_save,x ; Push Y
BC65 BCC done_set_dirty_flag ; Below offset: skip offset update
BC67 ADC #0 ; Carry set from BCS/BCC above
BC69 STA hazel_fcb_offset_save,x ; Update buffer offset in FCB
BC6C BNE done_set_dirty_flag ; Non-zero: keep offset flag
BC6E LDA #&df ; Mask &DF: clear bit 5
BC70 AND hazel_fcb_flags,x ; Clear offset flag
BC73 STA hazel_fcb_flags,x ; Update FCB status
BC76 .done_set_dirty_flag←2← BC65 BCC← BC6C BNE
LDA #1 ; Set bit 0 (dirty/active)
BC78 ORA hazel_fcb_flags,x ; Add to FCB flags
BC7B STA hazel_fcb_flags,x ; Update FCB status
BC7E LDA hazel_fcb_addr_lo,y ; Load byte count (= write position)
BC81 PHA ; Save count
BC82 TYA ; Get FCB slot index
BC83 TAX ; X = FCB slot
BC84 PLA ; Restore byte count
BC85 TAY ; Y = buffer write offset
BC86 LDA hazel_cur_fcb_index ; Load current FCB index
BC89 CLC ; Prepare addition
BC8A ADC #&c3 ; Add &11 for buffer page offset
BC8C STA fs_load_addr_3 ; Set pointer high byte
BC8E LDA #0 ; A=0: pointer low byte
BC90 STA fs_load_addr_2 ; Set pointer low byte
BC92 PLA ; Restore data byte
BC93 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).

BC95 .done_inc_byte_count←1← BC4F JMP
JSR inc_fcb_byte_count ; Increment byte count for this FCB
BC98 LDA #0 ; A=0: clear receive attribute
BC9A JSR store_rx_attribute ; Clear receive attribute (A=0)
BC9D PLA ; Restore caller's X
BC9E TAX ; X restored
BC9F PLA ; Discard saved data byte
BCA0 LDY hazel_chan_attr ; Restore channel attribute
BCA3 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
BCA4 .flush_fcb_if_stn_known←1← BA92 JSR
PHA ; Save A
BCA5 PHX ; Save X
BCA6 PHY ; Save Y
BCA7 LDA hazel_fcb_slot_attr,y ; Read FCB slot attribute byte
BCAA 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
BCAC .flush_fcb_with_init←1← BC5F JSR
PHA ; Save attribute byte (saved-station-test path)
BCAD PHX ; Save X again
BCAE PHY ; Save Y
BCAF LDA hazel_fcb_slot_attr,y ; Load station for this channel
BCB2 PHA ; Save station on stack
BCB3 LDY #0 ; Y=0: reset index
BCB5 JSR save_fcb_context ; Save current FCB context
BCB8 PLA ; Restore station from stack
BCB9 .store_station_and_flush←1← BCAA BNE
STA hazel_txcb_data ; Store station in command buffer
BCBC PLY ; Restore Y
BCBD PHY ; Save Y again for the next iteration
BCBE PHA ; Save station for later restore
BCBF LDX #0 ; X=0
BCC1 STX hazel_txcb_flag ; Clear function code
BCC4 LDA hazel_fcb_addr_lo,y ; Load byte count lo from FCB
BCC7 STA hazel_txcb_count ; Store as data byte count
BCCA LDA hazel_fcb_addr_mid,y ; Load byte count mid from FCB
BCCD STA hazel_txcb_result ; Store as reply command byte
BCD0 LDA hazel_fcb_addr_hi,y ; Load byte count hi from FCB
BCD3 STA hazel_exec_addr ; Store as load vector field
BCD6 LDY #&0d ; Y=&0D: TX command byte offset
BCD8 LDX #5 ; X=5: send 5 bytes
BCDA JSR save_net_tx_cb ; Send flush request to server
BCDD PLA ; Restore station from stack
BCDE TAY ; Y=station for wipe request
BCDF LDA hazel_saved_byte ; Load saved data byte
BCE2 JSR send_wipe_request ; Send close/wipe request to server
BCE5 JSR restore_catalog_entry ; Restore catalog state after flush
BCE8 PLY ; Restore Y
BCE9 PLX ; Restore X
BCEA PLA ; Restore A
BCEB 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
BCEC .send_wipe_request←2← BC4C JSR← BCE2 JSR
STY hazel_net_reply_buf_2 ; Store reply port
BCEF STA hazel_net_reply_buf_3 ; Store data byte
BCF2 TYA ; Save Y
BCF3 PHA ; Push Y to stack
BCF4 TXA ; Save X
BCF5 PHA ; Push X to stack
BCF6 LDA #&90 ; Function code &90
BCF8 STA hazel_net_reply_buf_0 ; Store in send buffer
BCFB JSR init_txcb ; Initialise TX control block
BCFE LDA #&dc ; TX start address low = &DC
BD00 STA txcb_start ; Set TX start in control block
BD02 LDA #&e0 ; TX end address low = &E0
BD04 STA txcb_end ; Set TX end in control block
BD06 LDA #9 ; Expected reply port = 9
BD08 STA hazel_net_reply_buf_1 ; Store reply port in buffer
BD0B LDX #&c0 ; TX control = &C0
BD0D LDY #0 ; Y=0: no timeout
BD0F LDA hazel_net_reply_buf_2 ; Load reply port for addressing
BD12 JSR send_disconnect_reply ; Send packet to server
BD15 LDA hazel_net_reply_buf_1 ; Load reply status
BD18 BEQ done_toggle_station ; Zero: success
BD1A STA hazel_fs_last_error ; Store error code
BD1D LDX #0 ; X=0: copy index
BD1F .loop_copy_wipe_err_msg←1← BD2A BNE
LDA hazel_net_reply_buf_0,x ; Load error message byte
BD22 STA error_block,x ; Copy to error block
BD25 CMP #&0d ; Is it CR (end of message)?
BD27 BEQ done_terminate_wipe_err ; Yes: terminate string
BD29 INX ; Next byte
BD2A BNE loop_copy_wipe_err_msg ; Continue copying error message
BD2C .done_terminate_wipe_err←1← BD27 BEQ
LDA #0 ; NUL terminator
BD2E STA error_block,x ; Terminate error string in block
BD31 DEX ; Back up position for error check
BD32 JMP check_net_error_code ; Process and raise network error
BD35 .done_toggle_station←1← BD18 BEQ
LDX hazel_chan_attr ; Load channel attribute index
BD38 LDA hazel_fcb_state_byte,x ; Load station number for channel
BD3B EOR #1 ; Toggle bit 0 (alternate station)
BD3D STA hazel_fcb_state_byte,x ; Update station number
BD40 PLA ; Restore X
BD41 TAX ; X restored
BD42 PLA ; Restore Y
BD43 TAY ; Y restored
BD44 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
BD45 .send_and_receive←2← BA1E JSR← BAA5 JSR
JSR set_options_ptr ; Set up FS options pointer
BD48 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
BD4B .read_rx_attribute←4← 99DD JSR← 99F0 JSR← BA11 JSR← BA98 JSR
LDY #&0a ; Y=&0A: receive attribute offset
BD4D LDA (net_rx_ptr),y ; Read byte from receive buffer
BD4F 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
BD50 .store_rx_attribute←10← 8BBC JSR← 9EF6 JSR← 9FDF JSR← A023 JSR← B8B2 JSR← BA25 JSR← BAA9 JSR← BBD5 JSR← BBFA JSR← BC9A JSR
LDY #&0a ; Y=&0A: receive attribute offset
BD52 STA (net_rx_ptr),y ; Store byte to receive buffer
BD54 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.

BD55 .abort_if_escape←1← BD89 JSR
BIT escape_flag ; Test bit 7 of escape flag
BD57 BMI error_escape_pressed ; Escape pressed: handle abort
BD59 RTS ; No escape: return
BD5A .error_escape_pressed←1← BD57 BMI
JSR close_ws_file ; Close the open file
BD5D .escape_error_close
JSR osnewl ; Print newline
BD60 LDA #osbyte_acknowledge_escape ; Acknowledge escape condition
BD62 JSR osbyte ; Clear escape condition and perform escape effects
BD65 LDA #&11 ; Error number &11
BD67 JSR error_inline ; Generate 'Escape' BRK error
BD6A 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
BD71 .cmd_dump
JSR open_file_for_read ; Open the file (handle stored in ws_page)
BD74 LDX #&14 ; X=&14: 21-byte stack buffer for dump line state
BD76 LDA #0 ; A=0: zero-fill
BD78 .loop_push_zero_buf←1← BD7A BPL
PHA ; Push zero
BD79 DEX ; Step counter
BD7A BPL loop_push_zero_buf ; Loop while X >= 0 (21 zeros)
BD7C TSX ; Capture stack pointer for later restore
BD7D JSR init_dump_buffer ; Parse address range and validate against file extent
BD80 LDA (work_ae),y ; Read low nibble of starting address
BD82 AND #&f0 ; Mask high nibble (top 4 bits)
BD84 BEQ loop_dump_line ; Aligned (high nibble zero): skip the header print
BD86 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.

BD89 .loop_dump_line←2← BD84 BEQ← BE29 JMP
JSR abort_if_escape ; Test escape and abort if pressed
BD8C LDA #&ff ; A=&FF: count counter starts here so first INC -> 0
BD8E STA osword_flag ; Save counter (-1)
BD90 .loop_read_dump_byte←1← BD9F BNE
LDY ws_page ; Y = file handle
BD92 JSR osbget ; Read one byte via OSBGET (C set on EOF)
BD95 BCS done_check_dump_eof ; EOF: finish off this line then exit
BD97 INC osword_flag ; Increment count counter
BD99 LDY osword_flag ; Y = current count (also buffer offset)
BD9B STA (work_ae),y ; Store byte in 16-byte line buffer at (work_ae)+Y
BD9D CPY #&0f ; Done all 16 bytes?
BD9F BNE loop_read_dump_byte ; No: read next byte
BDA1 CLC ; C clear: not EOF (clean line)
BDA2 .done_check_dump_eof←1← BD95 BCS
PHP ; Save the EOF/clean flag
BDA3 LDA osword_flag ; Reload counter byte
BDA5 BPL done_check_boundary ; Bit 7 clear (counter is 0..&7F): bytes were read
BDA7 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)
BDA9 .loop_pop_stack_buf←2← BDAB BPL← BE2E JMP
PLA ; Restore one stack byte
BDAA DEX ; Step
BDAB BPL loop_pop_stack_buf ; Loop while X >= 0 (22 pulls)
BDAD JMP close_ws_file ; Tail-jump to close_ws_file
BDB0 .done_check_boundary←1← BDA5 BPL
LDY #&10 ; Y=&10: read displayed-address byte 0
BDB2 LDA (work_ae),y ; Read low byte
BDB4 AND #&f0 ; Top nibble
BDB6 BNE done_start_dump_addr ; Non-zero: not a 256-byte boundary, skip header
BDB8 JSR print_dump_header ; Boundary: print column header
BDBB .done_start_dump_addr←1← BDB6 BNE
LDY #&13 ; Y=&13: highest byte of 4-byte address
BDBD .loop_print_addr_byte←1← BDC7 BNE
LDA (work_ae),y ; Read address byte (highest first)
BDBF PHA ; Save it (print_hex_byte clobbers A)
BDC0 JSR print_hex_byte ; Print as 2 hex digits
BDC3 PLA ; Restore A
BDC4 DEY ; Step backwards
BDC5 CPY #&0f ; Reached low byte (offset &0F)?
BDC7 BNE loop_print_addr_byte ; No: continue printing
BDC9 INY ; Y=&10: low byte of address
BDCA CLC ; Clear C
BDCB ADC #&10 ; Bump address by 16 bytes for next line
BDCD PHP ; Save C from the add
BDCE .loop_inc_dump_addr←1← BDD9 BNE
PLP ; Restore C from previous step
BDCF STA (work_ae),y ; Store updated address byte
BDD1 INY ; Step Y up
BDD2 LDA (work_ae),y ; Read next byte
BDD4 ADC #0 ; Add carry from below
BDD6 PHP ; Save C
BDD7 CPY #&14 ; Done all 4 bytes (Y=&14)?
BDD9 BNE loop_inc_dump_addr ; No: continue propagating
BDDB PLP ; Restore final C
BDDC JSR print_inline ; Print ' : ' separator before hex byte field
BDDF EQUS " : "
BDE2 LDY #0 ; Y=0: start of buffer
BDE4 LDX osword_flag ; X = byte counter (-1 initially, INC'd to 0..&0F)
BDE6 .loop_print_dump_hex←1← BDF1 BPL
LDA (work_ae),y ; Read byte from buffer
BDE8 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
BDEB .loop_next_dump_col←1← BDFC JMP
INY ; Step buffer offset
BDEC CPY #&10 ; Done all 16?
BDEE BEQ done_print_separator ; Yes: print separator before ASCII field
BDF0 DEX ; Step counter (Y was off-by-one from line read)
BDF1 BPL loop_print_dump_hex ; Have a real byte? Print it
BDF3 PHY ; End of partial line: pad with 3 spaces
BDF4 JSR print_inline ; Print ' ' inline
BDF7 EQUS " "
BDFA NOP ; NOP -- bit-7 terminator + harmless resume opcode
BDFB PLY ; Restore Y
BDFC JMP loop_next_dump_col ; Continue padding the rest of the hex column
BDFF .done_print_separator←1← BDEE BEQ
DEX ; Counter has finished -- step it once more for the ASCII test
BE00 JSR print_inline ; Print ': ' inline (ASCII field separator)
BE03 EQUS ": "
BE05 LDY #0 ; Y=0: rewind to start of line buffer
BE07 JSR inx16 ; Skip 16 padding spaces if needed (inx16)
BE0A .loop_print_dump_ascii←1← BE21 BPL
LDA (work_ae),y ; Read line buffer byte
BE0C AND #&7f ; Mask off bit 7 (DEL/inverted)
BE0E CMP #' ' ; Below ' '? (control char)
BE10 BCS done_test_del ; Yes: skip to substitution
BE12 .skip_non_printable←1← BE16 BEQ
LDA #'.' ; Substitute '.' for non-printables
BE14 .done_test_del←1← BE10 BCS
CMP #&7f ; Compare with DEL
BE16 BEQ skip_non_printable ; Equal: also non-printable, substitute '.'
BE18 JSR osasci ; Print the (possibly substituted) character
BE1B INY ; Step Y
BE1C CPY #&10 ; Done 16 chars?
BE1E BEQ done_end_dump_line ; Yes: end this line
BE20 DEX ; Step counter back
BE21 BPL loop_print_dump_ascii ; Loop while X >= 0
BE23 .done_end_dump_line←1← BE1E BEQ
JSR osnewl ; Print newline at end of line
BE26 PLP ; Restore EOF flag
BE27 BCS done_dump_eof ; EOF: tidy up and exit
BE29 JMP loop_dump_line ; More to dump: jump to next line
BE2C .done_dump_eof←1← BE27 BCS
LDX #&14 ; X=&14: balance the loop_pop_stack_buf counter
BE2E 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)
BE31 .print_dump_header←2← BD86 JSR← BDB8 JSR
LDA (work_ae),y ; Read low nibble of starting address from (work_ae),Y
BE33 PHA ; Save it (we'll print it 16 times incrementing each iteration)
BE34 JSR print_inline ; Print '<CR>Address : ' header via inline string
BE37 EQUS &0D, "Address : " ; *Dump column header
BE43 LDX #&0f ; X=&0F: print 16 column-number digits
BE45 PLA ; Pull the starting low nibble back into A
BE46 .loop_print_hex_row←1← BE4F BPL
JSR print_hex_and_space ; Print A as two hex digits + space
BE49 SEC ; Set C ready for the increment
BE4A ADC #0 ; A += 1 (column index increments, with C set on entry)
BE4C AND #&0f ; Wrap to nibble (0..15)
BE4E DEX ; Step column counter
BE4F BPL loop_print_hex_row ; Loop while X >= 0 (16 iterations)
BE51 JSR print_inline ; Print ': ASCII data<CR><CR>' trailer via inline
BE54 EQUS ": ASCII data", &0D, &0D ; *Dump trailer
BE65 NOP ; NOP -- bit-7 terminator + harmless resume opcode
BE66 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
BE67 .print_hex_and_space←2← BDE8 JSR← BE46 JSR
PHA ; Save A so the caller can re-use the value
BE68 JSR print_hex_byte ; Print A as two hex digits
BE6B LDA #' ' ; A=' ': trailing column separator
BE6D JSR osasci ; Print the space via OSASCI
BE70 .done_print_hex_space
PLA ; Restore caller's A
BE71 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)
BE72 .parse_dump_range←2← BEE2 JSR← BF6E JSR
TYA ; Move command-line offset Y into A for the X copy
BE73 TAX ; X = current command-line offset (live cursor)
BE74 LDA #0 ; A=0: zero-fill value
BE76 TAY ; Y=0: accumulator index
BE77 .loop_clear_hex_accum←1← BE7C BNE
STA (work_ae),y ; Zero accumulator byte at (work_ae)+Y
BE79 INY ; Step accumulator
BE7A CPY #4 ; Done all 4 bytes?
BE7C 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
BE7E .loop_parse_hex_digit←1← BEC5 JMP
TXA ; Reload command-line offset
BE7F INX ; Step cursor
BE80 TAY ; Y = stepped cursor (for the indirect read)
BE81 LDA (os_text_ptr),y ; Read next command-line byte
BE83 CMP #&0d ; CR? (end of address)
BE85 BEQ done_test_hex_space ; Yes: range parsed -- exit via space-skip
BE87 CMP #' ' ; Space?
BE89 BEQ done_test_hex_space ; Yes: also a separator -- exit
BE8B CMP #'0' ; Below '0'?
BE8D BCC error_bad_hex_value ; Yes: not hex -- raise 'Bad hex'
BE8F CMP #':' ; Above '9'?
BE91 BCC done_mask_hex_digit ; No: it's '0'-'9' -- skip the letter handling
BE93 AND #&5f ; Force uppercase via AND #&5F
BE95 ADC #&b8 ; Add &B8: 'A' (=&41) becomes &F9 with C set; 'F' becomes &FE; this maps 'A'-'F' to &FA-&FF in C
BE97 BCS error_bad_hex_value ; Carry out of ADC: digit was below 'A' -> bad hex
BE99 CMP #&fa ; Below &FA? (i.e. before 'A' in mapped range)
BE9B BCC error_bad_hex_value ; Yes (out of [&FA,&FF]): bad hex
BE9D .done_mask_hex_digit←1← BE91 BCC
AND #&0f ; Keep low nibble (0-15)
BE9F PHA ; Push the new nibble
BEA0 TXA ; Push X (current command-line offset)
BEA1 PHA ; Preserve on stack
BEA2 LDX #4 ; X=4: rotate the 4-byte accumulator left 4 times
BEA4 .loop_shift_nibble←1← BEBA BNE
LDY #0 ; Y=0: byte index for the rotate
BEA6 TYA ; A=0 (and C clear from TYA's flags)
BEA7 .loop_rotate_hex_accum←1← BEB3 BNE
PHA ; Save A onto stack so we can use PHP/PLP to round-trip carry through the rotate
BEA8 PLP ; Pull flags (effectively C clear from the TYA above; on later iterations C carries the bit shifted out)
BEA9 LDA (work_ae),y ; Read next accumulator byte
BEAB ROL ; Shift in C from below, shift out top bit to C
BEAC STA (work_ae),y ; Write back
BEAE PHP ; Save the new C
BEAF PLA ; Pull A back (PHA earlier)
BEB0 INY ; Step accumulator byte
BEB1 CPY #4 ; Done all 4 bytes?
BEB3 BNE loop_rotate_hex_accum ; No: rotate next byte
BEB5 PHA ; PHA/PLP: bring saved C into flag register
BEB6 PLP ; C = overflow bit
BEB7 BCS error_hex_overflow ; C set: a bit fell off the top -- overflow
BEB9 DEX ; Step rotate counter
BEBA BNE loop_shift_nibble ; Loop while X != 0 (4 rotates total)
BEBC PLA ; Pull saved X (command-line offset)
BEBD TAX ; Restore X
BEBE PLA ; Pull saved nibble into A
BEBF LDY #0 ; Y=0: low byte of accumulator
BEC1 ORA (work_ae),y ; OR new nibble into accumulator[0]
BEC3 STA (work_ae),y ; Write back
BEC5 JMP loop_parse_hex_digit ; Loop for next hex digit
BEC8 .error_hex_overflow←1← BEB7 BCS
PLA ; Discard saved nibble
BEC9 PLA ; Discard saved X
BECA SEC ; Set C: signal overflow to caller
BECB RTS ; Return with C=1
BECC .error_bad_hex_value←3← BE8D BCC← BE97 BCS← BE9B BCC
JSR close_ws_file ; Close the dump file before raising the error
BECF JMP err_bad_hex ; Raise 'Bad hex' error; never returns
BED2 .loop_skip_hex_spaces←1← BED7 BEQ
INY ; Step past current space
BED3 .done_test_hex_space←2← BE85 BEQ← BE89 BEQ
LDA (os_text_ptr),y ; Read next byte
BED5 CMP #' ' ; Still a space?
BED7 BEQ loop_skip_hex_spaces ; Yes: keep skipping
BED9 CLC ; Clear C: signal success
BEDA 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
BEDB .init_dump_buffer←1← BD7D JSR
INX ; Step Y past the *Dump command name into the argument
BEDC STX work_ae ; Save the cursor offset
BEDE LDX #1 ; Set bit 0 of addr_work to 1 -- 'mode' flag for parse_dump_range below
BEE0 STX addr_work ; Save mode flag
BEE2 JSR parse_dump_range ; Parse the start address (max 4 hex digits)
BEE5 BCS error_outside_file ; Overflow: too many digits
BEE7 TYA ; Save current Y (cursor after start address)
BEE8 PHA ; Push it
BEE9 LDY ws_page ; Y = file handle saved in ws_page
BEEB LDX #&aa ; X=&AA: zero-page address for OSARGS result
BEED LDA #2 ; A=2: OSARGS sub-fn 2 = read sequential file extent
BEEF JSR osargs ; Get file size into 4 bytes at &AA Get length of file into zero page address X (A=2)
BEF2 LDY #3 ; Y=3: compare 4-byte values (high to low)
BEF4 .loop_cmp_file_length←1← BEFC BPL
LDA osword_flag,y ; Read file size byte at &AA+Y
BEF7 CMP (work_ae),y ; Compare with parsed start address (work_ae+Y)
BEF9 BNE done_check_outside ; Mismatch: branch decides which is bigger
BEFB DEY ; Step to next byte
BEFC BPL loop_cmp_file_length ; Loop while Y >= 0 (covers indices 3, 2, 1, 0)
BEFE BMI done_advance_start ; All bytes equal: start = extent (allowed); jump to the post-validation path
BF00 .done_check_outside←1← BEF9 BNE
BCC error_outside_file ; C clear: parsed_start > file_size -- reject
BF02 LDY #&ff ; Y=&FF: signal 'no copy needed' to the loop below
BF04 BNE done_advance_start ; Always taken: skip directly to advance phase
BF06 .error_outside_file←2← BEE5 BCS← BF00 BCC
JSR close_ws_file ; Close the file before raising
BF09 LDA #&b7 ; A=&B7: 'Outside file' error code
BF0B JSR error_inline ; Raise via inline string; never returns
BF0E EQUS "Outside file", &00 ; *Dump range error
BF1B .loop_copy_osword_data←1← BF23 BNE
.loop_copy_start_addr←1← BF23 BNE
LDA (work_ae),y ; Copy file-extent byte from osword_flag to (work_ae)
BF1D STA osword_flag,y ; Store it (used as default end address)
BF20 .done_advance_start←2← BEFE BMI← BF04 BNE
INY ; Step Y
BF21 CPY #4 ; Done all 4 bytes?
BF23 BNE loop_copy_osword_data ; No: continue copying
BF25 LDX #&aa ; X=&AA: zero-page source for the OSARGS write-back
BF27 LDY ws_page ; Y = file handle
BF29 LDA #1 ; A=1: OSARGS sub-fn 1 = write sequential file pointer
BF2B JSR osargs ; Set the file's read pointer to the parsed start Write sequential file pointer from zero page address X (A=1)
BF2E PLA ; Pull saved cursor offset
BF2F TAY ; Restore into Y
BF30 LDA (os_text_ptr),y ; Read next command-line byte
BF32 CMP #&0d ; CR (end of args)?
BF34 BNE done_parse_disp_base ; No: there's a second arg -- handle below
BF36 LDY #1 ; Y=1: copy os_text_ptr (2 bytes) to work_ae as a displacement-base hint
BF38 .loop_copy_osfile_ptr←1← BF3E BPL
LDA os_text_ptr,y ; Read os_text_ptr+Y
BF3B STA (work_ae),y ; Save in work_ae+Y
BF3D DEY ; Step backwards
BF3E BPL loop_copy_osfile_ptr ; Loop while Y >= 0
BF40 LDA #osfile_read_catalogue_info ; A=5: OSFILE sub-fn 5 = read catalogue info
BF42 LDX work_ae ; X = filename pointer low (work_ae)
BF44 LDY addr_work ; Y = filename pointer high (addr_work)
BF46 JSR osfile ; Read load address into work_ae+0..3 osfile: read catalogue info
BF49 LDY #2 ; Y=2: shift 3 bytes down 2 positions to drop the first 2 bytes (action code + a flag)
BF4B .loop_shift_osfile_data←1← BF56 BNE
LDA (work_ae),y ; Read source byte
BF4D DEY ; Y -= 2 (destination)
BF4E DEY ; Continue decrement
BF4F STA (work_ae),y ; Store at destination
BF51 INY ; Y += 3 to advance to next source
BF52 INY ; (continued)
BF53 INY ; (continued)
BF54 CPY #6 ; Done 6 bytes shifted?
BF56 BNE loop_shift_osfile_data ; No: continue
BF58 DEY ; Y -= 2: position at high byte of load address
BF59 DEY ; Y=4: check from buf[4] downward
BF5A .loop_check_ff_addr←1← BF61 BNE
LDA (work_ae),y ; Read load-address byte at Y
BF5C CMP #&ff ; Is it &FF (signals no real load address)?
BF5E BNE done_add_disp_base ; No: have a real load address; add it as displacement
BF60 DEY ; Yes: step back to next higher byte
BF61 BNE loop_check_ff_addr ; Loop until Y=0
BF63 LDY #3 ; All four bytes were &FF: zero out the load address
BF65 LDA #0 ; A=0
BF67 .loop_zero_load_addr←1← BF6A BPL
STA (work_ae),y ; Zero work_ae+Y
BF69 DEY ; Step backwards
BF6A BPL loop_zero_load_addr ; Loop while Y >= 0
BF6C BMI done_add_disp_base ; Always taken (after BPL drops out): skip second-arg path
BF6E .done_parse_disp_base←1← BF34 BNE
JSR parse_dump_range ; Parse end-address argument
BF71 BCC done_add_disp_base ; Success: continue with displacement-add
BF73 JSR close_ws_file ; Parse error: close file then raise 'Bad address'
BF76 LDA #&fc ; A=&FC: 'Bad address' error code
BF78 JSR error_bad_inline ; Raise; never returns
BF7B EQUS "address", &00
BF83 .done_add_disp_base←3← BF5E BNE← BF6C BMI← BF71 BCC
LDY #0 ; Y=0: start of work_ae
BF85 LDX #4 ; X=4: 4-byte add
BF87 CLC ; Clear C for the add
BF88 .loop_add_disp_bytes←1← BF92 BNE
LDA (work_ae),y ; Read low byte of address from (work_ae)+Y
BF8A ADC osword_flag,y ; Add osword_flag+Y (low byte of length, with carry propagating)
BF8D STA osword_flag,y ; Store sum back to osword_flag+Y
BF90 INY ; Advance to next byte
BF91 DEX ; Decrement byte counter
BF92 BNE loop_add_disp_bytes ; Loop until 4 bytes added
BF94 LDY #&14 ; Y=&14: target offset = workspace+&13 (top of end-addr field, stored hi-byte-first)
BF96 LDX #3 ; X=3: source = osword_flag+3 (top byte of sum)
BF98 .loop_store_disp_addr←1← BF9E BPL
DEY ; Pre-decrement Y (so first store is to offset &13)
BF99 LDA osword_flag,x ; Read sum byte from osword_flag+X
BF9B STA (work_ae),y ; Store at (work_ae)+Y
BF9D DEX ; Decrement source index
BF9E BPL loop_store_disp_addr ; Loop until X wraps below 0
BFA0 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)
BFA1 .close_ws_file←5← BD5A JSR← BDAD JMP← BECC JSR← BF06 JSR← BF73 JSR
LDY ws_page ; Y = saved file handle from ws_page
BFA3 LDA #osfind_close ; A=0: OSFIND close
BFA5 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
BFA8 .open_file_for_read←1← BD71 JSR
PHP ; Save flags so caller's NZC survive
BFA9 TYA ; Move command-line offset Y into A for the add
BFAA CLC ; Clear C for the 16-bit add
BFAB ADC os_text_ptr ; A = os_text_ptr_lo + Y (filename address low byte)
BFAD PHA ; Push it (we need to restore os_text_ptr after OSFIND)
BFAE TAX ; Move filename low into X (OSFIND wants the address in X/Y)
BFAF LDA #0 ; A=0: zero high byte before the carry-add
BFB1 ADC os_text_ptr_hi ; Add os_text_ptr_hi with carry from the low add
BFB3 PHA ; Push filename high byte for the restore
BFB4 TAY ; Move filename high into Y
BFB5 LDA #osfind_open_input ; A=&40: OSFIND open-for-input mode
BFB7 JSR osfind ; Open the file; returns handle in A (zero on failure) osfind: open file for input
BFBA TAY ; Copy returned handle into Y (also sets Z if zero)
BFBB STA ws_page ; Stash the handle in ws_page for later close
BFBD BNE restore_text_ptr ; Non-zero: open succeeded, skip error path
BFBF LDA #&d6 ; A=&D6: 'Not found' error code
BFC1 JSR error_inline ; Raise the error with the inline string below; never returns
BFC4 EQUS "Not found", &00
BFCE .restore_text_ptr←1← BFBD 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
BFCF STA os_text_ptr_hi ; Store as os_text_ptr_hi
BFD1 PLA ; Restore filename low byte into os_text_ptr_lo (so (os_text_ptr) now points at the filename)
BFD2 STA os_text_ptr ; Store as os_text_ptr lo
BFD4 LDY #0 ; Y=0: scan from start of filename
BFD6 .loop_skip_filename←1← BFDF BNE
INY ; Step to next byte
BFD7 LDA (os_text_ptr),y ; Read filename byte
BFD9 CMP #&0d ; Hit CR? End of command line
BFDB BEQ done_skip_filename ; Yes: filename ended at CR (no trailing spaces)
BFDD CMP #' ' ; Hit space? End of filename
BFDF BNE loop_skip_filename ; No (still inside filename): keep scanning
BFE1 .loop_skip_fn_spaces←1← BFE6 BEQ
INY ; Step past spaces
BFE2 LDA (os_text_ptr),y ; Read next byte
BFE4 CMP #' ' ; Still a space?
BFE6 .hazel_minus_1a←1Used as index base by← AC7A LDA
.hazel_idx_bases←1Used as index base by← AC7A LDA
.skip_fn_space_cont←1Used as index base by← AC7A LDA
BEQ loop_skip_fn_spaces ; Yes: keep skipping
BFE8 .done_skip_filename←1← BFDB BEQ
PLP ; Done: Y points just past the filename and any spaces
BFE9 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
BFEA .inx16←3← 9ED8 JSR← AC52 JSR← BE07 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
BFED .inx8←1← BFEA 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
BFF0 .inx4←1← BFED JSR
INX ; X += 4
BFF1 INX ; (continued)
BFF2 INX ; (continued)
BFF3 INX ; (continued)
BFF4 RTS ; Return — total X advance depends on the entry: 4 (inx4), 8 (inx8), or 16 (inx16)

ROM-tail &FF padding (33 bytes positioning the HAZEL indexing bases)

33 bytes of &FF filler between the last real instruction at inx4 and the HAZEL indexing-base labels starting at hazel_minus_1a.

These bytes exist purely to push the indexing-base labels to specific addresses immediately before &C000 (the start of HAZEL). The labels themselves do the work -- see the hazel_idx_bases banner. The padding is never read or written; it is whatever the assembler emitted to fill the gap (the BeebAsm default of &FF).

BFF5 .rom_tail_padding
EQUB &FF, &FF ; ROM-tail padding (2 bytes &FF)
BFF7 EQUB &FF ; ROM-tail padding (1 byte &FF; on its own line for annotation)
BFF8 EQUB &FF, &FF, &FF, &FF, &FF, ; ROM-tail padding (30 bytes &FF ; &FF)
BFFE .hazel_minus_2←3Used as index base by← 8B8D STA← 906D LDA← ACA5 LDA
EQUB &FF ; Base for hazel_minus_2,Y reads/writes -- &BFFE + Y reaches into HAZEL for Y >= 2
BFFF .hazel_minus_1←2Used as index base by← A9F1 LDA← AA06 STA
EQUB &FF