Acorn ANFS 4.21 (variant 1)

Updated 3 May 2026

← All Acorn NFS and Acorn ANFS versions

; Sideways ROM header
; ANFS ROM 4.21 (variant 1) disassembly (Acorn Advanced Network ; Filing System)
; ============================================================== ; ==============
8000 .rom_header
.language_entry
.pydis_start
EQUB &00, &42, &43
8003 .service_entry
JMP service_handler
8006 .rom_type
EQUB &82 ; ROM type: service + language
8007 .copyright_offset
EQUB copyright - rom_header
8008 .binary_version
EQUB &04
8009 .title
EQUS "Acorn ANFS 4.21"
8018 .version
EQUB &00
8019 .copyright
EQUB &00 ; Null terminator before copyright
801A .copyright_string
EQUS "(C)1986 Acorn."

Service 5: unrecognised interrupt (Master 128 dispatch)

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)
8028 .svc5_irq_check
PHX ; Save X (the ROM slot we're being called on behalf of)
8029 PHY ; Save Y (the dispatch-path selector via its high bit)
802A LDY tx_op_type ; Read deferred-work flag at &0D65 (set by NMI when work queued)
802D BNE irq_check_dispatch ; Non-zero: there's work to dispatch
802F PLY ; Zero: no work; restore Y
8030 PLX ; Restore X
8031 RTS ; Return to MOS (service unclaimed)
8032 .irq_check_dispatch←1← 802D BNE
LDA #&80 ; A=&80: bit 7 -- the bit to clear in ACCCON
8034 TRB acccon ; Clear ACCCON bit 7 (release IRR mask)
8037 STZ tx_op_type ; Zero the deferred-work flag (we're handling it now)
803A TYA ; Copy to A for sign test Bring saved Y back into A so BMI can test bit 7
803B BMI dispatch_svc5 ; Bit 7 set: dispatch via table Bit 7 of caller's Y set: dispatch via PHA/PHA/RTS table
803D LDA #&fe ; A=&FE: Econet receive event
803F JSR generate_event ; Call event vector handler
8042 JMP tx_done_exit ; Fire event (enable: *FX52,150) Tail-jump to tx_done_exit which restores X/Y and claims the service

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
8045 .generate_event←1← 803F JSR
JMP (evntv) ; Dispatch through event vector

Service-5 PHA/PHA/RTS dispatch tail

Builds an RTS-target on the stack from the tx_done_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 tx_done_dispatch_lo+Y page-&85 handler.

On EntryYtx_done_dispatch_lo offset (post-&83 base bias)
8048 .dispatch_svc5←1← 803B BMI
LDA #&85 ; Push return addr high (&85)
804A PHA ; High byte on stack for RTS
804B LDA tx_done_dispatch_lo-&83,y ; Load dispatch target low byte
804E 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.

804F .svc_5_unknown_irq
RTS ; RTS = dispatch to PHA'd address

ADLC initialisation

Initialise ADLC hardware and Econet workspace. Disables NMIs via BIT master_intoff (the Master 128 INTOFF register at &FE38; the Model-B equivalent reads econet_station_id at &FE18 for the same side effect). 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.

8050 .adlc_init←1← 903C JSR
BIT master_intoff ; INTOFF: read station ID, disable NMIs
8053 JSR adlc_full_reset ; Full ADLC hardware reset
8056 LDA #&ea ; OSBYTE &EA: check Tube co-processor
8058 LDX #0 ; X=0 for OSBYTE
805A STX econet_init_flag ; Clear Econet init flag before setup
805D JSR osbyte_x0 ; Check Tube presence via OSBYTE &EA
8060 STX tube_present ; Store Tube presence flag from OSBYTE &EA
8063 LDA #&8f ; OSBYTE &8F: issue service request
8065 LDX #&0c ; X=&0C: NMI claim service
8067 JSR osbyte_yff ; Issue NMI claim service request
806A LDY #5 ; Y=5: NMI claim service number
806C .econet_restore
CPY #5 ; Check if NMI service was claimed (Y changed)
806E 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 econet_station_id at entry, BIT econet_nmi_enable before RTI) that toggles the IC97 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 econet_station_id
tx_complete_flag &80 mark idle
econet_init_flag &80 mark initialised

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

8070 .init_nmi_workspace
LDY #&20 ; Copy NMI shim from ROM to &0D0C area
8072 .copy_nmi_shim←1← 8079 BNE
LDA nmi_shim_source,y ; Read byte from NMI shim ROM source
8075 STA nmi_code_base,y ; Write to NMI shim RAM (start of NFS workspace)
8078 DEY ; Next byte (descending)
8079 BNE copy_nmi_shim ; Loop until all 32 bytes copied
807B LDA romsel_copy ; Patch current ROM bank into NMI shim
807D STA nmi_romsel ; Self-modifying code: ROM bank at &0D07
8080 STY tx_src_net ; Clear source network (Y=0 from copy loop)
8083 STY prot_flags ; Clear Tube release flag
8085 STY tx_op_type ; Clear TX operation type
8088 LDY #1 ; Y=1: tx_src_stn offset in NMI block
808A LDA (net_rx_ptr),y ; Read TX source station from (net_rx_ptr)+1
808C STA tx_src_stn ; Store as tx_src_stn
808F LDA #&80 ; &80 = Econet initialised
8091 STA tx_complete_flag ; Mark TX as complete (ready)
8094 STA econet_init_flag ; Mark Econet as initialised
8097 BIT master_inton ; INTON: re-enable NMIs (&FE20 read side effect)
809A .adlc_init_done←1← 806E 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 against our station ID. Reading econet_station_id (&FE18) also disables NMIs (INTOFF side effect).

809B .nmi_rx_scout←1← 89D5 JMP
LDA #1 ; A=&01: mask for SR2 bit0 (AP = Address Present)
809D BIT adlc_cr2 ; Z = A AND SR2 -- tests if AP is set
80A0 BEQ scout_error ; AP not set, no incoming data -- check for errors
80A2 LDA adlc_tx ; Read first RX byte (destination station address)
80A5 CMP tx_src_stn ; Compare to our station ID (&FE18 read = INTOFF, disables NMIs)
80A8 BEQ accept_frame ; Match -- accept frame
80AA CMP #&ff ; Check for broadcast address (&FF)
80AC BNE scout_reject ; Neither our address nor broadcast -- reject frame
80AE LDA #&40 ; Flag &40 = broadcast frame
80B0 STA rx_src_net ; Store broadcast flag in rx_src_net
80B3 .accept_frame←1← 80A8 BEQ
LDA #&b8 ; Install nmi_rx_scout_net NMI handler
80B5 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.

80B8 .nmi_rx_scout_net
BIT adlc_cr2 ; Test SR2 for RDA (bit7 = data available)
80BB BPL scout_error ; No RDA -- check errors
80BD LDA adlc_tx ; Read destination network byte
80C0 BEQ accept_local_net ; Network = 0 -- local network, accept
80C2 EOR #&ff ; Test if network = &FF (broadcast)
80C4 BEQ accept_scout_net ; Broadcast network -- accept
80C6 .scout_reject←1← 80AC BNE
LDA #&a2 ; Reject: wrong network. CR1=&A2: RIE|RX_DISCONTINUE
80C8 STA adlc_cr1 ; Write CR1 to discontinue RX
80CB JMP set_nmi_rx_scout ; Return to idle scout listening
80CE .accept_local_net←1← 80C0 BEQ
STA rx_src_net ; Network = 0 (local): clear tx_flags
80D1 .accept_scout_net←1← 80C4 BEQ
STA port_buf_len ; Store Y offset for scout data buffer
80D3 LDA #&e8 ; Install scout data handler
80D5 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.

80D8 .scout_error←5← 80A0 BEQ← 80BB BPL← 80ED BPL← 8121 BEQ← 8123 BPL
LDA adlc_cr2 ; Read SR2
80DB AND #&81 ; Test AP (b0) | RDA (b7)
80DD BEQ scout_discard ; Neither set -- clean end, discard frame
80DF JSR adlc_full_reset ; Unexpected data/status: full ADLC reset
80E2 JMP set_nmi_rx_scout ; Discard and return to idle
80E5 .scout_discard←1← 80DD BEQ
JMP reset_adlc_rx_listen ; Gentle discard: RX_DISCONTINUE
80E8 LDY port_buf_len ; Y = buffer offset
80EA LDA adlc_cr2 ; Read SR2
80ED .scout_loop_rda←1← 810D BNE
BPL scout_error ; No RDA -- error handler
80EF LDA adlc_tx ; Read data byte from RX FIFO
80F2 STA scout_buf,y ; Store at &0D3D+Y (scout buffer)
80F5 INY ; Advance buffer index
80F6 LDA adlc_cr2 ; Read SR2 again (FV detection point)
80F9 BMI scout_loop_second ; RDA set -- more data, read second byte
80FB BNE scout_complete ; SR2 non-zero (FV or other) -- scout completion
80FD .scout_loop_second←1← 80F9 BMI
LDA adlc_tx ; Read second byte of pair
8100 STA scout_buf,y ; Store at &0D3D+Y
8103 INY ; Advance and check buffer limit
8104 CPY #&0c ; Copied all 12 scout bytes?
8106 BEQ scout_complete ; Buffer full (Y=12) -- force completion
8108 STY port_buf_len ; Save final buffer offset
810A LDA adlc_cr2 ; Read SR2 for next pair
810D BNE scout_loop_rda ; SR2 non-zero -- loop back for more bytes
810F 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.
8112 .scout_complete←2← 80FB BNE← 8106 BEQ
LDA #0 ; Save Y for next iteration
8114 STA adlc_cr1 ; Write CR1
8117 LDA #&84 ; CR2=&84: disable PSE, enable RDA_SUPPRESS_FV
8119 STA adlc_cr2 ; Write CR2
811C LDA #2 ; A=&02: FV mask for SR2 bit1
811E BIT adlc_cr2 ; Test SR2 FV (Z) and RDA (N)
8121 BEQ scout_error ; No FV -- not a valid frame end, error
8123 BPL scout_error ; FV set but no RDA -- missing last byte, error
8125 LDA adlc_tx ; Read last byte from RX FIFO
8128 STA scout_buf,y ; Store last byte at &0D3D+Y
812B LDA #&44 ; CR1=&44: RX_RESET | TIE (switch to TX for ACK)
812D STA adlc_cr1 ; Write CR1: switch to TX mode
8130 SEC ; Set bit7 of need_release_tube flag
8131 ROR prot_flags ; Rotate C=1 into bit7: mark Tube release needed
8133 LDA scout_port ; Check port byte: 0 = immediate op, non-zero = data transfer
8136 BNE scout_match_port ; Port non-zero -- look for matching receive block
8138 .scout_no_match
JMP immediate_op ; Port = 0 -- immediate operation handler
813B .scout_match_port←1← 8136 BNE
BIT rx_src_net ; Check if broadcast (bit6 of tx_flags)
813E BVC scan_port_list ; Not broadcast -- skip CR2 setup
8140 LDA #7 ; CR2=&07: broadcast prep
8142 STA adlc_cr2 ; Write CR2: broadcast frame prep
8145 .scan_port_list←1← 813E BVC
BIT econet_flags ; Check if RX port list active (bit7)
8148 BPL try_nfs_port_list ; No active ports -- try NFS workspace
814A LDA #&c0 ; Start scanning port list at page &C0
814C LDY #0 ; Y=0: start offset within each port slot
814E .scan_nfs_port_list←1← 8193 BNE
STA port_ws_offset ; Store page to workspace pointer low
8150 STY rx_buf_offset ; Store page high byte for slot scanning
8152 .check_port_slot←1← 8185 BCC
LDY #0 ; Y=0: read control byte from start of slot
8154 .scout_ctrl_check
LDA (port_ws_offset),y ; Read port control byte from slot
8156 BEQ discard_no_match ; Zero = end of port list, no match
8158 CMP #&7f ; &7F = any-port wildcard
815A BNE next_port_slot ; Not wildcard -- check specific port match
815C INY ; Y=1: advance to port byte in slot
815D LDA (port_ws_offset),y ; Read port number from slot (offset 1)
815F BEQ check_station_filter ; Zero port in slot = match any port
8161 CMP scout_port ; Check if port matches this slot
8164 BNE next_port_slot ; Port mismatch -- try next slot
8166 .check_station_filter←1← 815F BEQ
INY ; Y=2: advance to station byte
8167 LDA (port_ws_offset),y ; Read station filter from slot (offset 2)
8169 BEQ port_match_found ; Zero station = match any station, accept
816B CMP scout_buf ; Check if source station matches
816E BNE next_port_slot ; Station mismatch -- try next slot
8170 .scout_port_match
INY ; Y=3: advance to network byte
8171 LDA (port_ws_offset),y ; Read network filter from slot (offset 3)
8173 BEQ port_match_found ; Zero = accept any network
8175 CMP scout_src_net ; Check if source network matches
8178 BEQ port_match_found ; Network matches or zero = accept
817A .next_port_slot←3← 815A BNE← 8164 BNE← 816E BNE
LDA rx_buf_offset ; Check if NFS workspace search pending
817C BEQ try_nfs_port_list ; No NFS workspace -- try fallback path
817E LDA port_ws_offset ; Load current slot base address
8180 CLC ; For 12-byte slot advance
8181 ADC #&0c ; Advance to next 12-byte port slot
8183 STA port_ws_offset ; Update workspace pointer to next slot
8185 BCC check_port_slot ; Always branches (page &C0 won't overflow)
8187 .discard_no_match←2← 8156 BEQ← 818D BVC
JMP nmi_error_dispatch ; No match found -- discard frame
818A .try_nfs_port_list←2← 8148 BPL← 817C BEQ
BIT econet_flags ; Try NFS workspace if paged list exhausted
818D BVC discard_no_match ; No NFS workspace RX (bit6 clear) -- discard
818F LDA #0 ; NFS workspace starts at offset 0 in page
8191 LDY nfs_workspace_hi ; NFS workspace high byte for port list
8193 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 rx_src_net (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)
8195 .port_match_found←4← 8169 BEQ← 8173 BEQ← 8178 BEQ← 84B9 JMP
LDA #3 ; Match found: set scout_status = 3
8197 STA rx_port ; Record match for completion handler
819A JSR tx_calc_transfer ; Calculate transfer parameters
819D BCC nmi_error_dispatch ; C=0: no Tube claimed -- discard
819F BIT rx_src_net ; Check broadcast flag for ACK path
81A2 BVC send_data_rx_ack ; Not broadcast -- normal ACK path
81A4 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)
81A7 .send_data_rx_ack←2← 81A2 BVC← 84AE JMP
LDA #&44 ; CR1=&44: RX_RESET | TIE
81A9 STA adlc_cr1 ; Write CR1: TX mode for ACK
81AC LDA #&a7 ; CR2=&A7: RTS | CLR_TX_ST | FC_TDRA | PSE
81AE STA adlc_cr2 ; Write CR2: enable TX with PSE
81B1 LDA #&b8 ; Install data_rx_setup at &81B8
81B3 LDY #&81 ; High byte of data_rx_setup handler
81B5 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.

81B8 .data_rx_setup
LDA #&82 ; CR1=&82: TX_RESET | RIE (switch to RX for data frame)
81BA STA adlc_cr1 ; Write CR1: switch to RX for data frame
81BD LDA #&c2 ; Install nmi_data_rx at &81E7
81BF 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).

81C2 .nmi_data_rx
LDA #1 ; A=1: AP mask for SR2 bit test
81C4 BIT adlc_cr2 ; Test SR2 AP bit
81C7 BEQ nmi_error_dispatch ; No AP: wrong frame or error
81C9 LDA adlc_tx ; Read first byte (dest station)
81CC CMP tx_src_stn ; Compare to our station ID (INTOFF)
81CF BNE nmi_error_dispatch ; Not for us: error path
81D1 LDA #&d6 ; Install nmi_data_rx_net check handler
81D3 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)
81D6 .nmi_data_rx_net
BIT adlc_cr2 ; Validate source network = 0
81D9 BPL nmi_error_dispatch ; SR2 bit7 clear: no data ready -- error
81DB LDA adlc_tx ; Read dest network byte
81DE BNE nmi_error_dispatch ; Network != 0: wrong network -- error
81E0 LDA #&ec ; Install skip handler at &8211
81E2 LDY #&81 ; High byte of &8211 handler
81E4 BIT adlc_cr1 ; SR1 bit7: IRQ, data already waiting
81E7 BMI nmi_data_rx_skip ; Data ready: skip directly, no return
81E9 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.

81EC .nmi_data_rx_skip←1← 81E7 BMI
BIT adlc_cr2 ; Test SR2 RDA (RX data byte ready)
81EF BPL nmi_error_dispatch ; SR2 bit7 clear: error
81F1 LDA adlc_tx ; Discard control byte
81F4 LDA adlc_tx ; 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 rx_src_net (tx_flags).

rx_src_net 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.

81F7 .install_data_rx_handler←1← 88D4 JMP
LDA #2 ; A=2: Tube transfer flag mask
81F9 BIT rx_src_net ; Check if Tube transfer active
81FC BNE install_tube_rx ; Tube active: use Tube RX path
81FE LDA #&23 ; A=&23: low byte of nmi_data_rx_bulk (&8223)
8200 LDY #&82 ; Y=&82: high byte of nmi_data_rx_bulk
8202 BIT adlc_cr1 ; SR1 bit7: more data already waiting?
8205 BMI nmi_data_rx_bulk ; Yes: enter bulk read directly
8207 JMP set_nmi_vector ; No: install handler
820A .install_tube_rx←1← 81FC BNE
LDA #&91 ; A=&91: low byte of nmi_data_rx_tube (&8291)
820C LDY #&82 ; Y=&82: high byte of nmi_data_rx_tube
820E 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.
8211 .page_boundary_restore←1← 823F BEQ
PLA ; Pull saved ACCCON from stack
8212 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 rx_src_net 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.

rx_src_net bit 7 Path
clear RX error – full ADLC reset; return to idle listen
set TX not-listening – JMP tx_result_fail
8215 .nmi_error_dispatch←11← 8187 JMP← 819D BCC← 81C7 BEQ← 81CF BNE← 81D9 BPL← 81DE BNE← 81EF BPL← 8279 BEQ← 827F BEQ← 833C JMP← 8488 JMP
LDA rx_src_net ; Check tx_flags for error path
8218 BPL rx_error_reset ; Bit7 clear: RX error path
821A JMP tx_result_fail ; Bit7 set: TX result = not listening
821D .rx_error_reset←1← 8218 BPL
JSR adlc_full_reset ; Full ADLC reset on RX error
8220 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.
8223 .nmi_data_rx_bulk←1← 8205 BMI
LDY port_buf_len ; Y = buffer offset, resume from last position
8225 LDA adlc_cr2 ; Read SR2 for next pair
8228 .data_rx_loop←1← 825F BNE
BPL data_rx_complete ; SR2 bit7 clear: frame complete (FV)
; 4.21 Master 128: 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.
822A LDA acccon ; Save current ACCCON on stack (Master 128)
822D PHA ; Push ACCCON snapshot
822E LDA escapable ; Load desired ACCCON from workspace &97
8230 STA acccon ; Set ACCCON for the upcoming buffer stores
8233 LDA adlc_tx ; Read first byte of pair from RX FIFO
8236 STA (open_port_buf),y ; Store byte to buffer
8238 INY ; Advance buffer offset
8239 BNE read_sr2_between_pairs ; Y != 0: no page boundary crossing
823B INC open_port_buf_hi ; Crossed page: increment buffer high byte
823D DEC port_buf_len_hi ; Decrement remaining page count
823F BEQ page_boundary_restore ; No pages left: handle as complete
8241 .read_sr2_between_pairs←1← 8239 BNE
LDA adlc_cr2 ; Read SR2 between byte pairs
8244 BMI read_second_rx_byte ; SR2 bit7 set: more data available
8246 BNE frame_complete_restore ; SR2 non-zero, bit7 clear: frame done
8248 .read_second_rx_byte←1← 8244 BMI
LDA adlc_tx ; Read second byte of pair from RX FIFO
824B STA (open_port_buf),y ; Store byte to buffer
824D INY ; Advance buffer offset
824E STY port_buf_len ; Save updated buffer position
8250 BNE byte_pair_restore ; Y != 0: no page boundary crossing
8252 INC open_port_buf_hi ; Crossed page: increment buffer high byte
8254 DEC port_buf_len_hi ; Decrement remaining page count
8256 BEQ frame_complete_restore ; No pages left: frame complete
8258 .byte_pair_restore←1← 8250 BNE
PLA ; Pull saved ACCCON from stack
8259 STA acccon ; Restore caller's ACCCON between byte pairs
825C .check_sr2_loop_again
LDA adlc_cr2 ; Re-poll ADLC SR2 for next byte pair
825F BNE data_rx_loop ; More data: loop back to data_rx_loop
8261 JMP nmi_rti ; No more data: return from NMI
8264 .frame_complete_restore←2← 8246 BNE← 8256 BEQ
PLA ; Pull saved ACCCON (frame-complete path)
8265 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.

8268 .data_rx_complete←1← 8228 BPL
LDA #&84 ; A=&84: CR2 value (disable PSE)
826A STA adlc_cr2 ; Write CR2 = &84 to disable PSE for bit testing
826D LDA #0 ; A=0: CR1 value (disable all interrupts)
826F STA adlc_cr1 ; Write CR1 = 0 to disable all interrupts
8272 STY port_buf_len ; Save Y (byte count from data RX loop)
8274 LDA #2 ; A=&02: FV mask
8276 BIT adlc_cr2 ; Test SR2 FV (Z) and RDA (N)
8279 BEQ nmi_error_dispatch ; No FV -- error
827B BPL send_ack ; FV set, no RDA -- proceed to ACK
827D LDA port_buf_len_hi ; Check if buffer space remains
827F .read_last_rx_byte←3← 829C BEQ← 82C3 BEQ← 82CF BEQ
BEQ nmi_error_dispatch ; No buffer space: error/discard frame
8281 LDA adlc_tx ; FV+RDA: read and store last data byte
8284 LDY port_buf_len ; Y = current buffer write offset
8286 STA (open_port_buf),y ; Store last byte in port receive buffer
8288 INC port_buf_len ; Advance buffer write offset
828A BNE send_ack ; No page wrap: proceed to send ACK
828C INC open_port_buf_hi ; Page boundary: advance buffer page
828E .send_ack←2← 827B BPL← 828A 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.

8291 .nmi_data_rx_tube
LDA adlc_cr2 ; Read SR2 for Tube data receive path
8294 .rx_tube_data←1← 82AF BNE
BPL data_rx_tube_complete ; RDA clear: no more data, frame complete
8296 LDA adlc_tx ; Read data byte from ADLC RX FIFO
8299 JSR advance_buffer_ptr ; Check buffer limits and transfer size
829C BEQ read_last_rx_byte ; Zero: buffer full, handle as error
829E STA tube_data_register_3 ; Send byte to Tube data register 3
82A1 LDA adlc_tx ; Read second data byte (paired transfer)
82A4 STA tube_data_register_3 ; Send second byte to Tube
82A7 JSR advance_buffer_ptr ; Check limits after byte pair
82AA BEQ data_rx_tube_complete ; Zero: Tube transfer complete
82AC LDA adlc_cr2 ; Re-read SR2 for next byte pair
82AF BNE rx_tube_data ; More data available: continue loop
82B1 .data_rx_tube_error
JMP nmi_rti ; Unexpected end: return from NMI
82B4 .data_rx_tube_complete←2← 8294 BPL← 82AA BEQ
LDA #0 ; CR1=&00: disable all interrupts
82B6 STA adlc_cr1 ; Write CR1 for individual bit testing
82B9 LDA #&84 ; CR2=&84: disable PSE
82BB STA adlc_cr2 ; Write CR2: same pattern as main path
82BE LDA #2 ; A=&02: FV mask for Tube completion
82C0 BIT adlc_cr2 ; Test SR2 FV (Z) and RDA (N)
82C3 BEQ read_last_rx_byte ; No FV: incomplete frame, error
82C5 BPL ack_tx ; FV set, no RDA: proceed to ACK
82C7 LDA port_buf_len ; Check if any buffer was allocated
82C9 ORA port_buf_len_hi ; OR all 4 buffer pointer bytes together
82CB ORA open_port_buf ; Check buffer low byte
82CD ORA open_port_buf_hi ; Check buffer high byte
82CF BEQ read_last_rx_byte ; All zero (null buffer): error
82D1 LDA adlc_tx ; Read extra trailing byte from FIFO
82D4 STA rx_extra_byte ; Save extra byte in workspace for later use
82D7 LDA #&20 ; Bit5 = extra data byte available flag
82D9 ORA rx_src_net ; Set extra byte flag in tx_flags
82DC STA rx_src_net ; 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 rx_src_net (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.

82DF .ack_tx←2← 828E JMP← 82C5 BPL
LDA rx_src_net ; Load TX flags to check ACK type
82E2 BPL ack_tx_configure ; Bit7 clear: normal scout ACK
82E4 JSR advance_rx_buffer_ptr ; Final ACK: call completion handler
82E7 JMP tx_result_ok ; Jump to TX success result
82EA .ack_tx_configure←1← 82E2 BPL
LDA #&44 ; CR1=&44: RX_RESET | TIE (switch to TX mode)
82EC STA adlc_cr1 ; Write CR1: switch to TX mode
82EF LDA #&a7 ; CR2=&A7: RTS|CLR_TX_ST|FC_TDRA|2_1_BYTE|PSE
82F1 STA adlc_cr2 ; Write CR2: enable TX with status clear
82F4 LDA #&86 ; Install saved next handler (scout ACK path)
82F6 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 adlc_cr1. 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 adlc_tx, 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
82F8 .ack_tx_write_dest←2← 81B5 JMP← 84F6 JMP
STA saved_nmi_lo ; Store next handler low byte
82FB STY saved_nmi_hi ; Store next handler high byte
82FE LDA scout_buf ; Load dest station from RX scout buffer
8301 BIT adlc_cr1 ; Test SR1 TDRA (V=bit6)
8304 BVC dispatch_nmi_error ; TDRA not ready -- error
8306 STA adlc_tx ; Write dest station to TX FIFO
8309 LDA scout_src_net ; Load dest network from RX scout buffer
830C STA adlc_tx ; Write dest net byte to FIFO
830F LDA #&16 ; A=&16: low byte of nmi_ack_tx_src (&8316)
8311 LDY #&83 ; High byte of nmi_ack_tx_src
8313 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 rx_src_net 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
8316 .nmi_ack_tx_src
LDA tx_src_stn ; Load our station ID from workspace copy
8319 BIT adlc_cr1 ; Test SR1 TDRA
831C BVC dispatch_nmi_error ; TDRA not ready -- error
831E STA adlc_tx ; Write our station to TX FIFO
8321 LDA #0 ; Write network=0 to TX FIFO
8323 STA adlc_tx ; Write network=0 (local) to TX FIFO
8326 LDA rx_src_net ; Check tx_flags for data phase
8329 BMI start_data_tx ; bit7 set: start data TX phase
832B 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.
832D .post_ack_scout
STA adlc_cr2 ; Write CR2 to clear status after ACK TX
8330 LDA saved_nmi_lo ; Install saved handler from &0D4B/&0D4C
8333 LDY saved_nmi_hi ; Load saved next handler high byte
8336 JMP set_nmi_vector ; Install next NMI handler
8339 .start_data_tx←1← 8329 BMI
JMP data_tx_begin ; Jump to start data TX phase
833C .dispatch_nmi_error←2← 8304 BVC← 831C 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
833F .advance_rx_buffer_ptr←2← 82E4 JSR← 8395 JSR
LDA #2 ; A=2: test bit1 of tx_flags
8341 BIT rx_src_net ; Check tx_flags data-transfer bit
8344 BEQ return_rx_complete ; Bit1 clear: no transfer -- return
8346 CLC ; Init carry for 4-byte add
8347 PHP ; Save carry on stack for loop
8348 LDY #8 ; Y=8: start at byte 0 of the 4-byte RXCB pointer
834A .add_rxcb_ptr←1← 8356 BCC
LDA (port_ws_offset),y ; Load RXCB[Y] (buffer pointer byte)
834C PLP ; Restore carry from stack
834D ADC net_tx_ptr,y ; Add transfer count byte
8350 STA (port_ws_offset),y ; Store updated pointer back to RXCB
8352 INY ; Next byte
8353 PHP ; Save carry for next iteration
8354 CPY #&0c ; Done 4 bytes? (Y reaches &0C)
8356 BCC add_rxcb_ptr ; No: continue adding
8358 PLP ; Discard final carry
8359 LDA #&20 ; A=&20: test bit5 of tx_flags
835B BIT rx_src_net ; Check tx_flags Tube bit
835E BEQ skip_tube_update ; No Tube: skip Tube update
8360 TXA ; Save X on stack
8361 PHA ; Push X
8362 LDA #8 ; A=8: offset for Tube address
8364 CLC ; For address calculation
8365 ADC port_ws_offset ; Add workspace base offset
8367 TAX ; X = address low for Tube claim
8368 LDY rx_buf_offset ; Y = address high for Tube claim
836A LDA #1 ; A=1: Tube claim type (read)
836C JSR tube_addr_data_dispatch ; Claim Tube address for transfer
836F LDA rx_extra_byte ; Load extra RX data byte
8372 STA tube_data_register_3 ; Send to Tube via R3
8375 SEC ; Init carry for increment
8376 LDY #8 ; Y=8: start at byte 0 of the 4-byte RXCB pointer
8378 .inc_rxcb_ptr←1← 837F BCS
LDA #0 ; A=0: add carry only (increment)
837A ADC (port_ws_offset),y ; Add carry to pointer byte
837C STA (port_ws_offset),y ; Store back to RXCB
837E INY ; Next byte
837F BCS inc_rxcb_ptr ; Keep going while carry propagates
8381 PLA ; Restore X from stack
8382 TAX ; Transfer to X register
8383 .skip_tube_update←1← 835E BEQ
LDA #&ff ; A=&FF: return value (transfer done)
8385 .return_rx_complete←1← 8344 BEQ
RTS ; Return

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
8386 .nmi_post_ack_dispatch
LDA scout_port ; Load received port byte
8389 BNE rx_complete_update_rxcb ; Port != 0: data transfer frame
838B LDY scout_ctrl ; Port=0: load control byte
838E CPY #&82 ; Ctrl = &82 (POKE)?
8390 BEQ rx_complete_update_rxcb ; Yes: POKE also needs data transfer
8392 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.

8395 .rx_complete_update_rxcb←3← 8389 BNE← 8390 BEQ← 8431 JMP
JSR advance_rx_buffer_ptr ; Update buffer pointer and check for Tube
8398 BNE skip_buf_ptr_update ; Transfer not done: skip buffer update
839A .add_buf_to_base
LDA port_buf_len ; Load buffer bytes remaining
839C CLC ; For address add
839D ADC open_port_buf ; Add to buffer base address
839F BCC store_buf_ptr_lo ; No carry: skip high byte increment
83A1 .inc_rxcb_buf_hi
INC open_port_buf_hi ; Carry: increment buffer high byte
83A3 .store_buf_ptr_lo←1← 839F BCC
LDY #8 ; Y=8: store updated buffer position
83A5 .store_rxcb_buf_ptr
STA (port_ws_offset),y ; Store updated low byte to RXCB
83A7 INY ; Y=9: buffer high byte offset
83A8 LDA open_port_buf_hi ; Load updated buffer high byte
83AA .store_rxcb_buf_hi
STA (port_ws_offset),y ; Store high byte to RXCB
83AC .skip_buf_ptr_update←1← 8398 BNE
LDA scout_port ; Check port byte again
83AF BEQ discard_reset_rx ; Port=0: immediate op, discard+listen
83B1 LDA scout_src_net ; Load source network from scout buffer
83B4 LDY #3 ; Y=3: RXCB source network offset
83B6 STA (port_ws_offset),y ; Store source network to RXCB
83B8 DEY ; Y=2: source station offset
83B9 LDA scout_buf ; Load source station from scout buffer
83BC STA (port_ws_offset),y ; Store source station to RXCB
83BE DEY ; Y=1: port byte offset
83BF LDA scout_port ; Load port byte
83C2 STA (port_ws_offset),y ; Store port to RXCB
83C4 DEY ; Y=0: control/flag byte offset
83C5 LDA scout_ctrl ; Load control byte from scout
83C8 ORA #&80 ; Set bit7: signals wait_net_tx_ack that reply arrived
83CA STA (port_ws_offset),y ; Store to RXCB byte 0 (bit 7 set = complete)
83CC LDA fs_flags ; Load callback event flags
83CF ROR ; Shift bit 0 into carry
83D0 BCC discard_reset_rx ; Bit 0 clear: no callback, skip to reset
83D2 LDA port_ws_offset ; Load RXCB workspace pointer low byte (carry set on entry)
83D4 .loop_count_rxcb_slot←1← 83D7 BCS
INY ; Count slots
83D5 SBC #&0c ; Subtract 12 bytes per RXCB slot
83D7 BCS loop_count_rxcb_slot ; Loop until pointer exhausted
83D9 DEY ; Adjust for off-by-one
83DA CPY #3 ; Check slot index >= 3
83DC BCC discard_reset_rx ; Slot < 3: no callback, skip to reset
83DE JSR discard_reset_listen ; Discard scout and reset listen state
83E1 TYA ; Pass slot index as callback parameter
83E2 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.

83E5 .discard_reset_rx←6← 8220 JMP← 83AF BEQ← 83D0 BCC← 83DC BCC← 8883 JMP← 88ED 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).

83E8 .reset_adlc_rx_listen←3← 80E5 JMP← 8434 BCS← 8529 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: &80CB (after init) and &80E2 (after error).

83EB .set_nmi_rx_scout←2← 80CB JMP← 80E2 JMP
LDA #&9b ; A=&9B: low byte of nmi_rx_scout
83ED LDY #&80 ; Y=&80: high byte of nmi_rx_scout
83EF 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 rx_src_net (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.

83F2 .discard_reset_listen←2← 83DE JSR← 83E5 JSR
LDA #2 ; Tube flag bit 1 AND tx_flags bit 1
83F4 AND tube_present ; Check if Tube transfer active
83F7 BIT rx_src_net ; Test tx_flags for Tube transfer
83FA BEQ return_from_discard_reset ; No Tube transfer active -- skip release
83FC JSR release_tube ; Release Tube claim before discarding
83FF .return_from_discard_reset←1← 83FA 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 rx_src_net (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 &81A4.

8400 .copy_scout_to_buffer←1← 81A4 JMP
TXA ; Save X on stack
8401 PHA ; Push X
8402 LDX #4 ; X=4: start at scout byte offset 4
8404 LDA #2 ; A=2: Tube transfer check mask
8406 .copy_scout_select
BIT rx_src_net ; 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.

8409 .save_acccon_for_shadow_ram
BNE copy_scout_via_tube ; Tube active: use R3 write path
; 4.21 Master 128: 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.
840B LDA acccon ; Save current ACCCON on stack (4.21 Master 128)
840E PHA ; Push ACCCON snapshot
840F LDA escapable ; Load desired ACCCON from workspace &97
8411 STA acccon ; Set ACCCON for the upcoming (open_port_buf),Y stores
8414 LDY port_buf_len ; Y = current buffer position
8416 .copy_scout_bytes←1← 8429 BNE
LDA scout_buf,x ; Load scout data byte
8419 STA (open_port_buf),y ; Store to port buffer
841B INY ; Advance buffer pointer
841C BNE next_scout_byte ; No page crossing
841E INC open_port_buf_hi ; Page crossing: inc buffer high byte
8420 DEC port_buf_len_hi ; Decrement remaining page count
8422 BEQ scout_page_overflow ; No pages left: overflow
8424 .next_scout_byte←1← 841C BNE
INX ; Next scout data byte
8425 STY port_buf_len ; Save updated buffer position
8427 CPX #&0c ; Done all scout data? (X reaches &0C)
8429 BNE copy_scout_bytes ; No: continue copying
842B .scout_copy_done
PLA ; Pull saved ACCCON from stack
842C STA acccon ; Restore caller's ACCCON before continuing
842F .scout_done_restore_x←2← 8446 BEQ← 8484 BEQ
PLA ; Pull saved X from stack
8430 TAX
8431 JMP rx_complete_update_rxcb ; Tail-jump to rx_complete_update_rxcb
8434 .dispatch_imm_op_fail←1← 846F BCS
BCS reset_adlc_rx_listen ; Reset ADLC if carry set
8436 .copy_scout_via_tube←2← 8409 BNE← 8444 BNE
LDA scout_buf,x ; Tube path: load scout data byte
8439 STA tube_data_register_3 ; Send byte to Tube via R3
843C JSR advance_buffer_ptr ; Increment buffer position counters
843F BEQ tube_overflow_restore ; Counter overflow: handle end of buffer
8441 INX ; Next scout data byte
8442 CPX #&0c ; Done all scout data?
8444 BNE copy_scout_via_tube ; No: continue Tube writes
8446 BEQ scout_done_restore_x ; ALWAYS branch

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
8448 .release_tube←2← 83FC JSR← 8961 JSR
BIT prot_flags ; Check if Tube needs releasing
844A BMI clear_release_flag ; Bit7 set: already released
844C LDA #&82 ; A=&82: Tube release claim type
844E JSR tube_addr_data_dispatch ; Release Tube address claim
8451 .clear_release_flag←1← 844A BMI
LSR prot_flags ; Clear release flag (LSR clears bit7)
8453 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 econet_flags immediate-op 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 immediate-op mask at econet_flags to determine whether this station accepts the operation. If accepted, dispatches via imm_op_dispatch_lo (PHA/PHA/RTS).

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

8454 .immediate_op←1← 8138 JMP
LDY scout_ctrl ; Control byte &81-&88 range check
8457 CPY #&81 ; Below &81: not an immediate op
8459 BCC imm_op_out_of_range ; Out of range low: jump to discard
845B CPY #&89 ; Above &88: not an immediate op
845D BCS imm_op_out_of_range ; Out of range high: jump to discard
845F CPY #&87 ; HALT(&87)/CONTINUE(&88) skip protection
8461 BCS dispatch_imm_op ; Ctrl >= &87: dispatch without mask check
8463 TYA ; Convert ctrl byte to 0-based index for mask
8464 SEC ; For subtract
8465 SBC #&81 ; A = ctrl - &81 (0-based operation index)
8467 TAY ; Y = index for mask rotation count
8468 LDA ws_0d68 ; Load protection mask from LSTAT
846B .rotate_prot_mask←1← 846D BPL
ROR ; Rotate mask right by control byte index
846C DEY ; Decrement rotation counter
846D BPL rotate_prot_mask ; Loop until bit aligned
846F BCS dispatch_imm_op_fail ; Bit set = operation disabled, discard
8471 .dispatch_imm_op←1← 8461 BCS
LDY scout_ctrl ; Reload ctrl byte for dispatch table
8474 LDA #&84 ; Hi byte: all handlers are in page &84
8476 PHA ; Push hi byte for PHA/PHA/RTS dispatch
8477 LDA imm_op_handler_lo_table,y ; Load handler low byte from jump table
847A PHA ; Push handler low byte
847B RTS ; RTS dispatches to handler
847C .scout_page_overflow←1← 8422 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.
847E .tube_overflow_restore←1← 843F BEQ
PLA ; Pull saved ACCCON from stack
847F STA acccon ; Restore caller's ACCCON on Tube-overflow exit
8482 .check_scout_done
CPX #&0b ; Check if scout data index reached 11
8484 BEQ scout_done_restore_x ; Yes: loop back to continue reading
8486 PLA ; Restore A from stack
8487 TAX ; Transfer to X
8488 .imm_op_out_of_range←2← 8459 BCC← 845D BCS
JMP nmi_error_dispatch ; Jump to discard handler

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

Eight low-byte entries at &848B-&8492 indexed by the immediate-op control byte (&81-&88) via LDA imm_op_dispatch_lo-&81,Y at the dispatch site. Each entry is the low byte of (handler-1) so PHA/PHA/RTS dispatch lands on the handler. The high byte pushed by the dispatcher is constant (&84), so all targets sit in &84xx. Per-entry inline comments identify each control byte's handler.

848B .imm_op_dispatch_lo
EQUB <(rx_imm_peek-1) ; ctrl &81: PEEK
848C EQUB <(rx_imm_poke-1) ; ctrl &82: POKE
848D EQUB <(rx_imm_exec-1) ; ctrl &83: JSR
848E EQUB <(rx_imm_exec-1) ; ctrl &84: UserProc
848F EQUB <(rx_imm_exec-1) ; ctrl &85: OSProc
8490 EQUB <(rx_imm_halt_cont-1) ; ctrl &86: HALT
8491 EQUB <(rx_imm_halt_cont-1) ; ctrl &87: CONTINUE
8492 EQUB <(rx_imm_machine_type-1) ; ctrl &88: machine-type

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 at &81C1.

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

8493 .rx_imm_exec
LDA #0 ; A=0: port buffer lo at page boundary
8495 STA open_port_buf ; Set port buffer lo
8497 LDA #&82 ; Buffer length lo = &82
8499 STA port_buf_len ; Set buffer length lo
849B LDA #1 ; Buffer length hi = 1
849D STA port_buf_len_hi ; Set buffer length hi
849F LDA net_rx_ptr_hi ; Load RX page hi for buffer
84A1 STA open_port_buf_hi ; Set port buffer hi
84A3 LDY #1 ; Y=1: copy 2 bytes (1 down to 0)
84A5 .copy_addr_loop←1← 84AC BPL
LDA scout_data,y ; Load remote address byte
84A8 STA exec_addr_lo,y ; Store to exec address workspace
84AB DEY ; Next byte (descending)
84AC BPL copy_addr_loop ; Loop until all 4 bytes copied
84AE .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.

84B1 .svc5_dispatch_lo
.rx_imm_poke
LDA #&2e ; Port workspace offset = &2E
84B3 STA port_ws_offset ; Store as port_ws_offset
84B5 LDA #&0d ; RX buffer page = &0D
84B7 STA rx_buf_offset ; Store as rx_buf_offset
84B9 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.

84BC .rx_imm_machine_type
LDA #1 ; Buffer length hi = 1
84BE .set_rx_buf_len_hi
STA port_buf_len_hi ; Set buffer length hi
84C0 LDA #&fc ; Buffer length lo = &FC
84C2 STA port_buf_len ; Set buffer length lo
84C4 LDA #&ee ; Buffer start lo = &EE
84C6 STA open_port_buf ; Set port buffer lo
84C8 LDA #&88 ; Buffer hi = &88 (response goes to &88EE area)
84CA STA open_port_buf_hi ; Set port buffer hi
84CC BNE set_tx_reply_flag ; ALWAYS branch

RX immediate: PEEK setup

Writes &0D2E to port_ws_offset / rx_buf_offset, sets scout_status = 2, then calls tx_calc_transfer to send the PEEK response data back to the requesting station.

84CE .rx_imm_peek
LDA #&2e ; Port workspace offset = &3D
84D0 STA port_ws_offset ; Store workspace offset lo
84D2 LDA #&0d ; RX buffer page = &0D
84D4 STA rx_buf_offset ; Store workspace offset hi
84D6 LDA #2 ; Scout status = 2 (PEEK response)
84D8 STA rx_port ; Store scout status
84DB JSR tx_calc_transfer ; Calculate transfer size for response
84DE BCC imm_op_discard ; C=0: transfer not set up, discard
84E0 .set_tx_reply_flag←1← 84CC BNE
LDA rx_src_net ; Mark TX flags bit 7 (reply pending)
84E3 ORA #&80 ; Set reply pending flag
84E5 STA rx_src_net ; Store updated TX flags
84E8 .rx_imm_halt_cont
LDA #&44 ; CR1=&44: TIE | TX_LAST_DATA
84EA STA adlc_cr1 ; Write CR1: enable TX interrupts
84ED .tx_cr2_setup
LDA #&a7 ; CR2=&A7: RTS|CLR_RX_ST|FC_TDRA|PSE
84EF STA adlc_cr2 ; Write CR2 for TX setup
84F2 .tx_nmi_setup
LDA #&0f ; NMI handler lo byte (self-modifying)
84F4 LDY #&85 ; Y=&85: NMI handler high byte
84F6 JMP ack_tx_write_dest ; Acknowledge and write TX dest

Build immediate-operation reply header

Writes the reply-frame header for a port-0 immediate operation into the RX buffer at offsets &7F..&81:

RX offset Source Meaning
&7F port_buf_len + &80 reply data length (raw count + header offset)
&80 scout_buf requesting station
&81 scout_src_net requesting network

Then loads the control byte from scout_ctrl into A and falls through into setup_sr_tx, which stores A as tx_op_type and configures the ADLC for the SR phase of the reply. Reached via the immediate-op dispatch path.

84F9 .imm_op_build_reply←1← 8392 JMP
LDA port_buf_len ; Get buffer position for reply header
84FB CLC ; Clear carry for offset addition
84FC ADC #&80 ; Data offset = buf_len + &80 (past header)
84FE LDY #&7f ; Y=&7F: reply data length slot
8500 STA (net_rx_ptr),y ; Store reply data length in RX buffer
8502 LDY #&80 ; Y=&80: source station slot
8504 LDA scout_buf ; Load requesting station number
8507 STA (net_rx_ptr),y ; Store source station in reply header
8509 INY ; Y=&81
850A LDA scout_src_net ; Load requesting network number
850D STA (net_rx_ptr),y ; Store source network in reply header
850F LDA scout_ctrl ; Load control byte from received frame
fall through ↓

Save TX op type and configure shift-register mode

Stores the TX operation type in tx_op_type.

Op code Path
≥ &86 (HALT / CONTINUE / machine-type) branch forward to the ACCCON IRR set; shift register untouched
< &86 load the workspace shadow at ws_0d68, copy it to ws_0d69 (preserved for later restore), ORA in the SR-mode-2 bits, write back to ws_0d68

The shadow is flushed to the real VIA ACR/SR registers later in the Master IRQ path. Single caller (&83E2 in scout_complete).

On EntryATX operation type
8512 .setup_sr_tx←1← 83E2 JMP
STA tx_op_type ; Save TX operation type for SR dispatch
8515 CMP #&86 ; Op codes >= &86 (HALT/CONTINUE/machine-type) skip the SR setup
8517 BCS enable_irq_pending ; Skip ahead to the ACCCON IRR set
8519 LDA ws_0d68 ; Load shadow ACR/IER state
851C STA ws_0d69 ; Stash a copy in ws_0d69 for later restore
851F ORA #&1c ; In shift-register mode-2 control bits
8521 STA ws_0d68 ; Write updated VIA ACR shadow back to ws_0d68
8524 .enable_irq_pending←1← 8517 BCS
LDA #&80 ; A=&80: ACCCON bit 7 (IRR -- raise interrupt)
8526 TSB acccon ; Set ACCCON IRR to flag a pending interrupt to MOS
8529 .imm_op_discard←1← 84DE 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)
852C .advance_buffer_ptr←3← 8299 JSR← 82A7 JSR← 843C JSR
INC port_buf_len ; Increment buffer length low byte
852E BNE return_from_advance_buf ; No overflow: done
8530 INC port_buf_len_hi ; Increment buffer length high byte
8532 BNE return_from_advance_buf ; No overflow: done
8534 INC open_port_buf ; Increment buffer pointer low byte
8536 BNE return_from_advance_buf ; No overflow: done
8538 INC open_port_buf_hi ; Increment buffer pointer high byte
853A .return_from_advance_buf←3← 852E BNE← 8532 BNE← 8536 BNE
RTS ; Return

TX done dispatch lo-byte table (5 entries)

Low bytes of PHA/PHA/RTS dispatch targets for TX operation types &83-&87. Read by the dispatch at dispatch_svc5 via LDA tx_done_dispatch_lo-&83,Y (the operand lands mid-instruction inside set_rx_buf_len_hi). The dispatch trampoline pushes &85 as the high byte, so targets are &85xx+1. Entries for Y < &83 read from preceding code bytes and are not valid operation types. Per-entry inline comments identify each TX operation type's handler.

853B .tx_done_dispatch_lo
EQUB <(tx_done_jsr-1) ; op &83: remote JSR
853C EQUB <(tx_done_econet_event-1) ; op &84: fire Econet event
853D EQUB <(tx_done_os_proc-1) ; op &85: OSProc call
853E EQUB <(tx_done_halt-1) ; op &86: HALT
853F EQUB <(tx_done_continue-1) ; op &87: CONTINUE

TX done: remote JSR execution

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.

8540 .tx_done_jsr
LDA #&85 ; A=&85: high byte of tx_done_exit-1 (&8581)
8542 PHA ; Push hi byte on stack
8543 LDA #&81 ; A=&81: low byte of tx_done_exit-1 (&8581)
8545 PHA ; Push lo byte on stack
8546 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
8549 .tx_done_econet_event
LDX exec_addr_lo ; X = remote address lo from exec_addr_lo
854C LDA exec_addr_hi ; A = remote address hi from exec_addr_hi
854F LDY #event_network_error ; Y = 8: Econet event number
8551 .tx_done_fire_event
JSR oseven ; Generate event Y='Network error'
8554 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
8557 .tx_done_os_proc
LDX exec_addr_lo ; X = remote address lo
855A LDY exec_addr_hi ; Y = remote address hi
855D JSR dir_op_dispatch ; Call ROM entry point at &8000
8560 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
8563 .tx_done_halt
LDA #4 ; A=&04: bit 2 mask (halt flag in econet_flags)
8565 BIT econet_flags ; Test if already halted
8568 BNE tx_done_exit ; Already halted: skip to exit
856A ORA econet_flags ; Set bit 2 in econet_flags (halt)
856D STA econet_flags ; Store halt flag
8570 LDA #4 ; A=4: re-load halt bit mask
8572 CLI ; Enable interrupts during halt wait
8573 .halt_spin_loop←1← 8576 BNE
BIT econet_flags ; Test halt flag
8576 BNE halt_spin_loop ; Still halted: keep spinning
8578 BEQ tx_done_exit ; ALWAYS branch

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
857A .tx_done_continue
LDA econet_flags ; Load current econet_flags
857D AND #&fb ; Clear bit 2: release halted station
857F 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 &8042 (the SVC 5 IRQ-check path in svc5_irq_check), plus the JMPs at &8554, &8560, &8568, and the fall-through at &8578.

On ExitA0 (success status)
X, Yrestored from stack
8582 .tx_done_exit←5← 8042 JMP← 8554 JMP← 8560 JMP← 8568 BNE← 8578 BEQ
PLA ; Restore Y from stack
8583 TAY ; Transfer to Y register
8584 PLA ; Restore X from stack
8585 TAX ; Transfer to X register
8586 LDA #0 ; A=0: success status
8588 RTS ; Return with A=0 (success)

Begin TX operation

Main TX initiation entry point (called via the NETV trampoline).

  1. Copies destination station / network from the TXCB to the scout buffer.
  2. Dispatches: control byte ≥ &81 → immediate-op setup; else normal data transfer.
  3. Calculates transfer sizes via tx_calc_transfer; copies extra parameters into the workspace.
  4. Enters the INACTIVE polling loop at inactive_poll.
8589 .tx_begin←3← 9BC3 JSR← A92A JMP← AC1E JSR
TXA ; Save X on stack
858A PHA ; Push X
858B LDY #2 ; Y=2: TXCB offset for dest station
858D LDA (nmi_tx_block),y ; Load dest station from TX control block
858F STA tx_dst_stn ; Store to TX scout buffer
8592 INY ; Y=&03
8593 LDA (nmi_tx_block),y ; Load dest network from TX control block
8595 STA tx_dst_net ; Store to TX scout buffer
8598 LDY #0 ; Y=0: first byte of TX control block
859A LDA (nmi_tx_block),y ; Load control/flag byte
859C BMI tx_imm_op_setup ; Bit7 set: immediate operation ctrl byte
859E JMP tx_bad_ctrl_error ; Bit7 clear: normal data transfer
85A1 .tx_imm_op_setup←1← 859C BMI
STA tx_ctrl_byte ; Store control byte to TX scout buffer
85A4 TAX ; X = control byte for range checks
85A5 INY ; Y=1: port byte offset
85A6 LDA (nmi_tx_block),y ; Load port byte from TX control block
85A8 STA tx_port ; Store port byte to TX scout buffer
85AB BNE tx_line_idle_check ; Port != 0: skip immediate op setup
85AD CPX #&83 ; Ctrl < &83: PEEK/POKE need address calc
85AF BCS tx_ctrl_range_check ; Ctrl >= &83: skip to range check
85B1 SEC ; Init borrow for 4-byte subtract
85B2 PHP ; Save carry on stack for loop
85B3 LDY #8 ; Y=8: high pointer offset in TXCB
85B5 .calc_peek_poke_size←1← 85C9 BCC
LDA (nmi_tx_block),y ; Load TXCB[Y] (end addr byte)
85B7 DEY ; Y -= 4: back to start addr offset
85B8 DEY ; (continued)
85B9 DEY ; (continued)
85BA DEY ; (continued)
85BB PLP ; Restore borrow from stack
85BC SBC (nmi_tx_block),y ; end - start = transfer size byte
85BE STA tx_data_start,y ; Store result to tx_data_start
85C1 INY ; Y += 5: advance to next end byte
85C2 INY ; (continued)
85C3 INY ; (continued)
85C4 INY ; (continued)
85C5 INY ; (continued)
85C6 PHP ; Save borrow for next byte
85C7 CPY #&0c ; Done all 4 bytes? (Y reaches &0C)
85C9 BCC calc_peek_poke_size ; No: next byte pair
85CB PLP ; Discard final borrow
85CC .tx_ctrl_range_check←1← 85AF BCS
CPX #&81 ; Ctrl < &81: not an immediate op
85CE BCC tx_bad_ctrl_error ; Below range: normal data transfer
85D0 .check_imm_range
CPX #&89 ; Ctrl >= &89: out of immediate range
85D2 BCS tx_bad_ctrl_error ; Above range: normal data transfer
85D4 LDY #&0c ; Y=&0C: start of extra data in TXCB
85D6 .copy_imm_params←1← 85DE BCC
LDA (nmi_tx_block),y ; Load extra parameter byte from TXCB
85D8 STA imm_param_base,y ; Copy to NMI shim workspace at &0D1A+Y
85DB INY ; Next byte
85DC CPY #&10 ; Done 4 bytes? (Y reaches &10)
85DE BCC copy_imm_params ; No: continue copying
85E0 .tx_line_idle_check←1← 85AB BNE
LDA #&20 ; A=&20: mask for SR2 INACTIVE bit
85E2 BIT adlc_cr2 ; Test SR2 if line is idle
85E5 BNE tx_no_clock_error ; Line not idle: handle as line jammed
85E7 LDA #&fd ; A=&FD: high byte of timeout counter
85E9 PHA ; Push timeout high byte to stack
85EA LDA #6 ; Scout frame = 6 address+ctrl bytes
85EC STA rx_ctrl ; Store scout frame length
85EF 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)
85F1 .inactive_poll
STA rx_remote_addr ; Save TX index
85F4 PHA ; Push timeout byte 1 on stack
85F5 PHA ; Push timeout byte 2 on stack
85F6 LDY #&e7 ; Y=&E7: CR2 value for TX prep (RTS|CLR_TX_ST|CLR_RX_ST|FC_TDRA|2_1_ BYTE|PSE)
85F8 .reload_inactive_mask←3← 861E BNE← 8623 BNE← 8628 BNE
LDA #4 ; A=&04: INACTIVE bit mask for SR2 test
85FA .test_inactive_retry
PHP ; Save interrupt state
85FB SEI ; Disable interrupts for ADLC access
fall through ↓

Disable NMIs and test INACTIVE

Disables NMIs via two BIT reads of master_intoff (the Master 128 INTOFF register; the Model-B equivalent reads econet_station_id at &FE18 for the same side-effect), then polls SR2 for the INACTIVE bit (bit 2):

SR2 INACTIVE Action
set read SR1, write CR2=&67 to clear status, then test CTS (SR1 bit 4); if CTS present, branch to tx_prepare
clear re-enable NMIs via master_inton (INTON) and decrement the 3-byte timeout counter on the stack

On timeout, falls through to tx_line_jammed.

On EntryA&04 (INACTIVE bit mask)
Y&E7 (CR2 value for tx_prepare)
85FC .intoff_test_inactive
BIT master_intoff ; INTOFF -- disable NMIs
85FF BIT master_intoff ; INTOFF again (belt-and-braces)
8602 .test_line_idle
BIT adlc_cr2 ; Z = &04 AND SR2 -- tests INACTIVE
8605 BEQ inactive_retry ; INACTIVE not set -- re-enable NMIs and loop
8607 LDA adlc_cr1 ; Read SR1 (acknowledge pending interrupt)
860A LDA #&67 ; CR2=&67: CLR_TX_ST|CLR_RX_ST|FC_TDRA|2_1_BYTE| PSE
860C STA adlc_cr2 ; Write CR2: clear status, prepare TX
860F LDA #&10 ; A=&10: CTS mask for SR1 bit4
8611 BIT adlc_cr1 ; Test SR1 CTS present
8614 BNE tx_prepare ; CTS set -- clock hardware detected, start TX
8616 .inactive_retry←1← 8605 BEQ
BIT master_inton ; INTON -- re-enable NMIs (&FE20 read)
8619 PLP ; Restore interrupt state
861A TSX ; 3-byte timeout counter on stack
861B INC error_text,x ; Increment timeout counter byte 1
861E BNE reload_inactive_mask ; Not overflowed: retry INACTIVE test
8620 INC stack_page_2,x ; Increment timeout counter byte 2
8623 BNE reload_inactive_mask ; Not overflowed: retry INACTIVE test
8625 INC stack_page_3,x ; Increment timeout counter byte 3
8628 BNE reload_inactive_mask ; Not overflowed: retry INACTIVE test
862A BEQ tx_line_jammed ; ALWAYS branch

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)
862C .tx_bad_ctrl_error←3← 859E JMP← 85CE BCC← 85D2 BCS
LDA #&44 ; Error &44: control byte out of valid range
862E BNE store_tx_error ; ALWAYS branch

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.
8630 .tx_line_jammed←1← 862A BEQ
LDA #7 ; CR2=&07: FC_TDRA | 2_1_BYTE | PSE (abort TX)
8632 STA adlc_cr2 ; Write CR2 to abort TX
8635 PLA ; Clean 3 bytes of timeout loop state
8636 PLA ; Pop saved register
8637 PLA ; Pop saved register
8638 LDA #&40 ; Error &40 = 'Line Jammed'
863A BNE store_tx_error ; ALWAYS branch to shared error handler
863C .tx_no_clock_error←1← 85E5 BNE
LDA #&43 ; Error &43 = 'No Clock'
863E .store_tx_error←2← 862E BNE← 863A BNE
LDY #0 ; Offset 0 = error byte in TX control block
8640 STA (nmi_tx_block),y ; Store error code in TX CB byte 0
8642 LDA #&80 ; &80 = TX complete flag
8644 STA tx_complete_flag ; Signal TX operation complete
8647 PLA ; Restore X saved by caller
8648 TAX ; Move to X register
8649 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 master_inton 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)
864A .tx_prepare←1← 8614 BNE
STY adlc_cr2 ; Write CR2 = Y (&E7: RTS|CLR_TX_ST|CLR_RX_ST|FC_ TDRA|2_1_BYTE|PSE)
864D LDX #&44 ; CR1=&44: RX_RESET | TIE (TX active, TX interrupts enabled)
864F STX adlc_cr1 ; Write to ADLC CR1
8652 LDX #&e7 ; X=&E7: low byte of nmi_tx_data (&86E7)
8654 LDY #&86 ; High byte of NMI handler address
8656 STX nmi_jmp_lo ; Write NMI vector low byte directly
8659 STY nmi_jmp_hi ; Write NMI vector high byte directly
865C SEC ; SEC: prepare carry for ROR into bit 7
865D ROR prot_flags ; Rotate carry into bit 7 of prot_flags (Tube-claimed)
865F BIT master_inton ; INTON -- NMIs now fire for TDRA (&FE20 read)
8662 LDA tx_port ; Load destination port number
8665 BNE setup_data_xfer ; Port != 0: standard data transfer
8667 LDY tx_ctrl_byte ; Port 0: load control byte for table lookup
866A LDA tx_flags_table,y ; Look up tx_flags from table
866D STA rx_src_net ; Store operation flags
8670 LDA tx_length_table,y ; Look up tx_length from table
8673 STA rx_ctrl ; Store expected transfer length
8676 LDA #&86 ; A=&86: high byte of tx_ctrl_* dispatch target
8678 PHA ; Push high byte for PHA/PHA/RTS dispatch
8679 LDA tx_ctrl_dispatch_lo-&81,y ; Look up handler address low from table
867C PHA ; Push low byte for PHA/PHA/RTS dispatch
867D RTS ; RTS dispatches to control-byte handler

TX ctrl dispatch lo-byte table (8 entries)

Low bytes of PHA/PHA/RTS dispatch targets for TX control byte types &81-&88. Read by the dispatch at &8679 via LDA tx_ctrl_dispatch_lo-&81,Y (the operand lands mid- instruction inside intoff_test_inactive). High byte pushed by the dispatcher is always &86, so targets are &86xx+1. Last entry (&88) dispatches to tx_ctrl_machine_type, the 4 bytes immediately after the table.

867E .tx_ctrl_dispatch_lo
EQUB <(tx_ctrl_peek-1) ; ctrl &81: PEEK
867F EQUB <(tx_ctrl_poke-1) ; ctrl &82: POKE
8680 EQUB <(proc_op_status2-1) ; ctrl &83: JSR
8681 EQUB <(proc_op_status2-1) ; ctrl &84: UserProc
8682 EQUB <(proc_op_status2-1) ; ctrl &85: OSProc
8683 EQUB <(tx_ctrl_exit-1) ; ctrl &86: HALT
8684 EQUB <(tx_ctrl_exit-1) ; ctrl &87: CONTINUE
8685 EQUB <(tx_ctrl_machine_type-1) ; ctrl &88: machine type

TX ctrl: machine-type query setup

Handler for control byte &88. Sets scout_status = 3 and branches to store_status_copy_ptr, skipping the 4-byte address addition (no address parameters needed for a machine-type query).

Reached only via PHA/PHA/RTS dispatch from tx_ctrl_dispatch_lo entry &88.

On ExitA3 (scout_status for machine type query)
8686 .tx_ctrl_machine_type
LDA #3 ; A=3: scout_status for machine type query
8688 BNE store_status_copy_ptr ; Skip address addition, store status
fall through ↓

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)
868A .tx_ctrl_peek
LDA #3 ; A=3: scout_status for PEEK op
868C BNE tx_ctrl_store_and_add ; ALWAYS branch

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)
868E .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)
8690 .tx_ctrl_store_and_add←1← 868C BNE
STA rx_port ; Store scout status
8693 CLC ; Clear carry for 4-byte addition
8694 PHP ; Save carry on stack
8695 LDY #&0c ; Y=&0C: start at offset 12
8697 .add_bytes_loop←1← 86A4 BCC
LDA tx_addr_base,y ; Load workspace address byte
869A PLP ; Restore carry from previous byte
869B ADC (nmi_tx_block),y ; Add TXCB address byte
869D STA tx_addr_base,y ; Store updated address byte
86A0 INY ; Next byte
86A1 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 &86A9, which dispatches between broadcast and unicast based on whether tx_dst_stn and tx_dst_net are both &FF.

86A2 .tx_ctrl_proc
CPY #&10 ; Compare Y with 16-byte boundary
86A4 BCC add_bytes_loop ; Below boundary: continue addition
86A6 PLP ; Restore processor flags
86A7 BNE skip_buf_setup ; Skip buffer setup if transfer size is zero
86A9 .setup_data_xfer←1← 8665 BNE
LDA tx_dst_stn ; Load dest station for broadcast check
86AC AND tx_dst_net ; AND with dest network
86AF CMP #&ff ; Both &FF = broadcast address?
86B1 BNE setup_unicast_xfer ; Not broadcast: unicast path
86B3 LDA #&0e ; Broadcast scout: 14 bytes total
86B5 STA rx_ctrl ; Store broadcast scout length
86B8 LDA #&40 ; A=&40: broadcast flag
86BA STA rx_src_net ; Set broadcast flag in tx_flags
86BD LDY #4 ; Y=4: start of address data in TXCB
86BF .copy_bcast_addr←1← 86C7 BCC
LDA (nmi_tx_block),y ; Copy TXCB address bytes to scout buffer
86C1 STA tx_src_stn,y ; Store to TX source/data area
86C4 INY ; Next byte
86C5 CPY #&0c ; Done 8 bytes? (Y reaches &0C)
86C7 BCC copy_bcast_addr ; No: continue copying
86C9 BCS tx_ctrl_exit ; ALWAYS branch
86CB .setup_unicast_xfer←1← 86B1 BNE
LDA #0 ; A=0: clear flags for unicast
86CD STA rx_src_net ; Clear tx_flags
86D0 .proc_op_status2
LDA #2 ; scout_status=2: data transfer pending
86D2 .store_status_copy_ptr←1← 8688 BNE
STA rx_port ; Store scout status
86D5 .skip_buf_setup←1← 86A7 BNE
LDA nmi_tx_block ; Copy TX block pointer to workspace ptr
86D7 STA port_ws_offset ; Store low byte
86D9 LDA nmi_tx_block_hi ; Copy TX block pointer high byte
86DB STA rx_buf_offset ; Store high byte
86DD JSR tx_calc_transfer ; Calculate transfer size from RXCB
86E0 .tx_ctrl_exit←1← 86C9 BCS
PLP ; Restore processor status from stack
86E1 PLA ; Restore stacked registers (4 PLAs)
86E2 PLA
86E3 PLA
86E4 PLA
86E5 TAX ; Restore X from A
86E6 RTS ; Return to caller

NMI TX data handler

Writes 2 bytes per NMI invocation to the TX FIFO at adlc_tx. Uses BIT adlc_cr1 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
86E7 .nmi_tx_data
LDY rx_remote_addr ; Load TX buffer index
86EA BIT adlc_cr1 ; SR1: V=bit6(TDRA), N=bit7(IRQ)
86ED .tx_fifo_write←1← 8708 BMI
BVC tx_fifo_not_ready ; TDRA not set -- TX error
86EF LDA tx_dst_stn,y ; Load byte from TX buffer
86F2 STA adlc_tx ; Write to TX_DATA (continue frame)
86F5 INY ; Next TX buffer byte
86F6 LDA tx_dst_stn,y ; Load second byte from TX buffer
86F9 INY ; Advance TX index past second byte
86FA STY rx_remote_addr ; Save updated TX buffer index
86FD STA adlc_tx ; Write second byte to TX_DATA
8700 CPY rx_ctrl ; Compare index to TX length
8703 BCS tx_last_data ; Frame complete -- go to TX_LAST_DATA
8705 BIT adlc_cr1 ; Check if we can send another pair
8708 BMI tx_fifo_write ; IRQ set -- send 2 more bytes (tight loop)
870A JMP nmi_rti ; Wait for next NMI
870D .tx_error←1← 8750 BEQ
LDA #&42 ; Error &42
870F BNE tx_store_error ; ALWAYS branch
8711 .tx_fifo_not_ready←1← 86ED BVC
LDA #&67 ; CR2=&67: clear status, return to listen
8713 STA adlc_cr2 ; Write CR2: clear status, idle listen
8716 LDA #&41 ; Error &41 (TDRA not ready)
8718 .tx_store_error←1← 870F BNE
LDY tx_src_stn ; INTOFF (also loads station ID)
871B .delay_nmi_disable←1← 871E BNE
PHA ; PHA/PLA delay loop (256 iterations for NMI disable)
871C PLA ; PHA/PLA delay (~7 cycles each)
871D INY ; Increment delay counter
871E BNE delay_nmi_disable ; Loop 256 times for NMI disable
8720 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 econet_nmi_enable (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.

8723 .tx_last_data←1← 8703 BCS
LDA #&3f ; CR2=&3F: TX_LAST_DATA | CLR_RX_ST | FLAG_IDLE | FC_TDRA | 2_1_BYTE | PSE
8725 STA adlc_cr2 ; Write to ADLC CR2
8728 LDA #&2f ; Install NMI handler at &8728 (TX completion)
872A LDY #&87 ; High byte of handler address
872C JMP set_nmi_vector ; Install and return via set_nmi_vector

TX completion: switch to RX mode

Called via NMI after the frame (including CRC and closing flag) has been fully transmitted. 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 rx_src_net 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
872F .nmi_tx_complete
LDA #&82 ; Jump to error handler
8731 STA adlc_cr1 ; Write CR1 to switch from TX to RX
8734 BIT rx_src_net ; Test workspace flags
8737 BVC check_handshake_bit ; bit6 not set -- check bit0
8739 JMP tx_result_ok ; bit6 set -- TX completion
873C .check_handshake_bit←1← 8737 BVC
LDA #1 ; A=1: mask for bit0 test
873E BIT rx_src_net ; Test tx_flags bit0 (handshake)
8741 BEQ install_reply_scout ; bit0 clear: install reply handler
8743 JMP handshake_await_ack ; bit0 set -- four-way handshake data phase
8746 .install_reply_scout←1← 8741 BEQ
LDA #&4b ; Install nmi_reply_validate at &874B
8748 JMP install_nmi_handler ; 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).

874B .nmi_reply_scout
LDA #1 ; A=&01: AP mask for SR2
874D BIT adlc_cr2 ; Test SR2 AP (Address Present)
8750 BEQ tx_error ; No AP -- error
8752 LDA adlc_tx ; Read first RX byte (destination station)
8755 CMP tx_src_stn ; Compare to our station ID (workspace copy)
8758 BNE reject_reply ; Not our station -- error/reject
875A LDA #&5f ; Install next handler at &8758 (reply continuation)
875C 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 adlc_cr1 / 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.

875F .nmi_reply_cont
BIT adlc_cr2 ; Read RX byte (destination station)
8762 BPL reject_reply ; No RDA -- error
8764 LDA adlc_tx ; Read destination network byte
8767 BNE reject_reply ; Non-zero -- network mismatch, error
8769 LDA #&76 ; A=&76: low byte of nmi_reply_validate (&8776)
876B BIT adlc_cr1 ; Test SR1 IRQ (N=bit7) -- more data ready?
876E BMI nmi_reply_validate ; IRQ set -- fall through to &8779
8770 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).

8773 .reject_reply←7← 8758 BNE← 8762 BPL← 8767 BNE← 8779 BPL← 8781 BNE← 8789 BNE← 8790 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).

8776 .nmi_reply_validate←1← 876E BMI
BIT adlc_cr2 ; Test SR2 RDA (bit7). Must be set for valid reply.
8779 BPL reject_reply ; No RDA -- error (FV masking RDA via PSE would cause this)
877B LDA adlc_tx ; Read source station
877E CMP tx_dst_stn ; Compare to original TX destination station (&0D20)
8781 BNE reject_reply ; Mismatch -- not the expected reply, error
8783 LDA adlc_tx ; Read source network
8786 CMP tx_dst_net ; Compare to original TX destination network (&0D21)
8789 BNE reject_reply ; Mismatch -- error
878B LDA #2 ; A=&02: FV mask for SR2 bit1
878D BIT adlc_cr2 ; Test SR2 FV -- frame must be complete
8790 BEQ reject_reply ; No FV -- incomplete frame, error
8792 LDA #&a7 ; CR2=&A7: RTS|CLR_TX_ST|FC_TDRA|2_1_BYTE| PSE (TX in handshake)
8794 STA adlc_cr2 ; Write CR2: enable RTS for TX handshake
8797 LDA #&44 ; CR1=&44: RX_RESET | TIE (TX active for scout ACK)
8799 STA adlc_cr1 ; Write CR1: reset RX, enable TX interrupt
879C LDA #&86 ; Install handshake_await_ack into &0D43/&0D44 (four-way data phase)
879E LDY #&88 ; High byte &88 of next handler address
87A0 STA saved_nmi_lo ; Store low byte to nmi_next_lo
87A3 STY saved_nmi_hi ; Store high byte to nmi_next_hi
87A6 LDA tx_dst_stn ; Load dest station for scout ACK TX
87A9 BIT adlc_cr1 ; Test SR1 TDRA (V=bit6)
87AC BVC tx_check_tdra_ready ; TDRA not ready -- error
87AE STA adlc_tx ; Write dest station to TX FIFO
87B1 LDA tx_dst_net ; Write dest network to TX FIFO
87B4 STA adlc_tx ; Write dest network to TX FIFO
87B7 LDA #&be ; Install handler at &87B7 (write src addr for scout ACK)
87B9 LDY #&87 ; High byte &87 of handler address
87BB 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 rx_src_net 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.

87BE .nmi_scout_ack_src
LDA tx_src_stn ; Load our station ID from workspace copy
87C1 BIT adlc_cr1 ; Test SR1 TDRA
87C4 .tx_check_tdra_ready←1← 87AC BVC
BVC data_tx_check_fifo ; TDRA not ready -- error
87C6 STA adlc_tx ; Write our station to TX FIFO
87C9 LDA #0 ; Write network=0 to TX FIFO
87CB STA adlc_tx ; Write network byte to TX FIFO
fall through ↓

Begin data-frame TX: install nmi_data_tx or alt

Tests bit 1 of rx_src_net (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).

87CE .data_tx_begin←1← 8339 JMP
LDA #2 ; Test bit 1 of tx_flags
87D0 BIT rx_src_net ; Check if immediate-op or data-transfer
87D3 BNE install_imm_data_nmi ; Bit 1 set: immediate op, use alt handler
87D5 LDA #&eb ; A=&EB: low byte of nmi_data_tx alt-entry (&87EB)
87D7 LDY #&87 ; Y=&87: high byte of nmi_data_tx
87D9 JMP set_nmi_vector ; Install and return via set_nmi_vector
87DC .install_imm_data_nmi←1← 87D3 BNE
LDA #&45 ; Install nmi_imm_data at &8837
87DE LDY #&88 ; High byte of handler address
87E0 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.

87E3 .nmi_data_tx←1← 87ED BEQ
LDY port_buf_len_hi ; Y = buffer offset, resume from last position
87E5 BEQ data_tx_last ; No pages left: send final partial page
87E7 LDY port_buf_len ; Load remaining byte count
87E9 BEQ check_tdra_status ; Zero bytes left: skip to TDRA check
87EB LDY port_buf_len ; Load remaining byte count (alt entry)
87ED BEQ nmi_data_tx ; Zero: loop back to top of handler
87EF .check_tdra_status←1← 87E9 BEQ
BIT adlc_cr1 ; Test SR1 TDRA (V=bit6)
87F2 .data_tx_check_fifo←2← 87C4 BVC← 8822 BMI
BVC tube_tx_fifo_write ; TDRA not ready -- error
; 4.21 Master 128: 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.
87F4 LDA acccon ; Save current ACCCON on stack (Master 128)
87F7 PHA ; Push ACCCON snapshot
87F8 LDA escapable ; Load desired ACCCON from workspace &97
87FA STA acccon ; Set ACCCON for the upcoming buffer reads
87FD LDA (open_port_buf),y ; Write data byte to TX FIFO
87FF STA adlc_tx ; Write first byte of pair to FIFO
8802 INY ; Advance buffer offset
8803 BNE write_second_tx_byte ; No page crossing
8805 DEC port_buf_len_hi ; Page crossing: decrement page count
8807 BEQ frame_end_restore ; No pages left: send last data
8809 INC open_port_buf_hi ; Increment buffer high byte
880B .write_second_tx_byte←1← 8803 BNE
LDA (open_port_buf),y ; Load second byte of pair
880D STA adlc_tx ; Write second byte to FIFO
8810 INY ; Advance buffer offset
8811 STY port_buf_len ; Save updated buffer position
8813 BNE check_fifo_loop ; No page crossing
8815 DEC port_buf_len_hi ; Page crossing: decrement page count
8817 BEQ frame_end_restore ; No pages left: send last data
8819 INC open_port_buf_hi ; Increment buffer high byte
881B .check_fifo_loop←1← 8813 BNE
PLA ; Pull saved ACCCON from stack
881C STA acccon ; Restore caller's ACCCON between byte pairs
881F .check_irq_loop
BIT adlc_cr1 ; Test ADLC SR1 IRQ flag for next byte pair
8822 BMI data_tx_check_fifo ; IRQ still set: more bytes to send
8824 JMP nmi_rti ; IRQ cleared: return from NMI
8827 .frame_end_restore←2← 8807 BEQ← 8817 BEQ
PLA ; Pull saved ACCCON (frame-end path)
8828 STA acccon ; Restore caller's ACCCON before TX_LAST_DATA
882B .data_tx_last←3← 87E5 BEQ← 885E BEQ← 8874 BEQ
LDA #&3f ; CR2=&3F: TX_LAST_DATA (close data frame)
882D STA adlc_cr2 ; Write CR2 to close frame
8830 LDA rx_src_net ; Check tx_flags for next action
8833 BPL install_saved_handler ; Bit7 clear: error, install saved handler
8835 LDA #&e5 ; Install discard_reset_listen at &83F2
8837 LDY #&83 ; High byte of &83F2 handler
8839 JMP set_nmi_vector ; Set NMI vector and return
883C .install_saved_handler←1← 8833 BPL
LDA saved_nmi_lo ; Load saved next handler low byte
883F LDY saved_nmi_hi ; Load saved next handler high byte
8842 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.

8845 .nmi_data_tx_tube
BIT adlc_cr1 ; Tube TX: test SR1 TDRA
8848 .tube_tx_fifo_write←2← 87F2 BVC← 8879 BMI
BVC tx_tdra_error ; TDRA not ready -- error
884A LDA tube_data_register_3 ; Read byte from Tube R3
884D STA adlc_tx ; Write to TX FIFO
8850 INC port_buf_len ; Increment 4-byte buffer counter
8852 BNE write_second_tube_byte ; Low byte didn't wrap
8854 INC port_buf_len_hi ; Carry into second byte
8856 BNE write_second_tube_byte ; No further carry
8858 INC open_port_buf ; Carry into third byte
885A BNE write_second_tube_byte ; No further carry
885C INC open_port_buf_hi ; Carry into fourth byte
885E BEQ data_tx_last ; Counter wrapped to zero: last data
8860 .write_second_tube_byte←3← 8852 BNE← 8856 BNE← 885A BNE
LDA tube_data_register_3 ; Read second Tube byte from R3
8863 STA adlc_tx ; Write second byte to TX FIFO
8866 INC port_buf_len ; Increment 4-byte counter (second byte)
8868 BNE check_tube_irq_loop ; Low byte didn't wrap
886A .tube_tx_inc_byte2
INC port_buf_len_hi ; Carry into second byte
886C BNE check_tube_irq_loop ; No further carry
886E .tube_tx_inc_byte3
INC open_port_buf ; Carry into third byte
8870 BNE check_tube_irq_loop ; No further carry
8872 .tube_tx_inc_byte4
INC open_port_buf_hi ; Carry into fourth byte
8874 BEQ data_tx_last ; Counter wrapped to zero: last data
8876 .check_tube_irq_loop←3← 8868 BNE← 886C BNE← 8870 BNE
BIT adlc_cr1 ; Test SR1 IRQ for tight loop
8879 BMI tube_tx_fifo_write ; IRQ still set: write 2 more bytes
887B JMP nmi_rti ; No IRQ: return, wait for next NMI
887E .tx_tdra_error←1← 8848 BVC
LDA rx_src_net ; TX error: check flags for path
8881 BPL tx_result_fail ; Bit7 clear: TX result = not listening
8883 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.

8886 .handshake_await_ack←1← 8743 JMP
LDA #&82 ; CR1=&82: TX_RESET | RIE (switch to RX for final ACK)
8888 STA adlc_cr1 ; Write to ADLC CR1
888B LDA #&92 ; Install nmi_final_ack handler
888D LDY #&88 ; High byte of handler address
888F 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.

8892 .nmi_final_ack
LDA #1 ; A=&01: AP mask
8894 BIT adlc_cr2 ; Test SR2 AP
8897 BEQ tx_result_fail ; No AP -- error
8899 LDA adlc_tx ; Read dest station
889C CMP tx_src_stn ; Compare to our station (workspace copy)
889F BNE tx_result_fail ; Not our station -- error
88A1 LDA #&a6 ; A=&A6: low byte of nmi_final_ack_net (&88A6)
88A3 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
88A6 .nmi_final_ack_net
BIT adlc_cr2 ; Test SR2 RDA
88A9 BPL tx_result_fail ; No RDA -- error
88AB LDA adlc_tx ; Read dest network
88AE BNE tx_result_fail ; Non-zero -- network mismatch, error
88B0 LDA #&ba ; Install nmi_final_ack_validate handler
88B2 BIT adlc_cr1 ; Test SR1 IRQ -- more data ready?
88B5 BMI nmi_final_ack_validate ; IRQ set -- fall through to validate
88B7 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.

88BA .nmi_final_ack_validate←1← 88B5 BMI
BIT adlc_cr2 ; Test SR2 RDA
88BD BPL tx_result_fail ; No RDA -- error
88BF LDA adlc_tx ; Read source station
88C2 CMP tx_dst_stn ; Compare to TX dest station (&0D20)
88C5 BNE tx_result_fail ; Mismatch -- error
88C7 LDA adlc_tx ; Read source network
88CA CMP tx_dst_net ; Compare to TX dest network (&0D21)
88CD BNE tx_result_fail ; Mismatch -- error
88CF LDA rx_src_net ; Load TX flags for next action
88D2 BPL check_fv_final_ack ; bit7 clear: no data phase
88D4 JMP install_data_rx_handler ; Install data RX handler
88D7 .check_fv_final_ack←1← 88D2 BPL
LDA #2 ; A=&02: FV mask for SR2 bit1
88D9 BIT adlc_cr2 ; Test SR2 FV -- frame must be complete
88DC 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)
88DE .tx_result_ok←2← 82E7 JMP← 8739 JMP
LDA #0 ; A=0: success result code
88E0 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)
88E2 .tx_result_fail←11← 821A JMP← 8773 JMP← 8881 BPL← 8897 BEQ← 889F BNE← 88A9 BPL← 88AE BNE← 88BD BPL← 88C5 BNE← 88CD BNE← 88DC 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)
88E4 .tx_store_result←2← 8720 JMP← 88E0 BEQ
LDY #0 ; Y=0: index into TX control block
88E6 STA (nmi_tx_block),y ; Store result/error code at (nmi_tx_block),0
88E8 LDA #&80 ; A=&80: TX-complete signal for tx_complete_flag
88EA STA tx_complete_flag ; Signal TX complete
88ED JMP discard_reset_rx ; Full ADLC reset and return to idle listen

Purpose unknown. Unreferenced, unreachable.

88F0 .rom_gap_88f0
EQUB &0E, &0E, &0A, &0A, &0A, &06, &06, &0A, &81, &00, &00, &00, &00, &01, &01, &81

Calculate transfer size and reclaim Tube buffer

Inspects RXCB[6..7] (buffer end address byte 2 and high) to detect a Tube buffer (high=&FF, byte 2 in [&FE, &FF]).

Buffer type Action
Tube compute 4-byte transfer size by subtracting RXCB[8..&B] (start) from RXCB[4..7] (end); store via (port_ws_offset),Y; re-claim Tube via JSR &0406 with claim type &C2
Non-Tube fall through to fallback_calc_transfer for a 1-byte size subtraction without the Tube reclaim

Three callers: scout_complete (&819A), rx_imm_peek (&84DB), tx_ctrl_proc (&86DD).

On EntryY0 -- caller convention
On ExitAtransfer status
Cset if Tube address claimed, clear otherwise
8900 .tx_calc_transfer←3← 819A JSR← 84DB JSR← 86DD JSR
LDA acccon ; Read ACCCON (Master 128 access-control register)
8903 ORA #8 ; Set bit 3 of A (transfer-mode flag)
8905 STA escapable ; Store as escapable mode
8907 LDY #7 ; Y=7: scout-bytes counter
8909 LDA (port_ws_offset),y ; Read RXCB[7] (buffer addr high byte)
890B CMP #&ff ; Compare to &FF
890D BNE check_tx_in_progress ; Not &FF: normal buffer, skip Tube check
890F DEY ; Y=&06
8910 LDA (port_ws_offset),y ; Read RXCB[6] (buffer addr byte 2)
8912 CMP #&fe ; Check if addr byte 2 >= &FE (Tube range)
8914 BCC check_tx_in_progress ; C clear: no Tube, plain transfer path
8916 BNE shadow_enable_flag ; Z clear (other state set): use fallback path
8918 LDA acccon ; Z set: re-read ACCCON for second decision
891B ROR ; Rotate bit 0 (E flag) into C
891C BCC shadow_enable_flag ; C clear: shadow not enabled, fallback path
891E LDA #4 ; Shadow enabled: set bit 2 of escapable
8920 TSB escapable ; Atomic bit-set on escapable
8922 .shadow_enable_flag←2← 8916 BNE← 891C BCC
BRA fallback_calc_transfer ; Branch to fallback_calc_transfer (always)
8924 .check_tx_in_progress←2← 890D BNE← 8914 BCC
LDA tube_present ; Transmit in progress?
8927 BEQ fallback_calc_transfer ; No: fallback path
8929 LDA rx_src_net ; Load TX flags for transfer setup
892C ORA #2 ; Set bit 1 (transfer complete)
892E STA rx_src_net ; Store with bit 1 set (Tube xfer)
8931 SEC ; Init borrow for 4-byte subtract
8932 PHP ; Save carry on stack
8933 LDY #4 ; Y=4: start at RXCB offset 4
8935 .calc_transfer_size←1← 8947 BCC
LDA (port_ws_offset),y ; Load RXCB[Y] (current ptr byte)
8937 INY ; Y += 4: advance to RXCB[Y+4]
8938 INY ; (continued)
8939 INY ; (continued)
893A INY ; (continued)
893B PLP ; Restore borrow from previous byte
893C SBC (port_ws_offset),y ; Subtract RXCB[Y+4] (start ptr byte)
893E STA net_tx_ptr,y ; Store result byte
8941 DEY ; Y -= 3: next source byte
8942 DEY ; (continued)
8943 DEY ; (continued)
8944 PHP ; Save borrow for next byte
8945 CPY #8 ; Done all 4 bytes?
8947 BCC calc_transfer_size ; No: next byte pair
8949 PLP ; Discard final borrow
894A TXA ; Save X
894B PHA ; Save X
894C LDA #4 ; Compute address of RXCB+4
894E CLC ; For base pointer addition
894F ADC port_ws_offset ; Add RXCB base to get RXCB+4 addr
8951 TAX ; X = low byte of RXCB+4
8952 LDY rx_buf_offset ; Y = high byte of RXCB ptr
8954 LDA #&c2 ; Tube claim type &C2
8956 JSR tube_addr_data_dispatch ; Claim Tube transfer address
8959 BCC restore_x_and_return ; No Tube: skip reclaim
895B LDA rx_port ; Tube: reclaim with scout status
895E JSR tube_addr_data_dispatch ; Reclaim with scout status type
8961 JSR release_tube ; Release Tube claim after reclaim
8964 SEC ; C=1: Tube address claimed
8965 .restore_x_and_return←1← 8959 BCC
PLA ; Restore X
8966 TAX ; Restore X from stack
8967 RTS ; Return with C = transfer status
8968 .fallback_calc_transfer←2← 8922 BRA← 8927 BEQ
LDY #4 ; Y=4: RXCB current pointer offset
896A LDA (port_ws_offset),y ; Load RXCB[4] (current ptr lo)
896C LDY #8 ; Y=8: RXCB start address offset
896E SEC ; Set carry for subtraction
896F SBC (port_ws_offset),y ; Subtract RXCB[8] (start ptr lo)
8971 STA port_buf_len ; Store transfer size lo
8973 LDY #5 ; Y=5: current ptr hi offset
8975 LDA (port_ws_offset),y ; Load RXCB[5] (current ptr hi)
8977 SBC #0 ; Propagate borrow only
8979 STA open_port_buf_hi ; Temp store of adjusted hi byte
897B LDY #8 ; Y=8: start address lo offset
897D LDA (port_ws_offset),y ; Copy RXCB[8] to open port buffer lo
897F STA open_port_buf ; Store to scratch (side effect)
8981 LDY #9 ; Y=9: start address hi offset
8983 LDA (port_ws_offset),y ; Load RXCB[9]
8985 SEC ; Set carry for subtraction
8986 SBC open_port_buf_hi ; Subtract adjusted hi byte
8988 STA port_buf_len_hi ; Store transfer size hi
898A SEC ; Return with C=1
898B .nmi_shim_rom_src
RTS ; Return with C=1 (success)

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
898C .adlc_full_reset←3← 8053 JSR← 80DF JSR← 821D JSR
LDA #&c1 ; CR1=&C1: TX_RESET | RX_RESET | AC (both sections in reset, address control set)
898E STA adlc_cr1 ; Write CR1 to ADLC register 0
8991 LDA #&1e ; CR4=&1E (via AC=1): 8-bit RX word length, abort extend enabled, NRZ encoding
8993 STA adlc_tx2 ; Write CR4 to ADLC register 3
8996 LDA #0 ; CR3=&00 (via AC=1): no loop-back, no AEX, NRZ, no DTR
8998 STA adlc_cr2 ; 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
899B .adlc_rx_listen←2← 83E8 JSR← 89C7 JMP
LDA #&82 ; CR1=&82: TX_RESET | RIE (TX in reset, RX interrupts enabled)
899D STA adlc_cr1 ; Write to ADLC CR1
89A0 LDA #&67 ; CR2=&67: CLR_TX_ST | CLR_RX_ST | FC_TDRA | 2_1_BYTE | PSE
89A2 STA adlc_cr2 ; Write to ADLC CR2
89A5 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
89A6 .wait_idle_and_reset
BIT econet_init_flag ; Check if Econet has been initialised
89A9 BPL reset_enter_listen ; Not initialised: skip to RX listen
89AB .poll_nmi_idle←2← 89B0 BNE← 89B7 BNE
LDA nmi_jmp_lo ; Read current NMI handler low byte
89AE CMP #&9b ; Expected: &B3 (nmi_rx_scout low)
89B0 BNE poll_nmi_idle ; Not idle: spin and wait
89B2 LDA nmi_jmp_hi ; Read current NMI handler high byte
89B5 EOR #&80 ; Test if high byte = &80 (page of nmi_rx_scout)
89B7 BNE poll_nmi_idle ; Not idle: spin and wait
fall through ↓

Reset Econet flags and enter RX-listen

Disables NMIs via BIT master_intoff (the Master 128 dedicated INTOFF at &FE38), then clears tx_complete_flag and econet_init_flag by storing the current A value. Sets Y=5 (service-call workspace page) and jumps to adlc_rx_listen to configure the ADLC for passive listening.

Used during the wait-idle-and-reset path (svc &0D) to safely tear down the Econet state before another ROM can claim the NMI workspace.

On EntryAvalue to store into tx_complete_flag / econet_init_flag (typically 0 to clear)
On ExitY5 (service-call workspace page)
89B9 .save_econet_state
BIT master_intoff ; INTOFF: disable NMIs
89BC BIT master_intoff ; INTOFF again (belt-and-braces)
89BF STA tx_complete_flag ; TX not in progress
89C2 STA econet_init_flag ; Econet not initialised
89C5 LDY #5 ; Y=5: service call workspace page
89C7 .reset_enter_listen←1← 89A9 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 master_intoff      ; INTOFF (Master 128 dedicated register)
PHA
TYA
PHA
LDA #romsel-bank
STA romsel
JMP nmi_rx_scout

The BIT of econet_station_id (INTOFF) at entry and BIT of econet_nmi_enable (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 (IC97) 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.

89CA .nmi_bootstrap_entry
BIT master_intoff ; INTOFF: force /NMI high (IC97 flip-flop clear)
89CD PHA ; Save A
89CE TYA ; Transfer Y to A
89CF PHA ; Save Y (via A)
89D0 LDA #0 ; ROM bank 0 (patched during init for actual bank)
89D2 STA romsel ; Select Econet ROM bank via ROMSEL
89D5 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 econet_nmi_enable (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.

89D8 .rom_set_nmi_vector
STY nmi_jmp_hi ; Store handler high byte at &0D0D
89DB STA nmi_jmp_lo ; Store handler low byte at &0D0C
89DE LDA romsel_copy ; Restore NFS ROM bank
89E0 STA romsel ; Page in via hardware latch
89E3 PLA ; Restore Y from stack
89E4 TAY ; Transfer ROM bank to Y
89E5 PLA ; Restore A from stack
89E6 BIT master_inton ; INTON: guaranteed /NMI edge if ADLC IRQ asserted
89E9 RTI ; Return from interrupt
89EA EQUB &05, &00, &21

svc_dispatch low-byte table (51 entries)

Low-byte half of the PHA/PHA/RTS dispatch table read by svc_dispatch as LDA &89ED,X. Paired with the high-byte half at svc_dispatch_hi.

Index 0 is a placeholder (target value unused – never reached); indices 1..50 cover:

  • service handlers
  • language reply handlers
  • FSCV reasons
  • FS reply handlers
  • net-handle / OSWORD &13 trampolines

Per-entry inline comments give the index and the call/reply each slot dispatches.

89ED .svc_dispatch_lo←1← 8E6A LDA
EQUB &04 ; &00: placeholder (never reached)
89EE EQUB <(dispatch_rts-1) ; &01: no-op (RTS only)
89EF EQUB <(svc_dispatch_idx_2-1) ; &02: workspace claim helper (CMOS bit 0)
89F0 EQUB <(svc_2_priv_ws-1) ; &03: svc &02: private workspace pages
89F1 EQUB <(svc_3_autoboot-1) ; &04: svc &03: auto-boot
89F2 EQUB <(svc_4_star_command-1) ; &05: svc &04: unrecognised *command
89F3 EQUB <(svc5_irq_check-1) ; &06: svc &05: IRQ check
89F4 EQUB <(dispatch_rts-1) ; &07: no-op (RTS only)
89F5 EQUB <(svc_7_osbyte-1) ; &08: svc &07: unrecognised OSBYTE
89F6 EQUB <(svc_8_osword_disp-1) ; &09: svc &08: OSWORD dispatch
89F7 EQUB <(svc_9_help-1) ; &0A: svc &09: *HELP
89F8 EQUB <(dispatch_rts-1) ; &0B: no-op (RTS only)
89F9 EQUB <(econet_restore-1) ; &0C: svc &0B: NMI release
89FA EQUB <(wait_idle_and_reset-1) ; &0D: svc &0D: wait idle and reset
89FB EQUB <(svc_18_fs_select-1) ; &0E: svc &12: FS select
89FC EQUB <(match_on_suffix-1) ; &0F: svc &18: interactive HELP 'ON ' matcher
89FD EQUB <(raise_y_to_c8-1) ; &10: svc &21: static workspace claim
89FE EQUB <(set_rom_ws_page-1) ; &11: svc &22: dynamic workspace offer
89FF EQUB <(store_ws_page_count-1) ; &12: svc &23: top-of-static-workspace
8A00 EQUB <(noop_dey_rts-1) ; &13: svc &24: dynamic workspace claim
8A01 EQUB <(copy_template_to_zp-1) ; &14: svc &25: FS name + info reply
8A02 EQUB <(svc_26_close_all_files-1) ; &15: svc &26: close all files
8A03 EQUB <(nfs_init_body-1) ; &16: svc &27: post-hard-reset re-init
8A04 EQUB <(print_fs_ps_help-1) ; &17: svc &28: print *FS/*PS no-arg syntax help
8A05 EQUB <(svc_29_status-1) ; &18: svc &29: *STATUS handler
8A06 EQUB <(lang_0_insert_key-1) ; &19: language reply 0: insert remote key
8A07 EQUB <(lang_1_remote_boot-1) ; &1A: language reply 1: remote boot
8A08 EQUB <(lang_2_save_palette_vdu-1) ; &1B: language reply 2: save palette/VDU
8A09 EQUB <(lang_3_exec_0100-1) ; &1C: language reply 3: execute at &0100
8A0A EQUB <(lang_4_validated-1) ; &1D: language reply 4: remote validated
8A0B EQUB <(fscv_0_opt_entry-1) ; &1E: FSCV 0: *OPT
8A0C EQUB <(fscv_1_eof-1) ; &1F: FSCV 1: EOF
8A0D EQUB <(cmd_run_via_urd-1) ; &20: FSCV 2: *RUN
8A0E EQUB <(fscv_3_star_cmd-1) ; &21: FSCV 3: *command
8A0F EQUB <(cmd_run_via_urd-1) ; &22: FSCV 4: *RUN (alias)
8A10 EQUB <(fscv_5_cat-1) ; &23: FSCV 5: *CAT
8A11 EQUB <(fscv_6_shutdown-1) ; &24: FSCV 6: shutdown
8A12 EQUB <(fscv_7_read_handles-1) ; &25: FSCV 7: read handles
8A13 EQUB <(dispatch_rts-1) ; &26: no-op (RTS only)
8A14 EQUB <(ps_scan_resume-1) ; &27: PS scan tail (after pop_requeue)
8A15 EQUB <(cmd_info_dispatch-1) ; &28: *Info dispatch
8A16 EQUB <(check_urd_present-1) ; &29: URD-present check
8A17 EQUB <(ex_init_scan_x0-1) ; &2A: *Ex scan init
8A18 EQUB <(fsreply_1_boot-1) ; &2B: FS reply 1: copy handles + boot
8A19 EQUB <(fsreply_2_copy_handles-1) ; &2C: FS reply 2: copy handles
8A1A EQUB <(fsreply_3_set_csd-1) ; &2D: FS reply 3: set CSD
8A1B EQUB <(cmd_run_via_urd-1) ; &2E: FS reply 4: *RUN (alias)
8A1C EQUB <(fsreply_5_set_lib-1) ; &2F: FS reply 5: set library
8A1D EQUB <(net_1_read_handle-1) ; &30: net handle 1: read handle
8A1E EQUB <(net_2_read_entry-1) ; &31: net handle 2: read handle entry
8A1F 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 by svc_dispatch as LDA &8A20,X. The dispatcher pushes the hi byte first then the lo, so RTS lands on target (the table stores target-1). The trailing byte at &8A53 is 1-byte padding – there are only 51 valid entries (0..50).

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

Service call dispatch (Master 128)

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
8A54 .service_handler←1← 8003 JMP
PHA ; Save service call number
8A55 CMP #&0f ; Service call &0F (vectors claimed)?
8A57 BNE restore_rom_slot_entry ; No: skip vectors-claimed handling
8A59 PHY ; Save Y on stack across the version-check
8A5A LDA #osbyte_read_os_version ; OSBYTE 0: read OS version
8A5C LDX #1 ; X=1 to request version number
8A5E JSR osbyte ; Read OS version number into X
8A61 CPX #3 ; OS 3.2/3.5 (Master 128)?
8A63 BEQ restore_rom_slot ; Yes: target OS, skip Bad ROM message
8A65 CPX #4 ; OS 4.0 (Master Econet Terminal)?
8A67 BEQ restore_rom_slot ; Yes: target OS, skip Bad ROM message
8A69 TXA ; Transfer OS version to A
8A6A PHP ; Save flags (Z set if OS 1.00) across print
8A6B JSR print_inline ; Print 'CR>Bad ROM ' to mark non-Master OS
8A6E EQUS ".Bad ROM " ; svc 13 fail path
8A77 LDA romsel_copy ; Load this ROM's slot number
8A79 JSR print_num_no_leading ; Print slot number as decimal
8A7C JSR print_newline_no_spool ; Print trailing newline, bypassing *SPOOL
8A7F LDX romsel_copy ; Reload ROM slot for workspace clearing
8A81 PLP ; Restore flags
8A82 BEQ clear_workspace_byte ; OS 1.00: skip INX (table starts at slot 0)
8A84 INX ; Adjust index for OS 1.20/2.00/5.00 layout
8A85 .clear_workspace_byte←1← 8A82 BEQ
LDA #0 ; A=0
8A87 STA rom_type_table,x ; Clear workspace byte for this ROM
8A8A .restore_rom_slot←2← 8A63 BEQ← 8A67 BEQ
LDX romsel_copy ; Restore ROM slot to X
8A8C PLY ; Restore Y from stack
8A8D .restore_rom_slot_entry←1← 8A57 BNE
PLA ; Pop service call number into A
8A8E PHA ; Re-save service call number
8A8F CMP #&24 ; Service call &24 (Dynamic Workspace requirements)?
8A91 BNE check_adlc_flag ; No: skip ADLC check
8A93 LDA adlc_cr1 ; Read ADLC status register 1
8A96 AND #&10 ; Mask relevant status bits
8A98 BNE check_adlc_flag ; Non-zero: ADLC absent, set flag
8A9A .set_adlc_absent
ROL rom_ws_pages,x ; Shift bit 7 into carry
8A9D SEC ; Set carry to mark ADLC absent
8A9E ROR rom_ws_pages,x ; Rotate carry into bit 7 of slot flag
8AA1 .check_adlc_flag←2← 8A91 BNE← 8A98 BNE
LDA rom_ws_pages,x ; Load ROM slot flag byte
8AA4 ASL ; Shift bit 7 (ADLC absent) into carry
8AA5 PLA ; Restore service call number
8AA6 BCC dispatch_svc_with_state ; ADLC present: continue dispatch
8AA8 RTS ; ADLC absent: decline service, return
8AA9 .dispatch_svc_with_state←1← 8AA6 BCC
TAX ; Transfer service number to X
8AAA LDA svc_state ; Save current service state
8AAC PHA ; Push old state
8AAD TXA ; Restore service number to A
8AAE STA svc_state ; Store as current service state
8AB0 CMP #&0d ; Service < 13?
8AB2 BCC dispatch_svc_index ; Yes: use as dispatch index directly
8AB4 SBC #5 ; Subtract 5 (map 13-17 to 8-12)
8AB6 CMP #&0d ; Mapped value = 13? (original was 18)
8AB8 BEQ dispatch_svc_index ; Yes: valid service 18 (FS select)
8ABA BCC dispatch_svc_state_check ; C clear: service number was below the prior CMP threshold, take dispatch fall-through
8ABC SBC #5 ; Subtract 5 to remap service range
8ABE CMP #&0e ; Compare with &0E
8AC0 BEQ dispatch_svc_index ; Equal: dispatch directly
8AC2 BCC dispatch_svc_state_check ; Below: take dispatch fall-through
8AC4 SBC #8 ; Subtract 8 to remap further
8AC6 CMP #&0f ; Compare with &0F
8AC8 BCC dispatch_svc_state_check ; Below: dispatch fall-through
8ACA CMP #&18 ; Compare with &18
8ACC BCC dispatch_svc_index ; Below: dispatch index now in A
8ACE .dispatch_svc_state_check←3← 8ABA BCC← 8AC2 BCC← 8AC8 BCC
LDA #0 ; Unknown service: set index to 0
8AD0 .dispatch_svc_index←4← 8AB2 BCC← 8AB8 BEQ← 8AC0 BEQ← 8ACC BCC
TAX ; Transfer dispatch index to X
8AD1 BEQ restore_svc_state ; Index 0: unhandled service, skip
8AD3 LDA ws_page ; Save current workspace page
8AD5 PHA ; Push old page
8AD6 STY ws_page ; Set workspace page from Y parameter
8AD8 TYA ; Transfer Y to A
8AD9 LDY #0 ; Y=0 for dispatch offset
8ADB JSR svc_dispatch ; Dispatch to service handler via table
8ADE PLA ; Restore old workspace page
8ADF STA ws_page ; Store it back
8AE1 .restore_svc_state←1← 8AD1 BEQ
LDX svc_state ; Get service state (return code)
8AE3 PLA ; Restore old service state
8AE4 STA svc_state ; Store it back
8AE6 TXA ; Transfer return code to A
8AE7 .restore_romsel_rts
LDX romsel_copy ; Restore ROM slot to X
8AE9 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
8AEA .cmd_roff
LDY #0 ; Offset 0 in receive block
8AEC LDA (net_rx_ptr),y ; Load remote operation flag
8AEE BEQ clear_svc_and_ws ; Zero: already off, skip to cleanup
8AF0 LDA #0 ; A=0
8AF2 TAX ; X=&00
8AF3 STA (net_rx_ptr),y ; Clear remote operation flag
8AF5 TAY ; Y=&00
8AF6 LDA #osbyte_read_write_econet_keyboard_disable ; OSBYTE &C9: keyboard disable
8AF8 JSR osbyte ; Enable keyboard (for Econet)
8AFB LDA #&0a ; A=&0A: workspace init parameter
8AFD 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)
8B00 .scan_remote_keys←1← 986F JSR
STX nfs_workspace ; Save X in workspace
8B02 LDA #&ce ; A=&CE: start of key range
8B04 .loop_scan_key_range←1← 8B0F BEQ
LDX nfs_workspace ; Restore X from workspace
8B06 LDY #&7f ; Y=&7F: OSBYTE scan parameter
8B08 JSR osbyte ; OSBYTE: scan keyboard
8B0B ADC #1 ; Advance to next key code
8B0D CMP #&d0 ; Reached &D0?
8B0F BEQ loop_scan_key_range ; No: loop back (scan &CE and &CF)
8B11 .clear_svc_and_ws←1← 8AEE BEQ
LDA #0 ; A=0
8B13 STA svc_state ; Clear service state
8B15 STA nfs_workspace ; Clear workspace byte
8B17 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
8B18 .save_text_ptr←3← 8C46 JSR← 8C71 JSR← A603 JSR
PHA ; Save A
8B19 LDA os_text_ptr ; Copy OS text pointer low
8B1B STA fs_crc_lo ; to fs_crc_lo
8B1D LDA os_text_ptr_hi ; Copy OS text pointer high
8B1F STA fs_crc_hi ; to fs_crc_hi
8B21 PLA ; Restore A
8B22 .return_from_save_text_ptr←2← 8B47 BNE← 8B50 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
8B23 .cmd_net_fs←2← 8B52 JSR← 900D JSR
JSR get_ws_page ; Get workspace page for this ROM slot
8B26 STA fs_load_addr_hi ; Store as high byte of load address
8B28 LDA #0 ; A=0
8B2A STA fs_load_addr ; Clear low byte of load address
8B2C CLC ; Clear carry for addition
8B2D LDY #&76 ; Y=&76: checksum range end
8B2F .loop_sum_rom_bytes←1← 8B32 BPL
ADC (fs_load_addr),y ; Add byte to running checksum
8B31 DEY ; Decrement index
8B32 BPL loop_sum_rom_bytes ; Loop until all bytes summed
8B34 LDY #&77 ; Y=&77: checksum storage offset
8B36 EOR (fs_load_addr),y ; Compare with stored checksum
8B38 RTS ; Return -- last instruction of cmd_net_fs body
8B39 .cmd_net_check_hw
LDA #&20 ; A=&20: ADLC IRQ-status mask (CR2 bit 5)
8B3B BIT adlc_cr2 ; Read ADLC CR2/SR2 (&FEA1)
8B3E BEQ select_fs_via_cmd_net_fs ; Z set (no carrier): proceed to FS-select
8B40 LDA #3 ; A=3: 'ROM has no NFS' error code
8B42 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
8B45 .svc_18_fs_select
CPY #5 ; Service 18 carries FS number in Y; Econet is FS 5
8B47 BNE return_from_save_text_ptr ; Not us: pass the call on (RTS via shared return)
8B49 LDA #0 ; A=0 to claim the service
8B4B 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 return_from_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)
8B4D .ensure_fs_selected←7← 8E8F JSR← 96B9 JSR← A9CC JSR← A9DA JSR← AAC2 JSR← AAD0 JSR← AC4C JSR
BIT fs_flags ; Test fs_flags bit 7 (ANFS active)
8B50 BMI return_from_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
8B52 .select_fs_via_cmd_net_fs←2← 8B3E BEQ← 8CDD JSR
JSR cmd_net_fs ; Auto-select ANFS via the *NFS handler
8B55 BEQ select_fs_cmd_net_fs ; Z=1 (A=0): selection succeeded
8B57 JMP error_net_checksum ; Otherwise raise 'net checksum' error
8B5A .select_fs_cmd_net_fs←1← 8B55 BEQ
LDA osword_pb_ptr_hi ; Read osword_pb_ptr_hi
8B5C PHA ; Push it
8B5D LDA osword_pb_ptr ; Read osword_pb_ptr lo
8B5F PHA ; Push it
8B60 .done_rom_checksum
JSR notify_new_fs ; Call FSCV with A=6 (new FS)
8B63 LDY #9 ; Y=9: end of FS context block
8B65 .loop_copy_fs_ctx←1← 8B6D BNE
LDA (net_rx_ptr),y ; Load byte from receive block
8B67 STA hazel_minus_2,y ; Store into FS workspace
8B6A DEY ; Decrement index
8B6B CPY #1 ; Reached offset 1?
8B6D BNE loop_copy_fs_ctx ; No: continue copying
8B6F ROL fs_flags ; Shift bit 7 of FS flags into carry
8B72 CLC ; Clear carry
8B73 ROR fs_flags ; Clear bit 7 of FS flags
8B76 LDY #&0d ; Y=&0D: vector table size - 1
8B78 .loop_set_vectors←1← 8B7F BPL
LDA fs_vector_table,y ; Load FS vector address
8B7B STA vec_filev,y ; Store into FILEV vector table
8B7E DEY ; Decrement index
8B7F BPL loop_set_vectors ; Loop until all vectors installed
8B81 JSR init_adlc_and_vectors ; Initialise ADLC and NMI workspace
8B84 LDY #&1b ; Y=&1B: extended vector offset
8B86 LDX #7 ; X=7: two more vectors to set up
8B88 JSR write_vector_entry ; Set up extended vectors
8B8B LDA #0 ; A=0
8B8D STA hazel_fs_pending_state ; Clear FS state byte
8B90 STA hazel_chan_attr ; Clear workspace byte
8B93 STA hazel_fs_lib_flags ; Clear workspace byte
8B96 JSR store_rx_attribute ; Clear receive attribute byte
8B99 STA hazel_fs_error_code ; Clear workspace byte
8B9C JSR setup_ws_ptr ; Set up workspace pointers
8B9F JSR init_channel_table ; Initialise FS state
8BA2 LDY #&77 ; Y=&77: workspace block size - 1
8BA4 .loop_copy_ws_page←1← 8BAA BPL
LDA (fs_ws_ptr),y ; Load byte from source workspace
8BA6 STA hazel_fcb_addr_lo,y ; Store to page &10 shadow copy
8BA9 DEY ; Decrement index
8BAA BPL loop_copy_ws_page ; Loop until all bytes copied
8BAC LDA #&80 ; A=&80: FS selected flag
8BAE TSB fs_flags ; Set bit 0 of fs_flags (= NFS active)
8BB1 JSR issue_svc_15 ; Issue Master service call &0F (vector update)
8BB4 PLA ; Pop saved osword_pb_ptr lo
8BB5 STA osword_pb_ptr ; Restore osword_pb_ptr lo
8BB7 PLA ; Pop saved osword_pb_ptr hi
8BB8 STA osword_pb_ptr_hi ; Restore osword_pb_ptr hi
8BBA 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
8BBB .help_print_nfs_cmds←1← 8C6E JMP
LDX #&35 ; X=&35: NFS command table offset
8BBD 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
8BC0 .help_utils
LDX #0 ; X=0: utility command table offset
8BC2 BEQ print_cmd_table ; ALWAYS branch

*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)
8BC4 .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_loop to enumerate commands.

On EntryXoffset into cmd_table_fs
Vset=print version header, clear=newline only
8BC6 .print_cmd_table←2← 8BBD JSR← 8BC2 BEQ
BVC print_table_newline ; V clear: take newline-only path (skip version header)
8BC8 PHX ; Save X (cmd-table offset)
8BC9 PHY ; Save Y (text-pointer offset)
8BCA JSR print_version_header ; Print the version-banner header
8BCD PLY
8BCE PLX
8BCF CLV ; Clear overflow flag
8BD0 BVC print_cmd_table_loop ; ALWAYS branch
8BD2 .print_table_newline←1← 8BC6 BVC
JSR osnewl ; Write newline (characters 10 and 13)
fall through ↓

Enumerate and print command table entries

Walks the ANFS command table from offset X, printing each command name padded to 9 characters followed by its syntax description.

Entry byte bit 7 Treatment
clear print this entry
set mark end-of-table

The syntax descriptor byte's low 5 bits index into cmd_syntax_table; index &0E triggers special handling that lists shared command names in parentheses. Calls help_wrap_if_serial to handle line continuation on serial output streams. Preserves Y.

On EntryXoffset into cmd_table_fs
8BD5 .print_cmd_table_loop←2← 8BD0 BVC← 8C61 JSR
TYA ; Save Y (command line offset)
8BD6 PHA ; Push it
8BD7 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 &8C22 in print_cmd_table's outer loop).

On EntryXcurrent cmd_table_fs offset
8BD8 .loop_next_entry←1← 8C22 JMP
LDA cmd_table_fs,x ; Load byte from command table
8BDB BPL print_indent ; Bit 7 clear: valid entry, continue
8BDD JMP done_print_table ; End of table: finish up
8BE0 .print_indent←1← 8BDB BPL
JSR print_inline ; Print two-space indent
8BE3 EQUS " "
8BE5 LDY #9 ; Y=9: cmd_table_fs sub-table 1 offset
8BE7 LDA cmd_table_fs,x ; Read cmd_table_fs+X (entry name byte)
8BEA .loop_print_cmd_name←1← 8BF2 BPL
JSR osasci ; Write character
8BED INX ; Advance table pointer
8BEE DEY ; Decrement padding counter
8BEF LDA cmd_table_fs,x ; Load next character
8BF2 BPL loop_print_cmd_name ; Bit 7 clear: more chars, continue
8BF4 .loop_pad_spaces←1← 8BFA BPL
LDA #&20 ; Pad with spaces
8BF6 JSR osasci ; Write character 32
8BF9 DEY ; Decrement remaining pad count
8BFA BPL loop_pad_spaces ; More padding needed: loop
8BFC LDA cmd_table_fs,x ; Load syntax descriptor byte
8BFF AND #&1f ; Mask to get syntax string index
8C01 TAY ; Use index as Y
8C02 LDA cmd_syntax_table,y ; Look up syntax string offset
8C05 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
8C06 .loop_print_syntax←2← 8C13 JMP← 8C19 JMP
INY ; Advance to next character
8C07 LDA syn_opt_dir,y ; Load syntax string character
8C0A BEQ done_entry_newline ; Zero terminator: end of syntax
8C0C CMP #&0d ; Carriage return: line continuation
8C0E BNE print_syntax_char ; No: print the character
8C10 JSR help_wrap_if_serial ; Handle line wrap in syntax output
8C13 JMP loop_print_syntax ; Continue with next character
8C16 .print_syntax_char←1← 8C0E BNE
JSR osasci ; Write character
8C19 JMP loop_print_syntax ; Continue with next character
8C1C .done_entry_newline←1← 8C0A BEQ
JSR osnewl ; Write newline (characters 10 and 13)
8C1F INX ; X += 3: skip syntax descriptor and address
8C20 INX ; (continued)
8C21 INX ; (continued)
8C22 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
8C25 .done_print_table←1← 8BDD JMP
PLP ; Restore processor status
8C26 PLA ; Restore Y
8C27 TAY ; Transfer to Y
8C28 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)
8C29 .help_wrap_if_serial←1← 8C10 JSR
LDA vdu_mode ; Read output stream type
8C2C BEQ return_from_help_wrap ; Stream 0 (VDU): no wrapping
8C2E CMP #3 ; Stream 3 (printer)?
8C30 BEQ return_from_help_wrap ; Yes: no wrapping
8C32 PHY ; Save Y across OS call
8C33 JSR osnewl ; Write newline (characters 10 and 13)
8C36 LDY #&0b ; Y=&0B: indent width - 1
8C38 LDA #&20 ; Space character
8C3A .loop_indent_spaces←1← 8C3E BPL
JSR osasci ; Write character 32
8C3D DEY ; Decrement indent counter
8C3E BPL loop_indent_spaces ; More spaces needed: loop
8C40 PLY
8C41 .return_from_help_wrap←2← 8C2C BEQ← 8C30 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
8C42 .svc_4_star_command
LDX #0 ; X=0: start of utility command table
8C44 LDY ws_page ; Get command line offset
8C46 .svc4_dispatch_lookup←2← 95EB JMP← 968E JMP
JSR save_text_ptr ; Save text pointer to fs_crc
8C49 JSR match_fs_cmd ; Try to match command in table
8C4C BCS svc_return_unclaimed ; No match: return to caller
8C4E 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
8C51 .svc_9_help
JSR check_credits_easter_egg ; Check for credits Easter egg
8C54 LDY ws_page ; Get command line offset
8C56 LDA (os_text_ptr),y ; Load character at offset
8C58 CMP #&0d ; Is it CR (bare *HELP)?
8C5A BNE check_help_topic ; No: check for specific topic
8C5C JSR print_version_header ; Print version string
8C5F LDX #&91 ; X=&91: start of help command list
8C61 JSR print_cmd_table_loop ; 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, &8CD5, and &95BE that hand a request back to MOS without acting on it.

On ExitYws_page (restored command-line offset)
8C64 .svc_return_unclaimed←5← 8C4C BCS← 8C91 BEQ← 8CD5 BNE← 95BE JMP← 9689 JMP
LDY ws_page ; Restore Y (command line offset)
8C66 RTS ; Return unclaimed
8C67 .check_help_topic←1← 8C5A BNE
BIT always_set_v_byte ; Test for topic match (sets flags)
8C6A CMP #&2e ; Is first char '.' (abbreviation)?
8C6C BNE match_help_topic ; No: try topic-specific help
8C6E JMP help_print_nfs_cmds ; '.' found: show full command list
8C71 .match_help_topic←1← 8C6C BNE
JSR save_text_ptr ; Save text pointer to fs_crc
8C74 .loop_dispatch_help←1← 8C8F BNE
PHP ; Save flags
8C75 LDX #&91 ; X=&91: help command table start
8C77 JSR match_fs_cmd ; Try to match help topic in table
8C7A BCS skip_if_no_match ; No match: try next topic
8C7C PLP ; Restore flags
8C7D LDA #&8c ; Push return address high (&8C)
8C7F PHA ; Push it for RTS dispatch
8C80 LDA #&73 ; Push return address low (&74)
8C82 PHA ; Push it for RTS dispatch
8C83 LDA cmd_dispatch_hi_table,x ; Load dispatch address high
8C86 PHA ; Push dispatch high for RTS
8C87 LDA cmd_dispatch_lo_table,x ; Load dispatch address low
8C8A PHA ; Push dispatch low for RTS
8C8B RTS ; Dispatch via RTS (returns to &8C80)
8C8C .skip_if_no_match←1← 8C7A BCS
PLP ; Restore flags from before match
8C8D CMP #&0d ; End of command line?
8C8F BNE loop_dispatch_help ; No: try matching next topic
8C91 BEQ svc_return_unclaimed ; ALWAYS branch

Print ANFS version string and station number

Uses an inline string after JSR to print_inline: CR + "Advanced NFS 4.21" + 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)
8C93 .print_version_header←2← 8BCA JSR← 8C5C JSR
JSR print_inline ; Print version string via inline
8C96 .version_string_cr
EQUS ".Advanced NFS 4.21."
8CA9 NOP ; NOP -- bit-7 terminator + harmless resume opcode
8CAA 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 &8CB3&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)
8CAD .get_ws_page←4← 8B23 JSR← 8CBD JSR← 8F2F JSR← B3A0 JSR
LDY romsel_copy ; Y = current ROM slot number from MOS copy at &F4
8CAF LDA rom_ws_pages,y ; Load workspace page byte for this ROM slot
8CB2 TAY ; Hold a copy of the slot byte in Y while we test bit 6
8CB3 ROL ; ROL puts pre-ROL bit 6 into the post-ROL N flag (and pre-ROL bit 7 into C)
8CB4 PHP ; Save those flags so the upcoming ROR doesn't lose N
8CB5 ROR ; ROR restores A to its original value (using the saved C)
8CB6 PLP ; Restore the ROL flags: N is now pre-ROL bit 6
8CB7 BPL get_ws_page_loop ; Bit 6 clear: skip the OR (no ADLC-absent flag)
8CB9 ORA #&80 ; Bit 6 set: re-set bit 7 in the returned page byte (the ADLC-absent flag uses bit 7 in callers)
8CBB .get_ws_page_loop←1← 8CB7 BPL
TAY ; Transfer to Y
8CBC 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
8CBD .setup_ws_ptr←2← 8B9C JSR← 8F4F JSR
JSR get_ws_page ; Get workspace page for ROM slot
8CC0 STY nfs_temp ; Store page in nfs_temp
8CC2 LDA #0 ; A=0
8CC4 STA fs_ws_ptr ; Clear low byte of pointer
8CC6 .return_from_setup_ws_ptr←1← 8CEC 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 (Master 128 service-call dispatch)
8CC7 .svc_3_autoboot
LDA #osbyte_scan_keyboard_from_16 ; OSBYTE &7A: scan keyboard from key 16
8CC9 JSR osbyte ; Keyboard scan starting from key 16
8CCC TXA ; X is key number if key is pressed, or &ff otherwise
8CCD BMI select_net_fs ; No key pressed: select Net FS
8CCF CMP #&19 ; Key &19 (N)?
8CD1 BEQ write_key_state ; Yes: write key state and boot
8CD3 EOR #&55 ; EOR with &55: maps to zero if 'N'
8CD5 BNE svc_return_unclaimed ; Not N key: return unclaimed
8CD7 .write_key_state←1← 8CD1 BEQ
TAY ; Y=key
8CD8 LDA #osbyte_write_keys_pressed ; OSBYTE &78: write keys pressed
8CDA JSR osbyte ; Write current keys pressed (X and Y)
8CDD .select_net_fs←1← 8CCD BMI
JSR select_fs_via_cmd_net_fs ; Select NFS as current filing system
8CE0 LDA #0 ; A=0: clear svc_state marker
8CE2 STA svc_state ; Store -> svc_state
8CE4 JSR print_station_id ; Print station number
8CE7 JSR osnewl ; Write newline (characters 10 and 13)
8CEA LDX ws_page ; Get workspace page
8CEC BNE return_from_setup_ws_ptr ; Non-zero: already initialised, return
8CEE LDA hazel_fs_lib_flags ; Load boot flags
8CF1 ORA #4 ; Set bit 2 (auto-boot in progress)
8CF3 STA hazel_fs_lib_flags ; Store updated boot flags
8CF6 LDX #&1c ; X=&1C: boot filename address low
8CF8 LDY #&8d ; Y=&8D: boot filename address high
8CFA JMP fscv_3_star_cmd ; 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 (&8b60 inside the FS-selection sequence).

On ExitA6 (clobbered by FSCV handler)
8CFD .notify_new_fs←1← 8B60 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
8CFF .call_fscv←1← A59E JMP
JMP (vec_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
8D02 .issue_svc_15←1← 8BB1 JSR
LDX #&0f ; X=&0F: service 15 (vectors claimed)
8D04 .issue_svc_osbyte
LDA #osbyte_issue_service_request ; A=&8F: OSBYTE 'Issue paged-ROM service request'
8D06 JMP osbyte ; Issue paged ROM service call

svc_dispatch table[2] handler

Reached only via PHA/PHA/RTS dispatch from the svc_dispatch 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)
8D09 .svc_dispatch_idx_2
PHY ; Save Y on stack
8D0A LDX #&11 ; X=&11: CMOS offset for Econet station-flags
8D0C JSR osbyte_a1 ; Read CMOS byte: result in Y
8D0F TYA ; A = CMOS byte
8D10 PLY ; Restore caller's Y
8D11 AND #1 ; Isolate bit 0 (page-&0B fallback flag)
8D13 BEQ return_1 ; Bit clear: keep caller's Y
8D15 CPY #&10 ; Caller's Y already >= &10?
8D17 BCS return_1 ; Yes: keep it
8D19 LDY #&10 ; Y < &10 with bit set: clamp to &10
8D1B .return_1←2← 8D13 BEQ← 8D17 BCS
RTS ; Return
8D1C EQUS "i .Boot"
8D23 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.

8D24 .check_credits_easter_egg←1← 8C51 JSR
LDY ws_page ; Y = ws_page (workspace high page)
8D26 LDX #5 ; X=5: start of credits keyword
8D28 .loop_match_credits←1← 8D31 BNE
LDA (os_text_ptr),y ; Load character from command line
8D2A CMP credits_keyword_start,x ; Compare with credits keyword
8D2D BNE done_credits_check ; Mismatch: check if keyword complete
8D2F INY ; Advance command line pointer
8D30 INX ; Advance keyword pointer
8D31 BNE loop_match_credits ; Continue matching
8D33 .done_credits_check←1← 8D2D BNE
CPX #&0c ; Reached end of keyword (X=&0D)?
8D35 BNE return_from_credits_check ; No: keyword not fully matched, return
8D37 LDX #0 ; X=0: start of credits text
8D39 .loop_emit_credits←1← 8D42 BNE
LDA credits_keyword_start,x ; Load character from credits string
8D3C BEQ return_from_credits_check ; Zero terminator: done printing
8D3E JSR osasci ; Write character
8D41 INX ; Advance string pointer
8D42 BNE loop_emit_credits ; Continue printing
8D44 .return_from_credits_check←2← 8D35 BNE← 8D3C BEQ
RTS ; Return
8D45 .credits_keyword_start←2← 8D2A CMP← 8D39 LDA
EQUB &0D ; CR
8D46 EQUS "The authors of ANFS are;"
8D5E EQUB &0D ; CR
8D5F EQUS "B Cockburn"
8D69 EQUB &0D
8D6A EQUS "J Du"
8D6E EQUS "nn"
8D70 EQUB &0D
8D71 EQUS "B Robertson"
8D7C EQUB &0D
8D7D EQUS "J Wills"
8D84 EQUB &0D ; CR
8D85 EQUB &0D, &00
8D87 .cmd_iam_save_ctx
PHY ; Save caller Y
8D88 LDA fs_last_byte_flag ; Read fs_last_byte_flag (work_bd)
8D8A LDX fs_options ; Read fs_options (work_bb)
8D8C LDY fs_block_offset ; Read fs_block_offset (work_bc)
8D8E PHA ; Push fs_last_byte_flag for restore on return
8D8F PHX
8D90 PHY
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
8D91 .cmd_iam
LDA #osbyte_close_spool_exec ; OSBYTE &77: close SPOOL/EXEC
8D93 STA hazel_fs_pending_state ; Store as pending operation marker
8D96 JSR osbyte ; Close any *SPOOL and *EXEC files
8D99 LDY #0 ; Y=0
8D9B STY fs_work_4 ; Clear password entry flag
8D9D JSR process_all_fcbs ; Reset FS connection state
8DA0 STZ hazel_fs_pending_state ; Clear hazel_fs_pending_state (connection-attempt flag)
8DA3 PLY
8DA4 PLX
8DA5 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.

8DA6 .load_transfer_params
JSR set_xfer_params ; Set up transfer parameters
8DA9 PLY
8DAA LDA (fs_options),y ; Load first option byte
8DAC JSR is_decimal_digit ; Parse station number if present
8DAF BCC cmd_pass ; Not a digit: skip to password entry
8DB1 JSR parse_addr_arg ; Parse user ID string
8DB4 BCS skip_no_fs_addr ; No user ID: go to password
8DB6 STA hazel_fs_network ; Store file server station low
8DB9 JSR clear_if_station_match ; Check and store FS network
8DBC INY ; Skip separator
8DBD JSR parse_addr_arg ; Parse next argument
8DC0 .skip_no_fs_addr←1← 8DB4 BCS
BEQ cmd_pass ; No FS address: skip to password
8DC2 STA hazel_fs_station ; Store file server station high
8DC5 LDX #&ff ; X=&FF: pre-decrement for loop
8DC7 .loop_copy_logon_cmd←1← 8DCE BPL
INX ; Advance index
8DC8 LDA cmd_table_nfs_iam,x ; Load logon command template byte
8DCB STA hazel_txcb_data,x ; Store into transmit buffer
8DCE BPL loop_copy_logon_cmd ; Bit 7 clear: more bytes, loop
8DD0 JSR copy_arg_validated ; Send logon with file server lookup
8DD3 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)
8DD5 .cmd_pass←2← 8DAF BCC← 8DC0 BEQ
JSR copy_arg_to_buf_x0 ; Build FS command packet
8DD8 .scan_pass_prompt←1← 8DD3 BEQ
LDY #&ff ; Y=&FF: pre-increment for loop
8DDA .loop_scan_colon←1← 8DE4 BNE
INY ; Advance to next byte
8DDB LDA hazel_txcb_data,y ; Load byte from reply buffer
8DDE CMP #&0d ; Is it CR (end of prompt)?
8DE0 BEQ send_pass_to_fs ; Yes: no colon found, skip to send
8DE2 CMP #&3a ; Is it ':' (password prompt)?
8DE4 BNE loop_scan_colon ; No: keep scanning
8DE6 JSR print_byte_no_spool ; Print byte no-spool
8DE9 STY fs_work_4 ; Save position of colon
8DEB .read_pw_char←4← 8DFB BNE← 8DFF BEQ← 8E02 BNE← 8E0E BNE
LDA #&ff ; A=&FF: mark as escapable
8DED STA need_release_tube ; Set escape flag
8DEF JSR check_escape_and_classify ; Check for escape condition
8DF2 JSR osrdch ; Read a character from the current input stream
8DF5 CMP #&15 ; A=character read
8DF7 BNE check_pw_special ; Not NAK (&15): check other chars
8DF9 LDY fs_work_4 ; Restore colon position
8DFB BNE read_pw_char ; Non-zero: restart from colon
8DFD .loop_erase_pw←1← 8E06 BEQ
CPY fs_work_4 ; At colon position?
8DFF BEQ read_pw_char ; Yes: restart password input
8E01 DEY ; Backspace: move back one character
8E02 BNE read_pw_char ; If not at start: restart input
8E04 .check_pw_special←1← 8DF7 BNE
CMP #&7f ; Delete key (&7F)?
8E06 BEQ loop_erase_pw ; Yes: handle backspace
8E08 STA hazel_txcb_data,y ; Store character in password buffer
8E0B INY ; Advance buffer pointer
8E0C CMP #&0d ; Is it CR (end of password)?
8E0E BNE read_pw_char ; No: read another character
8E10 JSR print_newline_no_spool ; Print newline no-spool
8E13 .send_pass_to_fs←1← 8DE0 BEQ
TYA ; Transfer string length to A
8E14 PHA ; Save string length
8E15 JSR init_txcb ; Set up transmit control block
8E18 JSR init_tx_ptr_for_pass ; Send to file server and get reply
8E1B PLX
8E1C INX ; Include terminator
8E1D LDY #0 ; Y=0
8E1F BEQ send_cmd_and_dispatch ; ALWAYS branch

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
8E21 .clear_if_station_match←2← 8DB9 JSR← A9EC JSR
JSR init_bridge_poll ; Ensure bridge initialised; A=spool_control_flag (bridge status)
8E24 EOR hazel_fs_network ; EOR with hazel_fs_network: zero result if equal
8E27 BNE return_from_station_match ; Different: return without clearing
8E29 STA hazel_fs_network ; Same: clear station byte
8E2C .return_from_station_match←1← 8E27 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 &9597).

8E2D .check_urd_prefix←1← 9597 JMP
LDY #0 ; Y=0: first character offset
8E2F LDA (fs_crc_lo),y ; Load first character of command text
8E31 CMP #&26 ; Is it '&' (URD prefix)?
8E33 BNE pass_send_cmd ; No: send as normal FS command
8E35 JMP cmd_run_via_urd ; Yes: route via *RUN for URD prefix handling
8E38 .pass_send_cmd←1← 8E33 BNE
JSR copy_arg_to_buf_x0 ; Build FS command packet
8E3B 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 (&8E1F via pass_send_cmd) and the JMP from send_fs_request (&9460).

On EntryYextra dispatch offset (0 from send_fs_request, non-zero for some specialised paths)
8E3C .send_cmd_and_dispatch←3← 8E1F BEQ← 9460 JMP← B370 JMP
JSR save_net_tx_cb ; Set up command and send to FS
8E3F LDX hazel_txcb_network ; Load reply function code
8E42 BEQ dispatch_rts ; Zero: no reply, return
8E44 LDA hazel_txcb_data ; Load first reply byte
8E47 LDY #&25 ; Y=&25: logon dispatch offset
8E49 BNE svc_dispatch ; ALWAYS branch
8E4B .fscv_handler
JSR set_xfer_params ; Parse reply as decimal number
8E4E CMP #&0c ; Result >= 8?
8E50 BCS dispatch_rts ; Yes: out of range, return
8E52 TAX ; Transfer handle to X
8E53 JSR mask_owner_access ; Look up in open files table
8E56 TYA ; Transfer result to A
8E57 LDY #&1d ; Y=&1D: handle dispatch offset
8E59 BNE svc_dispatch ; ALWAYS branch

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).

(In 4.18 the offset was &0E, reaching indices 15..19. The 4.21 shift to &18 puts the targets ten slots higher in the rebuilt dispatch table.)

On EntryXdirectory operation code (0-4)
8E5B .dir_op_dispatch←1← 855D JSR
CPX #5 ; Handle >= 5?
8E5D BCS dispatch_rts ; Yes: out of range, return
8E5F 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-&8E70 (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
8E61 .svc_dispatch←5← 8ADB JSR← 8E49 BNE← 8E59 BNE← 8E63 BPL← 8EE6 JMP
INX ; Advance X to target index
8E62 DEY ; Decrement Y offset counter
8E63 BPL svc_dispatch ; Y still positive: continue counting
8E65 TAY ; Y=&FF: will be ignored by caller
8E66 LDA svc_dispatch_hi,x ; Load dispatch address high byte
8E69 PHA ; Push high byte for RTS dispatch
8E6A .push_dispatch_lo
LDA svc_dispatch_lo,x ; Load dispatch address low byte
8E6D PHA ; Push low byte for RTS dispatch
8E6E LDX fs_options ; Load FS options pointer
8E70 .dispatch_rts←3← 8E42 BEQ← 8E50 BCS← 8E5D 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.

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

8E73 .copy_template_to_zp
LDX #&0a ; X = 10 (top of 11-byte template)
8E75 .loop_copy_return_template←1← 8E7C BPL
LDA fs_info_template,x ; Load template byte X from &8E7F+X
8E78 STA (os_text_ptr),y ; Store at (&F2),Y
8E7A INY ; Advance destination cursor
8E7B DEX ; Step to previous template byte
8E7C BPL loop_copy_return_template ; Loop until X has wrapped past 0
8E7E .fs_template_done←1← 8E8D 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).

8E7F .fs_info_template←1← 8E75 LDA
EQUB &05
8E80 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.

8E8A .svc_26_close_all_files
BIT fs_flags ; Test bit 6 of fs_flags (NFS currently selected?)
8E8D BVC fs_template_done ; Clear: return without acting
8E8F JSR ensure_fs_selected ; Ensure NFS is the selected FS
8E92 LDA #0 ; A=0
8E94 TAY ; Y=0 -- FILEV 'close all files' sub-call
8E95 JMP findv_handler ; Tail-call findv_handler (= FILEV)

Read CMOS RAM byte 0 (Master 128)

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)
8E98 .read_cmos_byte_0←1← 8FBB 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
8E9A .osbyte_a1←20← 8D0C JSR← 8F13 JSR← 8F60 JSR← 8F68 JSR← 8F70 JSR← 8F7A JSR← 8F9C JSR← 904F LDA← 9055 LDA← 95F7 JSR← 95FE JSR← 961B JSR← 9625 JSR← 9644 JSR← 9661 JSR← 9672 JSR← 967F JSR← A0E3 JSR← A70D JSR← B6DE JSR
LDA #osbyte_read_cmos_ram ; A=&A1: OSBYTE &A1 = read CMOS RAM
8E9C JMP osbyte ; Master and Compact: Read CMOS RAM/EEPROM byte X

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 =
; &8E9F). 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.
8E9F EQUS "PRINT " ; PS template: default name "PRINT "
8EA5 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.

8EA7 .fs_vector_table←1← 8B78 LDA
EQUW ev_filev ; FILEV dispatch
8EA9 EQUW ev_argsv ; ARGSV dispatch
8EAB EQUW ev_bgetv ; BGETV dispatch
8EAD EQUW ev_bputv ; BPUTV dispatch
8EAF EQUW ev_gbpbv ; GBPBV dispatch
8EB1 EQUW ev_findv ; FINDV dispatch
8EB3 EQUW ev_fscv ; FSCV dispatch
8EB5 EQUW filev_handler ; FILEV handler
8EB7 EQUB &4A ; (ROM bank — not read)
8EB8 EQUW argsv_handler ; ARGSV handler
8EBA EQUB &44 ; (ROM bank — not read)
8EBB EQUW bgetv_handler ; BGETV handler
8EBD EQUB &57 ; (ROM bank — not read)
8EBE EQUW bputv_handler ; BPUTV handler
8EC0 EQUB &42 ; (ROM bank — not read)
8EC1 EQUW gbpbv_handler ; GBPBV handler
8EC3 EQUB &41 ; (ROM bank — not read)
8EC4 EQUW findv_handler ; FINDV handler
8EC6 EQUB &52 ; (ROM bank — not read)
8EC7 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
8EC9 .osbyte_x0←4← 805D JSR← 9041 JSR← 9731 JSR← 99FD JSR
LDX #0 ; X=0 then fall through into osbyte_yff
fall through ↓

OSBYTE wrapper with Y=&FF

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

On EntryAOSBYTE function code
XOSBYTE X parameter
On ExitY&FF
8ECB .osbyte_yff←1← 8067 JSR
LDY #&ff ; Y=&FF: 'read' parameter for OSBYTE
8ECD .jmp_osbyte←1← 8ED6 BEQ
JMP osbyte ; Tail-call OSBYTE
8ED0 EQUB &FC, &AC

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
8ED2 .osbyte_x0_y0←1← 9A10 JSR
LDX #0 ; X=0: clear OSBYTE X arg
8ED4 LDY #0 ; Y=0
8ED6 BEQ jmp_osbyte ; ALWAYS branch

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)
8ED8 .svc_7_osbyte
LDA osbyte_a_copy ; Get original OSBYTE A parameter
8EDA SBC #&31 ; Subtract &31 (map &32-&35 to 1-4)
8EDC CMP #4 ; In range 0-3?
8EDE BCS return_from_raise_y_to_c8 ; No: not ours, return unclaimed
8EE0 TAX ; Transfer to X as dispatch index
8EE1 STZ svc_state ; Clear svc_state
8EE3 TYA ; Transfer Y to A (OSBYTE Y param)
8EE4 LDY #&2f ; Y=&2F: OSBYTE dispatch offset
8EE6 JMP svc_dispatch ; Dispatch to OSBYTE handler via table

Master 128 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)

8EE9 .raise_y_to_c8
CPY #&c8 ; Y already >= &C8?
8EEB BCS return_from_raise_y_to_c8 ; Yes: return Y unchanged
8EED LDY #&c8 ; No: raise Y to &C8
8EEF .return_from_raise_y_to_c8←2← 8EDE BCS← 8EEB 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
8EF0 .store_ws_page_count
TYA ; Transfer Y to A
8EF1 PHA ; Push for save
8EF2 CMP #&d3 ; Y >= &D3?
8EF4 BCC done_cap_ws_count ; No: use Y as-is
8EF6 LDA #&d3 ; Cap at &D3
8EF8 .done_cap_ws_count←1← 8EF4 BCC
LDY #&0b ; Offset &0B in receive block
8EFA STA (net_rx_ptr),y ; Store workspace page count
8EFC PLY ; Pop -- save Y temporarily
8EFD RTS ; Return -- ws_page count saved
8EFE .set_rom_ws_page←1← 8F35 BCS
TYA ; Caller's page (in Y) into A
8EFF LDY romsel_copy ; Y = current ROM slot from romsel_copy
8F01 PHA ; Push restored value
8F02 AND #&7f ; Mask bit 7 (workspace flag)
8F04 STA rom_ws_pages,y ; Publish page into rom_ws_pages[slot] (bit 7 cleared = workspace claimed)
8F07 LDY master_break_type_shadow ; Read Master break-type shadow (&FE2B)
8F0A LDY master_romsel_shadow ; Read &FE28 (Master ROMSEL shadow)
8F0D PLY ; Pop saved Y
8F0E INY ; Increment for next page
8F0F 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
8F10 .svc_2_priv_ws
PHY ; Save Y on stack (caller's claim)
8F11 LDX #&11 ; X=&11: CMOS RAM byte index
8F13 JSR osbyte_a1 ; Read CMOS &11 via osbyte_a1
8F16 TYA ; A = CMOS &11 value
8F17 AND #4 ; Mask bit 2 (workspace-size flag)
8F19 BNE private_ws_set_bit ; Bit 2 set: keep caller's Y, advance by 2
8F1B LDA #&0b ; Bit 2 clear: A=&0B (use 11-page minimum)
8F1D BRA commit_workspace_pages ; BRA to common tail
8F1F .private_ws_set_bit←1← 8F19 BNE
PLY ; Bit-2-set path: restore Y
8F20 TYA ; TYA / INY / INY -- raise Y by 2 pages
8F21 INY ; Y += 1
8F22 INY ; Y += 1 again (total +2)
8F23 PHY ; Push raised Y
8F24 .commit_workspace_pages←1← 8F1D BRA
STA net_rx_ptr_hi ; Store final page count high to net_rx_ptr_hi
8F26 INC ; Increment for nfs_workspace_hi
8F27 STA nfs_workspace_hi ; Store workspace high page
8F29 LDA #0 ; A=0: clear-byte for the lo halves below
8F2B STA net_rx_ptr ; Clear net_rx_ptr_lo (page-aligned)
8F2D STA nfs_workspace ; Clear nfs_workspace_lo (page-aligned)
8F2F JSR get_ws_page ; Compute workspace start page via get_ws_page
8F32 CPY #&dc ; Y >= &DC?
8F34 PLY ; Restore Y from stack
8F35 BCS set_rom_ws_page ; Yes: jump to set_rom_ws_page (error path)
8F37 RTS ; Return

ANFS initialisation body

Reached only via PHA/PHA/RTS dispatch (table index 22 in the svc_dispatch table at &89ED / &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 &903B.

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.

8F38 .nfs_init_body
LDA #0 ; A=0: clear-byte for the next four stores
8F3A STA ws_page ; Clear ws_page (workspace page count)
8F3C STA tx_complete_flag ; Clear tx_complete_flag
8F3F LDY #0 ; Y=0: receive-block offset 0 (remote-op flag)
8F41 STA (net_rx_ptr),y ; Clear remote-op flag at (net_rx_ptr)+0
8F43 LDA last_break_type ; Read l028D (current ROM number)
8F46 BNE nfs_init_check_fs_flags ; Non-zero (re-init): take nfs_init_check_fs_flags path
8F48 LDA #&10 ; A=&10: fs_flags bit 4 mask (checks 'workspace already set up')
8F4A BIT fs_flags
8F4D BEQ alloc_post_restore_check ; Zero: first ROM init, skip FS setup
8F4F .nfs_init_check_fs_flags←1← 8F46 BNE
JSR setup_ws_ptr ; Set up workspace pointers
8F52 STA fs_flags ; Clear FS flags
8F55 TAY ; A=0, transfer to Y
8F56 .loop_zero_workspace←1← 8F59 BNE
STA (fs_ws_ptr),y ; Clear byte in FS workspace
8F58 INY ; Next workspace byte
8F59 BNE loop_zero_workspace ; Loop until full page (256 bytes) cleared
8F5B JSR copy_ps_data_y1c ; Copy initial PS template (1C bytes) into ws
8F5E LDX #1 ; X=1: CMOS &01 = port number
8F60 JSR osbyte_a1 ; Read CMOS &01
8F63 STY hazel_fs_station ; Store at hazel_fs_station (workspace+0)
8F66 LDX #2 ; X=2: CMOS &02 = network number
8F68 JSR osbyte_a1 ; Read CMOS &02
8F6B STY hazel_fs_network ; Store at hazel_fs_network
8F6E LDX #3 ; X=3: CMOS &03 = FS station
8F70 JSR osbyte_a1 ; Read CMOS &03
8F73 TYA ; A = FS station
8F74 LDY #2 ; Y=2: nfs_workspace offset for FS station
8F76 STA (nfs_workspace),y ; Store FS station at (nfs_workspace)+2
8F78 LDX #4 ; X=4: CMOS &04 = FS network
8F7A JSR osbyte_a1 ; Read CMOS &04 (FS network)
8F7D TYA ; A = FS network
8F7E LDY #3 ; Y=3: nfs_workspace offset for FS network
8F80 STA (nfs_workspace),y ; Store at NFS workspace offset 2
8F82 LDX #3 ; X=3: init data byte count
8F84 .loop_copy_init_data←1← 8F8B BNE
LDA cdir_size_done,x ; Load initialisation data byte
8F87 STA fs_flags,x ; Store in workspace
8F8A DEX ; Decrement counter
8F8B BNE loop_copy_init_data ; More bytes: loop
8F8D STX hazel_fs_messages_flag ; Clear workspace flag
8F90 STX hazel_fs_flags ; Clear workspace byte
8F93 JSR reset_spool_buf_state ; Initialise ADLC protection table
8F96 DEX ; X=&FF (underflow from X=0)
8F97 STX spool_control_flag ; Initialise workspace flag to &FF
8F9A LDX #&11 ; X=&11: CMOS &11 (ANFS settings)
8F9C JSR osbyte_a1 ; Read CMOS &11
8F9F TYA ; A = settings byte
8FA0 AND #&40 ; Mask bit 6 (CMOS protection-state flag)
8FA2 BEQ init_copy_skip_cmos ; Bit clear: skip the &FF substitution
8FA4 LDA #&ff ; A=&FF -- enable protection
8FA6 .init_copy_skip_cmos←1← 8FA2 BEQ
JSR set_via_shadow_pair ; Set shadow ACR/IER pair
8FA9 .loop_alloc_handles←1← 8FB6 BNE
LDA ws_page ; Get current workspace page
8FAB JSR byte_to_2bit_index ; Allocate FS handle page
8FAE BCS done_alloc_handles ; Allocation failed: finish init
8FB0 LDA #&3f ; A=&3F: default handle permissions
8FB2 STA (nfs_workspace),y ; Store handle permissions
8FB4 INC ws_page ; Advance to next page
8FB6 BNE loop_alloc_handles ; Continue allocating: loop
8FB8 .done_alloc_handles←1← 8FAE BCS
JSR restore_fs_context ; Restore FS context from saved state
8FBB .alloc_post_restore_check←1← 8F4D BEQ
JSR read_cmos_byte_0 ; Read CMOS &00 (= station ID byte)
8FBE TYA ; Y (CMOS value) into A
8FBF BNE alloc_common_entry ; Non-zero: station ID valid -> alloc_common_entry
8FC1 .alloc_error_overflow←1← 9000 BEQ
JSR print_inline ; Print 'Station number in CMOS RAM invalid...' warning
8FC4 EQUS "Station number in CMOS RAM invalid..Using 1 instead!..."
8FFB LDA #1 ; A=1: default station ID
8FFD BRA alloc_store_station_id ; BRA to alloc_store_station_id with default
8FFF .alloc_common_entry←1← 8FBF BNE
INY ; Check next byte (CMOS station ID hi?)
9000 BEQ alloc_error_overflow ; INY wrapped past 0 (station=&FF then INY=&00): report 'CMOS RAM invalid' and default to 1
9002 BRA alloc_store_station_id ; BRA to alloc_store_station_id (always)
9004 .alloc_store_station_id←2← 8FFD BRA← 9002 BRA
LDY #1 ; Y=1: net_rx_ptr offset for station-ID byte
9006 STA (net_rx_ptr),y ; Store station ID into (net_rx_ptr)+1
9008 LDX #&40 ; X=&40: econet_flags init value
900A STX econet_flags ; Initialise econet_flags
900D JSR cmd_net_fs ; Call cmd_net_fs to select NFS
9010 BEQ complete_nfs_init ; Z: selection succeeded
9012 LDA #&10 ; A=&10: bit 4 marker for fs_flags
9014 ORA fs_flags
9017 STA fs_flags ; Store updated fs_flags
901A .complete_nfs_init←1← 9010 BEQ
JSR init_adlc_and_vectors ; Initialise ADLC and FILEV/ARGSV/...vectors
901D LDA #3 ; A=3: spool-ctrl byte 'init'
901F JSR handle_spool_ctrl_byte ; Initialise *SPOOL handle in workspace
9022 JSR init_bridge_poll ; Send a bridge-discovery packet and poll
9025 PHA ; Save current bridge byte
9026 EOR hazel_fs_network ; With stored hazel_fs_network (network number)
9029 BNE verify_copy_station_id ; Different: take verify_copy_station_id path
902B STA hazel_fs_network ; Same: store as new hazel_fs_network
902E LDY #3 ; Y=3: net_rx_ptr offset 3
9030 STA (net_rx_ptr),y ; Store at (net_rx_ptr)+3
9032 .verify_copy_station_id←1← 9029 BNE
PLA ; Restore saved byte
9033 LDY #3 ; Y=3: workspace offset
9035 EOR (nfs_workspace),y
9037 BNE return_2 ; Mismatch: skip store
9039 STA (nfs_workspace),y ; Match: store at (nfs_workspace)+3
903B .return_2←1← 9037 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)
903C .init_adlc_and_vectors←2← 8B81 JSR← 901A JSR
JSR adlc_init ; Initialise ADLC hardware
903F LDA #&a8 ; OSBYTE &A8: read ROM pointer table
9041 JSR osbyte_x0 ; Read ROM pointer table address
9044 STX fs_error_ptr ; Store table pointer low
9046 STY fs_crflag ; Store table pointer high
9048 LDY #&36 ; Y=&36: NETV vector offset
904A STY netv ; Set NETV address
904D 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
904F .write_vector_entry←2← 8B88 JSR← 9061 BNE
LDA osbyte_a1,y ; Load vector address low byte
9052 STA (fs_error_ptr),y ; Store into extended vector table
9054 INY ; Advance to high byte
9055 LDA osbyte_a1,y ; Load vector address high byte
9058 STA (fs_error_ptr),y ; Store into extended vector table
905A INY ; Advance to ROM ID byte
905B LDA romsel_copy ; Load current ROM slot number
905D STA (fs_error_ptr),y ; Store ROM ID in extended vector
905F INY ; Advance to next vector entry
9060 DEX ; Decrement vector counter
9061 BNE write_vector_entry ; More vectors to set: loop
9063 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)
9064 .restore_fs_context←3← 8FB8 JSR← 907B JSR← A6D2 JMP
LDY #9 ; Y=9: end of FS context block
9066 .loop_restore_ctx←1← 906E BNE
LDA hazel_minus_2,y ; Load FS context byte
9069 STA (net_rx_ptr),y ; Store into receive block
906B DEY ; Decrement index
906C CPY #1 ; Reached offset 1?
906E BNE loop_restore_ctx ; No: continue copying
9070 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.
9071 .fscv_6_shutdown
BIT fs_flags ; FS currently selected?
9074 BPL return_from_fs_shutdown ; No (bit 7 clear): return
9076 LDY #0 ; Y=0
9078 JSR process_all_fcbs ; Close all FCBs (process_all_fcbs)
907B JSR restore_fs_context ; Restore FS context to receive block
907E LDY #&76 ; Y=&76: checksum range end
9080 LDA #0 ; A=0: checksum accumulator
9082 CLC ; Clear carry for addition
9083 .loop_checksum_byte←1← 9087 BPL
ADC hazel_fcb_addr_lo,y ; Add byte from page &10 shadow
9086 DEY ; Decrement index
9087 BPL loop_checksum_byte ; Loop until all bytes summed
9089 LDY #&77 ; Y=&77: checksum storage offset
908B BPL store_ws_byte ; ALWAYS branch
908D .loop_copy_to_ws←1← 9093 BPL
LDA hazel_fcb_addr_lo,y ; Load byte from page &10 shadow
9090 .store_ws_byte←1← 908B BPL
STA (fs_ws_ptr),y ; Copy to FS workspace
9092 DEY ; Decrement index
9093 BPL loop_copy_to_ws ; Loop until all bytes copied
9095 LDA fs_flags ; Load FS flags
9098 AND #&7f ; Clear bit 7 (FS no longer selected)
909A STA fs_flags ; Store updated flags
909D .return_from_fs_shutdown←1← 9074 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)
909E .verify_ws_checksum←5← 9EAB JSR← A02F JSR← A10B JSR← A14C JSR← B99A JSR
PHP ; Save processor status
909F PHA ; Save A
90A0 PHY
90A1 LDY #&76 ; Y=&76: checksum range end
90A3 LDA #0 ; A=0: checksum accumulator
90A5 CLC ; Clear carry for addition
90A6 .loop_sum_ws←1← 90A9 BPL
ADC (fs_ws_ptr),y ; Add byte from FS workspace
90A8 DEY ; Decrement index
90A9 BPL loop_sum_ws ; Loop until all bytes summed
90AB LDY #&77 ; Y=&77: checksum storage offset
90AD CMP (fs_ws_ptr),y ; Compare with stored checksum
90AF BNE error_net_checksum ; Mismatch: raise checksum error
90B1 PLY
90B2 PLA ; Restore A
90B3 PLP ; Restore processor status
90B4 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.

90B5 .error_net_checksum←2← 8B57 JMP← 90AF BNE
LDA #&aa ; Error number &AA
90B7 JSR error_bad_inline ; Raise 'net checksum' error
90BA EQUS "net checksum."
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 (adlc_cr2) 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)
90C7 .print_station_id←2← 8CAA JMP← 8CE4 JSR
JSR print_inline ; Print 'Station ' inline string
90CA EQUS "Econet Station " ; Print 'Econet Station ' via inline
90D9 LDY #1 ; Y=1: PB station-byte offset
90DB LDA (net_rx_ptr),y ; Read RX[1] = station number
90DD JSR print_num_no_leading ; Print as decimal (no leading zeros)
90E0 LDA #&20 ; Space character
90E2 BIT adlc_cr2 ; Check ADLC status register 2
90E5 BEQ done_print_newline ; Clock present: skip warning
90E7 JSR print_inline ; Print ' No Clock' via inline
90EA EQUS " No Clock"
90F3 NOP ; String terminator
90F4 .done_print_newline←1← 90E5 BEQ
JSR osnewl ; Write newline (characters 10 and 13)
90F7 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.
90F8 .syn_opt_dir←1← 8C07 LDA
EQUS "(<dir>)" ; Syn 1: *Dir, *LCat, *LEx, *Wipe
90FF EQUB &00
9100 .syn_iam
EQUS "(<stn. id.>) <user id.> " ; Syn 2: *I Am (login)
9118 EQUB &0D ; Line break
9119 EQUS "((:<CR>)<password>)" ; syntax help for *Pass / *I am
912C EQUB &00
912D .syn_object
EQUS "<object>" ; Syn 3: *Delete, *FS, *Remove
9135 EQUB &00
9136 EQUS "<filename> (<offset> " ; Store as string pointer low Store as string pointer high
914B EQUB &0D
914C EQUS "(<address>))" ; Syn 4 continued: address clause
9158 EQUB &00 ; Null terminator
9159 .syn_dir
EQUS "<dir>" ; Syn 5: *Lib
915E EQUB &00
915F EQUS "<dir> (<number>)"
916F EQUB &00
9170 .syn_password
EQUS "(:<CR>) <password> " ; Syn 7: *Pass
9183 EQUB &0D
9184 EQUS "<new password>" ; Syn 7 continued: new password
9192 EQUB &00
9193 EQUS "(<stn. id.>|<ps type>)" ; syntax help for *PS / *Pollps
91A9 EQUB &00
91AA .syn_access
EQUS "<object> (L)(W)(R)(/(W)(R))" ; Syn 9: *Access
91C5 EQUB &00 ; Null terminator
91C6 .syn_rename
EQUS "<filename> <new filename>" ; Syn 10: *Rename
91DF EQUB &00 ; Null terminator
91E0 .syn_opt_stn
EQUS "(<stn. id.>)" ; Syn 11: (station id. argument)
91EC EQUB &00 ; Null terminator

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.
91ED .cmd_syntax_table←1← 8C02 LDA
EQUB syn_iam - syn_opt_dir - 2 ; Idx 0: 'opt_dir' (offset -2 variant for *Dir's INY-twice walker)
91EE EQUB &FF ; Idx 1: &FF = no syntax string for this index
91EF EQUB syn_iam - syn_opt_dir - 1 ; Idx 2: "(<stn.id.>) user id.>..."
91F0 EQUB syn_object - syn_opt_dir - 1 ; Idx 3: ""
91F1 EQUB &3D ; Idx 4: " (...)"
91F2 EQUB &60 ; Idx 5: '' (offset 0x60 = syn_dir)
91F3 EQUB &66 ; Idx 6: continued string region
91F4 EQUB &77 ; Idx 7: "(:) ..."
91F5 EQUB &9A ; Idx 8: "(<stn.id.>|)"
91F6 EQUB syn_access - syn_opt_dir - 1 ; Idx 9: " (L)(W)(R)..."
91F7 EQUB &CD ; Idx 10: ' ' (syn_rename)
91F8 EQUB syn_opt_stn - syn_opt_dir - 1 ; Idx 11: "(<stn. id.>)"

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 (&8E10, &9D3E).

On ExitA, X, Y, Ppreserved (print_char_no_spool brackets the call with full register save/restore via PHA/PHP/PLP/PLA)
91F9 .print_newline_no_spool←6← 8A7C JSR← 8E10 JSR← 9D3E JSR← B1E0 JSR← B227 JMP← B7B7 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 &9220 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: &925F, &92A4, &9D30, &9D5C, &B21F, &B2F9, &B321, &B752.

On EntryAbyte to print as ASCII char (CR is translated by OSASCI)
91FB .print_char_no_spool←11← 925F BRA← 92A4 JSR← 9D30 JSR← 9D5C JMP← B1B9 JSR← B21F JSR← B2F9 JSR← B321 JSR← B752 JSR← B776 JSR← B78D JSR
PHP ; Save caller's flags (V from caller is irrelevant — see &91FC)
91FC BIT always_set_v_byte ; Unconditionally sets V=1 (bit 6 of operand &FF)
91FF BVS save_regs_for_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 &9220 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
9201 .print_byte_no_spool←2← 8DE6 JSR← B76E JSR
PHP ; Alt entry: save caller's flags BEFORE forcing V=0
9202 CLV ; Force V=0 -> OSWRCH path at &9220 (raw byte)
9203 .save_regs_for_print_no_spool←1← 91FF BVS
PHX ; Save X
9204 PHY ; Save Y
9205 PHA ; Save A (the byte to print)
9206 PHP ; Save inner P — V here picks OSASCI vs OSWRCH later
9207 LDA #osbyte_read_write_spool_file_handle ; OSBYTE 199 (read/write *SPOOL file handle)
9209 LDX #0 ; X=0: handle value to write
920B LDY #0 ; Y=0: write mode (NEW = (OLD AND 0) EOR X = X = 0)
920D JSR osbyte ; Closes spool; X returns OLD handle
9210 CPX #&20 ; OLD < ' '? (likely 0 = was already closed)
9212 BCC do_print_no_spool ; Yes: leave spool closed for the print
9214 CPX #&30 ; OLD >= '0'?
9216 BCS do_print_no_spool ; Yes (>= &30): leave spool closed
9218 JSR osbyte ; OLD in [&20,&2F] (NFS handle range): re-open spool with X=OLD
921B LDX #0 ; Clear X for the post-print restore
921D .do_print_no_spool←2← 9212 BCC← 9216 BCS
PLP ; Restore inner P (V=1 OSASCI / V=0 OSWRCH)
921E PLA ; Pull A (the byte)
921F PHA ; Push it back so the final epilogue PLA still works
9220 BVC print_via_oswrch ; V=0 -> OSWRCH (raw); V=1 -> OSASCI (CR translation)
9222 JSR osasci ; OSASCI: writes A, translating CR to CR/LF
9225 BRA restore_spool_and_return ; Skip OSWRCH branch
9227 .print_via_oswrch←1← 9220 BVC
JSR oswrch ; OSWRCH: writes A as a raw byte
922A .restore_spool_and_return←1← 9225 BRA
LDA #osbyte_read_write_spool_file_handle ; OSBYTE 199 again to restore spool state
922C LDY #&ff ; Y=&FF (read mode): NEW = OLD EOR X
922E JSR osbyte ; X=0 -> no change; X=OLD -> writes OLD back
9231 PLA ; Pull A (preserved across the call)
9232 PLY ; Pull Y
9233 PLX ; Pull X
9234 PLP ; Pull caller's original flags
9235 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
9236 .print_hex_byte←2← BD90 JSR← BE38 JSR
PHA ; Save full byte
9237 LSR ; Shift high nybble to low
9238 LSR ; Continue shifting
9239 LSR ; Continue shifting
923A LSR ; High nybble now in bits 0-3
923B JSR print_hex_nybble ; Print high nybble as hex digit
923E 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)
923F .print_hex_nybble←1← 923B JSR
AND #&0f ; Mask to low nybble
9241 CMP #&0a ; Digit >= &0A?
9243 BCC add_ascii_base ; No: skip letter adjustment
9245 ADC #6 ; Add 7 to get 'A'-'F' (6 + carry)
9247 .add_ascii_base←1← 9243 BCC
ADC #&30 ; Add &30 for ASCII '0'-'9' or 'A'-'F'
9249 JMP osasci ; Write character

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 &9D53.

On EntryAbyte to print
On ExitApreserved
924C .print_hex_byte_no_spool←2← 9D53 JSR← B1A9 JSR
PHA ; Save full byte
924D LSR ; Shift high nybble to low (LSR x4)
924E LSR ; LSR / LSR / LSR -- shift hi nibble down to lo
924F LSR ; (continued)
9250 LSR ; (continued)
9251 JSR print_hex_nybble_no_spool ; Print high nybble as hex digit
9254 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)
9255 .print_hex_nybble_no_spool←1← 9251 JSR
AND #&0f ; Mask to low nybble
9257 CMP #&0a ; Digit >= &0A?
9259 BCC print_nybble_leading_zero ; No: skip letter adjustment
925B ADC #6 ; Add 7 to get 'A'-'F' (6 + carry)
925D .print_nybble_leading_zero←1← 9259 BCC
ADC #&30 ; Add &30 for ASCII '0'-'9' or 'A'-'F'
925F 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
9261 .print_inline←33← 8A6B JSR← 8BE0 JSR← 8C93 JSR← 8FC1 JSR← 90C7 JSR← 90E7 JSR← 95AC JSR← 95C1 JSR← 95C8 JSR← 95CD JSR← 95DA JSR← 964C JSR← 9653 JSR← 9668 JSR← 9679 JSR← B460 JSR← B46C JSR← B483 JSR← B48D JSR← B498 JSR← B568 JSR← B60A JSR← B61F JSR← B642 JSR← B64F JSR← B65E JSR← B66E JSR← B67D JSR← BDAC JSR← BDC4 JSR← BDD0 JSR← BE04 JSR← BE21 JSR
PLA ; Pop return address (low) — points to last byte of JSR
9262 STA fs_error_ptr ; Store as fs_error_ptr (return-addr saved)
9264 PLA ; Pop return address (high)
9265 STA fs_crflag ; Store as fs_crflag (entry flag)
9267 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.

9269 .loop_next_char←1← 9284 JMP
INC fs_error_ptr ; Advance pointer to next character
926B BNE load_char ; Z clear: continue with this char
926D INC fs_crflag ; Z set (CR): increment fs_crflag
926F .load_char←1← 926B BNE
LDA (fs_error_ptr),y ; Load next byte from inline string
9271 BMI resume_caller ; Bit 7 set? Done — this byte is the next opcode
9273 LDA fs_error_ptr ; Read fs_error_ptr (saved across OSASCI)
9275 PHA ; Push it
9276 LDA fs_crflag ; Read fs_crflag
9278 PHA ; Push it
9279 LDA (fs_error_ptr),y ; Reload character (pointer may have been clobbered)
927B JSR osasci ; Print character via OSASCI
927E PLA ; Pop saved fs_crflag
927F STA fs_crflag ; Restore fs_crflag
9281 PLA ; Pop saved fs_error_ptr
9282 STA fs_error_ptr ; Restore fs_error_ptr
9284 JMP loop_next_char ; Loop back
9287 .resume_caller←1← 9271 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: &981A (recv_and_process_reply), &B158/&B162 (cmd_ex), &B2F0 (ex_print_col_sep), &B75E (cmd_wipe), &B7CB (prompt_yn).

On ExitAterminator byte (bit 7 set, also next opcode)
Xcorrupted (by print_char_no_spool)
Y0
928A .print_inline_no_spool←13← 981A JSR← B158 JSR← B162 JSR← B170 JSR← B17B JSR← B197 JSR← B1AC JSR← B1BF JSR← B1CE JSR← B2F0 JSR← B75E JSR← B77E JSR← B7CB JSR
PLA ; Pop return-addr low byte (-> string pointer low)
928B STA fs_error_ptr ; Save in fs_error_ptr (the loop's pointer low)
928D PLA ; Pop return-addr high byte
928E STA fs_crflag ; Save in fs_crflag (the loop's pointer high)
9290 LDY #0 ; Y=0: indirect index for (fs_error_ptr),Y
9292 .loop_print_inline_string←1← 92AD BRA
INC fs_error_ptr ; Step pointer low byte to next char
9294 BNE print_next_string_char ; No carry: skip high-byte INC
9296 INC fs_crflag ; Page wrap: bump pointer high
9298 .print_next_string_char←1← 9294 BNE
LDA (fs_error_ptr),y ; Read next character from inline string
929A BMI print_char_terminator ; Bit 7 set: terminator -- this byte is the next opcode
929C LDA fs_error_ptr ; Save pointer low (print_char_no_spool may clobber)
929E PHA ; Push it
929F LDA fs_crflag ; Save pointer high
92A1 PHA ; Push it
92A2 LDA (fs_error_ptr),y ; Reload the character we're about to print
92A4 JSR print_char_no_spool ; Print it via the *SPOOL-bypassing OSASCI wrapper
92A7 PLA ; Pop pointer high back
92A8 STA fs_crflag ; Restore
92AA PLA ; Pop pointer low back
92AB STA fs_error_ptr ; Restore
92AD BRA loop_print_inline_string ; Always taken (BRA-style; A is non-zero from print)
92AF .print_char_terminator←1← 929A 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 &A3DE.

On EntryYindex into command-string buffer at (fs_crc_lo),Y
Aignored
On ExitCset if a number was parsed
92B2 .parse_addr_arg←5← 8DB1 JSR← 8DBD JSR← A3C9 JSR← A3DE JSR← B0B5 JSR
STZ fs_load_addr_2 ; Zero the accumulator (fs_load_addr_2)
92B4 LDA (fs_crc_lo),y ; Read first command-line byte
92B6 CMP #&26 ; Hex prefix '&'?
92B8 BNE next_dec_char ; No: try decimal path
92BA INY ; Yes: skip the '&'
92BB LDA (fs_crc_lo),y ; Read first hex digit
92BD BCS check_digit_range ; Always taken (CMP #'&' set C if A>='&'); jump into the hex digit-range check Convert to channel index
92BF .next_hex_char←1← 92EE BCC
INY ; Step to next character
92C0 LDA (fs_crc_lo),y ; Read next hex digit candidate
92C2 CMP #&2e ; Dot? Net.station separator
92C4 BEQ handle_dot_sep ; Yes: switch to station-parsing mode
92C6 CMP #&21 ; Below '!' (CR/space)? End of argument
92C8 BCC done_parse_num ; Yes: number complete
92CA .check_digit_range←1← 92BD BCS
CMP #&30 ; Below '0'?
92CC BCC skip_if_not_hex ; Yes: not a hex digit
92CE CMP #&3a ; Above '9'? (CMP #':')
92D0 BCC extract_digit_value ; No (it's '0'-'9'): straight to digit extraction Get stack pointer
92D2 AND #&5f ; Force uppercase via AND #&5F
92D4 ADC #&b8 ; Map 'A'-'F' to &FA-&FF (ADC #&B8 with C from earlier CMP #':' which set C) Convert to channel index
92D6 BCS err_bad_hex ; Carry out of ADC: was below 'A' -- bad hex
92D8 CMP #&fa ; Below &FA? (digit > 'F' overflowed past)
92DA .skip_if_not_hex←1← 92CC BCC
BCC err_bad_hex ; Yes: bad hex (out of [&FA,&FF])
92DC .extract_digit_value←1← 92D0 BCC
AND #&0f ; Mask to nibble
92DE STA fs_load_addr_3 ; Stash digit value in fs_load_addr_3
92E0 LDA fs_load_addr_2 ; Load accumulator
92E2 CMP #&10 ; Above 16? (would overflow when shifted left 4) Transfer back to X
92E4 BCS error_overflow ; Yes: overflow
92E6 ASL ; Shift accumulator left 4 (multiply by 16)
92E7 ASL ; (shift 2)
92E8 ASL ; (shift 3)
92E9 ASL ; (shift 4)
92EA ADC fs_load_addr_3 ; Add new nibble
92EC STA fs_load_addr_2 ; Save updated accumulator
92EE BCC next_hex_char ; No carry: continue (always taken since accumulator was checked < 16 above)
92F0 .next_dec_char←2← 92B8 BNE← 931A BNE
LDA (fs_crc_lo),y ; Read next decimal-digit candidate
92F2 CMP #&2e ; Dot? Net.station separator
92F4 BEQ handle_dot_sep ; Yes: switch to station-parsing mode
92F6 CMP #&21 ; Below '!' (CR/space)?
92F8 BCC done_parse_num ; Yes: number complete
92FA JSR is_dec_digit_only ; Test for '0'-'9' and reject '&'/'.'
92FD BCC error_bad_number ; Not a decimal digit: bad number
92FF AND #&0f ; Mask to nibble
9301 STA fs_load_addr_3 ; Stash digit
9303 ASL fs_load_addr_2 ; Accumulator * 2
9305 BCS error_overflow ; Overflowed: too big for byte
9307 LDA fs_load_addr_2 ; Reload doubled value
9309 ASL ;
  • 2 again (now * 4)
930A BCS error_overflow ; Overflow check
930C ASL ;
  • 2 again (now * 8)
930D BCS error_overflow ; Overflow check
930F ADC fs_load_addr_2 ; ul> li>accumulator (now * 8 + * 2 = * 10)/li> /ul>
9311 BCS error_overflow ; Overflow check
9313 ADC fs_load_addr_3 ;
  • new digit
9315 BCS error_overflow ; Overflow check
9317 STA fs_load_addr_2 ; Save * 10 + digit
9319 INY ; Step input cursor
931A BNE next_dec_char ; Always taken (Y wraps at 256, never zero in practice)
931C .done_parse_num←2← 92C8 BCC← 92F8 BCC
LDA fs_work_4 ; Read mode flag
931E BPL validate_station ; Bit 7 clear: in net.station mode -- validate result
9320 LDA fs_load_addr_2 ; Decimal-only mode: get result
9322 BEQ error_bad_param ; Result is zero: bad parameter
9324 RTS ; Return with parsed result in A (decimal-only path)
9325 .validate_station←1← 931E BPL
LDA fs_load_addr_2 ; Reload result
9327 CMP #&ff ; Station 255 is reserved (broadcast)
9329 BEQ err_bad_station_num ; Yes: bad station number
932B LDA fs_load_addr_2 ; Reload result for the next test
932D BNE return_parsed ; Non-zero: valid station, return
932F LDA fs_work_4 ; Zero result: must have followed a dot to be valid
9331 BEQ err_bad_station_num ; No dot was seen: bad station number
9333 DEY ; Dot seen: peek the byte before current cursor
9334 LDA (fs_crc_lo),y ; Read previous byte
9336 INY ; Restore Y
9337 EOR #&2e ; Was previous char '.'?
9339 BNE err_bad_station_num ; No: bad station number
933B .return_parsed←1← 932D BNE
SEC ; All checks passed: C=1 marks 'parsed successfully'
933C RTS ; Return
933D .handle_dot_sep←2← 92C4 BEQ← 92F4 BEQ
LDA fs_work_4 ; Dot already seen?
933F BNE error_bad_number ; Yes: 'Bad number' (multiple dots)
9341 INC fs_work_4 ; Set dot-seen flag
9343 LDA fs_load_addr_2 ; Get parsed network number (before dot)
9345 CMP #&ff ; Network 255 is reserved
9347 BEQ error_bad_net_num ; Yes: 'Bad network number'
9349 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.

934A .err_bad_hex←3← 92D6 BCS← 92DA BCC← BE9F JMP
LDA #&f1 ; Error code &F1
934C JSR error_bad_inline ; Raise 'Bad hex' error
934F EQUS "hex."
9353 .error_overflow←6← 92E4 BCS← 9305 BCS← 930A BCS← 930D BCS← 9311 BCS← 9315 BCS
BIT fs_work_4 ; Test fs_work_4 bit 7
9355 BMI error_bad_param ; Bit 7 set: redirect to error_bad_param
9357 .err_bad_station_num←3← 9329 BEQ← 9331 BEQ← 9339 BNE
LDA #&d0 ; A=&D0: 'Bad station' error code
9359 JSR error_bad_inline ; Raise via error_bad_inline (never returns)
935C EQUS "station number."
936B .error_bad_number←2← 92FD BCC← 933F BNE
LDA #&f0 ; A=&F0: 'Bad number' error code
936D JSR error_bad_inline ; Raise via error_bad_inline (never returns)
9370 EQUS "number."
9377 .error_bad_param←2← 9322 BEQ← 9355 BMI
LDA #&94 ; A=&94: 'Bad parameter' error code
9379 JSR error_bad_inline ; Raise via error_bad_inline (never returns)
937C EQUS "parameter."
9386 .error_bad_net_num←1← 9347 BEQ
LDA #&d1 ; A=&D1: 'Bad net number' error code
9388 JSR error_bad_inline ; Raise via error_bad_inline (never returns)
938B EQUS "network number."
fall through ↓

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

Compares A against '&' and '.' first; if either matches, returns with carry set via the shared return_from_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
939A .is_decimal_digit←3← 8DAC JSR← B3C3 JSR← B59A JSR
CMP #&26 ; Hex prefix '&'?
939C BEQ return_from_digit_test ; Yes: treat as digit-like (carry set on exit)
939E CMP #&2e ; Network/station separator '.'?
93A0 BEQ return_from_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
93A2 .is_dec_digit_only←1← 92FA JSR
CMP #&3a ; Above '9'? (CMP #':')
93A4 BCS not_a_digit ; Yes: not a digit -- jump to clear-carry exit
93A6 CMP #&30 ; Below '0'? (CMP sets carry if A >= '0')
93A8 .return_from_digit_test←2← 939C BEQ← 93A0 BEQ
RTS ; Carry now reflects '0'-'9' membership; return
93A9 .not_a_digit←1← 93A4 BCS
CLC ; Out-of-range exit: clear carry to signal not-a-digit
93AA 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)
93AB .get_access_bits←2← 9E0D JSR← 9E39 JSR
LDY #&0e ; Y=&0E: directory entry access byte offset
93AD LDA (fs_options),y ; Read access byte through fs_options pointer
93AF AND #&3f ; Mask to 6 protection bits (clears the unused top two)
93B1 LDX #4 ; X=4: encode-table column index for owner-access bits
93B3 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
93B5 .get_prot_bits←2← 9D17 JSR← 9E56 JSR
AND #&1f ; Mask to 5 protection bits (low 5)
93B7 LDX #&ff ; X=&FF; INX inside the loop bumps to 0 for column 0
93B9 .begin_prot_encode←1← 93B3 BNE
STA fs_error_ptr ; Park source bits in fs_error_ptr -- the LSR target
93BB LDA #0 ; A=0: accumulator for encoded result
93BD .loop_encode_prot←1← 93C5 BNE
INX ; Advance table column index
93BE LSR fs_error_ptr ; Shift next source bit into carry
93C0 BCC skip_clear_prot ; Source bit was 0: skip the OR for this column
93C2 ORA prot_bit_encode_table,x ; Source bit was 1: OR in this column's encoded mask
93C5 .skip_clear_prot←1← 93C0 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)
93C7 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
93C8 .prot_bit_encode_table←1← 93C2 ORA
EQUB &50 ; prot src bit 0 -> out bits 6,4
93C9 EQUB &20 ; prot src bit 1 -> out bit 5
93CA EQUB &05 ; prot src bit 2 -> out bits 2,0
93CB EQUB &02 ; prot src bit 3 -> out bit 1
93CC EQUB &88 ; prot src bit 4 -> out bits 7,3
93CD EQUB &04 ; access src bit 0 -> out bit 2
93CE EQUB &08 ; access src bit 1 -> out bit 3
93CF EQUB &80 ; access src bit 2 -> out bit 7
93D0 EQUB &10 ; access src bit 3 -> out bit 4
93D1 EQUB &01 ; access src bit 4 -> out bit 0
93D2 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
93D3 .set_text_and_xfer_ptr←2← A42F JSR← B0FE JSR
STX os_text_ptr ; Save text pointer low byte (where caller wants OS to scan from)
93D5 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
93D7 .set_xfer_params←6← 8DA6 JSR← 8E4B JSR← 9C22 JSR← A032 JSR← A14F JSR← B118 JSR
STA fs_last_byte_flag ; Stash transfer byte count (in A)
93D9 STX fs_crc_lo ; Source pointer low byte
93DB 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
93DD .set_options_ptr←2← 9EB0 JSR← BD15 JSR
STX fs_options ; Options pointer low byte (parameter block base)
93DF 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 (&9B72 in the recv-and-classify reply path).

93E1 .clear_escapable←1← 9B72 JMP
PHP ; Save flags so the LSR doesn't disturb caller's NZC
93E2 LSR need_release_tube ; Shift bit 0 of need_release_tube into carry, clearing the bit
93E4 PLP ; Restore caller's flags
93E5 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
93E6 .cmp_5byte_handle←2← 9C85 JSR← 9D88 JSR
LDX #4 ; X=4: loop from offset 4 down to 1 (skips offset 0)
93E8 .loop_cmp_handle←1← 93EF BNE
LDA addr_work,x ; Load saved-handle byte from addr_work[X]
93EA EOR fs_load_addr_3,x ; EOR with parsed handle byte; Z set iff bytes match
93EC BNE return_from_cmp_handle ; Mismatch: bail out with Z clear
93EE DEX ; Decrement to next byte
93EF BNE loop_cmp_handle ; Loop while X != 0 (offset 0 is intentionally not compared)
93F1 .return_from_cmp_handle←1← 93EC 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)
93F2 .fscv_7_read_handles
LDX #&20 ; X=&20: handle-table base offset
93F4 LDY #&2f ; Y=&2F: handle count + flag
93F6 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
93F7 .set_conn_active←2← 9F35 JSR← A1AC JSR
PHP ; Save flags so the rest of the routine is transparent
93F8 PHA ; Save A (the attribute byte we need to recover via stack)
93F9 PHX ; Save X
93FA TSX ; Capture S into X to address stack from below
93FB LDA stack_page_2,x ; Re-read the original A from stack[X+2] (above PHX/PHA)
93FE JSR attr_to_chan_index ; Convert attribute byte to channel-table index
9401 BMI clear_channel_flag ; No matching channel: skip the flag set, just restore
9403 LDA #&40 ; A=&40: bit 6 = connection-active mask
9405 ORA hazel_fcb_status,x ; OR with current status byte for this channel
9408 STA hazel_fcb_status,x ; Write back the updated status
940B 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
940D .clear_conn_active←2← 9F96 JSR← A1A7 JSR
PHP ; Save flags
940E PHA ; Save A
940F PHX ; Save X
9410 TSX ; Capture S into X for stack-relative reads
9411 LDA stack_page_2,x ; Re-read the attribute byte from stack[X+2]
9414 JSR attr_to_chan_index ; Convert attribute to channel index
9417 BMI clear_channel_flag ; No matching channel: just restore
9419 LDA #&bf ; A=&BF: bit 6 clear mask
941B AND hazel_fcb_status,x
941E STA hazel_fcb_status,x ; Write back the updated status
9421 .clear_channel_flag←3← 9401 BMI← 940B BNE← 9417 BMI
PLX ; Restore X (saved at PHX)
9422 PLA ; Restore A
9423 PLP ; Restore flags
9424 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)
9425 .cmd_fs_operation
JSR copy_fs_cmd_name ; Copy command name 'Access'/'Delete'/'Info'/'Lib' to TX buffer
9428 PHX
9429 JSR parse_quoted_arg ; Parse quoted filename argument from command line
942C JSR parse_access_prefix ; Parse the access prefix (e.g. L,W,R) into a bitmask
942F PLX
9430 JSR check_not_ampersand ; Reject '&' character in filename
9433 CMP #&0d ; End of line?
9435 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.

9437 .error_bad_filename←3← 944B BEQ← 953E JMP← B279 JMP
LDA #&cc ; Error number &CC
9439 JSR error_bad_inline ; Raise 'Bad file name' error
943C EQUS "file name."
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)
9446 .check_not_ampersand←2← 9430 JSR← 944E JSR
LDA hazel_parse_buf ; Load first parsed character
9449 CMP #&26 ; Is it '&'?
944B BEQ error_bad_filename ; Yes: invalid filename
944D 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
944E .read_filename_char←3← 9435 BNE← 945C BRA← 950F JMP
JSR check_not_ampersand ; Reject '&' in current char
9451 STA hazel_txcb_data,x ; Store character in TX buffer
9454 INX ; Advance buffer pointer
9455 CMP #&0d ; End of line?
9457 BEQ send_fs_request ; Yes: send request to file server
9459 JSR strip_token_prefix ; Strip BASIC token prefix byte
945C 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 (&9457) and the *RUN argument-handling tail at &9537.

945E .send_fs_request←2← 9457 BEQ← 9537 JMP
LDY #0 ; Y=0: ensure offset starts from beginning of TX command buffer
9460 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
9463 .copy_fs_cmd_name←2← 9425 JSR← 94C5 JSR
PHY ; Save Y on entry
9464 .loop_scan_flag←1← 9468 BPL
DEX ; Scan backwards in command table
9465 LDA cmd_table_fs,x ; Load table byte
9468 BPL loop_scan_flag ; Bit 7 clear: keep scanning
946A INX ; Point past flag byte to name start
946B LDY #0 ; Y=0: TX buffer offset
946D .loop_copy_name←1← 9477 BNE
LDA cmd_table_fs,x ; Load command name character
9470 BMI append_space ; Bit 7 set: end of name
9472 STA hazel_txcb_data,y ; Store character in TX buffer
9475 INX ; Advance table pointer
9476 INY ; Advance buffer pointer
9477 BNE loop_copy_name ; Continue copying name
9479 .append_space←1← 9470 BMI
LDA #&20 ; Space separator
947B STA hazel_txcb_data,y ; Append space after command name
947E INY ; Advance buffer pointer
947F TYA ; Transfer length to A
9480 TAX ; And to X (buffer position)
9481 PLY
9482 .return_from_copy_cmd_name←1← 94B7 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)
9483 .parse_quoted_arg←2← 9429 JSR← 94CC JSR
LDA #0 ; A=0: no quote mode
9485 TAX ; X=&00
9486 STA hazel_quote_mode ; Clear quote tracking flag
9489 .loop_skip_spaces←1← 9490 BNE
LDA (fs_crc_lo),y ; Load char from command line
948B CMP #&20 ; Space?
948D BNE check_open_quote ; No: check for opening quote
948F INY ; Skip leading space
9490 BNE loop_skip_spaces ; Continue skipping spaces
9492 .check_open_quote←1← 948D BNE
CMP #&22 ; Double-quote character?
9494 BNE loop_copy_arg_char ; No: start reading filename
9496 INY ; Skip opening quote
9497 EOR hazel_quote_mode ; Toggle quote mode flag
949A STA hazel_quote_mode ; Store updated quote mode
949D .loop_copy_arg_char←2← 9494 BNE← 94B2 BNE
LDA (fs_crc_lo),y ; Load char from command line
949F CMP #&22 ; Double-quote?
94A1 BNE store_arg_char ; No: store character as-is
94A3 EOR hazel_quote_mode ; Toggle quote mode
94A6 STA hazel_quote_mode ; Store updated quote mode
94A9 LDA #&20 ; Replace closing quote with space
94AB .store_arg_char←1← 94A1 BNE
STA hazel_parse_buf,x ; Store character in parse buffer
94AE INY ; Advance command line pointer
94AF INX ; Advance buffer pointer
94B0 CMP #&0d ; End of line?
94B2 BNE loop_copy_arg_char ; No: continue parsing
94B4 LDA hazel_quote_mode ; Check quote balance flag
94B7 BEQ return_from_copy_cmd_name ; Balanced: return OK
94B9 LDA brk_ptr ; Unbalanced: use BRK ptr for error
94BB JSR error_bad_inline ; Raise 'Bad string' error
94BE EQUS "string." ; 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
94C5 .cmd_rename
JSR copy_fs_cmd_name ; Copy 'Rename ' to TX buffer
94C8 PHX
94C9 JSR mask_owner_access ; Clear owner-only access bits before parsing
94CC JSR parse_quoted_arg ; Parse the quoted source filename
94CF JSR parse_access_prefix ; Parse access prefix on the source filename
94D2 PLX
94D3 .loop_copy_rename←1← 94F1 BRA
LDA hazel_parse_buf ; Load next parsed character
94D6 CMP #&0d ; End of line?
94D8 BNE store_rename_char ; No: store character
94DA .error_bad_rename←1← 950D BNE
LDA #&b0 ; Error number &B0
94DC JSR error_bad_inline ; Raise 'Bad rename' error
94DF EQUS "rename." ; Add 5 for header size
94E6 .store_rename_char←1← 94D8 BNE
STA hazel_txcb_data,x ; Store character in TX buffer
94E9 INX ; Advance buffer pointer
94EA CMP #&20 ; Space (name separator)?
94EC BEQ skip_rename_spaces ; Yes: first name complete
94EE JSR strip_token_prefix ; Strip BASIC token prefix byte
94F1 BRA loop_copy_rename ; BRA back to loop_copy_rename
94F3 .skip_rename_spaces←2← 94EC BEQ← 94FB BEQ
JSR strip_token_prefix ; Strip token from next char
94F6 LDA hazel_parse_buf ; Load next parsed character
94F9 CMP #&20 ; Still a space?
94FB BEQ skip_rename_spaces ; Yes: skip multiple spaces
94FD LDA hazel_fs_lib_flags ; Save current FS options
9500 PHA ; Push them
9501 JSR mask_owner_access ; Reset access mask for second name
9504 PHX ; Save loop index across the access parse
9505 JSR parse_access_prefix ; Parse access prefix on the second filename
9508 PLX ; Restore loop index
9509 PLA ; Restore original FS options
950A CMP hazel_fs_lib_flags ; Options changed (cross-FS)?
950D BNE error_bad_rename ; Yes: error (can't rename across FS)
950F 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
9512 .cmd_dir
LDA (fs_crc_lo),y ; Get first char of argument
9514 CMP #&26 ; Is it '&' (FS selector prefix)?
9516 BNE dir_pass_simple ; No: simple dir change
9518 INY ; Skip '&'
9519 LDA (fs_crc_lo),y ; Get char after '&'
951B CMP #&0d ; End of line?
951D BEQ setup_fs_root ; Yes: '&' alone (root directory)
951F CMP #&20 ; Space?
9521 BNE check_fs_dot ; No: check for '.' separator
9523 .setup_fs_root←1← 951D BEQ
LDY #&ff ; Y=&FF: pre-increment for loop
9525 .loop_copy_fs_num←1← 952D BNE
INY ; Advance index
9526 LDA (fs_crc_lo),y ; Load char from command line
9528 STA hazel_txcb_data,y ; Copy to TX buffer
952B CMP #&26 ; Is it '&' (end of FS path)?
952D BNE loop_copy_fs_num ; No: keep copying
952F LDA #&0d ; Replace '&' with CR terminator
9531 STA hazel_txcb_data,y ; Store CR in buffer
9534 INY ; Point past CR
9535 TYA ; Transfer length to A
9536 TAX ; And to X (byte count)
9537 JMP send_fs_request ; Send directory request to server
953A .check_fs_dot←1← 9521 BNE
CMP #&2e ; Is char after '&' a dot?
953C BEQ parse_fs_dot_dir ; Yes: &FS.dir format
953E JMP error_bad_filename ; No: invalid syntax
9541 .parse_fs_dot_dir←1← 953C BEQ
INY ; Skip '.'
9542 STY fs_load_addr ; Save dir path start position
9544 LDA #4 ; FS command 4: examine directory
9546 STA hazel_txcb_data ; Store in TX buffer
9549 LDA hazel_fs_lib_flags ; Load FS flags
954C ORA #&40 ; Set bit 6 (FS selection active)
954E STA hazel_fs_lib_flags ; Store updated flags
9551 LDX #1 ; X=1: buffer offset
9553 JSR copy_arg_validated ; Copy FS number to buffer
9556 LDY #&12 ; Y=&12: select FS command code
9558 JSR save_net_tx_cb ; Send FS selection command
955B LDA hazel_txcb_data ; Load reply status
955E CMP #2 ; Status 2 (found)?
9560 BEQ dir_found_send ; Yes: proceed to dir change
9562 LDA #&d6 ; Error number &D6
9564 JSR error_inline_log ; Raise 'Not found' error
9567 EQUS "Not found." ; Store null terminator (A=0 from EOR) Get message length Go to error dispatch
9571 .dir_found_send←1← 9560 BEQ
LDA hazel_fs_context_copy ; Load current FS station byte
9574 STA hazel_txcb_data ; Store in TX buffer
9577 LDX #1 ; X=1: buffer offset
9579 LDY #7 ; Y=7: change directory command code
957B JSR save_net_tx_cb ; Send directory change request
957E LDX #1 ; X=1
9580 STX hazel_txcb_data ; Store start marker in buffer
9583 STX hazel_txcb_flag ; Store start marker in buffer+1
9586 INX ; Non-zero: commit state and return
9587 LDY fs_load_addr ; Restore dir path start position
9589 JSR copy_arg_validated ; Copy directory path to buffer
958C LDY #6 ; Y=6: set directory command code
958E JSR save_net_tx_cb ; Send set directory command
9591 LDY hazel_txcb_data ; Load reply handle
9594 JMP fsreply_3_set_csd ; Select FS and return
9597 .dir_pass_simple←1← 9516 BNE
JMP check_urd_prefix ; Simple: pass command to FS
959A .print_fs_ps_help
LDA (os_text_ptr),y ; Read first command-line char at (os_text_ptr),Y
959C CMP #&0d ; Is it CR (no argument supplied)?
959E BNE dispatch_fs_ps_with_arg ; Non-CR: argument present -- exit via dispatch_fs_ps_with_arg (X=&A0)
95A0 JSR print_fs_station ; CR: print 'FS ' header
95A3 JSR print_dir_syntax ; Print '[.]\r'
95A6 JSR print_station_low ; Print 'PS ' header
95A9 JSR print_dir_syntax ; Print '[.]\r' again
95AC JSR print_inline ; Print final 'Space\rNoSpace\r' lines
95AF EQUS "Space.NoSpace."
95BD NOP ; NOP -- bit-7 terminator + resume opcode for the preceding inline string
95BE .bra_target_svc_return←1← 9617 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.

95C1 .print_station_low←2← 95A6 JSR← 963C JSR
JSR print_inline ; Print 'P' prefix
95C4 EQUS "P"
95C5 CLV ; CLV -- bit-7 terminator + resume (V flag is irrelevant here, used as 1-byte resume opcode)
95C6 BVC print_field_tail_s ; BVC: V was just cleared -> always taken; falls into the shared 'S ' tail at &95CD
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.

95C8 .print_fs_station←2← 95A0 JSR← 9636 JSR
JSR print_inline ; Print 'F' prefix
95CB EQUS "F"
95CC NOP ; NOP -- bit-7 terminator; falls through into the shared 'S ' tail at &95CD
95CD .print_field_tail_s←1← 95C6 BVC
JSR print_inline ; Print 'S ' (S + 7 spaces) -- the shared 8-char field used by both 'FS' and 'PS' callers
95D0 EQUS "S "
95D8 NOP ; Bit-7 terminator
95D9 RTS ; Return

Print '[.]\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.

95DA .print_dir_syntax←2← 95A3 JSR← 95A9 JSR
JSR print_inline ; Print '[D>.]D>\r' (file-name syntax fragment, shared between *FS/*PS no-arg help and *Dir)
95DD EQUS "[<D>.]<D>."
95E7 NOP ; Bit-7 terminator
95E8 RTS ; Return
95E9 .dispatch_fs_ps_with_arg←1← 959E BNE
LDX #&a0 ; X=&A0: index into svc4 dispatch table (no-arg path)
95EB JMP svc4_dispatch_lookup ; Tail-jump to svc4_dispatch_lookup with X=&A0

Write FS/PS station+network to Master 128 CMOS RAM

Reached via PHA/PHA/RTS dispatch from cmd_table_fs sub-table 4 (*FS at &A80F, *PS at &A814) 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
95EE .set_fs_or_ps_cmos_station
LDA cmd_table_fs,x ; Read flag byte for matched cmd entry (syntax idx in bits 0..4)
95F1 AND #&3f ; Mask off end-marker (bit 7) and V-if-no-arg flag (bit 6)
95F3 TAX ; X = CMOS byte index (1=FS stn, 3=PS stn)
95F4 PHX ; Save CMOS index
95F5 PHY ; Save caller's command-line cursor
95F6 PHX ; Save CMOS index again (consumed by first PLX below)
95F7 JSR osbyte_a1 ; Read existing CMOS[idx] (current station)
95FA STY fs_work_5 ; Default station if user gives no args
95FC PLX ; Recover CMOS index from stack
95FD INX ; X+=1: advance to network byte
95FE JSR osbyte_a1 ; Read existing CMOS[idx+1] (current network)
9601 STY fs_work_6 ; Default network if user gives no args
9603 PLY ; Restore command-line cursor
9604 JSR parse_fs_ps_args ; Parse 'net>.stn>'; updates fs_work_5/6/7 if args present
9607 PLX ; Recover CMOS index from stack
9608 PHX ; Re-save CMOS index for second write
9609 LDY fs_work_5 ; Y = station (parsed or pre-read default)
960B JSR osbyte_a2 ; Write CMOS[idx] = station
960E PLX ; Recover CMOS index from stack
960F INX ; X+=1: advance to network byte
9610 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 &95BE. 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 &9617 → &8C64).

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 &962E 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
9612 .osbyte_a2←3← 960B JSR← 962E BRA← A0FE JSR
LDA #osbyte_write_cmos_ram ; A=&A2: write CMOS RAM byte via OSBYTE
9614 JSR osbyte ; Master and Compact: Write to CMOS RAM/EEPROM byte X with value Y
9617 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.

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

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

Shared CMOS write-back tail

Common tail used by cmd_space (via BRA from &9621 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.

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

9630 .svc_29_status
LDA (os_text_ptr),y ; Read first command-line char
9632 CMP #&0d ; Is it CR (no argument)?
9634 BNE help_dispatch_setup ; Non-CR: parse the argument at help_dispatch_setup
9636 JSR print_fs_station ; Print 'FS ' header
9639 JSR print_fs_address ; Print FS network.station from CMOS &02/&01
963C JSR print_station_low ; Print 'PS ' header
963F JSR print_ps_address ; Print PS network.station from CMOS &04/&03
9642 LDX #&11 ; X=&11: CMOS RAM byte index
9644 JSR osbyte_a1 ; Read CMOS &11 (FS state)
9647 TYA ; A = CMOS &11
9648 AND #1 ; Mask bit 0 (FS-active flag)
964A BNE parse_object_space_print ; Bit set: skip 'No ' prefix
964C JSR print_inline ; Print 'No ' prefix via inline
964F EQUS "No "
9652 NOP ; Bit-7 terminator + resume
9653 .parse_object_space_print←1← 964A BNE
JSR print_inline ; Print 'Space ' or similar via inline
9656 EQUS "Space."
965C CLV ; Bit-7 terminator + resume opcode
965D BVC print_cmos_done ; ALWAYS branch

Print printer-server address from CMOS

Reads the printer-server's saved network number from CMOS byte &04, prints it as decimal (no leading zeros), prints a . separator, then sets X=3 and falls into print_cmos_decimal_nl to read CMOS &03 and print the printer-server station with a trailing newline. Returns via the print_cmos_done trampoline.

965F .print_ps_address←1← 963F JSR
LDX #4 ; X=4: CMOS RAM byte 4 (network number)
9661 JSR osbyte_a1 ; Read CMOS &04 via osbyte_a1
9664 TYA ; A = CMOS &04 value
9665 JSR print_num_no_leading ; Print as decimal (no leading zeros)
9668 JSR print_inline ; Print '.' separator via inline
966B EQUS "."
966C LDX #3 ; X=3: CMOS &03 (PS station)
966E BRA print_cmos_decimal_nl ; BRA print_cmos_decimal_nl: shared print-and-trail
fall through ↓

Print file-server address from CMOS

Reads the file-server's saved network number from CMOS byte &02, prints it as decimal (no leading zeros), prints a . separator, then sets X=1 and falls into print_cmos_decimal_nl to read CMOS &01 and print the file-server station with a trailing newline. Returns via the print_cmos_done trampoline.

9670 .print_fs_address←1← 9639 JSR
LDX #2 ; X=2: CMOS &02 (FS network)
9672 JSR osbyte_a1 ; Read CMOS &02 via osbyte_a1
9675 TYA ; A = CMOS &02
9676 JSR print_num_no_leading ; Print as decimal
9679 JSR print_inline ; Print '.' separator via inline
967C EQUS "."
967D LDX #1 ; X=1: CMOS &01 (port)
967F .print_cmos_decimal_nl←1← 966E BRA
JSR osbyte_a1 ; Read CMOS X via osbyte_a1
9682 TYA ; A = CMOS value
9683 JSR print_num_no_leading ; Print as decimal
9686 JSR osnewl ; Write newline (characters 10 and 13)
9689 .print_cmos_done←1← 965D BVC
JMP svc_return_unclaimed ; JMP svc_return_unclaimed (release service call)
968C .help_dispatch_setup←1← 9634 BNE
LDX #&bd ; X=&BD: 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.

968E .dispatch_help_command
JMP svc4_dispatch_lookup ; JMP svc4_dispatch_lookup -- shared parser dispatch
9691 EQUS "!Help." ; '!Help.' prefix bytes (not used by the matcher; may be visible as a fallback help-message head)
9697 .on_suffix_pattern←1← 96A9 EOR
EQUS "ON " ; 'ON ' -- 3-char pattern read by match_on_suffix at &969A via EOR &9697,X with X=0..2 to detect '... ON ' help-line suffix
969A .match_on_suffix
PHY
969B LDA os_text_ptr ; Copy os_text_ptr lo to work_ae
969D STA work_ae ; Store -> work_ae
969F LDA os_text_ptr_hi ; Copy os_text_ptr hi
96A1 STA addr_work ; Store -> addr_work
96A3 PLY ; Restore caller Y
96A4 PHY ; Save Y again (preserve across loop)
96A5 LDX #0 ; X=0: pattern offset starts at 0
96A7 .loop_match_on_suffix←1← 96B6 BCC
LDA (work_ae),y ; Read text byte at (work_ae)+Y
96A9 EOR on_suffix_pattern,x
96AC AND #&5f ; Mask bit 5 -- case-insensitive comparison
96AE BEQ match_char_found ; Equal: continue checking pattern
96B0 .match_char_loop_cmp←2← 96C2 BEQ← 96D1 BEQ
PLY
96B1 RTS ; Return (no match)
96B2 .match_char_found←1← 96AE BEQ
INY ; Advance text index
96B3 INX ; Advance pattern index
96B4 CPX #3 ; Done all 3 chars?
96B6 BCC loop_match_on_suffix ; No: continue
96B8 PHY ; Match: save Y
96B9 JSR ensure_fs_selected ; Ensure NFS is selected (auto-select if needed)
96BC .match_char_process←2← 970D BEQ← 971C BRA
PLY
96BD .loop_skip_non_spaces←1← 96C6 BNE
INY ; Advance Y to next char
96BE LDA (work_ae),y ; Read text byte at (work_ae)+Y
96C0 CMP #&0d ; Is it CR (end-of-line)?
96C2 BEQ match_char_loop_cmp ; Yes: nothing to load -> return
96C4 CMP #&20 ; Is it space?
96C6 BNE loop_skip_non_spaces ; No: continue scanning past non-space
96C8 .loop_help_skip_spaces←1← 96CD BEQ
INY ; Skip space char
96C9 LDA (work_ae),y ; Read next byte
96CB CMP #&20 ; Is it space?
96CD BEQ loop_help_skip_spaces ; Yes: keep skipping spaces
96CF CMP #&0d ; Is it CR?
96D1 BEQ match_char_loop_cmp ; Yes: nothing past spaces -> return
96D3 STY hazel_txcb_data ; Save Y as hazel_txcb_data (cmd buffer ptr)
96D6 STY hazel_txcb_flag ; Save Y as hazel_txcb_flag (cmd flag)
96D9 LDX #1 ; X=1: index for template walk
96DB .loop_copy_command_suffix←1← 96E4 BNE
INX ; Advance template index
96DC LDA help_topic_template,x ; Read template byte from help_topic_template+X
96DF STA hazel_txcb_data,x ; Store at hazel_txcb_data+X
96E2 CMP #&2e ; Compare with '.' (template terminator)
96E4 BNE loop_copy_command_suffix ; Not '.': continue copying template
96E6 PHY ; Save text-buffer index
96E7 .loop_copy_topic_name←1← 96F4 BNE
INX ; Advance dest index
96E8 LDA (work_ae),y ; Read topic char at (work_ae),Y
96EA INY ; Advance source
96EB .loop_store_topic_char←1← 96F8 BRA
STA hazel_txcb_data,x ; Store at hazel_txcb_data+X
96EE CMP #&0d ; CR? (end of name)
96F0 BEQ start_help_file_load ; Yes: take start_help_file_load path (open file)
96F2 CMP #&20 ; Space? (terminator)
96F4 BNE loop_copy_topic_name ; No: continue copying
96F6 LDA #&0d ; A=&0D: replace space with CR
96F8 BRA loop_store_topic_char ; BRA back to store the CR
96FA .start_help_file_load←1← 96F0 BEQ
INX ; Account for last char
96FB LDA hazel_fs_lib_flags ; Read fs_lib_flags (hazel_fs_lib_flags)
96FE AND #&3f ; Preserve low bits, clear high bits
9700 ORA #&80 ; Set bit 7 (load-pending flag)
9702 STA hazel_fs_lib_flags ; Store back to fs_lib_flags
9705 LDA #&40 ; A=&40: load mode flag
9707 STA fs_last_byte_flag ; Store as fs_last_byte_flag
9709 JSR send_open_file_request ; Open the help-topic file
970C TAY ; Y=file handle
970D BEQ match_char_process ; Y=0: open failed -> return
970F .loop_print_help_byte←2← 972C BRA← 973B BRA
JSR osbget ; Read a single byte from an open file Y
9712 BCC help_print_start ; C clear: byte read OK -> print it
9714 LDA #osfind_close ; A=0: OSFIND close mode
9716 JSR osfind ; Close one or all files
9719 JSR osnewl ; Write newline (characters 10 and 13)
971C BRA match_char_process ; BRA back to match_char_process (return)
971E .help_print_start←2← 9712 BCC← 9736 BNE
BIT escape_flag
9720 BPL help_print_char_check ; Bit 7 clear: not escaping, continue
9722 JMP escape_error_close ; Escape: jump to error path escape_error_close
9725 .help_print_char_check←1← 9720 BPL
CMP #&0d ; Compare with CR
9727 BEQ handle_help_paged_mode ; Z: CR -- handle line-end (newline)
9729 JSR oswrch ; Write character
972C BRA loop_print_help_byte ; BRA back to read next byte
972E .handle_help_paged_mode←1← 9727 BEQ
PHY ; Save file handle
972F LDA #&da ; A=&DA: OSBYTE &DA = read paged-mode flag
9731 JSR osbyte_x0 ; Issue OSBYTE &DA (X=0)
9734 PLY ; Restore handle
9735 TXA ; Result to A
9736 BNE help_print_start ; Non-zero: paged mode pending -> handle Escape
9738 JSR osnewl ; Write newline (characters 10 and 13)
973B BRA loop_print_help_byte ; BRA back to read next byte
fall through ↓

Set up open receive for FS reply on port &90

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

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

Create open receive control block on specified port

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

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

Initialise TX control block from ROM template

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

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

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

TXCB initialisation template (12 bytes)

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

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

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

9763 .txcb_init_template←1← 974E LDA
EQUB &80 ; Offset 0: txcb_ctrl = &80 (TX command)
9764 EQUB &99 ; Offset 1: txcb_port = &99 (FS command port)
9765 EQUB &00 ; Offset 2: txcb_dest lo placeholder (overwritten with hazel_fs_station[0])
9766 EQUB &00 ; Offset 3: txcb_dest hi placeholder (overwritten with hazel_fs_station[1])
9767 EQUB &00 ; Offset 4: txcb_start lo = 0
9768 EQUB &C1 ; Offset 5: txcb_start hi = &C1 (data buffer starts at &C100 in HAZEL)
9769 .always_set_v_byte←21← 8C67 BIT← 91FC BIT← 993D BIT← 9A6A BIT← 9E2E BIT← 9FF0 BIT← A3BB BIT← A4B8 BIT← A65A BIT← A685 BIT← A6BC BIT← AE02 BIT← B327 BIT← B3E3 BIT← B563 BIT← B5C3 BIT← B604 BIT← B694 BIT← B8F9 BIT← B937 BIT← BC22 BIT
EQUB &FF ; Offset 6: padding &FF; doubles as the always_set_v_byte BIT $abs target
976A .bit_test_ff
EQUB &FF ; Offset 7: txcb_pos = &FF (also labelled bit_test_ff)
976B EQUB &FF ; Offset 8: txcb_end lo = &FF
976C EQUB &C1 ; Offset 9: txcb_end hi = &C1 (buffer end &C1FF)
976D EQUB &FF ; Offset 10: extended-addr fill (&FF)
976E EQUB &FF ; Offset 11: extended-addr fill (&FF)

Send read-only FS request (carry set)

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

On EntryYFS function code (stored as TX[1] = txcb_func by txcb_copy_carry_set)
Asaved on stack at entry (consumed by the txcb send/receive path)
976F .send_request_nowrite←1← A231 JSR
PHA ; Save A
9770 SEC ; Set carry (read-only mode)
9771 BCS txcb_copy_carry_set ; ALWAYS branch

Send read-write FS request (V clear)

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

On EntryYFS function code (stored as TX[1] = txcb_func by txcb_copy_carry_clr)
Arequest payload byte (used by the txcb send path)
9773 .send_request_write←2← 9C45 JSR← 9CF9 JSR
CLV ; Clear V
9774 BVC txcb_copy_carry_clr ; ALWAYS branch

*Bye command handler

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

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

Save FS state and send command to file server

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

On EntryYFS function code (becomes TX[1] = txcb_func)
XTX buffer payload length (prep_send_tx_cb uses X+5 as txcb_end)
On ExitAFS reply status
978A .save_net_tx_cb←25← 8E3C JSR← 9558 JSR← 957B JSR← 958E JSR← 9E4A JSR← 9F2F JSR← 9F3F JSR← 9F8D JSR← A018 JSR← A091 JSR← A0C5 JSR← A199 JSR← A1BC JSR← A28C JSR← A347 JSR← A50B JSR← A533 JSR← A895 JSR← B0D2 JMP← B150 JSR← B18E JSR← B1FD JSR← B71B JSR← B7B2 JSR← BCAA JSR
CLV ; Clear V: standard send mode (callers set V via save_net_tx_cb_vset for the lib-flag variant)
fall through ↓

Save and send TXCB with V flag set

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

Called by check_and_setup_txcb, format_filename_field, and cmd_remove.

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

Build TXCB from scratch, send, and receive reply

Full send/receive cycle comprising two separate Econet transactions:

  1. Save flags, set reply port &90.

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

  3. Dispatch on carry:

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

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

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

Receive FS reply and dispatch on status codes

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

Iterates over reply bytes:

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

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

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

Language reply 1: remote-boot init / continue

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

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

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

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

987E .lang_3_exec_0100
JSR commit_state_byte ; Commit the language-reply state byte
9881 LDA #0 ; A=0: 'Bad' error code
9883 JSR error_inline_log ; Raise via error_inline_log (never returns)
9886 EQUS "Remoted.."
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 (&8DEF) for password-entry escape, and send_net_packet (&9B48) for in-flight TX escape.

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

Acknowledge escape and raise classified error

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

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

Language reply 4: validate remote session and apply

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

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

Language reply 0: insert remote keypress

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

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

Wait for reply on open receive with timeout

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

Uses a three-level nested polling loop:

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

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

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

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

Conditionally store error code to workspace

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

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

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

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

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

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

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

Load reply byte and classify error

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

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

Classify FS reply error code

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

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

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

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

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

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

On EntryAerror number
99A7 .error_bad_inline←11← 90B7 JSR← 934C JSR← 9359 JSR← 936D JSR← 9379 JSR← 9388 JSR← 9439 JSR← 94BB JSR← 94DC JSR← A5A3 JSR← BF48 JSR
JSR cond_save_error_code ; Conditionally log error code to workspace
99AA TAY ; Save error number in Y
99AB PLA ; Pop return address (low) — points to last byte of JSR
99AC STA fs_load_addr ; Store return address low
99AE PLA ; Pop return address (high)
99AF STA fs_load_addr_hi ; Store return address high
99B1 LDX #0 ; X=0: start of prefix string
99B3 .loop_copy_bad_prefix←1← 99BC BNE
INX ; Copy 'Bad ' prefix from lookup table
99B4 LDA bad_prefix_table,x ; Get next prefix character
99B7 STA error_text,x ; Store in error text buffer
99BA CMP #&20 ; Is it space (end of 'Bad ')?
99BC BNE loop_copy_bad_prefix ; No: copy next prefix character
99BE BEQ write_error_num_and_str ; ALWAYS branch

Generate BRK error from inline string (with logging)

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

On EntryAerror number
99C0 .error_inline_log←11← 9564 JSR← 9883 JSR← A5BA JSR← AF82 JSR← AF94 JSR← B81F JSR← B895 JSR← B8E6 JSR← BB79 JSR← BBB3 JSR← BBFD JSR
JSR cond_save_error_code ; Conditionally log error code to workspace
fall through ↓

Generate BRK error from inline string

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

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

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

Reads the receive-attribute byte:

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

Append 'net.station' decimal string to error text

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

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

into the error-text buffer at the current position.

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

Append space and decimal number to error text

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

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

Convert byte to decimal and append to error text

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

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

Extract and append one decimal digit

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

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

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

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

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

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

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

Net-error message strings

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

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

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

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

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

Point TX at zero-page TXCB and send

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

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

Transmit Econet packet with retry

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

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

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

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

Pass-through TX buffer template (12 bytes)

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

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

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

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

Set up TX pointer and send pass-through packet

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

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

Initialise TX buffer from pass-through template

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

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

Wait for TX ready, then start new transmission

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

Result in A:

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

Copy text pointer from FS options and parse string

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

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

Parse command line via GSINIT/GSREAD into hazel_parse_buf

Calls GSINIT to initialise string reading, then loops calling GSREAD to copy characters into hazel_parse_buf until end-of-string. Appends a CR terminator and sets fs_crc_lo/hi to point at the buffer for subsequent parsing routines. (Pre-HAZEL ROMs used fs_filename_buf at &0E30; the 4.21 build uses HAZEL.)

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

FILEV vector handler: OSFILE

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

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

Execute one iteration of a multi-step FS command

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

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

Send TXCB and swap start/end addresses

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

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

Compute display deltas and prep FS info request

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

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

Receive FS reply and stash result byte

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

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

Print exec address and file length in hex

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

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

Print hex byte sequence from FS options

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

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

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

Copy FS options address bytes to zero page

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

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

Advance Y by 5

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

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

Advance Y by 4

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

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

Copy workspace reply data to FS options

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

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

Retreat Y by 4

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

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

Retreat Y by 3

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

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

Set up data-transfer TXCB and dispatch reply

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

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

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

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

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

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

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

Format filename into fixed-width display field

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

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

ARGSV vector handler: OSARGS

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

On EntryAOSARGS function code
Xcontrol-block low byte
Ychannel handle
9EAB .argsv_handler
JSR verify_ws_checksum ; Verify workspace checksum
9EAE STA fs_last_byte_flag ; Store result as last byte flag
9EB0 JSR set_options_ptr ; Set FS options pointer
9EB3 ORA #0 ; OR with 0 to set flags
9EB5 BPL dispatch_osfind_op ; Positive: handle sub-operations
9EB7 ASL ; Shift left to check bit 6
9EB8 BEQ validate_chan_close ; Zero (was &80): close channel
9EBA JMP close_all_fcbs ; Other: process all FCBs first
9EBD .validate_chan_close←1← 9EB8 BEQ
TYA ; Transfer Y to A
9EBE CMP #&20 ; Compare with &20 (space)
9EC0 BCS check_chan_range ; Above &20: check further
9EC2 .error_invalid_chan←1← 9EC7 BCS
JMP err_net_chan_invalid ; Below &20: invalid channel char
9EC5 .check_chan_range←1← 9EC0 BCS
CMP #&30 ; Compare with '0'
9EC7 BCS error_invalid_chan ; Above '0': invalid channel char
9EC9 JSR process_all_fcbs ; Process all matching FCBs
9ECC TYA ; Transfer Y to A (FCB index)
9ECD PHA ; Push FCB index
9ECE TAX ; Copy to X
9ECF LDY #0 ; Y=0: clear counter
9ED1 STY fs_last_byte_flag ; Clear last byte flag
9ED3 STY fs_block_offset ; Clear block offset
9ED5 .loop_copy_fcb_fields←1← 9EE0 BNE
LDA hazel_fcb_addr_mid,x ; Load channel data from fcb_attr_or_count_mid+X
9ED8 STA (fs_options),y ; Store in FS options at Y
9EDA JSR advance_x_by_8 ; Advance X by 8 (next FCB field)
9EDD INY ; Advance destination index
9EDE CPY #4 ; Copied all 4 channel fields?
9EE0 BNE loop_copy_fcb_fields ; No: copy next field
9EE2 PLA ; Pull saved FCB index
9EE3 STA fs_block_offset ; Restore to fs_block_offset
9EE5 .dispatch_osfind_op←1← 9EB5 BPL
CMP #5 ; Compare with 5
9EE7 BCS done_return_flag ; 5 or above: return with last flag
9EE9 CPY #0 ; Compare Y with 0
9EEB BNE osfind_with_channel ; Non-zero: handle OSFIND with channel
9EED JMP osfind_close_or_open ; Y=0 (close): jump to OSFIND open
9EF0 .osfind_with_channel←1← 9EEB BNE
PHA ; Push sub-function
9EF1 TXA ; Transfer X to A
9EF2 PHA ; Push X (FCB slot)
9EF3 TYA ; Transfer Y to A
9EF4 PHA ; Push Y (channel char)
9EF5 JSR check_not_dir ; Check file is not a directory
9EF8 PLA ; Pull channel char
9EF9 JSR store_rx_attribute ; Store channel char as receive attribute
9EFC LDA hazel_fcb_slot_attr,x ; Load FCB flag byte from fcb_net_or_port
9EFF STA hazel_txcb_data ; Store in fs_cmd_data
9F02 PLA ; Pull X (FCB slot)
9F03 TAX ; Restore X
9F04 PLA ; Pull sub-function
9F05 LSR ; Shift right: check bit 0
9F06 BEQ osargs_ptr_dispatch ; Zero (OSFIND close): handle close
9F08 PHP ; Save flags (carry from LSR)
9F09 PHA ; Push sub-function
9F0A LDX fs_options ; Load FS options pointer low
9F0C LDY fs_block_offset ; Load block offset
9F0E JSR process_all_fcbs ; Process all matching FCBs
9F11 LDA hazel_fcb_addr_mid,y ; Load updated data from fcb_attr_or_count_mid
9F14 STA hazel_txcb_data ; Store in fs_cmd_data
9F17 PLA ; Pull sub-function
9F18 STA hazel_txcb_flag ; Store in fs_func_code
9F1B PLP ; Restore flags
9F1C TYA ; Transfer Y to A
9F1D PHA ; Push Y (offset)
9F1E BCC osargs_read_op ; Carry clear: read operation
9F20 LDY #3 ; Y=3: copy 4 bytes
9F22 .loop_copy_zp_to_buf←1← 9F29 BPL
LDA zp_work_3,x ; Load zero page data
9F24 STA hazel_txcb_count,y ; Store in fs_data_count buffer
9F27 DEX ; Decrement source
9F28 DEY ; Decrement counter
9F29 BPL loop_copy_zp_to_buf ; Loop for all 4 bytes
9F2B LDY #&0d ; Y=&0D: TX buffer size
9F2D LDX #5 ; X=5: argument offset
9F2F JSR save_net_tx_cb ; Send TX control block to server
9F32 STX fs_last_byte_flag ; Store X in last byte flag
9F34 PLA ; Pull saved offset
9F35 JSR set_conn_active ; Set connection active flag
9F38 .done_return_flag←1← 9EE7 BCS
JMP return_with_last_flag ; Return with last flag
9F3B .osargs_read_op←1← 9F1E BCC
LDY #&0c ; Y=&0C: TX buffer size (smaller)
9F3D LDX #2 ; X=2: argument offset
9F3F JSR save_net_tx_cb ; Send TX control block
9F42 STA fs_last_byte_flag ; Store A in last byte flag
9F44 LDX fs_options ; Load FS options pointer low
9F46 LDY #2 ; Y=2: zero page offset
9F48 STA zp_work_3,x ; Store A in zero page
9F4A .loop_copy_reply_to_zp←1← 9F51 BPL
LDA hazel_txcb_data,y ; Load buffer byte from fs_cmd_data+Y
9F4D STA zp_work_2,x ; Store in zero page at offset
9F4F DEX ; Decrement source X
9F50 DEY ; Decrement counter Y
9F51 BPL loop_copy_reply_to_zp ; Loop until all bytes copied
9F53 PLA ; Pull saved offset
9F54 JMP return_with_last_flag ; Return with last flag
9F57 .osargs_ptr_dispatch←1← 9F06 BEQ
BCS osargs_write_ptr ; Carry set: write file pointer
9F59 LDA fs_block_offset ; Load block offset
9F5B JSR attr_to_chan_index ; Convert attribute to channel index
9F5E LDY fs_options ; Load FS options pointer
9F60 LDA hazel_fcb_addr_lo,x ; Load FCB low byte from fcb_count_lo
9F63 STA zp_ptr_lo,y ; Store in zero page pointer low
9F66 LDA hazel_fcb_addr_mid,x ; Load FCB high byte from fcb_attr_or_count_mid
9F69 STA zp_ptr_hi,y ; Store in zero page pointer high
9F6C LDA hazel_fcb_addr_hi,x ; Load FCB extent from fcb_station_or_count_hi
9F6F STA zp_work_2,y ; Store in zero page work area
9F72 LDA #0 ; A=0: clear high byte
9F74 STA zp_work_3,y ; Store zero in work area high
9F77 BEQ return_with_last_flag ; ALWAYS branch to return with flag
9F79 .osargs_write_ptr←1← 9F57 BCS
STA hazel_txcb_flag ; Store write value in fs_func_code
9F7C TXA ; Transfer X to A
9F7D PHA ; Push X (zero page offset)
9F7E LDY #3 ; Y=3: copy 4 bytes
9F80 .loop_copy_ptr_to_buf←1← 9F87 BPL
LDA zp_work_3,x ; Load zero page data at offset
9F82 STA hazel_txcb_count,y ; Store in fs_data_count buffer
9F85 DEX ; Decrement source
9F86 DEY ; Decrement counter
9F87 BPL loop_copy_ptr_to_buf ; Loop for all 4 bytes
9F89 LDY #&0d ; Y=&0D: TX buffer size
9F8B LDX #5 ; X=5: argument offset
9F8D JSR save_net_tx_cb ; Send TX control block
9F90 STX fs_last_byte_flag ; Store X in last byte flag
9F92 PLA ; Pull saved zero page offset
9F93 TAY ; Transfer to Y
9F94 LDA fs_block_offset ; Load block offset (attribute)
9F96 JSR clear_conn_active ; Clear connection active flag
9F99 JSR attr_to_chan_index ; Convert attribute to channel index
9F9C LDA zp_ptr_lo,y ; Load zero page pointer low
9F9F STA hazel_fcb_addr_lo,x ; Store back to FCB fcb_count_lo
9FA2 LDA zp_ptr_hi,y ; Load zero page pointer high
9FA5 STA hazel_fcb_addr_mid,x ; Store back to FCB fcb_attr_or_count_mid
9FA8 LDA zp_work_2,y ; Load zero page work byte
9FAB STA hazel_fcb_addr_hi,x ; Store back to FCB fcb_station_or_count_hi
9FAE 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 &9EBA).

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

Load last-byte flag and finalise

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

On ExitAfs_last_byte_flag
Xfs_options (restored by finalise_and_return)
Yfs_block_offset (restored by finalise_and_return)
9FB4 .return_with_last_flag←12← 9C36 JMP← 9D41 JMP← 9E36 JMP← 9F38 JMP← 9F54 JMP← 9F77 BEQ← 9FAE JMP← A09C JMP← A15F JMP← A63B JMP← A641 JMP← A6FB 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
9FB6 .finalise_and_return←7← 9E7F JMP← 9FCB BNE← 9FD2 BPL← A068 JMP← A1EC JMP← A53B JMP← A599 JSR
PHA ; Push result on stack
9FB7 LDA #0 ; A=0: clear error flag
9FB9 JSR store_rx_attribute ; Clear receive attribute (A=0)
9FBC PLA ; Pull result back
9FBD LDX fs_options ; Restore X from FS options pointer
9FBF LDY fs_block_offset ; Restore Y from block offset
9FC1 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 &9EED).

On EntryAOSFIND function code (0=close-all, 1=close-one, >=2 = open variants)
9FC2 .osfind_close_or_open←1← 9EED JMP
CMP #2 ; Compare with 2 (open for output)
9FC4 BCS done_file_open ; 2 or above: handle file open
9FC6 TAY ; Transfer to Y (Y=0 or 1)
9FC7 BNE done_file_open ; Non-zero (1 = read pointer): copy data
9FC9 LDA #5 ; A=5: return code for close-all
9FCB BNE finalise_and_return ; ALWAYS branch to finalise
9FCD .done_file_open←2← 9FC4 BCS← 9FC7 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 &9FD6 and the catalogue tail at tail_update_ catalogue (&A329).

On ExitA0
C0 (LSR of 0)
9FCF .clear_result←2← 9FD6 BNE← A329 JMP
LDA #0 ; A=0: clear result
9FD1 .shift_and_finalise←1← 9FCD BEQ
LSR ; Shift right (always positive)
9FD2 BPL finalise_and_return ; Positive: jump to finalise
9FD4 .alloc_fcb_for_open←1← A042 BEQ
AND #&3f ; Mask to 6-bit access value
9FD6 BNE clear_result ; Non-zero: clear A and finalise
9FD8 TXA ; Transfer X to A (options pointer)
9FD9 JSR alloc_fcb_or_error ; Allocate FCB slot or raise error
9FDC EOR #&80 ; Toggle bit 7
9FDE ASL ; Shift left: build open mode
9FDF STA hazel_txcb_data ; Store open mode in fs_cmd_data
9FE2 ROL ; Rotate to complete mode byte
9FE3 STA hazel_txcb_flag ; Store in fs_func_code
9FE6 JSR parse_cmd_arg_y0 ; Parse command argument (Y=0)
9FE9 LDX #2 ; X=2: buffer offset
9FEB JSR copy_arg_to_buf ; Copy argument to TX buffer
fall through ↓
; Send file open request with V flag set for directory check.
9FEE .send_open_file_request←1← 9709 JSR
LDY #6 ; Y=6: open file command
9FF0 BIT always_set_v_byte ; Set V flag (skip directory check)
9FF3 SEC ; Set carry
9FF4 ROR need_release_tube ; Rotate carry into escapable flag bit 7
9FF6 JSR save_net_tx_cb_vset ; Send open request with V set
9FF9 BCS done_osfind ; Carry set (error): jump to finalise
9FFB LDA #&ff ; A=&FF: mark as newly opened
9FFD JSR store_rx_attribute ; Store &FF as receive attribute
A000 LDA hazel_txcb_data ; Load handle from fs_cmd_data
A003 PHA ; Push handle
A004 LDA #4 ; A=4: file info sub-command
A006 STA hazel_txcb_data ; Store sub-command
A009 LDX #1 ; X=1: shift filename
A00B .loop_shift_filename←1← A014 BNE
LDA hazel_txcb_flag,x ; Load filename byte from fs_func_code+X
A00E STA hazel_txcb_data,x ; Shift down to fs_cmd_data+X
A011 INX ; Advance source index
A012 CMP #&0d ; Is it CR (end of filename)?
A014 BNE loop_shift_filename ; No: continue shifting
A016 LDY #&12 ; Y=&12: file info request
A018 JSR save_net_tx_cb ; Send file info request
A01B LDA fs_last_byte_flag ; Load last byte flag
A01D AND #&bf ; Clear bit 6 (read/write bits)
A01F ORA hazel_txcb_data ; OR with reply access byte
A022 ORA #1 ; Set bit 0 (file is open)
A024 TAY ; Transfer to Y (access flags)
A025 AND #2 ; Check bit 1 (write access)
A027 BEQ check_open_mode ; No write access: check read-only
A029 PLA ; Pull handle from stack
A02A JSR alloc_fcb_slot ; Allocate FCB slot for channel
A02D BNE store_fcb_flags ; Non-zero: FCB allocated, store flags
A02F .findv_handler←1← 8E95 JMP
JSR verify_ws_checksum ; Verify workspace checksum
A032 JSR set_xfer_params ; Set up transfer parameters
A035 TAX ; Transfer A to X
A036 JSR mask_owner_access ; Set owner-only access mask
A039 TXA ; Transfer X back to A
A03A BEQ close_all_channels ; Zero: close file, process FCBs
A03C JSR save_ptr_to_os_text ; Save text pointer for OS
A03F LDY hazel_cur_dir_handle ; Load current directory handle
A042 BEQ alloc_fcb_for_open ; Zero: allocate new FCB
A044 TYA ; Transfer Y to A
A045 LDX #0 ; X=0: clear directory handle
A047 STX hazel_cur_dir_handle ; Store zero (clear handle)
A04A BEQ done_osfind ; ALWAYS branch to finalise
A04C .check_open_mode←1← A027 BEQ
LDA hazel_txcb_flag ; Load access/open mode byte
A04F ROR ; Rotate right: check bit 0
A050 BCS alloc_fcb_with_flags ; Carry set (bit 0): check read permission
A052 ROR ; Rotate right: check bit 1
A053 BCC alloc_fcb_with_flags ; Carry clear (no write): skip
A055 BIT hazel_txcb_count ; Test bit 7 of fs_data_count (lock flag)
A058 BPL alloc_fcb_with_flags ; Not locked: skip
A05A TYA ; Transfer Y to A (flags)
A05B ORA #&20 ; Set bit 5 (locked file flag)
A05D TAY ; Transfer back to Y
A05E .alloc_fcb_with_flags←3← A050 BCS← A053 BCC← A058 BPL
PLA ; Pull handle from stack
A05F JSR alloc_fcb_slot ; Allocate FCB slot for channel
A062 .store_fcb_flags←1← A02D BNE
TAX ; Transfer to X
A063 TYA ; Transfer Y to A (flags)
A064 STA hazel_fcb_state_byte,x ; Store flags in FCB table fcb_flags
A067 TXA ; Transfer X back to A (handle)
A068 .done_osfind←2← 9FF9 BCS← A04A BEQ
JMP finalise_and_return ; Jump to finalise and return
A06B .close_all_channels←1← A03A BEQ
JSR process_all_fcbs ; Process all matching FCBs
A06E TYA ; Transfer Y to A
A06F BNE close_specific_chan ; Non-zero channel: close specific
A071 LDA fs_options ; Load FS options pointer low
A073 PHA ; Push (save for restore)
A074 LDA #osbyte_close_spool_exec ; A=&77: OSBYTE close spool/exec files
A076 JSR osbyte ; Close any *SPOOL and *EXEC files
A079 PLA ; Pull saved options pointer
A07A STA fs_options ; Restore FS options pointer
A07C LDA #0 ; A=0: clear flags
A07E STA fs_last_byte_flag ; Save to fs_work_5
A080 STA fs_block_offset ; Load current FS station low
A082 BEQ send_close_request ; ALWAYS branch to send close request Save to fs_work_6
A084 .close_specific_chan←1← A06F BNE
JSR check_chan_char ; Validate channel character
A087 LDA hazel_fcb_slot_attr,x ; Is it CR (no argument)?
A08A .send_close_request←1← A082 BEQ
STA hazel_txcb_data ; Store as fs_cmd_data (file handle)
A08D LDX #1 ; X=1: argument size
A08F LDY #7 ; Y=7: close file command
A091 JSR save_net_tx_cb ; Send close file request
A094 LDY fs_block_offset ; Parameter block low
A096 BNE clear_single_fcb ; Parameter block high
A098 CLV ; Clear V flag
A099 JSR scan_fcb_flags ; Scan and clear all FCB flags
A09C .done_close←4← A0A7 BEQ← A0B9 BCC← A0CD BPL← A101 BRA
JMP return_with_last_flag ; Return with last flag
A09F .clear_single_fcb←1← A096 BNE
LDA #0 ; A=0: clear FCB entry
A0A1 STA hazel_fcb_addr_mid,y ; Clear hazel_fcb_addr_mid for slot Y
A0A4 STA hazel_fcb_state_byte,y ; Clear hazel_fcb_state_byte for slot Y
A0A7 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)
A0A9 .fscv_0_opt_entry
BEQ store_display_flag ; A=0 (init sub-code): jump to store_display_flag
A0AB CPX #4 ; Non-zero A: X==4? (read OSARGS args)
A0AD BNE osargs_dispatch ; X != 4: take normal OSARGS dispatch
A0AF CPY #4 ; X==4 path: Y < 4?
A0B1 BCC send_osargs_request ; Yes: send OSARGS request via TXCB
A0B3 .osargs_dispatch←1← A0AD BNE
DEX ; X-- (osargs_dispatch entry): step sub-code down
A0B4 BNE osargs_store_ptr_lo ; X != 1: take store-ptr-lo path
A0B6 .store_display_flag←1← A0A9 BEQ
STY hazel_fs_messages_flag ; Store Y as hazel_fs_messages_flag (display control)
A0B9 BCC done_close ; Tail-branch to done_close
A0BB .error_osargs←2← A0D1 BCS← A0DD BCS
LDA #7 ; A=7: error code (out-of-range OSARGS sub-code)
A0BD JMP classify_reply_error ; Raise BRK error
A0C0 .send_osargs_request←1← A0B1 BCC
STY hazel_txcb_data ; Store Y as TXCB data byte (OSARGS payload)
A0C3 LDY #&16 ; Y=&16: TXCB function code (OSARGS request)
A0C5 JSR save_net_tx_cb ; Send OSARGS request via TX control block
A0C8 LDY fs_block_offset ; Reload Y from fs_block_offset
A0CA STY hazel_fs_flags ; Update hazel_fs_flags from OSARGS reply
A0CD BPL done_close ; No error (positive): tail to done_close
A0CF .osargs_store_ptr_lo←1← A0B4 BNE
CPX #8 ; X >= 8?
A0D1 BCS error_osargs ; Yes: out-of-range OSARGS sub-code
A0D3 CPX #4 ; X == 4?
A0D5 BEQ osargs_check_length ; Yes: take fast read path (osargs_check_length)
A0D7 CPY #4 ; Y < 4?
A0D9 BCC osopt_check_cmos_protect ; Yes: take CMOS-protect path
A0DB .osargs_check_length←1← A0D5 BEQ
CPY #2 ; Y >= 2?
A0DD BCS error_osargs ; Yes: argument out of range
A0DF .osopt_check_cmos_protect←1← A0D9 BCC
PHY
A0E0 PHX ; Save sub-code across the CMOS read
A0E1 LDX #&11 ; X=&11: CMOS RAM byte index
A0E3 JSR osbyte_a1 ; Read CMOS &11 (Econet status) -> Y
A0E6 PLX ; Restore sub-code
A0E7 TYA ; Read CMOS &11 result to A
A0E8 AND cmos_opt_mask_table,x ; Mask CMOS &11 with cmos_opt_mask_table[X]
A0EB PLY
A0EC PHA ; Push CMOS value
A0ED LDA cmos_attr_table,x ; Load shift count from cmos_attr_table[X]
A0F0 TAX ; Value to X
A0F1 TYA ; Caller's Y back to A as the value to shift
A0F2 .loop_extract_attribute_bits←1← A0F4 BNE
ASL ; Shift CMOS bits
A0F3 DEX ; Count down shift iterations
A0F4 BNE loop_extract_attribute_bits ; Loop until X reaches 0
A0F6 STA fs_load_addr ; Stash shifted value in fs_load_addr scratch
A0F8 PLA ; Pop saved value
A0F9 ORA fs_load_addr ; OR with the CMOS-masked value
A0FB TAY
A0FC LDX #&11 ; X=&11: target CMOS byte for write-back
A0FE .osopt_cmos_writeback_jsr
JSR osbyte_a2 ; Write CMOS RAM byte (Y) to byte index (X)
A101 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.

A103 .cmos_opt_mask_table←1← A0E8 AND
EQUB &01 ; Idx 0: AND mask = &01 (extract CMOS &11 bit 0)
A104 EQUB &02 ; Idx 1: AND mask = &02 (extract CMOS &11 bit 1)
A105 EQUB &04 ; Idx 2: AND mask = &04 (extract CMOS &11 bit 2)
A106 EQUB &06 ; Idx 3: AND mask = &06 (extract CMOS &11 bits 1,2)
A107 EQUB &FD ; Idx 4: AND mask = &FD (clear CMOS &11 bit 1)
A108 EQUB &F3 ; Idx 5: AND mask = &F3 (clear CMOS &11 bits 2,3)
A109 EQUB &CF ; Idx 6: AND mask = &CF (clear CMOS &11 bits 4,5)
A10A 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
A10B .fscv_1_eof
JSR verify_ws_checksum ; Verify workspace checksum
A10E PHA ; Push checksum-verify result -- preserve it across the FCB lookups below
A10F LDA fs_block_offset ; Load block offset
A111 PHA ; Push block offset
A112 STX hazel_chan_attr ; Store X in cur_chan_attr
A115 JSR find_matching_fcb ; Find matching FCB entry
A118 BEQ mark_not_found ; Zero: no match found
A11A LDA hazel_fcb_addr_lo,y ; Load FCB low byte from fcb_count_lo
A11D CMP hazel_fcb_offset_save,x ; Compare with stored offset fcb_buf_offset
A120 BCC mark_not_found ; FCB lo-byte below stored offset -> not the matching FCB; mark_not_found
A122 LDX #&ff ; X=&FF: mark as found (all bits set)
A124 BMI restore_and_return ; ALWAYS branch (negative)
A126 .mark_not_found←2← A118 BEQ← A120 BCC
LDX #0 ; X=0: mark as not found
A128 .restore_and_return←1← A124 BMI
PLA ; Restore block offset from stack
A129 TAY ; Generate 'Syntax' error
A12A PLA ; Restore result from stack
A12B 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)
A12C .update_addr_from_offset9←1← A277 JSR
LDY #9 ; Y=9: FS options offset for high address
A12E 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
A131 .update_addr_from_offset1←1← A370 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
A133 .add_workspace_to_fsopts←1← A12E 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
A134 .adjust_fsopts_4bytes←2← A27D JSR← A37C JSR
LDX #&fc ; X=&FC: loop counter (-4 to -1)
A136 .loop_adjust_byte←1← A149 BNE
LDA (fs_options),y ; Load FS options byte at offset Y
A138 BIT fs_load_addr_2 ; Test fs_load_addr_2 bit 7 (add/subtract)
A13A BMI subtract_ws_byte ; Push high byte
A13C ADC hazel_fs_opts_addend,x ; Add workspace byte to FS options
A13F JMP store_adjusted_byte ; RTS dispatches to command handler
A142 .subtract_ws_byte←1← A13A 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 &A13F).

On EntryAbyte to store
Ycurrent FS-options index
Xremaining-byte counter
A145 .store_adjusted_byte←1← A13F JMP
STA (fs_options),y ; Store result back to FS options
A147 INY ; Advance to next byte
A148 INX ; Advance counter
A149 BNE loop_adjust_byte ; Loop until 4 bytes processed
A14B 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)
A14C .gbpbv_handler
JSR verify_ws_checksum ; Verify workspace checksum
A14F JSR set_xfer_params ; Set up transfer parameters
A152 PHA ; Push transfer type on stack
A153 JSR mask_owner_access ; Set owner-only access mask
A156 PLA ; Pull transfer type
A157 TAX ; Transfer to X
A158 BEQ skip_if_out_of_range ; Zero: no valid operation, return
A15A DEX ; Decrement (convert 1-based to 0-based)
A15B CPX #8 ; Compare with 8 (max operation)
A15D BCC valid_osgbpb_op ; Below 8: valid operation
A15F .skip_if_out_of_range←1← A158 BEQ
JMP return_with_last_flag ; Out of range: return with flag
A162 .valid_osgbpb_op←1← A15D BCC
TXA ; Transfer operation code to A
A163 LDY #0 ; Y=0: buffer offset
A165 PHA ; Push operation code
A166 CMP #4 ; Compare with 4 (write operations)
A168 BCC load_chan_handle ; Below 4: read operation
A16A JMP write_block_entry ; 4 or above: write data block
A16D .load_chan_handle←1← A168 BCC
LDA (fs_options),y ; Load channel handle from FS options
A16F PHA ; Push handle
A170 JSR check_not_dir ; Check file is not a directory
A173 PLA ; Pull handle
A174 TAY ; Transfer to Y
A175 JSR process_all_fcbs ; Process all matching FCBs
A178 LDA hazel_fcb_slot_attr,x ; Load FCB flag byte from fcb_net_or_port
A17B STA hazel_txcb_data ; Store file handle in fs_cmd_data
A17E LDA #0 ; A=0: clear direction flag
A180 STA hazel_txcb_flag ; Store in fs_func_code
A183 LDA hazel_fcb_addr_lo,x ; Load FCB low byte (position)
A186 STA hazel_txcb_count ; Store in fs_data_count
A189 LDA hazel_fcb_addr_mid,x ; Load FCB high byte
A18C STA hazel_txcb_result ; Store in fs_reply_cmd
A18F LDA hazel_fcb_addr_hi,x ; Load FCB extent byte
A192 STA hazel_exec_addr ; Store in fs_load_vector
A195 LDY #&0d ; Y=&0D: TX buffer size
A197 LDX #5 ; X=5: argument count
A199 JSR save_net_tx_cb ; Send TX control block to server
A19C PLA ; Pull operation code
A19D JSR setup_transfer_workspace ; Set up transfer workspace
A1A0 PHP ; Save flags (carry from setup)
A1A1 LDY #0 ; Y=0: index for channel handle
A1A3 LDA (fs_options),y ; Load channel handle from FS options
A1A5 BCS set_write_active ; Carry set (write): set active
A1A7 JSR clear_conn_active ; Read: clear connection active
A1AA BPL setup_gbpb_request ; Branch to continue (always positive)
A1AC .set_write_active←1← A1A5 BCS
JSR set_conn_active ; Write: set connection active
A1AF .setup_gbpb_request←1← A1AA BPL
STY hazel_txcb_flag ; Clear fs_func_code (Y=0)
A1B2 JSR lookup_cat_slot_data ; Look up channel slot data
A1B5 STA hazel_txcb_data ; Store flag byte in fs_cmd_data
A1B8 LDY #&0c ; Y=&0C: TX buffer size (short)
A1BA LDX #2 ; X=2: argument count
A1BC JSR save_net_tx_cb ; Send TX control block
A1BF JSR lookup_cat_entry_0 ; Look up channel entry at Y=0
A1C2 LDY #9 ; Y=9: FS options offset for position
A1C4 LDA hazel_txcb_data ; Load new position low from fs_cmd_data
A1C7 STA hazel_fcb_addr_lo,x ; Update FCB low byte in fcb_count_lo
A1CA STA (fs_options),y ; Store in FS options at Y=9
A1CC INY ; Y=&0A
A1CD LDA hazel_txcb_flag ; Load new position high from fs_func_code
A1D0 STA hazel_fcb_addr_mid,x ; Update FCB high byte in fcb_attr_or_count_mid
A1D3 STA (fs_options),y ; Store in FS options at Y=&0A
A1D5 INY ; Y=&0B
A1D6 LDA hazel_txcb_count ; Load new extent from fs_data_count
A1D9 STA hazel_fcb_addr_hi,x ; Update FCB extent in fcb_station_or_count_hi
A1DC STA (fs_options),y ; Store in FS options at Y=&0B
A1DE LDA #0 ; A=0: clear high byte of extent
A1E0 INY ; Y=&0C
A1E1 STA (fs_options),y ; Store zero in FS options at Y=&0C
A1E3 PLP ; Restore flags
A1E4 BCC return_success ; Carry clear: skip last-byte check
A1E6 LDA fs_last_byte_flag ; Load last-byte-of-transfer flag
A1E8 CMP #3 ; Is transfer still pending (flag=3)?
A1EA .return_success←1← A1E4 BCC
LDA #0 ; A=0: success
A1EC 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 &1030+X
Xchannel slot index
A1EF .lookup_cat_entry_0←2← A1BF JSR← A1FB JSR
LDY #0 ; Y=0: offset for channel handle
A1F1 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. (Pre-HAZEL ROMs read from &1030+X.)

On EntryAchannel handle
On ExitAFCB slot-attribute byte
Xchannel slot index
A1F3 .lookup_cat_slot_data←1← A1B2 JSR
JSR lookup_chan_by_char ; Look up channel by character
A1F6 LDA hazel_fcb_slot_attr,x ; Load slot-attribute byte from hazel_fcb_slot_attr,X
A1F9 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
A1FA .setup_transfer_workspace←2← A19D JSR← BD18 JMP
PHA ; Push operation code on stack
A1FB JSR lookup_cat_entry_0 ; Look up channel entry at Y=0
A1FE STA hazel_txcb_data ; Store flag byte in fs_cmd_data
A201 LDY #&0b ; Y=&0B: source offset in FS options
A203 LDX #6 ; X=6: copy 6 bytes
A205 .loop_copy_opts_to_buf←1← A211 BNE
LDA (fs_options),y ; Load FS options byte
A207 STA hazel_txcb_flag,x ; Store in fs_func_code buffer
A20A DEY ; Decrement source index
A20B CPY #8 ; Skip offset 8?
A20D BNE skip_struct_hole ; No: continue copy
A20F DEY ; Skip offset 8 (hole in structure)
A210 .skip_struct_hole←1← A20D BNE
DEX ; Decrement destination counter
A211 BNE loop_copy_opts_to_buf ; Loop until all 6 bytes copied
A213 PLA ; Pull operation code
A214 LSR ; Shift right: check bit 0 (direction)
A215 PHA ; Push updated code
A216 BCC store_direction_flag ; Carry clear: OSBGET (read)
A218 INX ; Carry set: OSBPUT (write), X=1
A219 .store_direction_flag←1← A216 BCC
STX hazel_txcb_flag ; Store direction flag in fs_func_code
A21C LDY #&0b ; Y=&0B: TX buffer size
A21E LDX #&91 ; X=&91: port for OSBGET
A220 PLA ; Pull operation code
A221 PHA ; Push back (keep on stack)
A222 BEQ store_port_and_send ; Zero (OSBGET): keep port &91
A224 LDX #&92 ; X=&92: port for OSBPUT
A226 DEY ; Y=&0A: adjusted buffer size
A227 .store_port_and_send←1← A222 BEQ
STX hazel_txcb_station ; Store port in fs_cmd_urd
A22A STX fs_error_ptr ; Store port in fs_error_ptr
A22C LDX #8 ; X=8: argument count
A22E LDA hazel_txcb_data ; Load file handle from fs_cmd_data
A231 JSR send_request_nowrite ; Send request (no write data)
A234 LDX #0 ; X=0: index
A236 LDA (fs_options,x) ; Load channel handle from FS options
A238 TAX ; Transfer to X as index
A239 LDA hazel_fcb_state_byte,x ; Load FCB flags from fcb_flags
A23C EOR #1 ; Toggle bit 0 (transfer direction)
A23E STA hazel_fcb_state_byte,x ; Store updated flags
A241 CLC ; Clear carry for addition
A242 LDX #4 ; X=4: process 4 address bytes
A244 .loop_setup_addr_bytes←1← A258 BNE
LDA (fs_options),y ; Load FS options address byte
A246 STA addr_work,y ; Store in zero page address area
A249 STA txcb_pos,y ; Store in TXCB position
A24C JSR advance_y_by_4 ; Advance Y by 4
A24F ADC (fs_options),y ; Add offset from FS options
A251 STA addr_work,y ; Store computed end address
A254 JSR retreat_y_by_3 ; Retreat Y by 3 for next pair
A257 DEX ; Decrement byte counter
A258 BNE loop_setup_addr_bytes ; Loop for all 4 address bytes
A25A INX ; X=1 (INX from 0)
A25B .loop_copy_offset←1← A262 BPL
LDA hazel_txcb_network,x ; Load offset from fs_cmd_csd
A25E STA hazel_txcb_flag,x ; Copy to fs_func_code
A261 DEX ; Decrement counter
A262 BPL loop_copy_offset ; Loop until both bytes copied
A264 PLA ; Pull operation code
A265 BNE send_with_swap ; Non-zero (OSBPUT): swap addresses
A267 LDA hazel_txcb_station ; Load port from fs_cmd_urd
A26A JSR check_and_setup_txcb ; Check and set up TXCB
A26D BCS recv_and_update ; Carry set: skip swap
A26F .send_with_swap←1← A265 BNE
JSR send_txcb_swap_addrs ; Send TXCB and swap start/end addresses
A272 .recv_and_update←1← A26D BCS
JSR recv_reply_preserve_flags ; Receive and process reply
A275 STX fs_load_addr_2 ; Store result in fs_load_addr_2
A277 JSR update_addr_from_offset9 ; Update addresses from offset 9
A27A DEC fs_load_addr_2 ; Decrement fs_load_addr_2
A27C SEC ; Set carry for subtraction
A27D JSR adjust_fsopts_4bytes ; Adjust FS options by 4 bytes
A280 ASL hazel_txcb_data ; Shift fs_cmd_data left (update status)
A283 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)
A284 .recv_reply_preserve_flags←1← A272 JSR
PHP ; Save flags before reply processing
A285 JSR recv_and_process_reply ; Process server reply
A288 PLP ; Restore flags after reply processing
A289 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.

A28A .send_osbput_data←1← A2BA BEQ
LDY #&15 ; Y=&15: TX buffer size for OSBPUT data
A28C JSR save_net_tx_cb ; Send TX control block
A28F LDA hazel_fs_flags ; Load display flag from hazel_fs_flags
A292 STA hazel_txcb_byte_16 ; Store in hazel_txcb_byte_16
A295 STX fs_load_addr ; Clear fs_load_addr (X=0)
A297 STX fs_load_addr_hi ; Clear fs_load_addr_hi
A299 LDA #&12 ; A=&12: byte count for data block
A29B STA fs_load_addr_2 ; Store in fs_load_addr_2
A29D BNE write_data_block ; ALWAYS branch to write data block

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 (&A16A in the OSWORD write path).

On EntryYignored (forced to 4)
A29F .write_block_entry←1← A16A JMP
LDY #4 ; Y=4: offset for station comparison
A2A1 LDA tube_present ; Load stored station from tube_present
A2A4 BEQ store_station_result ; Zero: skip station check
A2A6 CMP (fs_options),y ; Compare with FS options station
A2A8 BNE store_station_result ; Mismatch: skip subtraction
A2AA DEY ; Y=3
A2AB SBC (fs_options),y ; Subtract FS options value
A2AD .store_station_result←2← A2A4 BEQ← A2A8 BNE
STA svc_state ; Store result in svc_state
A2AF .loop_copy_opts_to_ws←1← A2B5 BNE
LDA (fs_options),y ; Load FS options byte at Y
A2B1 STA fs_last_byte_flag,y ; Store in workspace at fs_last_byte_flag+Y
A2B4 DEY ; Decrement index
A2B5 BNE loop_copy_opts_to_ws ; Loop until all bytes copied
A2B7 PLA ; Pull operation code
A2B8 AND #3 ; Mask to 2-bit sub-operation
A2BA BEQ send_osbput_data ; Zero: send OSBPUT data
A2BC LSR ; Shift right: check bit 0
A2BD BEQ handle_cat_update ; Zero (bit 0 clear): handle read
A2BF BCS update_cat_position ; Carry set: handle catalogue update
A2C1 .handle_cat_update←1← A2BD BEQ
TAY ; Transfer to Y (Y=0)
A2C2 LDA hazel_fs_context_copy,y ; Load data byte from fs_csd_handle
A2C5 STA hazel_txcb_network ; Store in fs_cmd_csd
A2C8 LDA hazel_fs_prefix_stn ; Load high data byte from fs_lib_handle
A2CB STA hazel_txcb_lib ; Store in fs_cmd_lib
A2CE LDA hazel_fs_saved_station ; Load port from fs_urd_handle
A2D1 STA hazel_txcb_station ; Store in fs_cmd_urd
A2D4 LDX #&12 ; X=&12: buffer size marker
A2D6 STX hazel_txcb_func_code ; Store in fs_cmd_y_param
A2D9 LDA #&0d ; A=&0D: count value
A2DB STA hazel_txcb_flag ; Store in fs_func_code
A2DE STA fs_load_addr_2 ; Store in fs_load_addr_2
A2E0 LSR ; Shift right (A=6)
A2E1 STA hazel_txcb_data ; Store in fs_cmd_data
A2E4 CLC ; Clear carry for addition
A2E5 JSR prep_send_tx_cb ; Prepare and send TX control block
A2E8 STX fs_load_addr_hi ; Store X in fs_load_addr_hi (X=0)
A2EA INX ; X=1 (after INX)
A2EB 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
A2ED .write_data_block←2← A29D BNE← A365 JSR
LDA svc_state ; Load svc_state (tube flag)
A2EF BNE tube_write_setup ; Non-zero: write via tube
A2F1 LDX fs_load_addr ; Load source index from fs_load_addr
A2F3 LDY fs_load_addr_hi ; Load destination index from fs_load_addr_hi
A2F5 .loop_copy_to_host←1← A2FE BNE
LDA hazel_txcb_data,x ; Load data byte from fs_cmd_data buffer
A2F8 STA (fs_crc_lo),y ; Store to destination via fs_crc pointer
A2FA INX ; Advance source index
A2FB INY ; Advance destination index
A2FC DEC fs_load_addr_2 ; Decrement byte counter
A2FE BNE loop_copy_to_host ; Loop until all bytes transferred
A300 BEQ tail_update_catalogue ; X=&10: scan 16 slots (15 to 0)
A302 .tube_write_setup←1← A2EF BNE
JSR tube_claim_c3 ; Clear V
A305 LDA #1 ; A=1: tube transfer type (write)
A307 LDX fs_options ; Load destination low from fs_options
A309 LDY fs_block_offset ; No match: try next
A30B INX ; Load slot status byte
A30C BNE set_tube_addr ; No wrap: skip high increment
A30E INY ; Test bit 2 (PS active flag)?
A30F .set_tube_addr←1← A30C BNE
JSR tube_addr_data_dispatch ; Set up tube transfer address
A312 LDX fs_load_addr ; Transfer Y to A
A314 .loop_write_to_tube←1← A322 BNE
LDA hazel_txcb_data,x ; Load data byte from buffer
A317 STA tube_data_register_3 ; Write to tube data register 3 Store Y to fs_urd_handle
A31A INX ; Advance source index
A31B LDY #6 ; Y=6: tube write delay
A31D .loop_tube_delay←1← A31E BNE
DEY ; Delay loop: decrement Y
A31E BNE loop_tube_delay ; Transfer Y to A
A320 DEC fs_load_addr_2 ; Decrement byte counter
A322 BNE loop_write_to_tube ; Store allocation result
A324 LDA #&83 ; A=&83: release tube claim
A326 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: &A300 (the success path) and &A38D (the no-change path). Never returns directly (clear_result loads A=0 and tail-falls into finalise_and_return).

A329 .tail_update_catalogue←2← A300 BEQ← A38D JMP
JMP clear_result ; Jump to clear A and finalise return
A32C .update_cat_position←1← A2BF BCS
LDY #9 ; Y=9: offset for position byte
A32E LDA (fs_options),y ; Try next slot
A330 STA hazel_txcb_flag ; Store in fs_func_code
A333 LDY #5 ; Y=5: offset for extent byte
A335 LDA (fs_options),y ; Load extent byte from FS options
A337 STA hazel_txcb_count ; Store in fs_data_count
A33A LDX #&0d ; X=&0D: byte count
A33C STX hazel_txcb_result ; Store in fs_reply_cmd
A33F LDY #2 ; Y=2: command sub-type
A341 STY fs_load_addr ; Set V (found match)
A343 STY hazel_txcb_data ; Store in fs_cmd_data Store Y to fs_csd_handle
A346 INY ; Y=3: TX buffer command byte
A347 JSR save_net_tx_cb ; V set: found, skip allocation
A34A STX fs_load_addr_hi ; Allocate FCB slot
A34C LDA hazel_txcb_flag ; Load data offset from fs_func_code
A34F STA (fs_options,x) ; Store as first byte of FS options
A351 LDA hazel_txcb_data ; Load data count from fs_cmd_data
A354 LDY #9 ; Y=9: position offset in FS options
A356 ADC (fs_options),y ; Add to current position
A358 STA (fs_options),y ; Store updated position
A35A LDA txcb_end ; Load TXCB end byte
A35C SBC #7 ; Subtract 7 (header overhead)
A35E STA hazel_txcb_flag ; Store remaining data size
A361 STA fs_load_addr_2 ; Store in fs_load_addr_2 (byte count)
A363 BEQ clear_buf_after_write ; Zero bytes: skip write
A365 JSR write_data_block ; Write data block to host/tube
A368 .clear_buf_after_write←1← A363 BEQ
LDX #2 ; X=2: clear 3 bytes (indices 0-2)
A36A .loop_clear_buf←1← A36E BPL
STA hazel_txcb_count,x ; Clear fs_data_count+X
A36D DEX ; Decrement index
A36E BPL loop_clear_buf ; Loop until all cleared
A370 JSR update_addr_from_offset1 ; Update addresses from offset 1
A373 SEC ; Set carry for subtraction
A374 DEC fs_load_addr_2 ; Decrement fs_load_addr_2
A376 LDA hazel_txcb_data ; Load data count from fs_cmd_data
A379 STA hazel_txcb_flag ; Copy to fs_func_code
A37C JSR adjust_fsopts_4bytes ; Adjust FS options by 4 bytes (subtract)
A37F LDX #3 ; X=3: check 4 bytes
A381 LDY #5 ; Y=5: starting offset
A383 SEC ; Set carry for comparison
A384 .loop_check_remaining←1← A38A BPL
LDA (fs_options),y ; Load FS options byte
A386 BNE done_write_block ; Non-zero: more data remaining
A388 INY ; Advance to next byte
A389 DEX ; Decrement counter
A38A BPL loop_check_remaining ; Loop until all bytes checked
A38C CLC ; All zero: clear carry (transfer complete)
A38D .done_write_block←1← A386 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)
A390 .tube_claim_c3←3← A302 JSR← A395 BCC← A627 JSR
LDA #&c3 ; A=&C3: tube claim protocol
A392 JSR tube_addr_data_dispatch ; Dispatch tube address/data claim
A395 BCC tube_claim_c3 ; Carry clear: claim failed, retry
A397 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
A398 .cmd_fs
LDA hazel_fs_station ; Read current FS station from workspace
A39B STA fs_work_5 ; Save in fs_work_5 (so 'no-arg' path can print it)
A39D LDA hazel_fs_network ; Read current FS network
A3A0 STA fs_work_6 ; Save in fs_work_6
A3A2 LDA (fs_crc_lo),y ; Look at the first command-line byte
A3A4 CMP #&0d ; Is it CR (no argument)?
A3A6 BEQ print_current_fs ; Yes: print the current FS address
A3A8 JSR parse_fs_ps_args ; Parse 'net.station' arg into fs_work_5/6
A3AB LDA #1 ; A=1: OSWORD &13 sub-function 1 = set file server station
A3AD STA fs_work_4 ; Store sub-function in PB[0]
A3AF LDA #&13 ; A=&13: OSWORD &13
A3B1 LDX #<(fs_work_4) ; X = lo of PB pointer (fs_work_4 = &B4)
A3B3 LDY #>(fs_work_4) ; Y = hi of PB pointer (=0, since fs_work_4 is in zero page)
A3B5 JMP osword ; Tail-jump into OSWORD; the OS routes us back through osword_13_set_station
A3B8 .print_current_fs←1← A3A6 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)
A3BB .print_fs_info_newline←1← B474 JSR
BIT always_set_v_byte ; Set V so print_station_addr suppresses the leading '0.' when the network number is zero
A3BE JSR print_station_addr ; Print the station/network address
A3C1 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)
A3C4 .parse_fs_ps_args←4← 9604 JSR← A3A8 JSR← B3CF JSR← B5A6 JSR
PHX ; Save caller's X (command-line offset cursor)
A3C5 LDA #0 ; A=0: clear the dot-seen flag for parse_addr_arg
A3C7 STA fs_work_4 ; Store cleared dot-seen flag
A3C9 JSR parse_addr_arg ; Parse first number (network or standalone station)
A3CC BCS skip_if_no_station ; C set: parse_addr_arg saw an empty argument -- skip station storage
A3CE STA fs_work_7 ; Save the network number in fs_work_7
A3D0 PHY ; Save Y (current command-line cursor) for after the bridge poll
A3D1 JSR init_bridge_poll ; Populate the bridge routing table -- returns local network number in A
A3D4 EOR fs_load_addr_2 ; EOR with parsed network: Z set iff parse matched local
A3D6 BEQ store_station_lo ; Match: keep A=0 to mark local network
A3D8 LDA fs_load_addr_2 ; Mismatch: A = parsed network number
A3DA .store_station_lo←1← A3D6 BEQ
STA fs_work_6 ; Store network number into fs_work_6 (the canonical form: 0=local, non-zero=remote)
A3DC PLY ; Restore Y
A3DD INY ; Step Y past the dot separator
A3DE JSR parse_addr_arg ; Parse station number after the dot
A3E1 .skip_if_no_station←1← A3CC BCS
BEQ no_station_loop ; C set: no station after dot -- leave fs_work_5 alone
A3E3 STA fs_work_5 ; Store parsed station in fs_work_5
A3E5 .no_station_loop←1← A3E1 BEQ
PLX ; Restore caller's X
A3E6 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)
A3E7 .get_pb_ptr_as_index←2← A405 JSR← A415 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)
A3E9 .byte_to_2bit_index←4← 8FAB JSR← A93D JSR← A956 JSR← B4C6 JSR
ASL ; Multiply A by 2
A3EA ASL ; Multiply A by 2 again -- A is now A_orig * 4
A3EB PHA ; Stash A_orig * 4 on the stack
A3EC ASL ; Multiply A by 2 -- A is now A_orig * 8 (C = bit 7 of A_orig*4)
A3ED TSX ; Capture S so we can read the just-pushed value
A3EE PHP ; Save the C flag from the third ASL
A3EF 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
A3F2 ROR ; Halve the result, putting the new C as bit 7
A3F3 PLP ; Restore the saved C (from the third ASL)
A3F4 ASL ; ASL doubles the halved value (effectively undoes the ROR's divide while reusing C)
A3F5 TAY ; Y = A_orig * 12 (the 12-byte-aligned index)
A3F6 PLA ; Recover A_orig * 4 (left on the stack at &A3EB)
A3F7 CMP #&48 ; Above &48 (i.e. A_orig * 4 >= 72, A_orig >= 18)?
A3F9 BCC return_from_2bit_index ; No: keep computed Y
A3FB LDY #0 ; Yes: clamp Y to 0 (out of range)
A3FD TYA ; Mirror Y -> A so callers can test Z
A3FE .return_from_2bit_index←1← A3F9 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
A3FF .net_1_read_handle
LDY #&6f ; Y=&6F: net_rx_ptr offset for the 'inline' handle byte
A401 LDA (net_rx_ptr),y ; Read handle byte directly from RX buffer
A403 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].

A405 .net_2_read_entry
JSR get_pb_ptr_as_index ; Convert PB pointer to workspace table offset
A408 BCS return_zero_uninit ; Out of range: return zero (uninitialised)
A40A LDA (nfs_workspace),y ; Read workspace handle byte
A40C CMP #&3f ; Slot marked '?' (uninitialised)?
A40E BNE store_pb_result ; Has a real handle: keep it and store
A410 .return_zero_uninit←1← A408 BCS
LDA #0 ; Force result to zero (uninitialised marker)
A412 .store_pb_result←2← A403 BCC← A40E BNE
STA osword_pb_ptr ; Write into PB[0] (handle return slot)
A414 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.

A415 .net_3_close_handle
JSR get_pb_ptr_as_index ; Convert PB pointer to workspace table offset
A418 BCC mark_ws_uninit ; Out of range: mark as uninitialised
A41A ROR fs_flags ; Shift bit 0 of fs_flags into C (save state)
A41D LDA osword_pb_ptr ; Read PB[0] (the handle to close)
A41F ROL ; Shift bit 7 of A into C
A420 ROL fs_flags ; Restore C into bit 0 of fs_flags
A423 RTS ; Return; the close action is dispatched elsewhere based on the saved C state
A424 .mark_ws_uninit←1← A418 BCC
ROR econet_flags ; Save bit 0 of econet_flags
A427 LDA #&3f ; A='?': uninitialised marker
A429 STA (nfs_workspace),y ; Write '?' to workspace[Y] (the slot is now free)
A42B ROL econet_flags ; Restore bit 0 of econet_flags
A42E RTS ; Return

FSCV reason 3: process * 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).

A42F .fscv_3_star_cmd←1← 8CFA JMP
JSR set_text_and_xfer_ptr ; Set text/transfer pointers from FS context
A432 LDY #&ff ; Y=&FF -- mark spool/Tube state inactive
A434 STY fs_spool_handle ; Store fs_spool_handle = &FF
A436 STY need_release_tube ; Store need_release_tube = &FF
A438 INY ; Y=&00
A439 LDX #&35 ; X=&35: NFS-commands sub-table offset
A43B JSR match_fs_cmd ; Match against the NFS sub-table
A43E 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).

A440 .cmd_fs_reentry←1← 8C4E JMP
BVC dispatch_fs_cmd ; V clear: re-enter dispatch_fs_cmd
A442 .error_syntax
LDA #&dc ; Error code &DC
A444 JSR error_inline ; Raise 'Syntax' error
A447 EQUS "Syntax."
A44E .dispatch_fs_cmd←2← A43E BCS← A440 BVC
LDA #0 ; A=0: clear svc_state
A450 STA svc_state ; Store -> svc_state
A452 LDA cmd_dispatch_hi_table,x ; Load dispatch hi byte from cmd_dispatch_hi_table+X
A455 PHA ; Push hi for RTS dispatch
A456 LDA cmd_dispatch_lo_table,x ; Load dispatch lo byte from cmd_dispatch_lo_table+X
A459 PHA ; Push lo for RTS dispatch
A45A 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
A45B .match_fs_cmd←3← 8C49 JSR← 8C77 JSR← A43B JSR
TYA ; Save command-line offset Y on stack
A45C PHA ; Push for save/restore
A45D .restart_table_scan←1← A483 BNE
PLA ; Reload saved Y (peek without popping)
A45E PHA ; Push it back to keep on stack
A45F TAY ; Y = saved command-line offset
A460 LDA cmd_table_fs,x ; First char of current entry name
A463 BMI check_char_type ; Bit 7 set already: end of table
A465 .loop_match_char←1← A472 BNE
LDA cmd_table_fs,x ; Next char from table
A468 BMI check_separator ; Bit 7 set: name fully matched
A46A EOR (fs_crc_lo),y
A46C AND #&df ; Mask off case bit (5)
A46E BNE skip_entry_chars ; Mismatch (after case mask): skip entry
A470 INY ; Advance command-line offset
A471 INX ; Advance table offset
A472 BNE loop_match_char ; ALWAYS branch: continue matching
A474 .skip_entry_chars←2← A46E BNE← A478 BPL
INX ; Skip remaining name chars
A475 LDA cmd_table_fs,x ; Load next table byte
A478 BPL skip_entry_chars ; Bit 7 clear: continue skipping
A47A LDA (fs_crc_lo),y ; Char on command line at current Y
A47C CMP #&2e ; Is it . (abbreviation)?
A47E BEQ skip_dot_and_spaces ; Yes: accept abbreviated match
A480 .loop_skip_to_next←1← A495 BNE
INX ; Skip 3-byte handler trailer (flag, lo, hi)
A481 INX ; (continued)
A482 INX ; (continued)
A483 BNE restart_table_scan ; ALWAYS branch: try next entry
A485 .check_separator←1← A468 BMI
TYA ; Save matched-name length on stack
A486 PHA ; Push for stack-based comparison
A487 LDA (fs_crc_lo),y ; Char on command line just past name
A489 LDY #9 ; Y=9: separator-table size - 1
A48B .loop_check_sep_table←1← A491 BPL
CMP sep_table_data,y ; Compare with separator
A48E BEQ separator_char_table ; Match: valid command boundary
A490 DEY ; Try next separator
A491 BPL loop_check_sep_table ; Loop through 10 separators
A493 PLA ; Restore matched-name length
A494 TAY ; A = matched offset, save in Y
A495 BNE loop_skip_to_next ; ALWAYS branch: try next entry
A497 .sep_table_data←1← A48B CMP
JSR separator_parse_dispatch ; Dispatch helper (sep_table_data path)
A49A BIT parse_separator_flag ; Check separator flag (zp_0026)
A49C ROL
A49D DEC
A49E RTI ; Effective unconditional jump
A49F EQUB &0D ; CR (carriage return)
A4A0 .separator_char_table←1← A48E BEQ
PLA ; Restore matched-name length
A4A1 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
A4A2 .loop_skip_trail_spaces←1← A4A9 JMP
LDA (fs_crc_lo),y ; Char on command line at current Y
A4A4 CMP #&20 ; Is it space?
A4A6 BNE check_cmd_flags ; No: check the entry's no-arg flag
A4A8 .skip_dot_and_spaces←1← A47E BEQ
INY ; Advance past the space (or .)
A4A9 JMP loop_skip_trail_spaces ; Loop: keep skipping
A4AC .check_cmd_flags←1← A4A6 BNE
LDA cmd_table_fs,x ; Load entry's flag byte (post-name)
A4AF ASL ; Shift bit 7 into C: the no-arg bit
A4B0 BPL clear_v_flag ; C=0: entry allows arguments
A4B2 LDA (fs_crc_lo),y ; Char on command line
A4B4 CMP #&0d ; Is it CR (no argument)?
A4B6 BNE clear_v_flag ; Argument present, V clear
A4B8 BIT always_set_v_byte ; Force V=1: entry validated as match
A4BB BVS clear_c_flag ; V set: skip the CLV
A4BD .clear_v_flag←2← A4B0 BPL← A4B6 BNE
CLV ; Clear V (no-arg flag not asserted)
A4BE .clear_c_flag←1← A4BB BVS
CLC ; Clear C (no error / no-arg path)
A4BF .return_with_result←1← A4DA BCS
PLA ; Discard saved Y on stack
A4C0 LDA (fs_crc_lo),y ; A = current command-line char
A4C2 RTS ; Return (Z=1 on match, C and V set per result)
A4C3 .loop_scan_past_word←1← A4D0 BNE
INY ; Advance command-line offset
A4C4 .check_char_type←1← A463 BMI
LDA (fs_crc_lo),y ; Char on command line
A4C6 CMP #&0d ; Is it CR (end of input)?
A4C8 BEQ set_c_and_return ; Yes: set C and return (no match)
A4CA CMP #&2e ; Is it .?
A4CC BEQ skip_sep_spaces ; Yes: skip separator spaces
A4CE CMP #&20 ; Is it space?
A4D0 BNE loop_scan_past_word ; No: keep scanning past word
A4D2 .skip_sep_spaces←2← A4CC BEQ← A4D7 BEQ
INY ; Advance past space
A4D3 LDA (fs_crc_lo),y ; Load next char
A4D5 CMP #&20 ; Still space?
A4D7 BEQ skip_sep_spaces ; Yes: keep skipping
A4D9 .set_c_and_return←1← A4C8 BEQ
SEC ; Set C: signal no-match return path
A4DA BCS return_with_result ; ALWAYS branch to common return
A4DC .check_urd_present
BIT fs_flags ; Test fs_flags bit 6
A4DF BVS fscv_2_star_run ; Bit 6 set: take fscv_2_star_run
A4E1 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.

A4E4 .fscv_2_star_run←1← A4DF BVS
JSR save_ptr_to_os_text ; Save text pointer (for GSREAD-driven parsing)
A4E7 JSR mask_owner_access ; Reset fs_lib_flags low bits to 5-bit access mask
A4EA ORA #2 ; Set bit 1 of A (mark *RUN-style invocation)
A4EC STA hazel_fs_lib_flags ; Update hazel_fs_lib_flags with the result
A4EF BNE cmd_run_load_mask ; ALWAYS branch

*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).

A4F1 .cmd_run_via_urd←1← 8E35 JMP
JSR save_ptr_to_os_text ; Save current OS text pointer
A4F4 JSR mask_owner_access ; Mask access bits
A4F7 AND #&fd ; Clear bit 1 of mask
A4F9 STA hazel_fs_lib_flags ; Save into fs_lib_flags
A4FC .cmd_run_load_mask←1← A4EF BNE
JSR parse_cmd_arg_y0 ; Begin parsing the *RUN argument
A4FF .open_file_for_run←1← A576 BNE
LDX #1 ; X=1: TX-buffer write index for argument
A501 JSR copy_arg_to_buf ; Copy argument to TX buffer
A504 LDA #2 ; A=2: open-input mode for OSFIND
A506 STA hazel_txcb_data ; Next byte down
A509 LDY #&12 ; Y=&12: cmd code for *RUN
A50B JSR save_net_tx_cb ; Send the request and wait for reply
A50E LDA hazel_txcb_data ; Read reply status from TX[5]
A511 CMP #1 ; Compare with 1 (not-found)
A513 BNE try_library_path ; Loop until all 6 restored
A515 LDX #3 ; Return from svc_8_osword
A517 .loop_check_handles←1← A520 BPL
INC hazel_txcb_size_hi,x ; Increment handle byte
A51A BEQ alloc_run_fcb ; Load handler address low byte
A51C JMP check_exec_addr ; Non-zero: handle valid, execute
A51F .alloc_run_fcb←1← A51A BEQ
DEX ; Decrement X (post-find adjustment)
A520 BPL loop_check_handles ; Loop while X >= 0 (scan all 4 handle slots)
A522 JSR alloc_fcb_or_error ; RTS dispatches to pushed handler
A525 LDX #1 ; X=1: target offset for the *RUN-channel command
A527 STX hazel_txcb_data ; Store X to hazel_txcb_data (cmd byte)
A52A STX hazel_txcb_flag ; Store X to hazel_txcb_flag (cmd flag)
A52D INX ; X=2
A52E JSR copy_arg_to_buf ; Copy filename arg into TX buffer
A531 LDY #6 ; Test station active flag
A533 JSR save_net_tx_cb ; Send re-open request
A536 BCS done_run_dispatch ; C set: error from save_net_tx_cb -- abort *RUN
A538 JMP alloc_run_channel ; Yes: handle clock read
A53B .done_run_dispatch←1← A536 BCS
JMP finalise_and_return ; Jump to finalise and return
A53E .try_library_path←1← A513 BNE
LDA hazel_parse_buf ; Return
A541 CMP #&24 ; Y=&10: length of TXCB to save
A543 BEQ error_bad_command ; Save current TX control block
A545 LDA hazel_fs_lib_flags ; Load library flag byte
A548 BMI library_tried ; Bit 7 set: library already tried
A54A ROL ; Shift bit 7 into carry
A54B ROL
A54C BMI restore_filename ; Store BCD seconds
A54E BCS error_bad_command ; Carry set: bad command
A550 LDX #&ff ; X=&FF -- start scan from end
A552 .loop_find_name_end←1← A558 BNE
INX ; Convert binary to BCD
A553 LDA hazel_parse_buf,x ; Load filename byte
A556 CMP #&0d ; Compare with CR (terminator)
A558 BNE loop_find_name_end ; Load hours from clock workspace
A55A .loop_shift_name_right←1← A561 BPL
LDA hazel_parse_buf,x ; Shift filename right by 8 bytes
A55D STA hazel_rtc_buffer,x ; Store shifted byte
A560 DEX ; Decrement scan index
A561 BPL loop_shift_name_right ; Clear hours high position
A563 LDX #7 ; Store zero
A565 .loop_copy_lib_prefix←1← A56C BPL
LDA library_dir_prefix,x ; Copy 'Library.' prefix
A568 STA hazel_parse_buf,x ; Store prefix byte
A56B DEX ; Decrement scan index
A56C BPL loop_copy_lib_prefix ; Loop until prefix copied
A56E LDA hazel_fs_lib_flags ; Load library flag
A571 ORA #&60 ; Mark byte as 'argument'
A573 STA hazel_fs_lib_flags ; Restore day+month byte
A576 .retry_with_library←1← A58D BNE
BNE open_file_for_run ; Retry file open with library path
A578 .restore_filename←1← A54C BMI
LDX #&ff ; X=&FF -- restart scan from end
A57A .loop_restore_name←1← A583 BNE
INX ; Store BCD month
A57B LDA hazel_rtc_buffer,x ; Load backup byte
A57E STA hazel_parse_buf,x ; Shift high nibble down
A581 EOR #&0d ; 4th shift: isolate high nibble
A583 BNE loop_restore_name ; No: continue restoring
A585 JSR mask_owner_access ; Set owner-only access mask
A588 ORA #&80 ; Mark caller's flags
A58A STA hazel_fs_lib_flags ; Copy 7 bytes (Y=6 down to 0)
A58D BNE retry_with_library ; ALWAYS branch
A58F .library_tried←1← A548 BMI
JSR mask_owner_access ; Store to parameter block
A592 LDA #2 ; Loop for all 7 bytes
A594 BIT hazel_fs_lib_flags ; Test hazel_fs_lib_flags bits 6 / 7
A597 BNE error_bad_command ; Either bit set: this is an invalid command path
A599 JSR finalise_and_return ; Otherwise finalise and return
A59C LDA #&0b ; A=&0B: FSCV reason 11 (filing-system change)
A59E 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.

A5A1 .error_bad_command←4← A4E1 JMP← A543 BEQ← A54E BCS← A597 BNE
LDA #&fe ; Error code &FE
A5A3 JSR error_bad_inline ; Raise 'Bad command' error
A5A6 EQUS "command."
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 (&A51C 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
A5AE .check_exec_addr←1← A51C JMP
LDX #3 ; X=3: check 4 execution bytes
A5B0 .loop_check_exec_bytes←1← A5B6 BNE
INC hazel_txcb_flag,x ; Increment execution address byte
A5B3 BNE library_path_string ; Low byte = &6F
A5B5 DEX ; Set osword_flag
A5B6 BNE loop_check_exec_bytes ; Loop until all checked
A5B8 LDA #&93 ; A=&93: error code 'Bad command'
A5BA JSR error_inline_log ; Generate 'No!' error
A5BD EQUS "Won't."
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 &A538 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)
A5C3 .alloc_run_channel←1← A538 JMP
LDA hazel_txcb_data ; Set workspace pointer high
A5C6 JSR alloc_fcb_slot ; Allocate FCB slot
A5C9 TAY ; A = parsed character
A5CA LDA #0 ; Y=OSWORD flag (slot specifier)
A5CC STA hazel_fcb_status,x ; Clear status in channel table
A5CF STY hazel_cur_dir_handle ; A=3: start searching from slot 3
A5D2 LDY #3 ; Y=3: skip past 3-byte FS header
A5D4 JMP boot_cmd_oscli ; C set: slot invalid, store result
A5D7 .library_dir_prefix←1← A565 LDA
EQUS "Library." ; Continue shift
A5DF .library_path_string←1← A5B3 BNE
JSR copy_arg_to_buf_x0 ; Copy parsed arg to TX buffer with X=0
A5E2 LDY #0 ; Y=0
A5E4 CLC ; For the loop entry
A5E5 JSR gsinit ; Transfer found slot to A
A5E8 .loop_read_gs_string←1← A5EB BCC
JSR gsread ; Store slot number to PB byte 0
A5EB BCC loop_read_gs_string ; Always (BCC after CLC) loop back
A5ED DEY ; C set: slot invalid, store result
A5EE .loop_skip_trailing←1← A5F3 BEQ
INY ; Advance Y past trailing space
A5EF LDA (os_text_ptr),y ; Y=Y-1: adjust workspace offset
A5F1 CMP #&20 ; Is it space?
A5F3 BEQ loop_skip_trailing ; Yes: skip it
A5F5 EOR #&0d ; Test for CR (terminator)
A5F7 CLC ; Clear C for arithmetic
A5F8 TYA ; Compare Y with OSWORD flag
A5F9 ADC os_text_ptr ; Add to text pointer low
A5FB STA fs_crc_lo ; Store low byte of (os_text_ptr + Y) -> fs_crc_lo (repurposed as a generic pointer)
A5FD LDA os_text_ptr_hi ; Load os_text_ptr_hi for the high-byte add
A5FF ADC #0 ; Add carry from low add (no extra increment)
A601 STA fs_crc_hi ; Store result high byte -> fs_crc_hi
A603 JSR save_text_ptr ; Save text pointer for later
A606 LDX #&c0 ; X=&C0: pointer-to-options high byte
A608 STX fs_block_offset ; Y=1: workspace flag offset
A60A LDA #&0e ; Store pending marker to workspace
A60C STA fs_options ; Store as fs_options
A60E STA hazel_retry_counter ; Increment retry counter
A611 LDX #&4a ; X=&4A: FS command table offset
A613 LDY #5 ; Store result A to PB via Y
A615 JSR do_fs_cmd_iteration ; Rotate Econet flags back (restore state)
A618 LDA tube_present ; Return from OSWORD 11 handler
A61B BEQ dispatch_via_vector ; Store to ws_ptr_lo
A61D AND hazel_txcb_tx_status ; Y=&7F: last byte of RX buffer
A620 AND hazel_txcb_osword_flag
A623 CMP #&ff ; All &FF?
A625 BEQ dispatch_via_vector ; X-1: adjust count
A627 JSR tube_claim_c3 ; Claim tube for data transfer
A62A LDX #9 ; X=9: parameter count
A62C LDY #&c1 ; Y=&C1: high byte of TX buffer pointer
A62E LDA #4 ; A=4: option byte for *RUN
A630 JMP tube_addr_data_dispatch ; Relocated execute path
A633 .dispatch_via_vector←2← A61B BEQ← A625 BEQ
LDA #1 ; A=1: dispatch flag
A635 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 &9594).

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

A63E .fsreply_5_set_lib
JSR flip_set_station_boot ; Record library station in station table
A641 JMP return_with_last_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
A644 .find_station_bit2←1← A6E9 JSR
LDX #&10 ; X=&10: scan 16 entries
A646 CLV ; Clear V (no-match marker)
A647 .loop_search_stn_bit2←2← A64D BNE← A654 BEQ
DEX ; Step to previous entry
A648 BMI done_search_bit2 ; Below 0: scan complete
A64A JSR match_station_net ; Compare entry X's stn/net with caller's
A64D BNE loop_search_stn_bit2 ; No match: continue
A64F LDA hazel_fcb_status,x ; Match: read entry's flag byte at hazel_fcb_status+X
A652 AND #4 ; Mask bit 2
A654 BEQ loop_search_stn_bit2 ; Bit 2 clear: keep scanning
A656 TYA ; Bit 2 set: A = matched entry index (Y)
A657 STA hazel_fcb_slot_attr,x ; Store Y at hazel_fcb_slot_attr+X (link entry to slot)
A65A BIT always_set_v_byte ; BIT always_set_v_byte: V <- 1 (match found)
A65D .done_search_bit2←1← A648 BMI
STY hazel_fs_saved_station ; Save Y at hazel_fs_saved_station (matched entry index)
A660 BVS set_flags_bit2 ; V set: skip new-slot alloc
A662 TYA ; A = caller's index
A663 JSR alloc_fcb_slot ; Allocate a fresh FCB slot
A666 STA hazel_fcb_slot_1 ; Save FCB slot index at hazel_fcb_slot_1
A669 BEQ jmp_restore_fs_ctx ; Z set: alloc failed -> restore FS context
A66B .set_flags_bit2←1← A660 BVS
LDA #&26 ; A=&26: workspace flag for bit 2 search
A66D BNE store_stn_flags_restore ; ALWAYS branch

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
A66F .find_station_bit3←3← A638 JSR← A6A1 JSR← A6EF JSR
LDX #&10 ; X=&10: scan 16 entries
A671 CLV ; Clear V (no-match marker)
A672 .loop_search_stn_bit3←2← A678 BNE← A67F BEQ
DEX ; Step to previous entry
A673 BMI done_search_bit3 ; Below 0: scan complete
A675 JSR match_station_net ; Compare entry's stn/net with caller's
A678 BNE loop_search_stn_bit3 ; No match: continue
A67A LDA hazel_fcb_status,x ; Match: read entry's flag byte at hazel_fcb_status+X
A67D AND #8 ; Mask bit 3
A67F BEQ loop_search_stn_bit3 ; Bit 3 clear: keep scanning
A681 TYA ; Bit 3 set: A = matched entry index (Y)
A682 STA hazel_fcb_slot_attr,x ; Store Y at hazel_fcb_slot_attr+X (link entry to slot)
A685 BIT always_set_v_byte ; BIT always_set_v_byte: V <- 1 (match found)
A688 .done_search_bit3←1← A673 BMI
STY hazel_fs_context_copy ; Save Y at hazel_fs_context_copy (matched entry index)
A68B BVS set_flags_bit3 ; V set: skip new-slot alloc
A68D TYA ; A = caller's index
A68E JSR alloc_fcb_slot ; Allocate a fresh FCB slot
A691 STA hazel_fcb_slot_2 ; Save FCB slot index at hazel_fcb_slot_2
A694 BEQ jmp_restore_fs_ctx ; Z set: alloc failed -> restore FS context
A696 .set_flags_bit3←1← A68B BVS
LDA #&2a ; A=&2A: workspace flag for bit 3 search
A698 BNE store_stn_flags_restore ; ALWAYS branch

*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
A69A .cmd_flip
LDA hazel_fs_context_copy ; Load current CSD handle
A69D PHA ; Save CSD handle
A69E LDY hazel_fs_prefix_stn ; Load library handle into Y
A6A1 JSR find_station_bit3 ; Install library as new CSD
A6A4 PLA ; Restore original CSD handle
A6A5 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
A6A6 .flip_set_station_boot←2← A63E JSR← A6F5 JSR
LDX #&10 ; X=&10: max 16 station entries
A6A8 CLV ; Clear V (no match found yet)
A6A9 .loop_search_stn_boot←2← A6AF BNE← A6B6 BEQ
DEX ; Decrement station index
A6AA BMI done_search_boot ; All searched: exit loop
A6AC JSR match_station_net ; Check if station[X] matches
A6AF BNE loop_search_stn_boot ; No match: try next station
A6B1 LDA hazel_fcb_status,x ; Load station flags byte
A6B4 AND #&10 ; Test bit 4 (active flag)
A6B6 BEQ loop_search_stn_boot ; Not active: try next station
A6B8 TYA ; Transfer boot type to A
A6B9 STA hazel_fcb_slot_attr,x ; Store boot setting for station
A6BC BIT always_set_v_byte ; Set V flag (station match found)
A6BF .done_search_boot←1← A6AA BMI
STY hazel_fs_prefix_stn ; Store boot type
A6C2 BVS set_flags_boot ; V set (matched): skip allocation
A6C4 TYA ; Boot type to A
A6C5 JSR alloc_fcb_slot ; Allocate FCB slot for new entry
A6C8 STA hazel_fcb_slot_3 ; Store allocation result
A6CB BEQ jmp_restore_fs_ctx ; Zero: allocation failed, exit
A6CD .set_flags_boot←1← A6C2 BVS
LDA #&32 ; A=&32: station flags (active+boot)
A6CF .store_stn_flags_restore←2← A66D BNE← A698 BNE
STA hazel_fcb_status,x ; Store station flags
A6D2 .jmp_restore_fs_ctx←3← A669 BEQ← A694 BEQ← A6CB BEQ
JMP restore_fs_context ; Restore FS context and return

FS reply 1: copy boot handles + flag boot pending

Closes all network channels via close_all_net_chans, sets bit 6 of fs_flags (TSB &0D6C, marking the boot-pending state), then loads the boot type from the FS reply at hazel_txcb_result and stores it into both the current-boot-type slot (hazel_fs_flags) and the FCB-flags table. Pushes the boot type for the fall-through into fsreply_2_copy_handles which copies the per-handle table.

A6D5 .fsreply_1_boot
JSR close_all_net_chans ; Close all network channels
A6D8 LDA #&40 ; A=&40: protection-level marker
A6DA TSB fs_flags
A6DD SEC ; Mark this path as 'success' for the caller
A6DE LDA hazel_txcb_result ; Load TX result code from hazel_txcb_result
A6E1 STA hazel_fs_flags ; Store as hazel_fs_flags
A6E4 PHA ; Save state
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 fsreply_2_handle_loop, which OSCLIs -NET-FindLib, optionally calls OSBYTE &6D to make the filing system permanent, clears the auto-boot flag in hazel_fs_lib_flags, and (unless CTRL is held) falls through to boot_suffix_string to execute the !Boot command. Reached only via the FS reply dispatch table.

On EntryAboot-type byte (saved on stack at entry)
CARRYset when boot processing should follow
A6E5 .fsreply_2_copy_handles
PHP ; Save processor status
A6E6 LDY hazel_txcb_data ; Load station number from reply
A6E9 JSR find_station_bit2 ; Find station entry with bit 2
A6EC LDY hazel_txcb_flag ; Load network number from reply
A6EF JSR find_station_bit3 ; Find station entry with bit 3
A6F2 LDY hazel_txcb_count ; Load boot type from reply
A6F5 JSR flip_set_station_boot ; Set boot config for station
A6F8 PLP ; Restore processor status
A6F9 BCS fsreply_2_handle_loop ; Carry set: proceed with boot
A6FB JMP return_with_last_flag ; Return with last flag
A6FE .fsreply_2_skip_handles
EQUS "-NET-FindLib"
A70A EQUB &0D
A70B .fsreply_2_handle_loop←1← A6F9 BCS
LDX #&11 ; X=&11: CMOS RAM byte index
A70D JSR osbyte_a1 ; Read CMOS &11 via osbyte_a1
A710 TYA ; Result to A
A711 AND #2 ; Mask bit 1 (auto-CLI flag)
A713 BEQ fsreply_2_store_handle ; Bit clear: skip auto-CLI
A715 LDX #<(fsreply_2_skip_handles) ; X = lo of fsreply_2_skip_handles (boot-cmd string ptr)
A717 LDY #>(fsreply_2_skip_handles) ; Y = hi of fsreply_2_skip_handles
A719 JSR oscli ; OSCLI to execute boot command
A71C .fsreply_2_store_handle←1← A713 BEQ
PLA ; Pop saved A
A71D CMP #2 ; Compare with 2
A71F BCC check_auto_boot_flag ; Below: skip making FS permanent
A721 LDA #osbyte_make_temporary_filing_system_permanent ; OSBYTE &6D: make filing system perman ent
A723 JSR osbyte ; Master: Make temporary filing system permanent
A726 .check_auto_boot_flag←1← A71F BCC
LDA hazel_fs_lib_flags ; Load config flags
A729 TAX ; Save copy in X
A72A AND #4 ; Test bit 2 (auto-boot flag)
A72C PHP ; Save bit 2 test result
A72D TXA ; Restore full flags
A72E AND #&fb ; Clear bit 2 (consume flag)
A730 STA hazel_fs_lib_flags ; Store cleared flags
A733 PLP ; Restore bit 2 test result
A734 BNE boot_suffix_string ; Bit 2 was set: skip to boot cmd
A736 LDA #osbyte_scan_keyboard ; OSBYTE &79: scan keyboard
A738 LDX #(255 - inkey_key_ctrl) EOR 128 ; X=internal key number EOR 128
A73A JSR osbyte ; Test for 'CTRL' key pressed (X=129)
A73D TXA ; X has top bit set if 'CTRL' pressed
A73E BPL boot_suffix_string ; CTRL not pressed: proceed to boot
A740 .boot_load_cmd←1← A762 BEQ
RTS ; CTRL pressed: cancel boot, return
A741 EQUS "L.-NET-!Boot" ; Boot command 'L.-NET-!Boot' (Load !Boot)
A74D EQUB &0D ; CR terminator
A74E EQUS "E.-NET-!Boot" ; Boot command 'E.-NET-!Boot' (Exec !Boot)
A75A EQUB &0D ; CR terminator
A75B .boot_prefix_string←1← A764 LDX
EQUS "ZAHN" ; Boot command low-byte index table -- 4 bytes spelling 'ZAHN', each the low byte of a boot-command address in the &A7xx page (Z=&5A, A=&41, H=&48, N=&4E)
A75F .boot_suffix_string←2← A734 BNE← A73E BPL
LDY hazel_fs_flags ; Read hazel_fs_flags (boot-state flag)
A762 BEQ boot_load_cmd ; Z: take boot_load_cmd path
fall through ↓

Look up boot command in boot_prefix_string table and OSCLI it

Loads X = boot_prefix_string,Y (the low byte of the boot-command address), sets Y=&A7 (high byte = &A7xx area where the boot strings live), then JMPs to oscli with (X,Y) pointing at a CR-terminated command string. Single caller (&A5D4 in the RUN-then- boot dispatch).

On EntryYboot-command index
A764 .boot_cmd_oscli←1← A5D4 JMP
LDX boot_prefix_string,y ; Load boot-command low byte from boot_prefix_string[Y]
A767 LDY #&a7 ; Y=&A7: high byte (boot strings live in &A7xx)
A769 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.

A76C .cmd_table_fs←11← 8BD8 LDA← 8BE7 LDA← 8BEF LDA← 8BFC LDA← 9465 LDA← 946D LDA← 95EE LDA← A460 LDA← A465 LDA← A475 LDA← A4AC LDA
EQUS "Net" ; Econet HW check + select NFS
A76F EQUB &80 ; no syn
A770 EQUW cmd_net_check_hw-1
A772 EQUS "Pollps" ; syn 8: (<stn. id.>|)
A778 EQUB &88 ; syn &8
A779 EQUW cmd_pollps-1
A77B EQUS "Prot" ; toggle CMOS protection bit
A77F EQUB &80 ; no syn
A780 EQUW cmd_prot-1
A782 EQUS "PS" ; syn 8: (<stn. id.>|)
A784 EQUB &88 ; syn &8
A785 EQUW cmd_ps-1
A787 EQUS "Roff" ; printer offline
A78B EQUB &80 ; no syn
A78C EQUW cmd_roff-1
A78E EQUS "Unprot" ; toggle CMOS protection bit
A794 EQUB &80 ; no syn
A795 EQUW cmd_unprot-1
A797 EQUS "Wdump" ; syn 4 -- *DUMP alias
A79C EQUB &C4 ; syn &4, V if no arg
A79D EQUW cmd_dump-1
A79F EQUB &80 ; Sub-table 1 end (walker reads &80 -> stop)
A7A0 EQUB &80 ; Padding (alignment before sub-table 2)
A7A1 .cmd_table_nfs
EQUS "Access" ; syn 9: (L)(W)(R)... *Access
A7A7 EQUB &C9 ; syn &9, V if no arg V no arg; syn 9: obj> (L)(W)(R)...
A7A8 EQUW cmd_fs_operation-1
A7AA EQUS "Bye" ; log off FS
A7AD EQUB &80 ; no syn
A7AE EQUW cmd_bye-1
A7B0 EQUS "Cdir" ; syn 6 -- create directory
A7B4 EQUB &C6 ; syn &6, V if no arg
A7B5 EQUW cmd_cdir-1
A7B7 EQUS "Dir" ; syn 1: ()
A7BA EQUB &81 ; syn &1
A7BB EQUW cmd_dir-1
A7BD EQUS "Flip" ; swap fs/private workspace
A7C1 EQUB &80 ; no syn
A7C2 EQUW cmd_flip-1
A7C4 EQUS "FS" ; syn &B -- file-server selection
A7C6 EQUB &8B ; syn &B
A7C7 EQUW cmd_fs-1
A7C9 .cmd_table_nfs_iam←1← 8DC8 LDA
EQUS "I am" ; syn 2: () ...
A7CD EQUB &C2 ; syn &2, V if no arg
A7CE EQUW cmd_iam_save_ctx-1
A7D0 EQUS "Lcat" ; syn 1: () -- *CAT of library
A7D4 EQUB &81 ; syn &1
A7D5 EQUW cmd_lcat-1
A7D7 EQUS "Lex" ; syn 1: () -- *EX of library
A7DA EQUB &81 ; syn &1
A7DB EQUW cmd_lex-1
A7DD EQUS "Lib" ; syn 5: -- set library
A7E0 EQUB &C5 ; syn &5, V if no arg
A7E1 EQUW cmd_fs_operation-1
A7E3 EQUS "Pass" ; syn 7: ...
A7E7 EQUB &C7 ; syn &7, V if no arg
A7E8 EQUW cmd_pass-1
A7EA EQUS "Rename" ; syn &A:
A7F0 EQUB &CA ; syn &A, V if no arg
A7F1 EQUW cmd_rename-1
A7F3 EQUS "Wipe" ; syn 1: () -- delete with confirm
A7F7 EQUB &81 ; syn &1
A7F8 EQUW cmd_wipe-1
A7FA EQUB &80 ; Sub-table 2 end (walker reads &80 -> stop)
A7FB EQUB &2C ; Padding -- &2C 8E happens to spell &8E2D = check_urd_prefix but is never read
A7FC EQUB &8E ; Padding (continued)
A7FD .cmd_table_help_topics
EQUS "Net" ; *HELP NET
A800 EQUB &80 ; no syn
A801 EQUW help_net-1
A803 EQUS "Utils" ; *HELP UTILS
A808 EQUB &80 ; no syn
A809 EQUW help_utils-1
A80B EQUB &80 ; Sub-table 3 end (walker reads &80 -> stop)
A80C .cmd_table_syntax_help
EQUS "FS" ; FS not selected
A80E EQUB &C1 ; syn &1, V if no arg
A80F EQUW set_fs_or_ps_cmos_station-1
A811 EQUS "PS" ; PS not selected
A813 EQUB &C3 ; syn &3, V if no arg
A814 EQUW set_fs_or_ps_cmos_station-1
A816 EQUS "NoSpace" ; caller &9623
A81D EQUB &80 ; no syn
A81E EQUW &9622
A820 EQUS "Space" ; caller &9619
A825 EQUB &80 ; no syn
A826 EQUW &9618
A828 EQUB &80 ; Sub-tables 4/5 separator
A829 EQUS "FS" ; caller &9670
A82B EQUB &81 ; syn &1
A82C EQUW print_fs_address-1
A82E EQUS "PS" ; caller &965F
A830 EQUB &83 ; syn &3
A831 EQUW print_ps_address-1
A833 EQUS "Space" ; caller &9641
A838 EQUB &80 ; no syn
A839 EQUW &9641

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
A83B .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
A83D EQUB &A5, &EF, ; OSWORD setup state (13 bytes -- constants &E9, &0D, ; and offsets used by svc_8_osword) A = &30, &2D, ; OSWORD - &0E (CLC+SBC = -&0E) Below &0E: &C9, &07, ; not ours, return Index >= 7? (OSWORD > &B0, &29, ; &14) Above &14: not ours, return X=OSWORD &AA, &A0, ; handler index (0-6) Y=6: save 6 workspace &06 ; bytes
A84A .loop_save_osword_workspace←1← A855 BNE
LDA svc_state,y ; Read svc_state[Y] (frame slot)
A84D PHA ; Save on stack
A84E LDA tx_imm_idx_base,y ; Load OSWORD parameter byte
A851 STA svc_state,y ; Copy parameter to workspace
A854 DEY ; Next slot
A855 .osword_store_svc_state←1← A83B BRA
BNE loop_save_osword_workspace ; Loop until Y wraps
A857 JSR osword_setup_handler ; Set up dispatch and save state
A85A LDY #&fa ; Y=&FA: restore 6 workspace bytes
A85C .loop_restore_osword_workspace←1← A861 BNE
PLA ; Restore saved workspace byte
A85D STA nmi_buf_idx_base,y ; Store to osword_flag workspace
A860 INY ; Next byte
A861 BNE loop_restore_osword_workspace ; Loop until all 6 restored
A863 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)
A864 .osword_setup_handler←1← A857 JSR
LDA osword_subcode_dispatch,x ; X = OSWORD index (0-6)
A867 PHA ; Push for stack frame manipulation
A868 LDA osword_pb_ready,x ; Load handler address low byte
A86B PHA ; Push again
A86C LDA (ws_ptr_hi),y ; Copy 3 bytes (Y=2,1,0)
A86E STY svc_state ; Load from osword_flag workspace
A870 .return_from_osword_setup
RTS ; RTS dispatches to pushed handler
A871 .osword_pb_ready←1← A868 LDA
EQUB &7E, ; PB-ready / parameter table (3 bytes) read by &6F, ; osword_setup_handler at &A868 via LDA &0F ; osword_pb_ready,X
A874 .osword_0e_handler
BIT suffix_copy_loop ; BIT $abs -- 3-byte skip-trick that jumps over the extract_osword_subcode prologue when called via &A874
fall through ↓

Decode OSWORD &0E parameter byte and branch to handler

Right-shifts ws_page (PB[0]) into A, transfers it to Y for the dispatcher, then runs an EOR/CMP chain against ws_precomputed_value to classify the requested sub-code:

Test Path
CMP #4 = save_txcb_and_convert (clock)
CMP #3 = save_txcb_done (write back)
anything else set svc_state = 8 and RTS

The two LDA #&A9 filler bytes preceding the EOR are a 4-byte BIT-trick skip used when the alternate entry osword_0e_handler is taken via the BIT $abs at &A874. Reached only via the OSWORD &0E (CMOS clock read) handler chain.

A877 .extract_osword_subcode
LSR ws_page ; Shift ws_page right -- splits parameter byte into upper / lower nibbles
A879 TAY ; A = sub-code
A87A LDA #&a9 ; LDA #&A9 -- 2-byte BIT-trick filler (skipped when entered at &A87E)
A87C LDA #&a9 ; LDA #&A9 -- 2-byte BIT-trick filler
A87E LDY ws_template_source ; Load template source pointer
A881 EOR ws_precomputed_value
A884 CMP #4 ; Compare with &04
A886 BEQ save_txcb_and_convert ; Equal: take save_txcb_and_convert path
A888 CMP #3 ; Restore A (OSWORD sub-code)
A88A BEQ save_txcb_done ; Equal: take save_txcb_done path
A88C LDA #8 ; Other sub-codes: set state = 8
A88E STA svc_state ; Store service state
A890 .return_from_osword_0e
RTS ; Return
A891 .save_txcb_and_convert←2← A886 BEQ← A8E7 JSR
LDX #0 ; X=0: start of TX control block
A893 LDY #&10 ; Y=&10: length of TXCB to save
A895 JSR save_net_tx_cb ; Save current TX control block
A898 LDA hazel_exec_addr ; Load seconds from clock workspace
A89B JSR bin_to_bcd ; Convert binary to BCD
A89E STA hazel_txcb_tx_status ; Store BCD seconds
A8A1 LDA hazel_txcb_result ; Load minutes from clock workspace
A8A4 JSR bin_to_bcd ; Convert binary to BCD
A8A7 STA hazel_txcb_size_hi ; Store BCD minutes
A8AA LDA hazel_txcb_count ; Load hours from clock workspace
A8AD JSR bin_to_bcd ; Convert binary to BCD
A8B0 STA hazel_exec_addr ; Store BCD hours
A8B3 LDA #0 ; Clear hours high position
A8B5 STA hazel_txcb_result ; Store zero
A8B8 LDA hazel_txcb_flag ; Load day+month byte
A8BB PHA ; Save for later high nibble extract
A8BC LDA hazel_txcb_data ; Load day value
A8BF JSR bin_to_bcd ; Convert day to BCD
A8C2 STA hazel_txcb_count ; Store BCD day
A8C5 PLA ; Restore day+month byte
A8C6 PHA ; Push current A
A8C7 AND #&0f ; Mask low nibble (month low bits)
A8C9 JSR bin_to_bcd ; Convert to BCD
A8CC STA hazel_txcb_flag ; Store BCD month
A8CF PLA ; Pop saved value
A8D0 LSR ; Shift high nibble down
A8D1 LSR ; Divide by 4
A8D2 LSR ; (continued)
A8D3 LSR ; 4th shift: isolate high nibble
A8D4 ADC #&51 ; Add &51 (offset base)
A8D6 JSR bin_to_bcd ; Convert year to BCD
A8D9 STA hazel_txcb_data ; Store BCD year
A8DC LDY #6 ; Copy 7 bytes (Y=6 down to 0)
A8DE .loop_copy_bcd_to_pb←1← A8E4 BPL
LDA hazel_txcb_data,y ; Load BCD byte from workspace
A8E1 STA (ws_ptr_hi),y ; Store to parameter block
A8E3 DEY ; Next byte down
A8E4 BPL loop_copy_bcd_to_pb ; Loop for all 7 bytes
A8E6 RTS ; Return
A8E7 .save_txcb_done←1← A88A BEQ
JSR save_txcb_and_convert ; Convert TXCB date/time bytes to BCD
A8EA LDY #7 ; Y=7: copy 8 bytes (Y=7 down to 0)
A8EC .loop_copy_pbytes_to_workspace←1← A8F2 BNE
LDA hazel_txcb_lib,y ; Load BCD byte from TXCB area (hazel_txcb_lib + Y)
A8EF STA (ws_ptr_hi),y ; Store to PB[Y]
A8F1 DEY ; Decrement Y (advance backwards)
A8F2 BNE loop_copy_pbytes_to_workspace ; Loop until Y wraps
A8F4 LDA #2 ; A=2: PB[0] parameter for OSWORD &0E (seconds-since-midnight format)
A8F6 STA (ws_ptr_hi),y ; Store parameter at PB[0]
A8F8 LDA #osword_read_cmos_clock ; A=&0E: OSWORD &0E (read CMOS RTC)
A8FA LDX ws_ptr_hi ; X = PB pointer low
A8FC LDY table_idx ; Y = PB pointer high (via table_idx scratch)
A8FE JMP osword ; Read CMOS clock

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
A901 .bin_to_bcd←6← A89B JSR← A8A4 JSR← A8AD JSR← A8BF JSR← A8C9 JSR← A8D6 JSR
PHP ; Save caller flags (D may be in any state)
A902 TAX ; Save A across decimal-mode arithmetic
A903 BEQ done_bcd_convert ; Zero: result is 0, skip loop
A905 SED ; Enter decimal mode
A906 LDA #0 ; Start BCD result at 0
A908 .loop_bcd_add←1← A90C BNE
CLC ; Clear carry for BCD add
A909 ADC #1 ; Add 1 in decimal mode
A90B DEX ; Count down binary value
A90C BNE loop_bcd_add ; Loop until zero
A90E .done_bcd_convert←1← A903 BEQ
PLP ; Restore caller flags (incl. D)
A90F 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)
A910 .osword_10_handler
ASL tx_complete_flag ; ASL tx_complete_flag: old bit 7 -> C
A913 TYA ; A = Y (saved index)
A914 BCS setup_ws_rx_ptrs ; C=1 (TX idle): start new transmission
A916 STA (ws_ptr_hi),y ; C=0 (TX busy): write status byte back to PB
A918 RTS ; Return (TX still in progress)
A919 .setup_ws_rx_ptrs←1← A914 BCS
LDA net_rx_ptr_hi ; Read net_rx_ptr_hi
A91B STA ws_ptr_lo ; Copy to ws_ptr_lo
A91D STA nmi_tx_block_hi ; Also set as NMI TX block high
A91F LDA #&6f ; Low byte = &6F
A921 STA osword_flag ; Set osword_flag
A923 STA nmi_tx_block ; Set NMI TX block low
A925 LDX #&0f ; X=&0F: byte count for copy
A927 JSR copy_pb_byte_to_ws ; Copy data and begin transmission
A92A JMP tx_begin ; 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)
A92D .osword_11_handler
LDX nfs_workspace_hi ; Load NFS workspace page high byte
A92F STX ws_ptr_lo ; Set workspace pointer high
A931 STY osword_flag ; Set workspace pointer low from Y
A933 ROR econet_flags ; Rotate Econet flags (save interrupt state)
A936 TAY ; Y=OSWORD flag (slot specifier)
A937 STA work_ae ; Store OSWORD flag
A939 BNE use_specified_slot ; Non-zero: use specified slot
A93B LDA #3 ; A=3: start searching from slot 3
A93D .loop_find_rx_slot←1← A94F BNE
JSR byte_to_2bit_index ; Convert slot to 2-bit workspace index
A940 BCS store_rx_result ; C set: slot invalid, store result
A942 LSR ; Divide by 2
A943 LSR ; Continue shift
A944 TAX ; Index to X
A945 LDA (osword_flag),y ; Load workspace byte at offset
A947 BEQ store_rx_result ; Zero: slot empty, store result
A949 CMP #&3f ; Compare with &3F ('?' marker)
A94B BEQ store_rx_slot_found ; Match: slot found for receive
A94D INX ; Step to next slot
A94E TXA ; Transfer back to A
A94F BNE loop_find_rx_slot ; Loop back (A != 0)
A951 .store_rx_slot_found←1← A94B BEQ
TXA ; Found slot index
A952 LDX #0 ; X=0: index for indirect store
A954 STA (ws_ptr_hi,x) ; Store slot number to PB byte 0
A956 .use_specified_slot←1← A939 BNE
JSR byte_to_2bit_index ; Convert specified slot to workspace index
A959 BCS store_rx_result ; C set: slot invalid, store result
A95B DEY ; Back up scan
A95C STY osword_flag ; Update workspace pointer low
A95E LDA #&c0 ; A=&C0: slot active marker
A960 LDY #1 ; Y=1: result-byte offset
A962 LDX #&0b ; X=&0B: byte count for PB copy
A964 CPY work_ae ; Compare Y with OSWORD flag
A966 ADC (osword_flag),y ; Add workspace byte (check slot state)
A968 BEQ copy_pb_and_mark ; Zero: slot ready, copy PB and mark
A96A BMI increment_and_retry ; Negative: slot busy, increment and retry
A96C .loop_copy_slot_data←1← A97C BNE
CLC ; For the ADC chain
A96D .copy_pb_and_mark←1← A968 BEQ
JSR copy_pb_byte_to_ws ; Copy PB byte to workspace slot
A970 BCS osword_11_done ; C set: copy done, finish
A972 LDA #&3f ; A=&3F: mark slot as pending ('?')
A974 LDY #1 ; Y=1: workspace flag offset
A976 STA (osword_flag),y ; Store pending marker to workspace
A978 BNE osword_11_done ; ALWAYS branch
A97A .increment_and_retry←1← A96A BMI
ADC #1 ; Increment retry counter
A97C BNE loop_copy_slot_data ; Non-zero: retry copy loop
A97E DEY ; Decrement Y (adjust offset)
A97F .store_rx_result←3← A940 BCS← A947 BEQ← A959 BCS
STA (ws_ptr_hi),y ; Store result A to PB via Y
A981 .osword_11_done←2← A970 BCS← A978 BNE
ROL econet_flags ; Rotate Econet flags back (restore state)
A984 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)
A985 .osword_12_handler
LDA net_rx_ptr_hi ; Set workspace from RX ptr high
A987 STA ws_ptr_lo ; Store to ws_ptr_lo
A989 LDY #&7f ; Y=&7F: last byte of RX buffer
A98B LDA (net_rx_ptr),y ; Load port/count from RX buffer
A98D INY ; Y=&80: set workspace pointer
A98E STY osword_flag ; Store as osword_flag
A990 TAX ; X = port/count value
A991 DEX ; X-1: adjust count
A992 LDY #0 ; Y=0 for copy
A994 JSR copy_pb_byte_to_ws ; Copy workspace data
A997 JMP commit_state_byte ; Update state and return
A99A .osword_13_dispatch
TAX ; X = sub-code
A99B CMP #&13 ; Sub-code < &13?
A99D BCS return_from_osword_13 ; Out of range: return
A99F LDA osword_13_dispatch_hi,x ; Read dispatch hi from osword_13_dispatch_hi+X
A9A2 PHA ; Push hi for RTS dispatch
A9A3 LDA osword_13_dispatch_lo,x ; Read dispatch lo from osword_13_dispatch_lo+X
A9A6 PHA ; Push lo for RTS dispatch
A9A7 .return_from_osword_13←1← A99D BCS
RTS ; RTS -> dispatched OSWORD &13 sub-handler

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

Read by osword_13_dispatch as LDA &A9A8,X. Paired with the high-byte half at osword_13_dispatch_hi. Sub-codes 0..&11 cover read/set station, read/write workspace pair, read/write protection, read/set handles, read RX flag/port/error, read context, read/write CSD, read free buffers, read/write context 3, and bridge query.

A9A8 .osword_13_dispatch_lo←1← A9A3 LDA
EQUB <(osword_13_read_station-1) ; sub &00: osword_13_read_station (read FS station)
A9A9 EQUB <(osword_13_set_station-1) ; sub &01: osword_13_set_station (set FS station)
A9AA EQUB <(osword_13_read_ws_pair-1) ; sub &02: osword_13_read_ws_pair (read workspace pair)
A9AB EQUB <(osword_13_write_ws_pair-1) ; sub &03: osword_13_write_ws_ pair (write workspace pair)
A9AC EQUB <(osword_13_read_prot-1) ; sub &04: osword_13_read_prot (read protection mask)
A9AD EQUB <(osword_13_write_prot-1) ; sub &05: osword_13_write_prot (write protection mask)
A9AE EQUB <(osword_13_read_handles-1) ; sub &06: osword_13_read_handles (read transfer handles)
A9AF EQUB <(osword_13_set_handles-1) ; sub &07: osword_13_set_handles (set transfer handles)
A9B0 EQUB <(osword_13_read_rx_flag-1) ; sub &08: osword_13_read_rx_flag (read RX flag)
A9B1 EQUB <(osword_13_read_rx_port-1) ; sub &09: osword_13_read_rx_port (read RX port)
A9B2 EQUB <(osword_13_read_error-1) ; sub &0A: osword_13_read_error (read last error)
A9B3 EQUB <(osword_13_read_context-1) ; sub &0B: osword_13_read_context (read context)
A9B4 EQUB <(osword_13_read_csd-1) ; sub &0C: osword_13_read_csd (read CSD)
A9B5 EQUB <(osword_13_write_csd-1) ; sub &0D: osword_13_write_csd (write CSD)
A9B6 EQUB <(osword_13_read_free_bufs-1) ; sub &0E: osword_13_read_free_ bufs (read free buffers)
A9B7 EQUB <(osword_13_read_ctx_3-1) ; sub &0F: osword_13_read_ctx_3 (read context byte 3)
A9B8 EQUB <(osword_13_write_ctx_3-1) ; sub &10: osword_13_write_ctx_3 (write context byte 3)
A9B9 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).

A9BA .osword_13_dispatch_hi←1← A99F LDA
EQUB >(osword_13_read_station-1) ; sub &00: osword_13_read_station
A9BB EQUB >(osword_13_set_station-1) ; sub &01: osword_13_set_station
A9BC EQUB >(osword_13_read_ws_pair-1) ; sub &02: osword_13_read_ws_pair
A9BD EQUB >(osword_13_write_ws_pair-1) ; sub &03: osword_13_write_ws_ pair
A9BE EQUB >(osword_13_read_prot-1) ; sub &04: osword_13_read_prot
A9BF EQUB >(osword_13_write_prot-1) ; sub &05: osword_13_write_prot
A9C0 EQUB >(osword_13_read_handles-1) ; sub &06: osword_13_read_handles
A9C1 EQUB >(osword_13_set_handles-1) ; sub &07: osword_13_set_handles
A9C2 EQUB >(osword_13_read_rx_flag-1) ; sub &08: osword_13_read_rx_flag
A9C3 EQUB >(osword_13_read_rx_port-1) ; sub &09: osword_13_read_rx_port
A9C4 EQUB >(osword_13_read_error-1) ; sub &0A: osword_13_read_error
A9C5 EQUB >(osword_13_read_context-1) ; sub &0B: osword_13_read_context
A9C6 EQUB >(osword_13_read_csd-1) ; sub &0C: osword_13_read_csd
A9C7 EQUB >(osword_13_write_csd-1) ; sub &0D: osword_13_write_csd
A9C8 EQUB >(osword_13_read_free_bufs-1) ; sub &0E: osword_13_read_free_ bufs
A9C9 EQUB >(osword_13_read_ctx_3-1) ; sub &0F: osword_13_read_ctx_3
A9CA EQUB >(osword_13_write_ctx_3-1) ; sub &10: osword_13_write_ctx_3
A9CB 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.

A9CC .osword_13_read_station
JSR ensure_fs_selected ; Ensure NFS is currently the selected FS
A9CF .read_station_bytes
LDY #2 ; Y=2: copy 2 bytes
A9D1 .loop_copy_station←1← A9D7 BNE
LDA hazel_minus_1,y ; Load station byte
A9D4 STA (osword_pb_ptr),y ; Store to PB[Y]
A9D6 DEY ; Step back
A9D7 BNE loop_copy_station ; Loop for bytes 2..1
A9D9 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.

A9DA .osword_13_set_station
JSR ensure_fs_selected ; Ensure NFS is currently the selected FS
A9DD .osword_13_set_station_body
LDY #0 ; Y=0 for process_all_fcbs
A9DF JSR process_all_fcbs ; Close all open FCBs
A9E2 LDY #2 ; Y=2: copy 2 bytes
A9E4 .loop_store_station←1← A9EA BNE
LDA (osword_pb_ptr),y ; Load new station byte from PB
A9E6 STA hazel_minus_1,y ; Store to fs_server_base
A9E9 DEY ; Step back to previous byte
A9EA BNE loop_store_station ; Loop for bytes 2..1
A9EC JSR clear_if_station_match ; Clear handles if station matches
A9EF LDA #&0e ; A=&0E: bits 1..3 (FS-state mask)
A9F1 TSB fs_flags ; Set fs_flags bits 1..3
A9F4 LDA #&40 ; A=&40: FS-active flag bit
A9F6 TRB fs_flags ; Clear FS-active flag (bit 6)
A9F9 LDX #&0f ; X=&0F: scan all 16 FCB slots (X = 15 down to 0)
A9FB .scan_fcb_entry←1← AA63 BPL
LDA hazel_fcb_status,x ; Load FCB flags
A9FE TAY ; Save flags in Y
A9FF AND #2 ; Test bit 1 (FCB allocated?)
AA01 BEQ next_fcb_entry ; No: skip to next entry
AA03 TYA ; Entry index to A
AA04 AND #&df ; Mask bit 5
AA06 STA hazel_fcb_status,x ; Store updated flags
AA09 TAY ; Save in Y
AA0A JSR match_station_net ; Does FCB match new station?
AA0D BNE next_fcb_entry ; No match: skip to next
AA0F CLC ; Clear carry
AA10 TYA ; Restore flags
AA11 AND #4 ; Test bit 2 (handle 1 active?)
AA13 BEQ check_handle_2 ; No: check handle 2
AA15 TYA ; Restore flags
AA16 ORA #&20 ; Set bit 5 (handle reassigned)
AA18 TAY
AA19 LDA hazel_fcb_slot_attr,x ; Get FCB high byte
AA1C STA hazel_fs_saved_station ; Store as handle 1 station
AA1F TXA ; FCB index
AA20 ADC #&20 ; Add &20 for FCB table offset
AA22 STA hazel_fcb_slot_1 ; Store as handle 1 FCB index
AA25 LDA #2 ; A=2: fs_flags bit 1 mask
AA27 TRB fs_flags ; Clear fs_flags bit 1
AA2A .check_handle_2←1← AA13 BEQ
TYA ; Y still holds the saved FCB status -- TYA so we can re-test bit 3 (handle-2 active flag)
AA2B AND #8 ; Test bit 3 (handle 2 active?)
AA2D BEQ check_handle_3 ; No: check handle 3
AA2F TYA
AA30 ORA #&20 ; Set bit 5
AA32 TAY
AA33 LDA hazel_fcb_slot_attr,x ; Get FCB high byte
AA36 STA hazel_fs_context_copy ; Store as handle 2 station
AA39 TXA ; FCB index
AA3A ADC #&20 ; Add &20 for FCB table offset
AA3C STA hazel_fcb_slot_2 ; Store as handle 2 FCB index
AA3F LDA #4 ; A=4: fs_flags bit 2 mask
AA41 TRB fs_flags ; Clear fs_flags bit 2
AA44 .check_handle_3←1← AA2D BEQ
TYA ; Y still holds the saved FCB status -- TYA so we can re-test bit 4 (handle-3 active flag)
AA45 AND #&10 ; Test bit 4 (handle 3 active?)
AA47 BEQ store_updated_status ; No: store final flags
AA49 TYA ; Restore flags
AA4A ORA #&20 ; Set bit 5
AA4C TAY ; Save updated flags
AA4D LDA hazel_fcb_slot_attr,x ; Get FCB high byte
AA50 STA hazel_fs_prefix_stn ; Store as handle 3 station
AA53 TXA ; FCB index
AA54 ADC #&20 ; Add &20 for FCB table offset
AA56 STA hazel_fcb_slot_3 ; Store as handle 3 FCB index
AA59 LDA #8 ; A=8: fs_flags bit 3 (FS-error pending)
AA5B TRB fs_flags ; Clear FS-error-pending flag
AA5E .store_updated_status←1← AA47 BEQ
TYA ; A = Y for store
AA5F STA hazel_fcb_status,x ; Store updated status into hazel_fcb_status[X]
AA62 .next_fcb_entry←2← AA01 BEQ← AA0D BNE
DEX ; Decrement entry counter
AA63 BPL scan_fcb_entry ; Loop while X >= 0 (scan all FCBs)
AA65 LDA #&0e ; A=&0E: status flag value
AA67 BIT fs_flags ; Test fs_flags bits 1..3
AA6A BNE return_4 ; Non-zero: skip the FS-active set
AA6C LDA #&40 ; A=&40: FS-active flag bit
AA6E TSB fs_flags ; Set FS-active flag (bit 6 of fs_flags)
AA71 .return_4←1← AA6A 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.

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

AA75 .osword_13_write_csd
SEC ; C=1: PB-to-workspace direction
AA76 .setup_csd_copy←1← AA73 BCC
LDA #&17 ; Workspace offset &17
AA78 STA osword_flag ; Save A as osword_flag (counter)
AA7A LDA net_rx_ptr_hi ; Page from RX pointer high byte
AA7C STA ws_ptr_lo ; Set ws_ptr_hi
AA7E LDY #1 ; Y=1: first PB data byte
AA80 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
AA82 .copy_pb_byte_to_ws←5← A927 JSR← A96D JSR← A994 JSR← AA8E BPL← AA9B BCC
BCC copy_ws_byte_to_pb ; C=0: skip PB-to-WS copy
AA84 LDA (ws_ptr_hi),y ; C=1: load from parameter block
AA86 STA (osword_flag),y ; Store to workspace
AA88 .copy_ws_byte_to_pb←1← AA82 BCC
LDA (osword_flag),y ; Load from workspace
AA8A STA (ws_ptr_hi),y ; Store to parameter block
AA8C INY ; Next byte
AA8D DEX ; Count down
AA8E BPL copy_pb_byte_to_ws ; Loop for all bytes
AA90 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.

AA91 .osword_13_read_ws_pair
LDA nfs_workspace_hi ; Load workspace page high byte
AA93 STA ws_ptr_lo ; Set ws_ptr_hi
AA95 INY ; Y=1
AA96 TYA ; A = current byte index
AA97 STA osword_flag ; Set ws_ptr_lo = 1
AA99 TAX ; X=1: copy 2 bytes
AA9A CLC ; WS-to-PB direction
AA9B 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.

AA9D .osword_13_write_ws_pair
INY ; Y=1: first PB data byte
AA9E LDA (ws_ptr_hi),y ; Load PB[1]
AAA0 INY ; Next byte
AAA1 STA (nfs_workspace),y ; Store to (nfs_workspace)+2
AAA3 LDA (ws_ptr_hi),y ; Load PB[2]
AAA5 INY ; Y=3
AAA6 STA (nfs_workspace),y ; Store to (nfs_workspace)+3
AAA8 JSR init_bridge_poll ; Reinitialise bridge routing
AAAB EOR (nfs_workspace),y ; Compare result with workspace
AAAD BNE return_from_write_ws_pair ; Different: leave unchanged
AAAF STA (nfs_workspace),y ; Same: clear workspace byte
AAB1 .return_from_write_ws_pair←1← AAAD BNE
RTS ; Return

OSWORD &13 sub 4: read protection mask

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

AAB2 .osword_13_read_prot
LDA ws_0d68 ; Load protection mask
AAB5 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_via_shadow_pair which mirrors it into both shadow ACR (ws_0d68) and shadow IER (ws_0d69).

AAB8 .osword_13_write_prot
INY ; Y=1: PB data offset
AAB9 LDA (ws_ptr_hi),y ; Load new mask from PB[1]
fall through ↓

Store A in both shadow ACR/IER bytes

Copies A to both ws_0d68 (shadow ACR) and ws_0d69 (shadow IER), then RTS. Two callers: nfs_init_body at &8FA6 (where A is 0 or &FF based on FS-options bit 6) and cmd_prot at &B6D9 (the *Prot path). A 2-store-and-return convenience to keep both call sites flat.

On EntryAvalue to mirror into both shadow VIA bytes
AABB .set_via_shadow_pair←2← 8FA6 JSR← B6D9 JSR
STA ws_0d68 ; Mirror A into ws_0d68 (shadow ACR)
AABE STA ws_0d69 ; Mirror A into ws_0d69 (shadow IER)
AAC1 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.

AAC2 .osword_13_read_handles
JSR ensure_fs_selected ; Ensure NFS is currently the selected FS
AAC5 LDY #3 ; Y=3: copy 3 bytes
AAC7 .loop_copy_handles←1← AACD BNE
LDA hazel_fs_lib_flags,y ; Load handle byte
AACA STA (ws_ptr_hi),y ; Store to PB[Y]
AACC DEY ; Previous byte
AACD BNE loop_copy_handles ; Loop for bytes 3..1
AACF 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.

AAD0 .osword_13_set_handles
JSR ensure_fs_selected ; Ensure NFS is currently the selected FS
AAD3 .start_set_handles
LDY #1 ; Y=1: first handle in PB
AAD5 .validate_handle←1← AB15 BNE
LDA (ws_ptr_hi),y ; Load handle value from PB[Y]
AAD7 CMP #&20 ; Must be >= &20
AAD9 BCC handle_invalid ; Below range: invalid
AADB CMP #&30 ; Must be < &30
AADD BCS handle_invalid ; Above range: invalid
AADF TAX ; X = handle value
AAE0 LDA hazel_fcb_addr_mid,x ; Load fcb_attr_or_count_mid[handle]
AAE3 BNE check_handle_alloc ; Non-zero: FCB exists
AAE5 .handle_invalid←3← AAD9 BCC← AADD BCS← AAF1 BEQ
LDA #0 ; A=0: invalid-handle marker
AAE7 TAX ; X=&00
AAE8 STA (ws_ptr_hi,x) ; Clear PB[0] status
AAEA BEQ next_handle_slot ; Skip to next handle
AAEC .check_handle_alloc←1← AAE3 BNE
LDA hazel_fcb_state_byte,x ; Load fcb_flags[handle] flags
AAEF AND #2 ; Test bit 1 (allocated?)
AAF1 BEQ handle_invalid ; Not allocated: invalid
AAF3 TXA ; X = handle value
AAF4 STA hazel_fs_lib_flags,y ; Store handle to fs_lib_flags+Y
AAF7 LDA hazel_fcb_addr_mid,x ; Load station from fcb_attr_or_count_mid
AAFA STA hazel_fs_network,y ; Store station to fs_server_net+Y
AAFD CPY #1 ; Is this handle 1 (Y=1)?
AAFF BNE assign_handle_2 ; No: check handle 2
AB01 TYA ; Save Y for processing
AB02 PHA ; Push Y
AB03 LDY #4 ; Bit mask &04 for handle 1
AB05 JSR update_fcb_flag_bits ; Update flags across all FCBs
AB08 PLA ; Pop saved Y
AB09 TAY ; Back to Y
AB0A LDA hazel_fcb_state_byte,x ; Reload fcb_flags flags
AB0D ORA #&24 ; Set bits 2+5 (active+updated)
AB0F STA hazel_fcb_state_byte,x ; Store updated flags
AB12 .next_handle_slot←3← AAEA BEQ← AB2E BNE← AB41 BNE
INY ; Next handle slot
AB13 CPY #4 ; Compare with 4
AB15 BNE validate_handle ; No: process next handle
AB17 DEY ; Y=3 for return
AB18 RTS ; Return
AB19 .assign_handle_2←1← AAFF BNE
CPY #2 ; Is this handle 2 (Y=2)?
AB1B BNE assign_handle_3 ; No: must be handle 3
AB1D TYA ; Save current Y
AB1E PHA ; Push Y
AB1F LDY #8 ; Y=8 (handle-bit shift index)
AB21 JSR update_fcb_flag_bits ; Update flags across all FCBs
AB24 PLA ; Restore Y
AB25 TAY ; Back to Y
AB26 LDA hazel_fcb_state_byte,x ; Reload fcb_flags flags
AB29 ORA #&28 ; Set bits 3 and 5
AB2B STA hazel_fcb_state_byte,x ; Store updated flags
AB2E BNE next_handle_slot ; Next handle slot
AB30 .assign_handle_3←1← AB1B BNE
TYA ; Handle 3: save Y
AB31 PHA ; Push for save/restore
AB32 LDY #&10 ; Bit mask &10 for handle 3
AB34 JSR update_fcb_flag_bits ; Update flags across all FCBs
AB37 PLA ; Pop saved value
AB38 TAY ; Back to Y
AB39 LDA hazel_fcb_state_byte,x ; Reload fcb_flags flags
AB3C ORA #&30 ; Set bits 4+5 (active+updated)
AB3E STA hazel_fcb_state_byte,x ; Store updated flags
AB41 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)
AB43 .update_fcb_flag_bits←3← AB05 JSR← AB21 JSR← AB34 JSR
TXA ; A = caller X
AB44 PHA ; Push X
AB45 LDX #&0f ; X=&0F: scan all 16 FCB slots
AB47 .loop_scan_fcb_flags←1← AB63 BPL
LDA hazel_fcb_status,x ; Load FCB flags
AB4A ROL ; Shift bits 6-7 into bits 7-0
AB4B ROL ; Shift bit into carry for test
AB4C BPL next_flag_entry ; Bit 6 clear: skip entry
AB4E TYA ; Restore Y (bit mask)
AB4F AND hazel_fcb_status,x ; Test mask bits against flags
AB52 BEQ no_flag_match ; Zero: no matching bits
AB54 TYA ; Matching: restore Y
AB55 ORA #&20 ; Set bit 5 (updated)
AB57 BNE clear_flag_bits ; Skip clear path
AB59 .no_flag_match←1← AB52 BEQ
TYA ; No match: restore Y
AB5A .clear_flag_bits←1← AB57 BNE
EOR #&ff ; Invert all bits
AB5C AND hazel_fcb_status,x ; Clear tested bits in flags
AB5F STA hazel_fcb_status,x ; Store updated flags
AB62 .next_flag_entry←1← AB4C BPL
DEX ; Decrement FCB index
AB63 BPL loop_scan_fcb_flags ; Loop for all 16 entries
AB65 PLA ; Restore original X
AB66 TAX ; Back to X
AB67 RTS ; Return

OSWORD &13 sub 8: read RX control block flag

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

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

AB71 .osword_13_read_rx_port
LDY #&7f ; Y=&7F: port byte offset
AB73 LDA (net_rx_ptr),y ; Load (net_rx_ptr)+&7F
AB75 LDY #1 ; Y=1
AB77 STA (ws_ptr_hi),y ; Store to PB[1]
AB79 INY ; Y=&02
AB7A LDA #&80 ; A=&80
AB7C STA (ws_ptr_hi),y ; Store &80 to PB[2]
AB7E 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.

AB7F .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
AB82 .store_a_to_pb_1←4← AAB5 JMP← AB6E JMP← AB89 BPL← AB91 BCS
INY ; Y=1: parameter block offset 1
AB83 STA (ws_ptr_hi),y ; Store result to PB[1]
AB85 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.

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

AB8B .osword_13_read_free_bufs
LDA #&6f ; Total buffers = &6F
AB8D SEC ; PB-to-WS direction (write)
AB8E SBC spool_buf_idx ; Free = &6F - spool_buf_idx
AB91 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.

AB93 .osword_13_read_ctx_3←1← AB9B BNE
INY ; Next ctx byte
AB94 LDA fs_flags,y ; Return
AB97 STA (ws_ptr_hi),y ; Store to PB[Y]
AB99 CPY #3 ; Done 3 bytes?
AB9B BNE osword_13_read_ctx_3 ; No: loop
AB9D 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.

AB9E .osword_13_write_ctx_3←1← ABA6 BNE
INY ; Next byte offset
AB9F LDA (ws_ptr_hi),y ; Load PB[Y]
ABA1 STA fs_flags,y ; Store to tx_retry_count[Y]
ABA4 CPY #3 ; Done 3 bytes?
ABA6 BNE osword_13_write_ctx_3 ; No: loop
ABA8 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.

ABA9 .osword_13_bridge_query
JSR init_bridge_poll ; Poll for bridge
ABAC LDY #0 ; Y=0
ABAE LDA spool_control_flag ; Load bridge status
ABB1 CMP #&ff ; Is it &FF (no bridge)?
ABB3 BNE bridge_found ; No: bridge found
ABB5 TYA ; A=&00
ABB6 STA (ws_ptr_hi),y ; PB[0] = 0 (no bridge)
ABB8 INY ; Y=&01
ABB9 BNE store_bridge_station ; ALWAYS branch
ABBB .bridge_found←1← ABB3 BNE
INY ; Y=1
ABBC STA (ws_ptr_hi),y ; PB[1] = bridge status
ABBE INY ; Advance Y
ABBF INY ; Y=3
ABC0 LDA (ws_ptr_hi),y ; Load PB[3] (caller value)
ABC2 BEQ use_default_station ; Zero: use default station
ABC4 .compare_bridge_status
EOR spool_control_flag ; Compare with bridge status
ABC7 BNE return_from_bridge_query ; Non-zero: take return path
ABC9 BEQ store_bridge_station ; Same: confirm station
ABCB .use_default_station←1← ABC2 BEQ
LDA hazel_fs_network ; Load default from fs_server_net
ABCE .store_bridge_station←2← ABB9 BNE← ABC9 BEQ
STA (ws_ptr_hi),y ; Store to PB[3]
ABD0 .return_from_bridge_query←1← ABC7 BNE
RTS ; Return
ABD1 .bridge_txcb_init_table←1← ABFE LDA
EQUB &82 ; TX 0: ctrl = &82 (immediate mode)
ABD2 EQUB &9C ; TX 1: port = &9C (bridge discovery)
ABD3 EQUB &FF ; TX 2: dest station = &FF (broadcast)
ABD4 EQUB &FF ; TX 3: dest network = &FF (all nets)
ABD5 EQUS "BRIDGE" ; TX 4-9: immediate data payload
ABDB EQUB &9C, &00 ; TX 11: &00 (terminator)
ABDD .bridge_rxcb_init_data
EQUB &7F, &9C, ; RX 0: ctrl = &7F (receive) RX 1: port = &00, &00, ; &9C (bridge discovery) RX 2: station = &00 &71, &0D, ; (any) RX 3: network = &00 (any) RX 6: &FF, &FF, ; extended addr fill (&FF) RX 7: extended &73, &0D, ; addr fill (&FF) RX 9: buf end hi (&0D) -> &FF, &FF ; &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
ABE9 .init_bridge_poll←5← 8E21 JSR← 9022 JSR← A3D1 JSR← AAA8 JSR← ABA9 JSR
LDA spool_control_flag ; Check bridge status
ABEC CMP #&ff ; Is it &FF (uninitialised)?
ABEE BNE return_from_bridge_poll ; No: bridge already active, return
ABF0 TYA ; Save Y
ABF1 PHA ; Preserve Y on stack
ABF2 LDY #&18 ; Y=&18: workspace offset for init
ABF4 LDX #&0b ; X=&0B: 12 bytes to copy
ABF6 ROR econet_flags ; Rotate econet_flags right (save flag)
ABF9 .loop_copy_bridge_init←1← AC05 BPL
LDA bridge_err_table,y ; Load init data byte
ABFC STA (nfs_workspace),y ; Store to workspace
ABFE LDA bridge_txcb_init_table,x ; Load TXCB template byte
AC01 STA txcb_ctrl,x ; Store to TX control block
AC03 INY ; Next workspace byte
AC04 DEX ; Next template byte
AC05 BPL loop_copy_bridge_init ; Loop for all 12 bytes
AC07 STX spool_control_flag ; Store X (-1) as bridge counter
AC0A ROL econet_flags ; Restore econet_flags flag
AC0D .loop_wait_ws_status←2← AC10 BCC← AC35 BPL
ASL tx_complete_flag ; Shift ws_0d60 left (check status)
AC10 BCC loop_wait_ws_status ; C=0: status clear, retry
AC12 LDA #&82 ; Control byte &82 for TX
AC14 STA txcb_ctrl ; Set in TX control block
AC16 LDA #&c0 ; Data block at &00C0
AC18 STA nmi_tx_block ; Set NMI TX block low
AC1A LDA #0 ; High byte = 0 (page 0)
AC1C STA nmi_tx_block_hi ; Set NMI TX block high
AC1E JSR tx_begin ; Begin Econet transmission
AC21 .loop_wait_tx_done←1← AC23 BMI
BIT txcb_ctrl ; Test TX control block bit 7
AC23 BMI loop_wait_tx_done ; Negative: TX still in progress
AC25 PHX ; Push X (saved across delay)
AC26 LDA #osbyte_vsync ; A=&13: OSBYTE 'wait for VSYNC'
AC28 JSR osbyte ; Wait for vertical sync
AC2B PLX ; Restore caller's X
AC2C LDY #&18 ; Y=&18: status-byte offset
AC2E LDA (nfs_workspace),y ; Load bridge response
AC30 BMI bridge_responded ; Negative: bridge responded
AC32 JSR advance_x_by_8 ; Advance retry counter by 8
AC35 BPL loop_wait_ws_status ; Positive: retry poll loop
AC37 .bridge_responded←1← AC30 BMI
LDA #&3f ; Set response to &3F (OK)
AC39 STA (nfs_workspace),y ; Store to workspace
AC3B PLA ; Restore saved Y
AC3C TAY ; Result byte to Y
AC3D LDA spool_control_flag ; Load bridge status
AC40 TAX ; X = bridge status
AC41 EOR #&ff ; Invert (presence -> absence)
AC43 BEQ return_from_bridge_poll ; Status was &FF: return (no bridge)
AC45 TXA ; Return bridge station in A
AC46 .return_from_bridge_poll←2← ABEE BNE← AC43 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)
AC47 .osword_14_handler
CMP #1 ; Compare sub-code with 1
AC49 BCS handle_tx_request ; Sub-code >= 1: handle TX request
AC4B PHA ; Save state
AC4C JSR ensure_fs_selected ; Ensure NFS is currently the selected FS
AC4F PLA ; Pop saved A from the stack frame
AC50 LDY #&23 ; Y=&23: workspace offset for params
AC52 JSR mask_owner_access ; Set owner access mask
AC55 .loop_copy_txcb_init←1← AC62 BNE
LDA init_txcb,y ; Load TXCB init byte
AC58 BNE store_txcb_init_byte ; Non-zero: use template value
AC5A LDA hazel_minus_1a,y ; Zero: use workspace default value
AC5D .store_txcb_init_byte←1← AC58 BNE
STA (nfs_workspace),y ; Store to workspace
AC5F DEY ; Next byte down
AC60 CPY #&17 ; Until Y reaches &17
AC62 BNE loop_copy_txcb_init ; Loop for all bytes
AC64 INY ; Next byte
AC65 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.

AC67 .store_osword_pb_ptr
LDY #&1c ; Y=&1C: workspace offset for PB pointer
AC69 LDA ws_ptr_hi ; Load PB page number
AC6B ADC #1 ; PB starts at next page boundary (+1)
AC6D JSR store_ptr_at_ws_y ; Store PB start pointer at ws[&1C]
AC70 LDY #1 ; Y=1: PB byte 1 (transfer length)
AC72 LDA (ws_ptr_hi),y ; Load transfer length from PB
AC74 LDY #&20 ; Y=&20: TXCB offset
AC76 ADC ws_ptr_hi ; Add PB base for buffer end address
AC78 JSR store_ptr_at_ws_y ; Store PB pointer to workspace
AC7B LDY #2 ; Y=2: parameter offset
AC7D LDA #&90 ; Control byte &90
AC7F STA need_release_tube ; Set escapable flag
AC81 STA (ws_ptr_hi),y ; Store control byte to PB
AC83 INY ; Y=&03
AC84 INY ; Y=&04
AC85 .loop_copy_ws_to_pb←1← AC8D BNE
LDA hazel_minus_2,y ; Load workspace data
AC88 STA (ws_ptr_hi),y ; Store to parameter block
AC8A INY ; Next byte
AC8B CPY #7 ; Until Y reaches 7
AC8D BNE loop_copy_ws_to_pb ; Loop for 3 bytes (Y=4,5,6)
AC8F LDA nfs_workspace_hi ; Read nfs_workspace_hi
AC91 STA net_tx_ptr_hi ; Store to net_tx_ptr_hi
AC93 JSR enable_irq_and_poll ; Enable interrupts
AC96 LDY #&20 ; Y=&20: workspace offset
AC98 LDA #&ff ; Set to &FF (pending)
AC9A STA (nfs_workspace),y ; Mark send pending in workspace
AC9C INY ; Y=&21
AC9D STA (nfs_workspace),y ; Also mark offset &21
AC9F LDY #&19 ; Y=&19: control offset
ACA1 LDA #&90 ; Control byte &90
ACA3 STA (nfs_workspace),y ; Store to workspace
ACA5 DEY ; Y=&18: RX control offset
ACA6 LDA #&7f ; Control byte &7F
ACA8 STA (nfs_workspace),y ; Store RX control
ACAA 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
ACAD .store_ptr_at_ws_y←2← AC6D JSR← AC78 JSR
STA (nfs_workspace),y ; Store address low byte at ws[Y]
ACAF INY ; Advance to high byte offset
ACB0 LDA table_idx ; Load high byte base (table_idx)
ACB2 ADC #0 ; Add carry for page crossing
ACB4 STA (nfs_workspace),y ; Store address high byte at ws[Y+1]
ACB6 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 &AC49 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 in 4.21, 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
ACB7 .handle_tx_request←1← AC49 BCS
PHP ; Save processor flags
ACB8 LDY #1 ; Y=1: workspace offset
ACBA LDA (ws_ptr_hi),y ; Load station number from PB
ACBC TAX ; X = station number
ACBD INY ; Y=&02
ACBE LDA (ws_ptr_hi),y ; Load network number from PB
ACC0 INY ; Y=3: workspace start offset
ACC1 STY osword_flag ; Store Y as ws_ptr_lo
ACC3 LDY #&72 ; Y=&72: workspace offset for dest
ACC5 STA (net_rx_ptr),y ; Store network to workspace
ACC7 DEY ; Y=&71
ACC8 TXA ; A = station (from X)
ACC9 STA (net_rx_ptr),y ; Store station to workspace
ACCB PLP ; Restore flags from PHP
ACCC BNE handle_burst_xfer ; Non-zero sub-code: handle burst
ACCE .loop_send_pb_chars←1← ACEA BNE
LDY osword_flag ; Load current offset
ACD0 INC osword_flag ; Advance offset for next byte
ACD2 LDA (ws_ptr_hi),y ; Load next char from PB
ACD4 BEQ return_5 ; Zero: end of data, return
ACD6 LDY #&7d ; Y=&7D: workspace pointer offset
ACD8 STA (net_rx_ptr),y ; Store char to RX buffer
ACDA PHA ; Save char for later test
ACDB JSR init_ws_copy_wide ; Init workspace copy for wide xfer
ACDE SEC ; Set carry
ACDF ROR need_release_tube ; Set bit 7: Tube needs release
ACE1 JSR enable_irq_and_poll ; Enable IRQ and send packet
ACE4 .loop_bridge_tx_delay←1← ACE5 BNE
DEX ; Delay countdown
ACE5 BNE loop_bridge_tx_delay ; Loop while X != 0
ACE7 PLA ; Restore char
ACE8 EOR #&0d ; Test if char was CR (&0D)
ACEA BNE loop_send_pb_chars ; Loop while not CR
ACEC .return_5←1← ACD4 BEQ
RTS ; CR sent: return

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

Reached from handle_tx_request's BNE at &ACCC. 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)
ACED .handle_burst_xfer←1← ACCC BNE
JSR init_ws_copy_wide ; Init workspace for wide copy
ACF0 LDY #&7b ; Y=&7B: end-byte offset
ACF2 LDA (net_rx_ptr),y ; Load buffer size
ACF4 ADC #3 ; Add 3 (end-of-buffer adjust)
ACF6 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)
ACF8 .enable_irq_and_poll←2← AC93 JSR← ACE1 JSR
CLI ; Re-enable IRQs
ACF9 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
ACFC .netv_handler
PHP ; Save processor flags
ACFD PHA ; Save A
ACFE TXA ; Save X
ACFF PHA ; Push X
AD00 TYA ; Save Y
AD01 PHA ; Push Y
AD02 TSX ; Get stack pointer
AD03 LDA stack_page_3,x ; Read OSWORD number from stack
AD06 CMP #9 ; OSWORD >= 9?
AD08 BCS restore_regs_return ; Yes: out of range, restore + return
AD0A TAX ; X = OSWORD number
AD0B JSR push_osword_handler_addr ; Push handler address for dispatch
AD0E .restore_regs_return←1← AD08 BCS
PLA ; Restore Y
AD0F TAY ; Back to Y
AD10 PLA ; Restore X
AD11 TAX ; Back to X
AD12 PLA ; Restore A
AD13 PLP ; Restore flags
AD14 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)
AD15 .push_osword_handler_addr←1← AD0B JSR
LDA netv_dispatch_hi,x ; Load handler high byte from hi-table column X
AD18 PHA ; Push for the eventual RTS dispatch
AD19 LDA netv_dispatch_lo,x ; Load handler low byte from lo-table column X
AD1C PHA ; Push lo so RTS pulls (lo, hi)+1 -> handler entry
AD1D LDA osbyte_a_copy ; Reload original OSWORD number into A for the handler Reload OSWORD number for handler
AD1F RTS ; RTS jumps to handler with A=OSWORD number

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

Read by push_osword_handler_addr as LDA &AD20,X. Paired with the high-byte half at netv_dispatch_hi. The wrapper at netv_handler reads the original A from the MOS stack frame (&0103,X after TSX) and gates 9..&FF away to return_6 before dispatching reasons 0..8.

AD20 .netv_dispatch_lo←1← AD19 LDA
EQUB <(dispatch_rts-1) ; reason &00: dispatch_rts (no-op (RTS only))
AD21 EQUB <(netv_print_data-1) ; reason &01: netv_print_data (NETV reason 1: print data)
AD22 EQUB <(netv_print_data-1) ; reason &02: netv_print_data (NETV reason 2: print data (alias))
AD23 EQUB <(netv_print_data-1) ; reason &03: netv_print_data (NETV reason 3: print data (alias))
AD24 EQUB <(osword_4_handler-1) ; reason &04: osword_4_handler (NETV reason 4: OSWORD &04)
AD25 EQUB <(netv_spool_check-1) ; reason &05: netv_spool_check (NETV reason 5: spool check)
AD26 EQUB <(dispatch_rts-1) ; reason &06: dispatch_rts (no-op (RTS only))
AD27 EQUB <(netv_claim_release-1) ; reason &07: netv_claim_release (NETV reason 7: claim/release)
AD28 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).

AD29 .netv_dispatch_hi←1← AD15 LDA
EQUB >(dispatch_rts-1) ; reason &00: dispatch_rts
AD2A EQUB >(netv_print_data-1) ; reason &01: netv_print_data
AD2B EQUB >(netv_print_data-1) ; reason &02: netv_print_data
AD2C EQUB >(netv_print_data-1) ; reason &03: netv_print_data
AD2D EQUB >(osword_4_handler-1) ; reason &04: osword_4_handler
AD2E EQUB >(netv_spool_check-1) ; reason &05: netv_spool_check
AD2F EQUB >(dispatch_rts-1) ; reason &06: dispatch_rts hi OSWORD 6: no-op (RTS)
AD30 EQUB >(netv_claim_release-1) ; reason &07: netv_claim_release
AD31 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)
AD32 .osword_4_handler
TSX ; Read the MOS stack frame holding caller flags
AD33 ROR stack_page_6,x ; Shift carry out of caller P (stack[&106+X])
AD36 ASL stack_page_6,x ; Carry is now cleared in caller P
AD39 TYA ; A = original Y
AD3A LDY #&da ; Y=&DA: workspace osword-4 result offset
AD3C STA (nfs_workspace),y ; Store Y at (nfs_workspace)+&DA
AD3E 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)
AD40 .tx_econet_abort←3← 8AFD JSR← AD93 JSR← ADF9 JSR
LDY #&d9 ; Y=&D9: workspace offset for the abort code byte Y=&D9: workspace abort offset
AD42 STA (nfs_workspace),y ; Store the abort code (passed in A) at workspace[&D9] Store abort code to workspace
AD44 LDA #&80 ; A=&80: control = immediate-operation flag
AD46 LDY #&0c ; Y=&0C: TXCB control-byte offset
AD48 STA (nfs_workspace),y ; Set TXCB[&0C] = &80 (immediate / abort)
AD4A LDA net_tx_ptr ; Save current net_tx_ptr low (we'll repoint TX at the abort packet) Save current TX ptr low
AD4C PHA ; Push it for restore on exit
AD4D LDA net_tx_ptr_hi ; Save net_tx_ptr high too
AD4F PHA ; Push it
AD50 STY net_tx_ptr ; TX low = &0C (abort packet starts at workspace[&0C]) Set TX ptr to workspace offset
AD52 LDX nfs_workspace_hi ; Get nfs_workspace high byte
AD54 STX net_tx_ptr_hi ; TX high = workspace page (so net_tx_ptr now points at the abort packet in workspace) Set TX ptr high
AD56 JSR send_net_packet ; Send the abort packet via the standard TX path Send the abort packet
AD59 LDA #&3f ; A=&3F: TXCB status = abort-complete sentinel Set status to &3F (complete)
AD5B STA (net_tx_ptr,x) ; Write status via (net_tx_ptr,X) -- mark TX done Store at TX ptr offset 0
AD5D PLA ; Pull saved net_tx_ptr high
AD5E STA net_tx_ptr_hi ; Restore
AD60 PLA ; Pull saved net_tx_ptr low
AD61 STA net_tx_ptr ; Restore -- caller's TX state intact
AD63 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)
AD64 .netv_claim_release
LDY osword_pb_ptr_hi ; Y = OSWORD parameter-block pointer high byte (used as an 'unrecognised' sentinel below) Load PB pointer high
AD66 CMP #&81 ; Code &81? (compatibility shortcut for one specific claim type) Compare with &81 (special case)
AD68 BEQ process_match_result ; Yes: skip table scan, use match-result with Y already set non-zero Match: skip to processing
AD6A LDY #1 ; Y=1: state 2 marker
AD6C LDX #&0a ; X=&0A: scan first 11 entries (table indices 0..&0A) X=&0A: 11 codes to check
AD6E JSR match_rx_code ; Look up A in the claim code table
AD71 BEQ process_match_result ; Match: handle as state 2
AD73 DEY ; DEY: Y=0 (state 3 marker, two DEYs from 1)
AD74 DEY ; Y=-1: flag second range
AD75 LDX #&11 ; X=&11: scan all 18 entries (state 3 also accepts the extended range) X=&11: 18 codes to check
AD77 JSR match_rx_code ; Look up A again with extended range
AD7A BEQ process_match_result ; Match: handle as state 3
AD7C INY ; Y=1 again (no match found, will return below) Not found: increment Y
AD7D .process_match_result←3← AD68 BEQ← AD71 BEQ← AD7A BEQ
LDX #2 ; X=2: default state code passed to tx_econet_abort X=2: default state
AD7F TYA ; Move match marker (Y) into A for the BEQ test A = Y (search result)
AD80 BEQ return_from_claim_release ; Y=0 (no match): return without action
AD82 PHP ; Save flags so we can branch later on Y's sign Save result flags
AD83 BPL save_tube_state ; Y > 0 (state 2): skip the X bump
AD85 INX ; State 3: X=3 (different abort code)
AD86 .save_tube_state←1← AD83 BPL
LDY #&dc ; Y=&DC: workspace offset for tube state bytes Y=&DC: workspace offset for save
AD88 .loop_save_tube_bytes←1← AD90 BPL
LDA tube_claimed_id,y ; Read tube_claimed_id,Y
AD8B STA (nfs_workspace),y ; Save in workspace[&DC..]
AD8D DEY ; Step backwards
AD8E CPY #&da ; Done at &DA?
AD90 BPL loop_save_tube_bytes ; Loop while Y > &DA (saves &DA, &DB, &DC -- 3 bytes) Loop for 3 bytes
AD92 TXA ; Move state code (2 or 3) into A for the abort A = state (2 or 3)
AD93 JSR tx_econet_abort ; Send abort with the state code
AD96 PLP ; Restore the saved flags (Y's sign)
AD97 BPL return_from_claim_release ; Y was positive (state 2): just return
AD99 LDA #&7f ; A=&7F: 'pending response' control value
AD9B LDY #&0c ; Y=&0C: TXCB control offset
AD9D STA (nfs_workspace),y ; Mark TXCB as pending
AD9F .loop_poll_ws_status←1← ADA1 BPL
LDA (nfs_workspace),y ; Read TXCB status byte
ADA1 BPL loop_poll_ws_status ; Bit 7 still clear: keep polling for response Positive: keep waiting
ADA3 TSX ; Capture S so we can patch the caller's stack frame Get stack pointer
ADA4 LDY #&dd ; Y=&DD: highest workspace offset for the response copy Y=&DD: workspace result offset
ADA6 LDA (nfs_workspace),y ; Read first response byte (workspace[&DD])
ADA8 ORA #&44 ; Set bit 6 and bit 2
ADAA BNE store_stack_byte ; Always taken (after ORA result is non-zero); store into stack[&106+X] then walk down Always branch (NZ from ORA)
ADAC .loop_restore_stack←1← ADB5 BNE
DEY ; Step Y down
ADAD DEX ; Step X down (stack offset)
ADAE LDA (nfs_workspace),y ; Read next workspace byte
ADB0 .store_stack_byte←1← ADAA BNE
STA stack_page_6,x ; Patch caller's stack frame at &106+X
ADB3 CPY #&da ; Reached &DA (lower workspace bound)?
ADB5 BNE loop_restore_stack ; No: keep restoring
ADB7 .return_from_claim_release←2← AD80 BEQ← AD97 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
ADB8 .match_rx_code←3← AD6E JSR← AD77 JSR← ADBE BPL
CMP osword_claim_codes,x ; Compare A with table entry at index X
ADBB BEQ return_from_match_rx_code ; Match: return with Z set
ADBD DEX ; Step to next earlier table entry
ADBE BPL match_rx_code ; Loop while X >= 0 (table walked top-down)
ADC0 .return_from_match_rx_code←2← ADBB BEQ← ADDB 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.

ADC1 .osword_claim_codes←1← ADB8 CMP
EQUB &04 ; Range 1+2: OSWORD &04
ADC2 EQUB &09 ; Range 1+2: OSWORD &09
ADC3 EQUB &0A
ADC4 EQUB &14 ; Range 1+2: OSWORD &14
ADC5 EQUB &15 ; Range 1+2: OSWORD &15
ADC6 EQUB &9A
ADC7 EQUB &9B ; Range 1+2: OSWORD &9B
ADC8 EQUB &E1 ; Range 1+2: OSWORD &E1
ADC9 EQUB &E2
ADCA EQUB &E3 ; Range 1+2: OSWORD &E3
ADCB EQUB &E4 ; Range 1+2: OSWORD &E4
ADCC EQUB &0B
ADCD EQUB &0C ; Range 2 only: OSWORD &0C
ADCE EQUB &0F ; Range 2 only: OSWORD &0F
ADCF EQUB &79 ; Range 2 only: OSWORD &79
ADD0 EQUB &7A
ADD1 EQUB &86
ADD2 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)
ADD3 .osword_8_handler
LDY #&0e ; Y=&0E: scan 15 bytes (offsets 14..0) of the PB Y=&0E: copy 15 bytes (0-14)
ADD5 CMP #7 ; Is the OSWORD number 7?
ADD7 BEQ copy_pb_to_ws ; Yes: handle as either 7 or 8 -- both copy PB to ws Yes: handle
ADD9 CMP #8 ; Is the OSWORD number 8?
ADDB BNE return_from_match_rx_code ; Neither 7 nor 8: return early (other OSWORDs handled elsewhere) No: return
ADDD .copy_pb_to_ws←1← ADD7 BEQ
LDX #&db ; X=&DB: workspace offset for the PB copy Workspace low = &DB
ADDF STX nfs_workspace ; Temporarily reuse nfs_workspace as the destination low byte (high byte already points at the workspace page) Set nfs_workspace low byte
ADE1 .loop_copy_pb_to_ws←1← ADE6 BPL
LDA (osword_pb_ptr),y ; Read PB[Y]
ADE3 STA (nfs_workspace),y ; Write to (nfs_workspace),Y -- effectively writes to workspace[&DB+Y] Store to workspace[Y]
ADE5 DEY ; Step backwards through the 15 bytes
ADE6 BPL loop_copy_pb_to_ws ; Loop while Y >= 0
ADE8 INY ; Bring Y back to 0 for the next single-byte write Y=0
ADE9 DEC nfs_workspace ; Decrement nfs_workspace low byte: now points at workspace[&DA] (one before the copied region) Workspace low = &DA
ADEB LDA osbyte_a_copy ; Read original OSWORD number from osbyte_a_copy Load OSWORD number
ADED STA (nfs_workspace),y ; Store at workspace[&DA] -- so the abort packet header carries the OSWORD number Store at workspace+0 (= &DA)
ADEF STY nfs_workspace ; Restore nfs_workspace to its proper low byte (Y=0) Workspace low = 0 (restore)
ADF1 LDY #&14 ; Y=&14: TXCB control offset
ADF3 LDA #&e9 ; A=&E9: status code for OSWORD-passthrough abort Control value &E9
ADF5 STA (nfs_workspace),y ; Store status at TXCB[&14]
ADF7 LDA #1 ; A=1: abort code for tx_econet_abort
ADF9 JSR tx_econet_abort ; Send the abort packet
ADFC STX nfs_workspace ; Restore nfs_workspace from X (X is unchanged across tx_econet_abort) Restore nfs_workspace low
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)
ADFE .init_ws_copy_wide←2← ACDB JSR← ACED JSR
LDX #&0d ; X=&0D: 14 template bytes to process
AE00 LDY #&7c ; Y=&7C: workspace destination offset for wide variant Y=&7C: workspace destination offset
AE02 BIT always_set_v_byte ; BIT &FF unconditionally sets V (the always_set_v_byte trick) Test bit 6 (V flag check)
AE05 BVS loop_copy_ws_template ; V=1 always: skip the narrow-mode prologue and CLV V=1: skip to wide mode copy
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
AE07 .init_ws_copy_narrow←1← 9872 JSR
LDY #&17 ; Y=&17: workspace destination offset for narrow variant Y=&17: narrow mode dest offset
AE09 LDX #&1a ; X=&1A: 27 template bytes to process; fall into ws_copy_vclr_entry which CLVs X=&1A: 27 bytes to copy
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
AE0B .ws_copy_vclr_entry←1← AECC JSR
CLV ; Clear V: narrow mode (writes via nfs_workspace pointer) Clear V flag for narrow mode
AE0C .loop_copy_ws_template←2← AE05 BVS← AE2D BPL
LDA ws_txcb_template_data,x ; Read next template byte
AE0F CMP #&fe ; &FE: end-of-template marker?
AE11 BEQ done_ws_template_copy ; Yes: finalise and return
AE13 CMP #&fd ; &FD: skip-this-offset marker?
AE15 BEQ advance_template_idx ; Yes: advance index without storing
AE17 CMP #&fc ; &FC: substitute-workspace-page- pointer marker? Is it &FC? (page ptr marker)
AE19 BNE select_store_target ; No special marker: store this byte verbatim
AE1B LDA net_rx_ptr_hi ; Wide path: page pointer is net_rx_ptr's high byte &FC: load RX buffer page
AE1D BVS store_tx_ptr_hi ; V=1 (wide): keep the rx_ptr high byte
AE1F LDA nfs_workspace_hi ; V=0 (narrow): use nfs_workspace high byte instead V=0: use nfs_workspace_hi
AE21 .store_tx_ptr_hi←1← AE1D BVS
STA net_tx_ptr_hi ; Stash whichever page byte we picked into net_tx_ptr_hi Store as TX ptr high
AE23 .select_store_target←1← AE19 BNE
BVS store_via_rx_ptr ; V=1 (wide): store via net_rx_ptr,Y
AE25 STA (nfs_workspace),y ; V=0 (narrow): store via nfs_workspace,Y
AE27 BVC advance_template_idx ; Always branch: V is still clear here
AE29 .store_via_rx_ptr←1← AE23 BVS
STA (net_rx_ptr),y ; Wide-mode store via net_rx_ptr
AE2B .advance_template_idx←2← AE15 BEQ← AE27 BVC
DEY ; Step Y down (workspace offset)
AE2C DEX ; Step X down (template index)
AE2D BPL loop_copy_ws_template ; Loop while X >= 0
AE2F .done_ws_template_copy←1← AE11 BEQ
INY ; Bump Y back to first written offset
AE30 STY net_tx_ptr ; Save it as net_tx_ptr low for the caller
AE32 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.

AE33 .ws_txcb_template_data←1← AE0C LDA
EQUB &85 ; Wide &6F: ctrl=&85
AE34 EQUB &00
AE35 EQUB &FD ; Wide &71: skip (dest station)
AE36 EQUB &FD
AE37 EQUB &7D
AE38 EQUB &FC ; Wide &74: buf start hi=page ptr
AE39 EQUB &FF ; Wide &75: buf start ext lo
AE3A EQUB &FF ; Wide &76: buf start ext hi
AE3B EQUB &7E ; Wide &77: buf end lo=&7E
AE3C EQUB &FC
AE3D EQUB &FF
AE3E EQUB &FF ; Wide &7A: buf end ext hi
AE3F EQUB &00 ; Wide &7B: zero
AE40 EQUB &00 ; Wide &7C: zero
AE41 EQUB &FE ; Narrow stop (&FE terminator)
AE42 EQUB &80
AE43 EQUB &93
AE44 EQUB &FD ; Narrow &0E: skip (dest station)
AE45 EQUB &FD
AE46 EQUB &D9
AE47 EQUB &FC ; Narrow &11: buf start hi=page ptr
AE48 EQUB &FF ; Narrow &12: buf start ext lo
AE49 EQUB &FF ; Narrow &13: buf start ext hi
AE4A EQUB &DE ; Narrow &14: buf end lo=&DE
AE4B EQUB &FC
AE4C EQUB &FF
AE4D EQUB &FF ; Narrow &17: buf end ext hi
AE4E EQUB &FE ; Spool stop (&FE terminator)
AE4F EQUB &D1
AE50 EQUB &FD
AE51 EQUB &FD ; Spool &03: skip (dest network)
AE52 EQUB &21
AE53 EQUB &FD
AE54 EQUB &FF ; Spool &06: buf start ext lo
AE55 EQUB &FF ; Spool &07: buf start ext hi
AE56 EQUB &FD ; Spool &08: skip (buf end lo)
AE57 EQUB &FD
AE58 EQUB &FF
AE59 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)
AE5A .netv_spool_check
DEX ; Step counter
AE5B CPX osword_pb_ptr ; Match osword_pb_ptr?
AE5D BNE return_from_spool_reset ; No: return (not our PB)
AE5F LDA vdu_status ; Load spool state byte
AE61 ROR ; Shift bit 0 into C
AE62 BCS return_from_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
AE64 .reset_spool_buf_state←2← 8F93 JSR← AEB5 JMP
LDA #&21 ; Buffer start offset = &21
AE66 STA spool_buf_idx ; Store as buffer pointer
AE69 LDA #&41 ; Control state &41
AE6B STA ws_0d6a ; Store as spool control state
AE6E .return_from_spool_reset←4← AE5D BNE← AE62 BCS← AE71 BNE← AE85 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
AE6F .netv_print_data
CPY #4 ; Check Y == 4
AE71 BNE return_from_spool_reset ; Non-zero: nothing to print, return
AE73 TXA ; A = X (control byte)
AE74 DEX ; Step counter back
AE75 BNE handle_spool_ctrl_byte ; Non-zero: handle spool ctrl byte
AE77 TSX ; Read MOS stack frame
AE78 ORA stack_page_6,x ; OR with stack value
AE7B STA stack_page_6,x ; Store back to stack
AE7E .loop_drain_printer_buf←2← AE8D BCC← AE92 BCC
LDA #osbyte_read_buffer ; OSBYTE &91: read buffer
AE80 LDX #buffer_printer ; X=3: printer buffer
AE82 JSR osbyte ; Read character from buffer
AE85 BCS return_from_spool_reset ; C set: return path
AE87 TYA ; A = extracted character
AE88 JSR append_byte_to_rxbuf ; Add byte to RX buffer
AE8B CPY #&6e ; Buffer past &6E limit?
AE8D BCC loop_drain_printer_buf ; No: read more from buffer
AE8F JSR process_spool_data ; Print accumulated spool data
AE92 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
AE94 .append_byte_to_rxbuf←3← AE88 JSR← AEAF JSR← AF66 JSR
LDY spool_buf_idx ; Y = spool_buf_idx
AE97 STA (net_rx_ptr),y ; Store A at (net_rx_ptr)+Y
AE99 INC spool_buf_idx ; Advance spool_buf_idx
AE9C 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
AE9D .handle_spool_ctrl_byte←2← 901F JSR← AE75 BNE
ROR ; Rotate bit 0 into carry
AE9E BCC check_spool_state ; C clear: take check_spool_state path
AEA0 LDA ws_0d6a ; Load spool control state Equal: take fill path
AEA3 PHA ; Save state byte
AEA4 ROR ; Rotate bit 0 into carry
AEA5 PLA ; Restore state
AEA6 BCS done_spool_ctrl ; C=1: already started, reset
AEA8 ORA #3 ; Set bits 0-1 (active + pending)
AEAA STA ws_0d6a ; Store updated state Stop: process_spool_data and return
AEAD LDA #3 ; A=3: spool-data result code
AEAF JSR append_byte_to_rxbuf ; Append result to RX buffer
AEB2 JSR process_spool_data ; Process the accumulated spool data
AEB5 .done_spool_ctrl←1← AEA6 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)
AEB8 .process_spool_data←4← AE8F JSR← AEB2 JSR← AEFB BCC← AF69 JSR
LDY #8 ; Y=8: buf_start_lo TXCB offset
AEBA LDA spool_buf_idx ; Load current spool-buffer index
AEBD STA (nfs_workspace),y ; Store at workspace+8 (buf_start_lo)
AEBF LDA net_rx_ptr_hi ; Load RX page (= net_rx_ptr_hi)
AEC1 INY ; Y=&09
AEC2 STA (nfs_workspace),y ; Store at workspace+9 (buf_start_hi)
AEC4 LDY #5 ; Y=5: alt buf_start_hi offset
AEC6 STA (nfs_workspace),y ; Store at workspace+5 (also buf-start hi)
AEC8 LDY #&0b ; Y=&0B: TXCB offset for following copy
AECA LDX #&26 ; X=&26: template offset for vclr region
AECC JSR ws_copy_vclr_entry ; Copy 12-byte ws-template region (V-clear)
AECF DEY ; Step back to offset &0A
AED0 LDA ws_0d6a ; Read shadow ACR (ws_0d6a)
AED3 PHA ; Save state
AED4 ROL ; Shift bit 7 into C
AED5 PLA ; Restore state
AED6 EOR #&80 ; Toggle bit 7
AED8 STA ws_0d6a ; Store updated shadow back to ws_0d6a
AEDB ROL ; Shift bit 0 into bit 1
AEDC STA (nfs_workspace),y ; Store at workspace+&0A
AEDE LDA vdu_status ; Read vdu_status
AEE0 PHA ; Push for later restore
AEE1 AND #&fe ; Clear bit 0 of vdu_status
AEE3 STA vdu_status ; Store updated
AEE5 LDY #&21 ; Y=&21: spool_buf_idx reset value
AEE7 STY spool_buf_idx ; Reset spool_buf_idx
AEEA LDA #0 ; A=0
AEEC TAX ; X=0
AEED LDY nfs_workspace_hi ; Y = workspace high page
AEEF CLI ; Re-enable IRQs (NMI window over)
AEF0 JSR send_disconnect_reply ; Send disconnect reply
AEF3 PLA ; Restore vdu_status
AEF4 STA vdu_status ; Restore vdu_status
AEF6 RTS ; Return
AEF7 .check_spool_state←1← AE9E BCC
LDA ws_0d6a ; Read shadow ACR
AEFA ROR ; Shift bit 0 into C
AEFB BCC process_spool_data ; C clear: re-process spool data
AEFD LDA vdu_status ; Read vdu_status
AEFF PHA ; Push for restore
AF00 AND #&fe ; Clear bit 0 of vdu_status
AF02 STA vdu_status ; Store updated
AF04 LDA #&14 ; A=&14: TX command byte
AF06 .start_spool_retry←1← AF7A BNE
PHA ; Save TX command
AF07 LDX #&0b ; X=&0B: tx_econet_txcb_template offset
AF09 LDY #&2c ; Y=&2C: dest TXCB offset
AF0B .loop_copy_spool_tx←1← AF12 BPL
LDA tx_econet_txcb_template,x ; Read template byte at tx_econet_txcb_template+X
AF0E STA (net_rx_ptr),y ; Store at (net_rx_ptr)+Y
AF10 DEY ; Decrement Y
AF11 DEX ; Decrement X
AF12 BPL loop_copy_spool_tx ; Loop until X wraps below 0
AF14 STX need_release_tube ; Store X (= &FF) as need_release_tube
AF16 LDY #2 ; Y=2: workspace offset for source
AF18 LDA (nfs_workspace),y ; Read (nfs_workspace)+2
AF1A PHA ; Save station
AF1B INY ; Y=3
AF1C LDA (nfs_workspace),y ; Read (nfs_workspace)+3
AF1E LDY #&24 ; Y=&24: dest offset in TXCB
AF20 STA (net_rx_ptr),y ; Store at (net_rx_ptr)+Y
AF22 DEY ; Y=&23
AF23 PLA ; Restore station
AF24 STA (net_rx_ptr),y ; Store at (net_rx_ptr)+Y
AF26 LDX #&0b ; X=&0B: rx_palette_txcb_template offset
AF28 LDY #&0b ; Y=&0B: dest offset in workspace
AF2A .loop_copy_spool_rx←1← AF3B BPL
LDA rx_palette_txcb_template,x ; Read template byte at rx_palette_txcb_template+X
AF2D CMP #&fd ; Compare with &FD (skip-byte marker)
AF2F BEQ advance_spool_rx_idx ; Equal: skip this byte
AF31 CMP #&fc ; Compare with &FC (page-ptr marker)
AF33 BNE store_spool_rx_byte ; Not &FC: store as-is
AF35 LDA net_rx_ptr_hi ; &FC: substitute net_rx_ptr_hi
AF37 .store_spool_rx_byte←1← AF33 BNE
STA (nfs_workspace),y ; Store at (nfs_workspace)+Y
AF39 .advance_spool_rx_idx←1← AF2F BEQ
DEY ; Next dest
AF3A DEX ; Next source
AF3B BPL loop_copy_spool_rx ; Loop until X wraps
AF3D LDA #&21 ; A=&21: TXCB control byte
AF3F STA net_tx_ptr ; Store at net_tx_ptr lo
AF41 LDA net_rx_ptr_hi ; Read net_rx_ptr_hi
AF43 STA net_tx_ptr_hi ; Store as net_tx_ptr hi
AF45 JSR setup_pass_txbuf ; Set up the pass-through TX buffer
AF48 JSR send_net_packet ; Send the TX packet
AF4B LDA #0 ; A=0: clear net_tx_ptr lo
AF4D STA net_tx_ptr ; Store -> net_tx_ptr lo
AF4F LDA nfs_workspace_hi ; Read nfs_workspace_hi
AF51 STA net_tx_ptr_hi ; Store -> net_tx_ptr hi
AF53 JSR wait_net_tx_ack ; Wait for TX ack
AF56 LDY #&2d ; Y=&2D: spool result-byte offset
AF58 LDA (net_rx_ptr),y ; Read result via (net_rx_ptr)+Y
AF5A BEQ spool_tx_succeeded ; Z: success path
AF5C CMP #3 ; Compare with 3 (retry threshold)
AF5E BNE spool_tx_retry ; Other: take retry path
AF60 .spool_tx_succeeded←1← AF5A BEQ
PLA ; Discard saved TX cmd
AF61 PLA ; Restore vdu_status
AF62 STA vdu_status ; Restore vdu_status
AF64 LDA #0 ; A=0: success-return code
AF66 JSR append_byte_to_rxbuf ; Append byte to RX buffer
AF69 JSR process_spool_data ; Recurse: process_spool_data
AF6C LDA ws_0d6a ; Read shadow ACR
AF6F AND #&f0 ; Mask high nibble
AF71 STA ws_0d6a ; Store updated shadow
AF74 RTS ; Return
AF75 .spool_tx_retry←1← AF5E BNE
TAX ; Save retry counter
AF76 PLA ; Pop saved TX cmd
AF77 SEC ; Set carry
AF78 SBC #1 ; Decrement retry
AF7A BNE start_spool_retry ; Non-zero: retry from start_spool_retry
AF7C CPX #1 ; Check the saved retry counter
AF7E BNE printer_busy_msg ; 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.

AF80 .err_printer_busy←1← B3B3 JMP
LDA #&a6 ; A=&A6: 'Printer busy' error code
AF82 JSR error_inline_log ; Raise via error_inline_log (never returns)
AF85 EQUS "Printer busy."
AF92 .printer_busy_msg←1← AF7E BNE
LDA #&a7 ; A=&A7: 'Printer jammed' error code
AF94 JSR error_inline_log ; Raise via error_inline_log (never returns)
AF97 EQUS "Printer jammed."
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
AFA6 .send_disconnect_reply←3← 97E6 JSR← AEF0 JSR← BCE2 JSR
STX net_tx_ptr ; X = caller's TX-ptr low byte
AFA8 STY net_tx_ptr_hi ; Y = caller's TX-ptr high byte
AFAA PHA ; Save A (the disconnect status to send)
AFAB ORA #0 ; Test if A=0 (broadcast disconnect)
AFAD BEQ send_disconnect_status ; Yes: skip the per-station scan
AFAF LDX #&ff ; X=&FF: scan counter -- INX in loop bumps to 0 X=&FF: start search from -1
AFB1 TAY ; Y=A: status code (also used as station-table key) Y = disconnect code
AFB2 .loop_scan_disconnect←2← AFBB BNE← AFC5 BNE
TYA ; Restore status into A for the compare
AFB3 INX ; Step station-table index
AFB4 CMP hazel_fcb_slot_attr,x ; Compare with table[X] at &C230 (per-station status) Compare with station table entry
AFB7 BEQ verify_stn_match ; Match: verify station address still matches
AFB9 CPX #&0f ; Reached end of 16-slot table?
AFBB BNE loop_scan_disconnect ; No: keep scanning
AFBD LDA #0 ; All slots tested, no match: A=0
AFBF BEQ send_disconnect_status ; Always taken: jump to send-status
AFC1 .verify_stn_match←1← AFB7 BEQ
TAY ; Y = matching index
AFC2 JSR match_station_net ; Verify station/network at this slot still matches caller Check station and network match
AFC5 BNE loop_scan_disconnect ; Mismatch: station moved, keep scanning
AFC7 LDA hazel_fcb_status,x ; Read connection-active flag at &C260+X
AFCA AND #1 ; Mask to bit 0 (active flag)
AFCC .send_disconnect_status←2← AFAD BEQ← AFBF BEQ
LDY #0 ; Y=0: TX[0] = control byte
AFCE ORA (net_tx_ptr),y ; OR active-flag bit into the status
AFD0 PHA ; Save the combined status
AFD1 STA (net_tx_ptr),y ; Write it to TX[0]
AFD3 JSR send_net_packet ; Send the disconnect packet via four-way handshake Send the packet
AFD6 LDA #&ff ; A=&FF: sentinel
AFD8 LDY #8 ; Y=8: TX[8] / TX[9] = packet trailer markers
AFDA STA (net_tx_ptr),y ; Write &FF at TX[8]
AFDC INY ; Step Y
AFDD STA (net_tx_ptr),y ; Write &FF at TX[9]
AFDF PLA ; Pull the saved status
AFE0 TAX ; Move into X for the test
AFE1 LDY #&d1 ; Y=&D1: control byte for ack-mode TXCB[1]
AFE3 PLA ; Pull caller's original A again (was double-saved) Check original disconnect code
AFE4 PHA ; Push it back
AFE5 BEQ store_tx_ctrl_byte ; A=0: skip the override
AFE7 LDY #&90 ; Non-zero: use Y=&90 (FS reply port instead) Non-zero: use &90 control
AFE9 .store_tx_ctrl_byte←1← AFE5 BEQ
TYA ; Move chosen control/port into A
AFEA LDY #1 ; Y=1: TX[1] is the port byte
AFEC STA (net_tx_ptr),y ; Write to TX[1]
AFEE TXA ; Move saved status into A
AFEF DEY ; Y=0: TX[0] for ack poll
AFF0 PHA ; Push the status (we'll EOR with reply below) Save status on stack
AFF1 .loop_wait_disc_tx_ack←1← AFFD BCS
LDA #&7f ; A=&7F: marker pattern
AFF3 STA (net_tx_ptr),y ; Write to TX[0]
AFF5 JSR wait_net_tx_ack ; Wait for the TX/RX flip
AFF8 PLA ; Pull saved status (peek without consuming) Restore status
AFF9 PHA ; Push it back
AFFA EOR (net_tx_ptr),y ; EOR with TX[0]: zero iff reply matches saved Compare with current TX buffer
AFFC ROR ; Rotate result; C set if bit 0 differs Rotate result bit 0 to carry
AFFD BCS loop_wait_disc_tx_ack ; C set: keep waiting
AFFF PLA ; Discard saved status
B000 PLA ; Discard caller's saved A
B001 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.

B002 .tx_econet_txcb_template←1← AF0B LDA
EQUB &80 ; ctrl=&80 (standard TX)
B003 EQUB &9F ; port=&9F
B004 EQUB &00 ; dest station=&00 (filled later)
B005 EQUB &00 ; dest network=&00 (filled later)
B006 EQUB &9F ; buf start lo (&9F)
B007 EQUB &8E ; buf start hi (&8E); start = &8E9F
B008 EQUB &FF ; buf start ext lo=&FF
B009 EQUB &FF ; buf start ext hi=&FF
B00A EQUB &A7 ; buf end lo (&A7)
B00B EQUB &8E ; buf end hi (&8E); end = &8EA7
B00C EQUB &FF ; buf end ext lo=&FF
B00D 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.

B00E .rx_palette_txcb_template←1← AF2A LDA
EQUB &7F ; ctrl=&7F (RX listen)
B00F EQUB &9E ; port=&9E
B010 EQUB &FD ; skip: preserve dest station
B011 EQUB &FD
B012 EQUB &2D
B013 EQUB &FC ; buf start hi=page ptr (&FC)
B014 EQUB &FF ; buf start ext lo=&FF
B015 EQUB &FF ; buf start ext hi=&FF
B016 EQUB &30
B017 EQUB &FC
B018 EQUB &FF ; buf end ext lo=&FF
B019 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.

B01A .lang_2_save_palette_vdu
LDA osword_flag ; Read osword_flag (preserved across the dispatch)
B01C PHA ; Save state byte
B01D LDA #&e9 ; A=&E9: workspace start lo for palette save
B01F STA nfs_workspace ; Store as nfs_workspace lo
B021 LDY #0 ; Y=0
B023 STY osword_flag ; Reset osword_flag = 0
B025 LDA vdu_screen_mode ; Read vdu_screen_mode (MOS state byte)
B028 STA (nfs_workspace),y ; Store at (nfs_workspace)+0
B02A INC nfs_workspace ; Advance nfs_workspace lo
B02C LDA vdu_display_start_hi ; Read vdu_display_start_hi (next MOS byte)
B02F PHA ; Save another byte
B030 TYA ; A=0 for first palette entry
B031 .loop_read_palette←1← B050 BNE
STA (nfs_workspace),y ; Store at (nfs_workspace)
B033 LDX nfs_workspace ; Read updated nfs_workspace lo
B035 LDY nfs_workspace_hi ; Read nfs_workspace hi
B037 LDA #osword_read_palette ; A=&0B: OSWORD &0B = read palette entry
B039 JSR osword ; Read palette entry
B03C PLA ; Restore inner saved
B03D LDY #0 ; Y=0
B03F STA (nfs_workspace),y ; Store palette result at workspace
B041 INY ; Y=1: physical colour offset
B042 LDA (nfs_workspace),y ; Re-read palette result
B044 PHA ; Save for next iteration
B045 LDX nfs_workspace ; Read updated workspace lo
B047 INC nfs_workspace ; Advance workspace
B049 INC osword_flag ; Increment osword_flag (palette index)
B04B DEY ; Y=0
B04C LDA osword_flag ; Read updated osword_flag
B04E CPX #&f9 ; Compare with &F9 (last palette entry)
B050 BNE loop_read_palette ; Not done: loop
B052 PLA ; Restore outer saved
B053 STY osword_flag ; Reset osword_flag = 0 after palette loop
B055 INC nfs_workspace ; Advance workspace
B057 JSR serialise_palette_entry ; Serialise the next palette entry
B05A INC nfs_workspace ; Advance workspace
B05C PLA ; Restore final saved
B05D 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
B05F .commit_state_byte←4← 9856 JMP← 987E JSR← 98B6 JSR← A997 JMP
LDA ws_0d69 ; Read shadow ACR copy from ws_0d69
B062 STA ws_0d68 ; Store as live ACR shadow at ws_0d68
B065 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)
B066 .serialise_palette_entry←1← B057 JSR
LDA vdu_mode ; Read vdu_mode (current palette index)
B069 ORA #&40 ; Mark as palette entry
B06B STA (nfs_workspace),y ; Store at (nfs_workspace)+Y
B06D LDX vdu_mode ; Read vdu_mode
B070 INC nfs_workspace ; Advance workspace
B072 TYA ; A = current Y (= 0)
B073 STA (nfs_workspace),y ; Store 0 at (nfs_workspace)+Y
B075 LDA read_osbyte_table,x ; Read lookup byte from read_osbyte_table+X
B078 LDX #0 ; X=0: indexed-indirect mode
B07A INC nfs_workspace ; Advance workspace
B07C STA (nfs_workspace,x) ; Store at (nfs_workspace,X)
B07E 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)
B081 .read_osbyte_to_ws_x0←1← B07E 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
B083 .read_osbyte_to_ws
LDY osword_flag ; Y = osword_flag (OSBYTE-table index)
B085 INC osword_flag ; Increment osword_flag for next call
B087 INC nfs_workspace ; Advance nfs_workspace
B089 LDA read_osbyte_return,y ; Load OSBYTE number from read_osbyte_return+Y
B08C LDY #&ff ; Y=&FF -- OSBYTE arg (read mode)
B08E JSR osbyte ; Issue OSBYTE
B091 TXA ; Result to A
B092 LDX #0 ; X=0: indexed-indirect mode
B094 STA (nfs_workspace,x) ; Store at (nfs_workspace,X)
B096 RTS ; Return
B097 .read_osbyte_return←1← B089 LDA
EQUB &C2, &C3
B099 .read_osbyte_table←1← B075 LDA
EQUS "000@XX`"
; *CDir command handler
; Parses an optional allocation size argument: if absent, ; defaults to
; index 2 (standard 19-entry directory, &200 bytes); if present,
; parses the decimal value and searches a 26-entry threshold ; table to
; find the matching allocation size index. Parses the directory ; name
; via parse_filename_arg, copies it to the TX buffer, and sends ; FS
; command code &1B to create the directory.
; Reached via PHA/PHA/RTS dispatch from cmd_table_fs entry
; *Cdir; the byte at the entry-1 address &B0A0
; happens to decode as JMP (cdir_unused_dispatch_table,X) but is ; never executed.
; On Entry:
;
Y: command line offset in text pointer
B0A0 .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
B0A3 JSR mask_owner_access ; Set owner-only access mask
B0A6 JSR skip_to_next_arg ; Skip to optional size argument
B0A9 CMP #&0d ; End of line?
B0AB BNE parse_cdir_size ; No: parse size argument
B0AD LDX #2 ; Default allocation size index = 2
B0AF BNE done_cdir_size ; ALWAYS branch
B0B1 .parse_cdir_size←1← B0AB BNE
LDA #&ff ; A=&FF: mark as decimal parse
B0B3 STA fs_work_4 ; Store decimal parse flag
B0B5 JSR parse_addr_arg ; Parse numeric size argument
B0B8 LDX #&1b ; X=&1B: top of 26-entry size table
B0BA .loop_find_alloc_size←1← B0BE BCC
DEX ; Try next lower index
B0BB CMP cdir_size_thresholds,x ; Compare size with threshold
B0BE BCC loop_find_alloc_size ; A < threshold: keep searching
B0C0 .done_cdir_size←1← B0AF BNE
STX hazel_txcb_data ; Store allocation size index
B0C3 PLA ; Restore command line offset
B0C4 TAY ; Transfer to Y
B0C5 JSR save_ptr_to_os_text ; Save text pointer for filename parse
B0C8 JSR parse_filename_arg ; Parse directory name argument
B0CB LDX #1 ; X=1: one argument to copy
B0CD JSR copy_arg_to_buf ; Copy directory name to TX buffer
B0D0 LDY #&1b ; Y=&1B: *CDir FS command code
B0D2 .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.

B0D5 EQUB &00 ; Index 1: threshold 0 (catch-all)
B0D6 EQUB &0A ; Index 2: threshold 10 (default)
B0D7 EQUB &14 ; Index 3: threshold 20
B0D8 EQUB &1D ; Index 4: threshold 29
B0D9 EQUB &27
B0DA EQUB &31
B0DB EQUB &3B ; Index 7: threshold 59
B0DC EQUB &45 ; Index 8: threshold 69
B0DD EQUB &4F
B0DE EQUB &58 ; Index 10: threshold 88
B0DF EQUB &62 ; Index 11: threshold 98
B0E0 EQUB &6C ; Index 12: threshold 108
B0E1 EQUB &76
B0E2 EQUB &80
B0E3 EQUB &8A ; Index 15: threshold 138
B0E4 EQUB &94 ; Index 16: threshold 148
B0E5 EQUB &9D
B0E6 EQUB &A7 ; Index 18: threshold 167
B0E7 EQUB &B1 ; Index 19: threshold 177
B0E8 EQUB &BB
B0E9 EQUB &C5 ; Index 21: threshold 197
B0EA EQUB &CF
B0EB EQUB &D8 ; Index 23: threshold 216
B0EC EQUB &E2 ; Index 24: threshold 226
B0ED EQUB &EC ; Index 25: threshold 236
B0EE .cdir_size_done←1← 8F84 LDA
EQUB &F6 ; Index 26: threshold &F6 (246) -- last cdir-size threshold; doubles as cdir_size_done[0] (unread by init loop)
B0EF EQUB &FF ; cdir_size_done[1] = &FF -> tx_retry_count (retry counter init)
B0F0 EQUB &28 ; cdir_size_done[2] = &28 -> rx_wait_timeout (40 retries)
B0F1 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)
B0F2 .cmd_lcat
ROR hazel_fs_lib_flags ; Rotate carry into lib flag bit 7
B0F5 SEC ; Set carry (= library directory)
B0F6 BCS cat_set_lib_flag ; ALWAYS branch

*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)
B0F8 .cmd_lex
ROR hazel_fs_lib_flags ; Rotate carry into lib flag bit 7
B0FB SEC ; Set carry (= library directory)
B0FC BCS ex_set_lib_flag ; ALWAYS branch
B0FE .ps_scan_resume
JSR set_text_and_xfer_ptr ; Set OS text pointer and FS-options transfer ptr
B101 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
B103 .cmd_ex
ROR hazel_fs_lib_flags ; Rotate carry into lib flag bit 7
B106 CLC ; Clear carry (= current directory)
B107 .ex_set_lib_flag←1← B0FC BCS
ROL hazel_fs_lib_flags ; Rotate carry back, clearing bit 7
B10A LDA #&ff ; A=&FF: initial column counter
B10C STA fs_spool_handle ; Store column counter
B10E LDA #1 ; One entry per line (Ex format)
B110 STA fs_work_7 ; Store entries per page
B112 LDA #3 ; FS command code 3: Examine
B114 STA fs_work_5 ; Store command code
B116 BNE setup_ex_request ; ALWAYS branch

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.

B118 .fscv_5_cat
JSR set_xfer_params ; Set transfer parameters
B11B LDY #0 ; Y=0: start from entry 0
B11D ROR hazel_fs_lib_flags ; Rotate carry into lib flag
B120 CLC ; Clear carry (= current directory)
B121 .cat_set_lib_flag←1← B0F6 BCS
ROL hazel_fs_lib_flags ; Rotate carry back, clearing bit 7
B124 LDA #3 ; Three entries per column (Cat)
B126 STA fs_spool_handle ; Store column counter
B128 STA fs_work_7 ; Store entries per page
B12A LDA #&0b ; FS command code &0B: Catalogue
B12C STA fs_work_5 ; Store command code
B12E .setup_ex_request←1← B116 BNE
JSR save_ptr_to_os_text ; Save text pointer
B131 LDA #&ff ; A=&FF: enable escape checking
B133 STA need_release_tube ; Set escapable flag
B135 LDA #6 ; Command code 6
B137 STA hazel_txcb_data ; Store in TX buffer
B13A JSR parse_filename_arg ; Parse directory argument
B13D LDX #1 ; X=1: offset in buffer
B13F JSR copy_arg_to_buf ; Copy argument to TX buffer
B142 LDA hazel_fs_lib_flags ; Get library/FS flags
B145 LSR ; Shift bit 0 to carry
B146 BCC store_owner_flags ; Bit 0 clear: skip
B148 ORA #&40 ; Set bit 6 (owner access flag)
B14A .store_owner_flags←1← B146 BCC
ROL ; Rotate back
B14B STA hazel_fs_lib_flags ; Store modified flags
B14E LDY #&12 ; Y=&12: FS command for examine
B150 JSR save_net_tx_cb ; Send request to file server
B153 LDX #3 ; X=3: offset to directory title
B155 JSR print_10_chars ; Print directory title (10 chars)
B158 JSR print_inline_no_spool ; Print '('
B15B EQUS "("
B15C LDA hazel_txcb_objtype ; Load FS object-type code from hazel_txcb_objtype (file/dir/etc)
B15F JSR print_decimal_3dig_no_spool
B162 JSR print_inline_no_spool ; Print ') ' to close the type-code field
B165 EQUS ") "
B16B LDY hazel_txcb_type ; Read hazel_txcb_type (FS reply opcode)
B16E BNE print_public_label ; Non-zero (private library): take the public-label branch
B170 JSR print_inline_no_spool ; Print 'Owner' + CR
B173 EQUS "Owner."
B179 BNE cat_after_label_print ; Non-zero: branch to cat_after_label_print
B17B .print_public_label←1← B16E BNE
JSR print_inline_no_spool ; Print 'Public' + CR
B17E EQUS "Public."
B185 .cat_after_label_print←1← B179 BNE
LDA hazel_fs_lib_flags ; Read hazel_fs_lib_flags
B188 PHA ; Push for stack-based saves
B189 JSR mask_owner_access ; Mask owner access bits
B18C LDY #&15 ; Y=&15: FS command for dir info
B18E JSR save_net_tx_cb ; Send request to file server
B191 INX ; Advance X past header
B192 LDY #&10 ; Y=&10: print 16 chars
B194 JSR print_chars_from_buf ; Print file entry
B197 JSR print_inline_no_spool ; Print ' Option '
B19A EQUS " Option "
B1A5 LDA hazel_fs_flags ; Read hazel_fs_flags
B1A8 TAX ; Transfer to X for table lookup
B1A9 JSR print_hex_byte_no_spool ; Print option as hex
B1AC JSR print_inline_no_spool ; Print ' ('
B1AF EQUS " ("
B1B1 LDY option_str_offset_data,x ; Look up option-string offset for index X
B1B4 .loop_print_dir_format←1← B1BD BNE
LDA option_offset_table,y ; Look up option byte at the resolved offset
B1B7 BMI print_dir_header ; Bit 7 of A set (negative): print directory header
B1B9 JSR print_char_no_spool ; Print char (no spool)
B1BC INY ; Advance Y
B1BD BNE loop_print_dir_format ; Loop until Y wraps
B1BF .print_dir_header←1← B1B7 BMI
JSR print_inline_no_spool ; Print ')\rDir. ' header for the directory listing
B1C2 EQUS ").Dir. "
B1C9 LDX #&11 ; X=&11: filename offset in TX buffer
B1CB JSR print_10_chars ; Print 10-char filename
B1CE JSR print_inline_no_spool ; Print inline 'attr-bits' fragment
B1D1 EQUS " Lib. " ; label for *Ex output
B1DB LDX #&1b ; X=&1B: extension offset in TX buffer
B1DD JSR print_10_chars ; Print 10-char extension
B1E0 JSR print_newline_no_spool ; Print newline
B1E3 PLA ; Pop saved counter
B1E4 STA hazel_fs_lib_flags ; Store as fs_lib_flags
B1E7 .setup_ex_pagination←1← B218 BNE
STY hazel_txcb_flag ; Save Y as hazel_txcb_flag (next-entry index)
B1EA STY fs_work_4 ; Save Y as fs_work_4
B1EC LDX fs_work_5 ; Load fs_work_5 (page count)
B1EE STX hazel_txcb_count ; Store at hazel_txcb_count
B1F1 LDX fs_work_7 ; Load fs_work_7
B1F3 STX hazel_txcb_data ; Store at hazel_txcb_data
B1F6 LDX #3 ; X=3: TX[3] is start of arg buffer
B1F8 JSR copy_arg_to_buf ; Copy filename arg
B1FB LDY #3 ; Y=3: cmd code 3 (catalog)
B1FD JSR save_net_tx_cb ; Send TX request
B200 INX ; X advances entry counter
B201 LDA hazel_txcb_data ; Read reply status
B204 BEQ jmp_osnewl ; Z: empty reply -> exit cat
B206 PHA ; Push reply status
B207 .loop_scan_entry_data←1← B20B BPL
INY ; Advance Y
B208 LDA hazel_txcb_data,y ; Read entry byte from hazel_txcb_data+Y
B20B BPL loop_scan_entry_data ; Bit 7 clear: keep scanning
B20D STA hazel_txcb_lib,y ; Store with high-bit clear at hazel_txcb_lib+Y
B210 JSR ex_print_col_sep ; Print column separator
B213 PLA ; Pop saved status
B214 CLC ; Clear carry for the ADC below
B215 ADC fs_work_4 ; Add fs_work_4 (page accumulator)
B217 TAY ; New index
B218 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
B21A .print_10_chars←3← B155 JSR← B1CB JSR← B1DD 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
B21C .print_chars_from_buf←2← B194 JSR← B224 BNE
LDA hazel_txcb_data,x ; Read next character from reply buffer at offset X
B21F JSR print_char_no_spool ; Print via OSASCI, bypassing the *SPOOL file
B222 INX ; Step buffer offset
B223 DEY ; Step character counter
B224 BNE print_chars_from_buf ; Loop until Y=0
B226 RTS ; Return; X points just past the last printed byte
B227 .jmp_osnewl←1← B204 BEQ
JMP 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
B22A .parse_cmd_arg_y0←3← 9FE6 JSR← A4FC JSR← B367 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
B22C .parse_filename_arg←3← B0C8 JSR← B13A JSR← B6FD 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.

B22F .parse_access_prefix←4← 942C JSR← 94CF JSR← 9505 JSR← 9C2B JSR
LDA hazel_parse_buf ; Read first parsed-buffer character (the candidate prefix)
B232 EOR #&26 ; EOR with '&'; Z set iff the byte was '&'
B234 BNE check_colon_prefix ; Not '&': try ':' (and '#') instead
B236 LDA hazel_fs_lib_flags ; Read fs_lib_flags
B239 ORA #&40 ; Set bit 6 (URD-relative resolution flag)
B23B STA hazel_fs_lib_flags ; Write back updated flags
B23E JSR strip_token_prefix ; Strip the '&' from the buffer (shift left + trim)
B241 DEX ; Step caller's X back to account for the consumed character
B242 LDA hazel_parse_buf ; Re-read the (now first) buffer byte after the strip
B245 EOR #&2e ; EOR with '.'; Z set iff '&.' pair (URD root)
B247 BNE check_hash_prefix ; Not '&.': just '&' alone -- check for trailing '#'
B249 LDA hazel_parse_buf_1 ; It was '&.': peek the byte after the dot
B24C EOR #&0d ; EOR with CR; Z set iff '&.CR>' (illegal: dot needs a name to follow)
B24E BEQ error_bad_prefix ; '&.CR>' is invalid: raise 'Bad filename'
B250 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
B251 .strip_token_prefix←5← 9459 JSR← 94EE JSR← 94F3 JSR← B23E JSR← B293 JSR
TXA ; Save caller's X (TX buffer offset)
B252 PHA ; Push it
B253 LDX #&ff ; X=&FF: INX in loop bumps to 0 for first byte
B255 .loop_shift_str_left←1← B25E BNE
INX ; Step to next byte position
B256 LDA hazel_parse_buf_1,x ; Read byte X+1 (the next character)
B259 STA hazel_parse_buf,x ; Store it back at byte X (shifting left by one)
B25C EOR #&0d ; EOR with CR; Z set if we just shifted the terminator
B25E BNE loop_shift_str_left ; More to shift: continue
B260 TXA ; X is now the buffer length (excluding CR)
B261 BEQ done_strip_prefix ; Empty after shift: skip trim, restore X, return
B263 .loop_trim_trailing←1← B270 BNE
LDA hazel_parse_buf_m1,x ; Read last buffer byte (X-1 because we count from 0)
B266 EOR #&20 ; EOR with space; Z set iff it's a trailing space
B268 BNE done_strip_prefix ; Not a space: trim done, restore X, return
B26A LDA #&0d ; It is a space: replace with CR (truncate the string)
B26C STA hazel_parse_buf_m1,x ; Store CR at the now-trimmed position
B26F DEX ; Step backwards
B270 BNE loop_trim_trailing ; Loop while X > 0
B272 .done_strip_prefix←2← B261 BEQ← B268 BNE
PLA ; Restore caller's TX buffer offset
B273 TAX ; Transfer back to X
B274 .return_from_strip_prefix←3← B277 BEQ← B27E BNE← B289 BNE
RTS ; Return
B275 .check_hash_prefix←1← B247 BNE
EOR #&23 ; Test for '#' prefix (3 ^ &23 = 0)
B277 BEQ return_from_strip_prefix ; Equal: '#' was the prefix, return
B279 .error_bad_prefix←2← B24E BEQ← B2AF BEQ
JMP error_bad_filename ; Other: not a recognised prefix -> error
B27C .check_colon_prefix←1← B234 BNE
EOR #&1c ; Test for ':' (&3F ^ &1C)
B27E BNE return_from_strip_prefix ; Different: caller had no prefix, return
B280 LDA hazel_parse_buf_2 ; ':' confirmed -- read next char from parse buffer
B283 EOR #&2e ; Test for '.' (path separator)
B285 BEQ set_fs_select_flag ; Equal: ':.' qualified prefix
B287 EOR #&23 ; Test for '#'
B289 BNE return_from_strip_prefix ; Other: no recognised tail prefix, return
B28B .set_fs_select_flag←1← B285 BEQ
LDA hazel_fs_lib_flags ; Recognised: load fs_lib_flags
B28E ORA #&40 ; Set bit 6 (FS-select pending)
B290 STA hazel_fs_lib_flags ; Store updated fs_lib_flags
B293 JSR strip_token_prefix ; Recurse to strip the trailing component
B296 DEX ; Decrement X (consume processed char)
B297 RTS ; Return
B298 .option_str_offset_data←1← B1B1 LDY
EQUB &00 ; Data: option string offset table
B299 EQUS "/<c"
B29C .option_offset_table←1← B1B4 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
B29F .copy_arg_to_buf_x0←5← 8DD5 JSR← 8E38 JSR← 9C39 JSR← 9E29 JSR← A5DF JSR
LDX #0 ; X=0: place the argument at the start of the TX buffer; fall into copy_arg_to_buf X=0: start of buffer
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
B2A1 .copy_arg_to_buf←11← 9CEB JSR← 9E22 JSR← 9E45 JSR← 9FEB JSR← A501 JSR← A52E JSR← B0CD JSR← B13F JSR← B1F8 JSR← B36C JSR← B712 JSR
LDY #0 ; Y=0: scan from start of command line (CLC entry skips '&' validation) Y=0: start of argument
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
B2A3 .copy_arg_validated←3← 8DD0 JSR← 9553 JSR← 9589 JSR
SEC ; Set C: this entry validates against '&'
B2A4 .loop_copy_char←1← B2B7 BNE
LDA (fs_crc_lo),y ; Read next source byte through fs_crc_lo pointer Get character from command line
B2A6 STA hazel_txcb_data,x ; Store into TX buffer at offset X
B2A9 BCC advance_positions ; Validation off (C clear): just advance positions Carry clear: skip validation
B2AB CMP #&21 ; Test against '!' to bias the EOR comparison
B2AD EOR #&26 ; EOR with '&'; Z set iff source byte was '&'
B2AF BEQ error_bad_prefix ; '&' inside the argument is illegal: raise 'Bad filename' Yes: '&' not allowed in filenames
B2B1 .restore_after_check
EOR #&26 ; Restore A by undoing the EOR (so the loop terminator test below sees the original byte) '&' in filename: bad filename
B2B3 .advance_positions←1← B2A9 BCC
INX ; Advance TX buffer offset
B2B4 INY ; Advance command-line offset
B2B5 EOR #&0d ; EOR with CR; Z set iff we just stored the terminator Is it CR (end of line)?
B2B7 BNE loop_copy_char ; More to copy: continue
B2B9 .loop_trim_trailing_spaces←1← B2C6 BNE
LDA hazel_txcb_network,x ; Look at the byte just before the CR we stopped on Load character from end of buffer
B2BC EOR #&20 ; EOR with space; Z set iff that byte was a trailing space Test for space (&20)
B2BE BNE done_trim_spaces ; Not a space: trim done
B2C0 DEX ; Step back over the space
B2C1 LDA #&0d ; A=&0D: replace the trailing space with CR
B2C3 STA hazel_txcb_lib,x ; Store CR at the now-truncated end
B2C6 BNE loop_trim_trailing_spaces ; Always taken (A=&0D from LDA #&0D so Z is clear); look at the next byte back ALWAYS: trim next character back
B2C8 .done_trim_spaces←1← B2BE BNE
LDA #0 ; All trailing spaces consumed (or none present) A=0: success return code
B2CA .return_from_copy_arg←1← B2E0 BMI
RTS ; Return
B2CB 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
B2CF .mask_owner_access←15← 8E53 JSR← 94C9 JSR← 9501 JSR← 9C28 JSR← A036 JSR← A153 JSR← A4E7 JSR← A4F4 JSR← A585 JSR← A58F JSR← AC52 JSR← B0A3 JSR← B189 JSR← B357 JSR← B6F3 JSR
LDA hazel_fs_lib_flags ; Read fs_lib_flags (now at &C271 in 4.21)
B2D2 AND #&1f ; Keep only the 5-bit owner access mask
B2D4 STA hazel_fs_lib_flags ; Store back, clearing FS-selection and other high bits Store masked flags
B2D7 RTS ; Return
B2D8 EQUS "Run"
B2DB .ex_init_scan_x0
LDX #0 ; X=0: scan from start of TX entry
B2DD .loop_scan_entries←1← B2FD BNE
LDA hazel_txcb_data,x ; Read entry byte at hazel_txcb_data+X
B2E0 BMI return_from_copy_arg ; Bit 7 set: end-of-entries -> return
B2E2 BNE col_sep_print_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.

B2E4 .ex_print_col_sep←1← B210 JSR
LDY fs_spool_handle ; Read fs_spool_handle (also column counter in *Cat mode)
B2E6 BMI col_sep_eol_check ; Negative: *Ex mode (one-per-line) -- skip column logic, just print newline
B2E8 INY ; Bump column counter
B2E9 TYA ; Get the new value into A
B2EA AND #3 ; Wrap to 0..3 (4 columns per row)
B2EC STA fs_spool_handle ; Save the new column index
B2EE BEQ col_sep_eol_check ; Wrapped to 0: end of row, print newline
B2F0 JSR print_inline_no_spool ; Mid-row: print 2-space column separator via inline
B2F3 EQUS " "
B2F5 BNE col_sep_print_char ; Non-zero: take col_sep_print_char tail
B2F7 .col_sep_eol_check←2← B2E6 BMI← B2EE BEQ
LDA #&0d ; A=&0D: CR character
B2F9 .col_sep_print_cr←1← B2E2 BNE
JSR print_char_no_spool ; Print CR (no spool)
B2FC .col_sep_print_char←1← B2F5 BNE
INX ; Next entry
B2FD BNE loop_scan_entries ; Loop until X wraps
B2FF EOR zp_0078
B301 ADC 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
B303 .print_decimal_3dig_no_spool←1← B15F JSR
TAY ; Y = value to convert (digits read off via successive divisions)
B304 LDA #&64 ; Divisor for hundreds digit
B306 JSR print_decimal_digit_no_spool ; Print hundreds digit
B309 LDA #&0a ; Divisor for tens digit
B30B JSR print_decimal_digit_no_spool ; Print tens digit
B30E 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 (&B338) 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
B310 .print_decimal_digit_no_spool←2← B306 JSR← B30B JSR
STA fs_error_ptr ; Stash divisor in fs_error_ptr (the SBC target below)
B312 TYA ; Convert remaining value to A
B313 LDX #&2f ; X = '0'-1: digit counter, INX in the loop steps to '0' first
B315 SEC ; Set carry
B316 .loop_divide_decimal_digit←1← B319 BCS
INX ; Step quotient digit
B317 SBC fs_error_ptr ; Subtract divisor
B319 BCS loop_divide_decimal_digit ; No underflow: keep dividing
B31B ADC fs_error_ptr ; Underflow: add divisor back to recover the remainder
B31D TAY ; Remainder -> Y, ready for the next digit
B31E TXA ; Move digit ('0'-'9') from X into A for printing
B31F LDX fs_error_ptr ; Save divisor in X across the print (print_char_no_spool preserves X is not guaranteed)
B321 JSR print_char_no_spool ; Print the digit, bypassing *SPOOL
B324 STX fs_error_ptr ; Restore divisor from X
B326 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)
B327 .print_num_no_leading←5← 8A79 JSR← 90DD JSR← 9665 JSR← 9676 JSR← 9683 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
B32A .print_decimal_3dig←2← B55B JSR← B572 JMP
TAY ; Transfer value to Y (remainder)
B32B LDA #&64 ; A=100: hundreds divisor
B32D JSR print_decimal_digit ; Print hundreds digit
B330 LDA #&0a ; A=10: tens divisor
B332 JSR print_decimal_digit ; Print tens digit
B335 CLV ; Clear V (always print units)
B336 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
B338 .print_decimal_digit←2← B32D JSR← B332 JSR
STA fs_error_ptr ; Store divisor
B33A TYA ; Get remaining value
B33B LDX #&2f ; X='0'-1: digit counter
B33D SEC ; Set carry for subtraction
B33E PHP ; Save V flag for leading zero check
B33F .loop_divide_digit←1← B342 BCS
INX ; Count quotient digit
B340 SBC fs_error_ptr ; Subtract divisor
B342 BCS loop_divide_digit ; No underflow: continue dividing
B344 ADC fs_error_ptr ; Add back divisor (get remainder)
B346 TAY ; Remainder to Y for next digit
B347 TXA ; Digit character to A
B348 PLP ; Restore V flag
B349 BVC print_nonzero_digit ; V clear: always print digit
B34B CMP #&30 ; V set: is digit '0'?
B34D BEQ return_from_print_digit ; Yes: suppress leading zero
B34F .print_nonzero_digit←1← B349 BVC
LDX fs_error_ptr ; Save divisor across OSASCI call
B351 JSR osasci ; Write character
B354 STX fs_error_ptr ; Restore divisor
B356 .return_from_print_digit←1← B34D 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
B357 .cmd_info_dispatch
JSR mask_owner_access ; Clear owner-only access bits before checking the URD
B35A LDA #&69 ; A=&69: 'i' character (info prefix)
B35C STA hazel_txcb_data ; Store 'i' as start of FS command name in the TX buffer
B35F LDA #&2e ; A='.': abbreviation terminator
B361 STA hazel_txcb_flag ; Store '.' as command-name terminator
B364 JSR save_ptr_to_os_text ; Save the command-line pointer for the dispatcher
B367 JSR parse_cmd_arg_y0 ; Parse the *Info argument from the command line
B36A LDX #2 ; X=2: TX-buffer offset to copy the arg into (after 'i.')
B36C JSR copy_arg_to_buf ; Append parsed argument to the TX command buffer
B36F TAY ; A = next index
B370 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)
B373 .save_ptr_to_os_text←9← A03C JSR← A4E4 JSR← A4F1 JSR← B0C5 JSR← B12E JSR← B364 JSR← B3F6 JSR← B5D4 JSR← B6FA JSR
PHA ; Save A
B374 LDA fs_crc_lo ; Copy text pointer low byte
B376 STA os_text_ptr ; To OS text pointer low
B378 LDA fs_crc_hi ; Copy text pointer high byte
B37A STA os_text_ptr_hi ; To OS text pointer high
B37C PLA ; Restore A
B37D RTS ; Return
B37E .loop_advance_char←1← B389 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
B37F .skip_to_next_arg←1← B0A6 JSR
LDA (fs_crc_lo),y ; Load char from command line
B381 CMP #&20 ; Space?
B383 BEQ loop_skip_space_chars ; Yes: skip trailing spaces
B385 CMP #&0d ; CR (end of line)?
B387 BEQ return_from_skip_arg ; Yes: return (at end)
B389 BNE loop_advance_char ; ALWAYS branch
B38B .loop_skip_space_chars←2← B383 BEQ← B390 BEQ
INY ; Advance past space
B38C LDA (fs_crc_lo),y ; Load next character
B38E CMP #&20 ; Still a space?
B390 BEQ loop_skip_space_chars ; Yes: skip multiple spaces
B392 .return_from_skip_arg←1← B387 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)
B393 .save_ptr_to_spool_buf←2← B3B9 JSR← B590 JSR
PHA ; Save A
B394 LDA fs_crc_lo ; Copy text pointer low byte
B396 STA fs_options ; To spool buffer pointer low
B398 LDA fs_crc_hi ; Copy text pointer high byte
B39A STA fs_block_offset ; To spool buffer pointer high
B39C PLA ; Restore A
B39D 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)
B39E .init_spool_drive←2← B3B6 JSR← B583 JSR
TYA ; Save Y
B39F PHA ; Push it
B3A0 JSR get_ws_page ; Get workspace page number
B3A3 STA addr_work ; Store as spool drive page high
B3A5 PLA ; Restore Y
B3A6 TAY ; Transfer to Y
B3A7 LDA #0 ; A=0
B3A9 STA work_ae ; Clear spool drive page low
B3AB 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
B3AC .cmd_ps
LDA #1 ; A=1: check printer ready
B3AE BIT ws_0d6a ; Test printer server workspace flag
B3B1 BNE done_ps_available ; Non-zero: printer available
B3B3 JMP err_printer_busy ; Printer not available: error
B3B6 .done_ps_available←1← B3B1 BNE
JSR init_spool_drive ; Initialise spool drive
B3B9 JSR save_ptr_to_spool_buf ; Save pointer to spool buffer
B3BC LDA (fs_options),y ; Read fs_options[Y]
B3BE CMP #&0d ; End of command line?
B3C0 BEQ no_ps_name_given ; Yes: no argument given
B3C2 CLV ; Clear V (= explicit PS name given)
B3C3 JSR is_decimal_digit ; Is first char a decimal digit?
B3C6 BCC save_ps_cmd_ptr ; C clear: save ptr and continue
B3C8 TYA ; A = current Y
B3C9 PHA ; Save Y
B3CA JSR load_ps_server_addr ; Load PS server address
B3CD PLA ; Restore Y
B3CE TAY ; Back to Y register
B3CF JSR parse_fs_ps_args ; Parse FS/PS arguments
B3D2 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)
B3D5 .copy_ps_data_y1c←1← 8F5B JSR
LDY #&18 ; Y=&18: standard offset for the PS template; fall into copy_ps_data Start at offset &18
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 (&8E9F). 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
B3D7 .copy_ps_data←1← B5B6 JSR
LDX #&f8 ; X=&F8: walks 0..7 via wraparound (loads from ps_template_base+&F8 = ps_template_data &8E9F) X=&F8: offset into template
B3D9 .loop_copy_ps_tmpl←1← B3E0 BNE
LDA ps_template_base,x ; Read template byte from ps_template_data + (X-&F8) Get template byte
B3DC STA (net_rx_ptr),y ; Store into RX buffer at offset Y
B3DE INY ; Step destination
B3DF INX ; Step source -- wraps from &FF to &00 to terminate Next source offset
B3E0 BNE loop_copy_ps_tmpl ; Loop while X != 0 (8 iterations: &F8..&FF)
B3E2 RTS ; Return
B3E3 .no_ps_name_given←1← B3C0 BEQ
BIT always_set_v_byte ; Set V (= no explicit PS name)
B3E6 .save_ps_cmd_ptr←1← B3C6 BCC
STY ws_ptr_hi ; Save Y at ws_ptr_hi
B3E8 BVS done_ps_name_parse ; V set: skip PS name parsing
B3EA LDX #6 ; Max 6 characters for PS name
B3EC LDY #&18 ; Buffer offset &1C for PS name
B3EE LDA #&20 ; Space character
B3F0 .loop_pad_ps_name←1← B3F4 BNE
STA (net_rx_ptr),y ; Fill buffer with space
B3F2 INY ; Advance Y past padding
B3F3 DEX ; Count down
B3F4 BNE loop_pad_ps_name ; Loop while Y wraps
B3F6 JSR save_ptr_to_os_text ; Save text pointer
B3F9 LDY ws_ptr_hi ; Restore Y from ws_ptr_hi
B3FB JSR gsinit ; Initialise string reading
B3FE BEQ done_ps_name_parse ; Empty string: skip to send
B400 LDX #6 ; X=6: scan up to 6 PS slots
B402 STY ws_ptr_hi ; Save updated string pointer
B404 LDY #&18 ; Buffer offset for PS name
B406 STY table_idx ; Save buffer position
B408 .loop_read_ps_char←1← B414 BNE
LDY ws_ptr_hi ; Restore string pointer
B40A JSR gsread ; Read next character
B40D STY ws_ptr_hi ; Save updated pointer
B40F BCS done_ps_name_parse ; C set: end of slots
B411 JSR store_char_uppercase ; Store char uppercased in buffer
B414 BNE loop_read_ps_char ; Loop for more characters
B416 .done_ps_name_parse←3← B3E8 BVS← B3FE BEQ← B40F BCS
JSR reverse_ps_name_to_tx ; Copy reversed PS name to TX
B419 JSR send_net_packet ; Send PS status request
B41C JSR pop_requeue_ps_scan ; Pop and requeue PS scan
B41F JSR load_ps_server_addr ; Load PS server address
B422 LDA #0 ; A=0
B424 TAX ; X=&00
B425 LDY #&20 ; Offset &24 in buffer
B427 STA (net_rx_ptr),y ; Clear PS status byte
B429 .loop_pop_ps_slot←1← B455 BNE
PLA ; Pop saved slot index
B42A BEQ done_ps_scan ; Zero: all slots done
B42C PHA ; Push it back (for retry)
B42D TAY ; Transfer to Y
B42E LDA (nfs_workspace),y ; Read slot status
B430 BPL done_ps_slot_mark ; Bit 7 clear: slot inactive
B432 JSR advance_y_by_4 ; Advance Y by 4 (next slot)
B435 LDA (nfs_workspace),y ; Read ws byte at (nfs_workspace)+Y
B437 STA work_ae ; Save as work_ae lo
B439 LDA (work_ae,x) ; Read indirect via (work_ae,X)
B43B BEQ read_ps_station_addr ; Z set: zero -> read station addr
B43D CMP #3 ; Compare with 3
B43F BNE done_ps_slot_mark ; Other than 3: skip slot mark
B441 .read_ps_station_addr←1← B43B BEQ
DEY ; Back up to network byte
B442 LDA (nfs_workspace),y ; Read network byte
B444 STA fs_work_6 ; Save as fs_work_6
B446 DEY ; Back up to station byte
B447 LDA (nfs_workspace),y ; Read station byte
B449 STA fs_work_5 ; Save as fs_work_5
B44B LDY #&20 ; Y=&20: PS marker offset
B44D STA (net_rx_ptr),y ; Store station to (net_rx_ptr)+&20
B44F .done_ps_slot_mark←2← B430 BPL← B43F BNE
PLA ; Pop saved slot index
B450 TAY ; Transfer to Y
B451 LDA #&3f ; A=&3F: 'processed' marker
B453 STA (nfs_workspace),y ; Mark slot as processed
B455 BNE loop_pop_ps_slot ; ALWAYS branch
B457 .done_ps_scan←1← B42A BEQ
JSR print_printer_server_is ; Print 'Printer server is ' fragment
B45A LDY #&20 ; Y=&20: marker offset
B45C LDA (net_rx_ptr),y ; Read marker byte
B45E BNE print_ps_now ; Non-zero: print 'now '
B460 JSR print_inline ; Print 'still ' fragment
B463 EQUS "still "
B469 CLV ; Bit-7 terminator (next opcode)
B46A BVC print_ps_padding ; ALWAYS branch
B46C .print_ps_now←1← B45E BNE
JSR print_inline ; Print 'now ' fragment
B46F EQUS "now "
B473 NOP ; Bit-7 terminator
B474 .print_ps_padding←1← B46A 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).

B477 .store_ps_station←1← B3D2 JMP
LDY #2 ; Y=2: workspace offset for stored station
B479 LDA fs_work_5 ; Load station number
B47B STA (nfs_workspace),y ; Store at (nfs_workspace)+2
B47D INY ; Y=&03
B47E LDA fs_work_6 ; Load network number
B480 STA (nfs_workspace),y ; Store at (nfs_workspace)+3
B482 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)
B483 .print_file_server_is←1← A3B8 JSR
JSR print_inline ; Print 'File' via inline string
B486 EQUS "File"
B48A CLV ; Clear V so the BVC below is taken
B48B 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)
B48D .print_printer_server_is←2← B457 JSR← B5FE JSR
JSR print_inline ; Print 'Printer' via inline string
B490 EQUS "Printer"
B497 NOP ; NOP -- bit-7 terminator + harmless resume opcode
B498 .print_server_is_suffix←1← B48B BVC
JSR print_inline ; Print ' server is ' via inline string
B49B EQUS " server is " ; fragment for 'File/Printer server is ...' messages
B4A6 NOP ; NOP -- bit-7 terminator + harmless resume opcode
B4A7 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
B4A8 .load_ps_server_addr←4← B3CA JSR← B41F JSR← B5A1 JSR← B601 JSR
LDY #2 ; Y=2: workspace offset of PS station byte
B4AA LDA (nfs_workspace),y ; Read station byte
B4AC STA fs_work_5 ; Stash in fs_work_5 (PS station)
B4AE INY ; Y=3: workspace offset of PS network byte
B4AF LDA (nfs_workspace),y ; Read network byte
B4B1 STA fs_work_6 ; Stash in fs_work_6 (PS network)
B4B3 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
B4B4 .pop_requeue_ps_scan←2← B41C JSR← B5FB JSR
PLA ; Pull saved upper byte of ws_ptr_lo+osword_flag pair Pop return address low
B4B5 STA osword_flag ; Save into osword_flag
B4B7 PLA ; Pull lower byte
B4B8 STA ws_ptr_lo ; Save into ws_ptr_lo
B4BA LDA #0 ; Push 0 -- placeholder, will be the stacked return marker Push 0 as end-of-list marker
B4BC PHA ; Push it
B4BD LDA #&84 ; ws_ptr_hi base = &84 (start of PS slot table area) Start scanning from offset &84
B4BF STA ws_ptr_hi ; Save base
B4C1 LSR econet_flags ; Shift bit 0 of econet_flags into C (saved scan state) Shift PS slot flags right
B4C4 LDA #3 ; A=3: PS slot index counter
B4C6 .loop_scan_ps_slots←1← B4D8 BNE
JSR byte_to_2bit_index ; Convert slot index to 12-byte-aligned table offset Convert to 2-bit workspace index
B4C9 BCS done_ps_slot_scan ; Out of range (clamped to 0): all slots scanned Carry set: no more slots
B4CB LSR ; A /= 2 (shift down)
B4CC LSR ; A /= 2 again (now slot index * 4 / 4 = slot index) To get slot offset
B4CD TAX ; X = slot index
B4CE LDA (nfs_workspace),y ; Read slot's status byte at workspace[Y]
B4D0 BEQ done_ps_slot_scan ; Slot empty (0): scan done
B4D2 CMP #&3f ; Slot is '?' (uninitialised marker)?
B4D4 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
B4D6 .skip_next_ps_slot←1← B4FF JMP
INX ; Step slot index
B4D7 TXA ; Move to A for next iteration
B4D8 BNE loop_scan_ps_slots ; Loop while X != 0 (wraps when all slots done) Loop for more slots
B4DA .reinit_ps_slot←1← B4D4 BEQ
TYA ; Save Y (slot table offset)
B4DB PHA ; Push it
B4DC LDA #&7f ; A=&7F: slot status 'busy/active'
B4DE STA (nfs_workspace),y ; Mark slot active
B4E0 INY ; Step Y to control byte
B4E1 LDA #&9e ; A=&9E: control byte (Master 128 PS-init pattern) Low byte: workspace page
B4E3 STA (nfs_workspace),y ; Store control byte
B4E5 LDA #0 ; A=0: zero-fill the next two bytes
B4E7 JSR write_two_bytes_inc_y ; Write two zeros, advance Y
B4EA LDA ws_ptr_hi ; Read current ws_ptr_hi
B4EC STA (nfs_workspace),y ; Store as buffer-link low byte
B4EE CLC ; Clear C ready for the +3
B4EF PHP ; Save flags so the ADC's C doesn't leak
B4F0 ADC #3 ; Bump ws_ptr_hi by 3 (next slot's base)
B4F2 PLP ; Restore flags
B4F3 STA ws_ptr_hi ; Save updated ws_ptr_hi
B4F5 JSR write_ps_slot_byte_ff ; Write buffer page + two &FF sentinels
B4F8 LDA ws_ptr_hi ; Read ws_ptr_hi (now updated)
B4FA STA (nfs_workspace),y ; Store as second-link byte
B4FC .write_ps_slot_hi_link
JSR write_ps_slot_byte_ff ; Write another buffer page + two &FF sentinels Write another page + &FF bytes
B4FF JMP skip_next_ps_slot ; Continue scanning slots
B502 .done_ps_slot_scan←2← B4C9 BCS← B4D0 BEQ
ASL econet_flags ; Restore bit 0 of econet_flags via ASL (recovers from the LSR at &B4C1) Shift PS slot flags back
B505 LDA ws_ptr_lo ; Pull saved ws_ptr_lo
B507 PHA ; Push it back (the caller's return-resume sequence) Push onto stack
B508 LDA osword_flag ; Pull saved osword_flag
B50A PHA ; Push it back
B50B LDA #&0a ; A=&0A: outer counter
B50D TAY ; Y=&0A: inner counter
B50E TAX ; X=&0A: middle counter
B50F STA fs_work_4 ; Save outer in fs_work_4
B511 .loop_ps_delay←3← B512 BNE← B515 BNE← B519 BNE
DEY ; Decrement inner counter
B512 BNE loop_ps_delay ; Inner not zero: keep delaying
B514 DEX ; Decrement middle
B515 BNE loop_ps_delay ; Middle not zero: refresh inner and continue Middle loop: 10 iterations
B517 DEC fs_work_4 ; Decrement outer in fs_work_4
B519 BNE loop_ps_delay ; Outer not zero: another full sweep (~1000 cycles) Outer loop: ~1000 delay cycles
B51B 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)
B51C .write_ps_slot_byte_ff←2← B4F5 JSR← B4FC JSR
INY ; Step Y to next workspace slot byte
B51D LDA addr_work ; Load buffer page byte from addr_work
B51F STA (nfs_workspace),y ; Write at offset Y
B521 LDA #&ff ; A=&FF: sentinel; fall into write_two_bytes_inc_y to store two of them A=&FF
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
B523 .write_two_bytes_inc_y←1← B4E7 JSR
INY ; Step Y to next destination
B524 STA (nfs_workspace),y ; Write A at workspace offset Y
B526 INY ; Step Y again
B527 STA (nfs_workspace),y ; Write A at the next offset (two consecutive copies) Write byte to workspace
B529 INY ; Final INY leaves Y pointing past the second write Advance Y
B52A 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
B52B .reverse_ps_name_to_tx←2← B416 JSR← B588 JSR
LDY #&18 ; Y=&18: source offset (start of PS name in RX buffer) Start of PS name at offset &1C
B52D .loop_push_ps_name←1← B533 BNE
LDA (net_rx_ptr),y ; Read RX byte at offset Y
B52F PHA ; Push it (the stack reverses the order)
B530 INY ; Step source
B531 CPY #&20 ; Reached &20 (one past the 8-byte name)?
B533 BNE loop_push_ps_name ; No: continue pushing
B535 LDY #&17 ; Y=&17: destination offset for the reversed name End of TX name field at &1B
B537 .loop_pop_ps_name←1← B53D BNE
PLA ; Pull next pushed byte (LIFO -> reversed order) Pop byte (reversed order)
B538 STA (net_rx_ptr),y ; Store at destination offset Y
B53A DEY ; Step destination back
B53B CPY #&0f ; Reached &0F (one before the destination range)? Start of TX field (&0F)?
B53D BNE loop_pop_ps_name ; No: continue popping
B53F LDA net_rx_ptr_hi ; Copy net_rx_ptr_hi as the TX page (TX shares the same page as RX for this packet) Copy RX page to TX
B541 STA net_tx_ptr_hi ; Set net_tx_ptr_hi
B543 LDA #&0c ; TX low byte = &0C: skip past the TX header to where the reversed name lives TX offset &10
B545 STA net_tx_ptr ; Set net_tx_ptr lo
B547 LDY #3 ; Y=3: copy 4-byte TX header (offsets 3..0)
B549 .loop_copy_tx_hdr←1← B54F BPL
LDA ps_tx_header_template,y ; Read template byte
B54C STA (net_tx_ptr),y ; Write to TX buffer at offset Y
B54E DEY ; Step backwards
B54F BPL loop_copy_tx_hdr ; Loop while Y >= 0
B551 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.

B552 .ps_tx_header_template←1← B549 LDA
EQUB &80 ; Control byte &80 (immediate TX)
B553 EQUB &9F ; Port &9F (printer server)
B554 EQUB &FF ; Station &FF (any)
B555 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)
B556 .print_station_addr←4← A3BE JSR← B607 JSR← B63F JSR← B697 JSR
PHP ; Save caller's V (controls leading-zero padding via the BVS at &B566) Save V flag (controls padding)
B557 LDA fs_work_6 ; Read network number (fs_work_6)
B559 BEQ skip_if_local_net ; Network 0 means local: skip the 'NN.' prefix
B55B JSR print_decimal_3dig ; Network non-zero: print as 3-digit decimal
B55E LDA #&2e ; A='.': separator between network and station '.' separator
B560 JSR osasci ; Print the dot
B563 BIT always_set_v_byte ; Set V so the next BVS branches over the padding (we just printed digits, no padding needed) Set V (suppress station padding)
B566 .skip_if_local_net←1← B559 BEQ
BVS local_net_prefix ; V set: skip leading-space padding
B568 JSR print_inline ; V clear (caller wanted padding): print 4 leading spaces via inline string Print 4 spaces (padding)
B56B EQUS " "
B56F .local_net_prefix←1← B566 BVS
LDA fs_work_5 ; Read station number (fs_work_5)
B571 PLP ; Restore caller's V (so print_decimal_3dig honours its own leading-zero suppression) Restore flags
B572 JMP print_decimal_3dig ; Tail-call print_decimal_3dig for the station number Print station as 3 digits

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.

B575 EQUB &80 ; Offset 0: txcb_ctrl = &80 (standard)
B576 EQUB &9F ; Offset 1: txcb_port = &9F (PS port)
B577 EQUB &00 ; Offset 2: dest station (placeholder, &00)
B578 EQUB &00 ; Offset 3: dest network (placeholder, &00)
B579 EQUB &10 ; Offset 4: buf1 start lo = &10
B57A EQUB &00 ; Offset 5: buf1 start hi (page from net_rx_ptr)
B57B EQUB &FF ; Offset 6: buf1 end lo placeholder = &FF
B57C EQUB &FF ; Offset 7: buf1 end hi placeholder = &FF
B57D EQUB &18 ; Offset 8: buf2 start lo = &18
B57E EQUB &00 ; Offset 9: buf2 start hi (page from net_rx_ptr)
B57F EQUB &FF ; Offset 10: buf2 end lo placeholder = &FF
B580 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
B581 .cmd_pollps
STY ws_ptr_hi ; Save command line pointer high
B583 JSR init_spool_drive ; Initialise spool/print drive
B586 STA fs_work_4 ; Save spool drive number
B588 JSR reverse_ps_name_to_tx ; Copy PS name to TX buffer
B58B JSR init_ps_slot_from_rx ; Init PS slot from RX data
B58E LDY ws_ptr_hi ; Restore command line pointer
B590 JSR save_ptr_to_spool_buf ; Save pointer to spool buffer
B593 LDA (fs_options),y ; Get first argument character
B595 CMP #&0d ; End of command line?
B597 BEQ no_poll_name_given ; Yes: no argument given
B599 CLV ; Clear V (= explicit PS name given)
B59A JSR is_decimal_digit ; Is first char a decimal digit?
B59D BCC skip_if_no_poll_arg ; Yes: station number, skip PS name
B59F TYA ; PS name follows
B5A0 PHA ; Save Y
B5A1 JSR load_ps_server_addr ; Load PS server address
B5A4 PLA ; Restore Y
B5A5 TAY ; Back to Y register
B5A6 JSR parse_fs_ps_args ; Parse FS/PS arguments
B5A9 LDY #&7a ; Offset &7A in slot buffer
B5AB LDA fs_work_5 ; Get parsed station low
B5AD STA (work_ae),y ; Store station number low
B5AF INY ; Y=&7b
B5B0 LDA fs_work_6 ; Get parsed network number
B5B2 STA (work_ae),y ; Store station number high
B5B4 LDY #&10 ; Offset &14 in TX buffer
B5B6 JSR copy_ps_data ; Copy PS data to TX buffer
B5B9 LDA addr_work ; Get buffer page high
B5BB STA net_tx_ptr_hi ; Set TX pointer high byte
B5BD LDA #&78 ; Offset &78 in buffer
B5BF STA net_tx_ptr ; Set TX pointer low byte
B5C1 BNE done_poll_name_parse ; ALWAYS branch
B5C3 .no_poll_name_given←1← B597 BEQ
BIT always_set_v_byte ; Set V (= no explicit PS name)
B5C6 .skip_if_no_poll_arg←1← B59D BCC
BVS done_poll_name_parse ; V set (no arg): skip to send
B5C8 LDX #6 ; Max 6 characters for PS name
B5CA LDY #&10 ; Buffer offset for PS name
B5CC LDA #&20 ; Space character
B5CE .loop_pad_poll_name←1← B5D2 BNE
STA (net_rx_ptr),y ; Fill buffer position with space
B5D0 INY ; Next position
B5D1 DEX ; Count down
B5D2 BNE loop_pad_poll_name ; Loop until 6 spaces filled
B5D4 JSR save_ptr_to_os_text ; Save pointer to OS text
B5D7 LDY ws_ptr_hi ; Restore command line pointer
B5D9 JSR gsinit ; Initialise string reading
B5DC BEQ done_poll_name_parse ; Empty string: skip to send
B5DE LDX #6 ; Max 6 characters
B5E0 STY ws_ptr_hi ; Save updated string pointer
B5E2 LDY #&10 ; Buffer offset for PS name
B5E4 STY table_idx ; Save buffer position
B5E6 .loop_read_poll_char←1← B5F2 BNE
LDY ws_ptr_hi ; Restore string pointer
B5E8 JSR gsread ; Read next char from string
B5EB STY ws_ptr_hi ; Save updated string pointer
B5ED BCS done_poll_name_parse ; End of string: go to send
B5EF JSR store_char_uppercase ; Store char uppercased in buffer
B5F2 BNE loop_read_poll_char ; Loop if more chars to copy
B5F4 .done_poll_name_parse←4← B5C1 BNE← B5C6 BVS← B5DC BEQ← B5ED BCS
LDA #&80 ; Enable escape checking
B5F6 STA need_release_tube ; Set escapable flag
B5F8 JSR send_net_packet ; Send the poll request packet
B5FB JSR pop_requeue_ps_scan ; Pop and requeue PS scan
B5FE JSR print_printer_server_is ; Print 'Printer server '
B601 JSR load_ps_server_addr ; Load PS server address
B604 BIT always_set_v_byte ; Set V and N flags
B607 JSR print_station_addr ; Print station address
B60A JSR print_inline ; Print ' "'
B60D EQUS " ""
B60F LDY #&18 ; Y=&18: name field offset in RX buffer
B611 .loop_print_poll_name←1← B61D BNE
LDA (net_rx_ptr),y ; Get character from name field
B613 CMP #&20 ; Is it a space?
B615 BEQ done_poll_name_print ; Yes: end of name
B617 JSR osasci ; Write character
B61A INY ; Next character
B61B CPY #&1e ; Past end of name field?
B61D BNE loop_print_poll_name ; No: continue printing name
B61F .done_poll_name_print←1← B615 BEQ
JSR print_inline ; Print '"' + CR
B622 EQUS ""."
B624 NOP ; Bit-7 terminator from preceding stringhi
B625 .loop_pollps_next_slot←1← B6A3 BNE
PLA ; Pop saved slot index
B626 BEQ return_from_poll_slots ; Zero: all slots done, return
B628 PHA ; Save slot offset
B629 TAY ; Transfer to Y
B62A LDA (nfs_workspace),y ; Read slot status byte
B62C BPL done_poll_slot_mark ; Bit 7 clear: slot inactive
B62E INY ; Advance to station number
B62F INY ; Offset+2 in slot
B630 LDA (nfs_workspace),y ; Read station number low
B632 STA fs_work_5 ; Store station low
B634 INY ; Next byte (offset+3)
B635 LDA (nfs_workspace),y ; Read network number
B637 STA fs_work_6 ; Store network number
B639 INY ; Next byte (offset+4)
B63A LDA (nfs_workspace),y ; Read status page pointer
B63C STA work_ae ; Store pointer low
B63E CLV ; Clear V flag
B63F JSR print_station_addr ; Print station address (V=0)
B642 JSR print_inline ; Print ' is '
B645 EQUS " is "
B649 LDX #0 ; X=0: indexed-indirect access mode
B64B LDA (work_ae,x) ; Read printer status byte
B64D BNE check_poll_jammed ; Non-zero: not ready
B64F JSR print_inline ; Print 'ready'
B652 EQUS "ready"
B657 CLV ; Ensure V clear so next BVC always taken
B658 BVC done_poll_status_line ; ALWAYS branch
B65A .check_poll_jammed←1← B64D BNE
CMP #2 ; Status = 2?
B65C BNE check_poll_busy ; No: check for busy
B65E .print_poll_jammed←1← B66C BNE
JSR print_inline ; Print 'jammed'
B661 EQUS "jammed"
B667 CLV ; Clear V
B668 BVC done_poll_status_line ; ALWAYS branch
B66A .check_poll_busy←1← B65C BNE
CMP #1 ; Status = 1?
B66C BNE print_poll_jammed ; Not 1 or 2: default to jammed
B66E JSR print_inline ; Print 'busy'
B671 EQUS "busy"
B675 INC work_ae ; Advance work_ae to next status byte (lo)
B677 LDA (work_ae,x) ; Read client station number
B679 STA fs_work_5 ; Store station low
B67B BEQ done_poll_status_line ; Zero: no client info, skip
B67D JSR print_inline ; Print ' with station '
B680 EQUS " with station "
B68E INC work_ae ; Advance work_ae to next status byte (lo)
B690 LDA (work_ae,x) ; Read network number byte via (work_ae,X)
B692 STA fs_work_6 ; Store network number
B694 BIT always_set_v_byte ; Set V flag
B697 JSR print_station_addr ; Print client station address
B69A .done_poll_status_line←3← B658 BVC← B668 BVC← B67B BEQ
JSR osnewl ; Write newline (characters 10 and 13)
B69D .done_poll_slot_mark←1← B62C BPL
PLA ; Retrieve slot offset
B69E TAY ; Transfer to Y
B69F LDA #&3f ; Mark slot as processed (&3F)
B6A1 STA (nfs_workspace),y ; Write marker to workspace
B6A3 BNE loop_pollps_next_slot ; ALWAYS branch
B6A5 .return_from_poll_slots←1← B626 BEQ
RTS ; Return

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
B6A6 .init_ps_slot_from_rx←1← B58B JSR
LDY #&78 ; Start at offset &78
B6A8 .loop_copy_slot_tmpl←1← B6BA BNE
LDA ps_print_template,y ; Load template byte
B6AB CPY #&7d ; At offset &7D?
B6AD BEQ subst_rx_page_byte ; Yes: substitute RX page
B6AF CPY #&81 ; At offset &81?
B6B1 BNE store_slot_tmpl_byte ; No: use template byte
B6B3 .subst_rx_page_byte←1← B6AD BEQ
LDA net_rx_ptr_hi ; Use RX buffer page instead
B6B5 .store_slot_tmpl_byte←1← B6B1 BNE
STA (work_ae),y ; Store byte in slot buffer
B6B7 INY ; Next offset
B6B8 CPY #&84 ; Past end of slot (&84)?
B6BA BNE loop_copy_slot_tmpl ; No: continue copying
B6BC 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
B6BD .store_char_uppercase←2← B411 JSR← B5EF JSR
LDY table_idx ; Y = current buffer position
B6BF AND #&7f ; Strip high bit
B6C1 CMP #&61 ; Is it lowercase 'a' or above?
B6C3 BCC done_uppercase_store ; Below 'a': not lowercase
B6C5 CMP #&7b ; Above 'z'?
B6C7 BCS done_uppercase_store ; Yes: not lowercase
B6C9 AND #&5f ; Convert to uppercase
B6CB .done_uppercase_store←2← B6C3 BCC← B6C7 BCS
STA (net_rx_ptr),y ; Store in RX buffer
B6CD INY ; Next buffer position
B6CE STY table_idx ; Update buffer position
B6D0 DEX ; Decrement character count
B6D1 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 (ws_0d68) and shadow IER (ws_0d69).
  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)
B6D2 .cmd_prot
LDA #&ff ; Load &FF (protect)
B6D4 BNE unprot_clear ; ALWAYS branch

*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)
B6D6 .cmd_unprot
LDA #0 ; Load &00 (unprotect)
B6D8 .unprot_clear←1← B6D4 BNE
PHP ; Save Z flag (1 = unprot, 0 = prot) for later
B6D9 JSR set_via_shadow_pair ; Mirror A into shadow ACR / shadow IER
B6DC LDX #&11 ; X=&11: CMOS offset for Econet flags
B6DE JSR osbyte_a1 ; OSBYTE &A1 reads CMOS byte &11 -> Y
B6E1 TYA ; A = current CMOS byte
B6E2 PLP ; Restore the saved Z flag
B6E3 BEQ unprot_check ; Z=1: unprot path
B6E5 ORA #&40 ; Set bit 6 (protection on)
B6E7 BNE unprot_apply ; ALWAYS branch to write-back
B6E9 .unprot_check←1← B6E3 BEQ
AND #&bf ; Clear bit 6 (protection off)
B6EB .unprot_apply←1← B6E7 BNE
TAY ; Y = new flag byte
B6EC LDA #&a2 ; OSBYTE &A2: write CMOS byte
B6EE .loop_match_prot_attr
LDX #&11 ; X=&11: CMOS offset for Econet flags
B6F0 JMP osbyte ; Tail-call OSBYTE

*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
B6F3 .cmd_wipe
JSR mask_owner_access ; Reset access flags before parsing the new argument
B6F6 LDA #0 ; A=0: clear the file-iteration counter
B6F8 STA fs_work_5 ; Store iteration counter (steps to next file each loop)
B6FA JSR save_ptr_to_os_text ; Save text pointer for re-reading the wildcard each iteration
B6FD JSR parse_filename_arg ; Parse the wildcard filename into the &C030 buffer
B700 INX ; Step X past the CR terminator (so X = filename length+1)
B701 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 &B73F that loops cmd_wipe over each match).

B703 .request_next_wipe←1← B73F JMP
LDA #1 ; FS function code byte 0 = 1 (examine)
B705 STA hazel_txcb_data ; TXCB[5] = 1: 'examine directory entry'
B708 STA hazel_txcb_count ; TXCB[7] = 1: ditto for the second buffer slot
B70B LDX fs_work_5 ; Load current iteration index
B70D STX hazel_txcb_flag ; TXCB[6] = iteration index (which directory entry)
B710 LDX #3 ; X=3: copy starting at TX[3] (after the FS header bytes)
B712 JSR copy_arg_to_buf ; Copy the parsed filename into the TX buffer
B715 LDY #3 ; Y=3: FS function code 'Examine'
B717 LDA #&80 ; A=&80: set bit 7 of need_release_tube to flag long-lived TX
B719 STA need_release_tube ; Store flag
B71B JSR save_net_tx_cb ; Send the examine request and wait for reply
B71E LDA hazel_txcb_data ; Read FS reply byte 0 (status code)
B721 BNE check_wipe_attr ; Non-zero status: process the response
B723 LDA #osbyte_flush_buffer_class ; OSBYTE &0F: flush input buffer class
B725 LDX #1 ; X=1: flush keyboard buffer
B727 JSR osbyte ; Flush keyboard buffer (clear pending Y/N keypress)
B72A LDA #osbyte_scan_keyboard_from_16 ; OSBYTE &7A: scan keyboard from key 16 (clear keypress queue)
B72C JSR osbyte ; Run the scan
B72F LDY #0 ; Y=0: no key
B731 LDA #osbyte_write_keys_pressed ; OSBYTE &78: write keys-pressed state
B733 JMP osbyte ; Tail-call OSBYTE: clean up and return
B736 .check_wipe_attr←1← B721 BNE
LDA hazel_txcb_end ; Read attribute byte from FS reply (TXCB[&2F])
B739 .loop_check_if_locked←1← B749 BNE
CMP #&4c ; Is it 'L' (locked)?
B73B BNE check_wipe_dir ; Not locked: check for directory
B73D .skip_wipe_locked←1← B7BA JMP
INC fs_work_5 ; Locked: skip this file, advance to next
B73F JMP request_next_wipe ; Loop back to request the next directory entry
B742 .check_wipe_dir←1← B73B BNE
CMP #&44 ; Is it 'D' (directory)?
B744 BNE show_wipe_prompt ; Not a directory: prompt the user
B746 LDA hazel_examine_attr ; Directory: check second attribute byte (size)
B749 BNE loop_check_if_locked ; Loop back to attribute test (re-checks if non-empty)
B74B .show_wipe_prompt←1← B744 BNE
LDX #1 ; X=1: scan name starting at TX[1]
B74D LDY fs_work_6 ; Y = end-of-buffer offset (saved earlier in fs_work_6)
B74F .loop_copy_wipe_name←1← B75C BNE
LDA hazel_txcb_flag,x ; Read filename byte from TX[6+X]
B752 JSR print_char_no_spool ; Print via *SPOOL-bypassing OSASCI
B755 STA hazel_parse_buf,y ; Also store into the parse buffer for later use
B758 INY ; Step parse-buffer offset
B759 INX ; Step TX-buffer offset
B75A CPX #&0c ; Reached &0C (12 chars)?
B75C BNE loop_copy_wipe_name ; No: continue copying
B75E JSR print_inline_no_spool ; Print '(?/' prompt prefix and read response
B761 EQUS "(?/" ; Inline string '(?/' is read by the hook above
B764 NOP ; NOP -- bit-7 terminator + resume opcode for the '(?/' stringhi
B765 JSR prompt_yn ; Print 'Y/N) ' via prompt_yn (reads keypress)
B768 CMP #&3f ; Was the keypress '?' (help)?
B76A BNE check_wipe_response ; Not '?': process Y/N response
B76C LDA #&0d ; '?': print CR before help text
B76E JSR print_byte_no_spool ; Print CR character
B771 LDX #2 ; X=2: start of name in TX[2]
B773 .loop_print_wipe_info←1← B77C BNE
LDA hazel_txcb_data,x ; Read name byte from TX[5+X] (FS reply)
B776 JSR print_char_no_spool ; Print name char (no spool)
B779 INX ; Advance index
B77A CPX #&3e ; End of TX[5+X] name field at offset &3E?
B77C BNE loop_print_wipe_info ; No: continue printing
B77E JSR print_inline_no_spool ; Print 'Wipe? ' help suffix via inline string
B781 EQUS " ("
B783 NOP ; Bit-7 terminator + resume
B784 JSR prompt_yn ; Re-prompt user with prompt_yn
B787 .check_wipe_response←1← B76A BNE
AND #&df ; Mask to upper-case ('A'..'Z' map to themselves)
B789 CMP #&59 ; Was the response 'Y'?
B78B BNE skip_wipe_to_next ; No: skip this entry, advance to next
B78D JSR print_char_no_spool ; Yes: echo the keypress
B790 LDX #0 ; X=0: start scanning the parse-buffer name
B792 LDA hazel_parse_buf,x ; Read first parse-buffer byte at hazel_parse_buf
B795 CMP #&0d ; Is it CR (no path component)?
B797 BEQ use_wipe_leaf_name ; Yes: use leaf-name only path at &B7BD
B799 .loop_build_wipe_cmd←1← B7AE BNE
LDA hazel_parse_buf,x ; Read parse-buffer byte at hazel_parse_buf+X
B79C CMP #&0d ; Is it CR (end of name)?
B79E BNE skip_if_not_space ; No: check for space separator
B7A0 LDA #&2e ; CR: substitute '.' so the dir prefix terminates with a separator
B7A2 .skip_if_not_space←1← B79E BNE
CMP #&20 ; Is it space?
B7A4 BNE store_wipe_tx_char ; No: store byte as-is
B7A6 .set_wipe_cr_end←1← B7C9 BEQ
LDA #&0d ; Yes: substitute CR (end-of-cmd)
B7A8 .store_wipe_tx_char←1← B7A4 BNE
STA hazel_txcb_data,x ; Store byte into TX[5+X] (delete-command buffer)
B7AB INX ; Advance index
B7AC CMP #&0d ; Was that byte CR (just stored)?
B7AE BNE loop_build_wipe_cmd ; No: continue copying
B7B0 LDY #&14 ; Y=&14: FS function code &14 = delete
B7B2 JSR save_net_tx_cb ; Send the delete request and wait for reply
B7B5 DEC fs_work_5 ; Decrement iteration counter so we re-examine the now-shifted-up slot
B7B7 .skip_wipe_to_next←1← B78B BNE
JSR print_newline_no_spool ; Print newline before next entry
B7BA JMP skip_wipe_locked ; Loop back to skip_wipe_locked (= request next entry)
B7BD .use_wipe_leaf_name←1← B797 BEQ
DEX ; DEX: pre-decrement before the INX in the loop
B7BE .loop_copy_wipe_leaf←1← B7C7 BNE
INX ; Advance index
B7BF LDA hazel_parse_buf_1,x ; Read parse-buffer byte at hazel_parse_buf_1+X (skip CR at hazel_parse_buf)
B7C2 STA hazel_txcb_data,x ; Store into TX[5+X] (delete-command buffer)
B7C5 CMP #&20 ; Reached space (end-of-leaf)?
B7C7 BNE loop_copy_wipe_leaf ; No: continue copying
B7C9 BEQ set_wipe_cr_end ; ALWAYS branch

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)
B7CB .prompt_yn←2← B765 JSR← B784 JSR
JSR print_inline_no_spool ; Print 'Y/N) ' via the inline-string helper
B7CE 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)
B7D3 .flush_and_read_char
LDA #osbyte_flush_buffer_class ; OSBYTE &0F: flush buffer class
B7D5 LDX #1 ; X=1: flush input buffers
B7D7 JSR osbyte ; Flush keyboard buffer before read
B7DA JSR osrdch ; Read character from input stream
B7DD BCC return_6 ; C clear: character read OK
B7DF JMP raise_escape_error ; Escape pressed: raise error
B7E2 .return_6←1← B7DD 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
B7E3 .init_channel_table←1← 8B9F JSR
LDA #0 ; A=0: clear value
B7E5 TAY ; Y=0: start index
B7E6 .loop_clear_chan_table←1← B7EA BNE
STA hazel_fcb_addr_lo,y ; Clear channel table entry
B7E9 INY ; Next entry
B7EA BNE loop_clear_chan_table ; Loop until all 256 bytes cleared
B7EC LDY #&0b ; Offset &0F in receive buffer
B7EE LDA (net_rx_ptr),y ; Get number of available channels
B7F0 SEC ; Prepare subtraction
B7F1 SBC #&0c ; Subtract 'Z' to get negative count
B7F3 TAY ; Y = negative channel count (index)
B7F4 LDA #&40 ; Channel marker &40 (available)
B7F6 .loop_mark_chan_avail←1← B7FC BPL
STA hazel_fcb_addr_lo,y ; Mark channel slot as available
B7F9 DEY ; Previous channel slot
B7FA CPY #&b8 ; Reached start of channel range?
B7FC BPL loop_mark_chan_avail ; No: continue marking channels
B7FE INY ; Point to first channel slot
B7FF LDA #&c0 ; Active channel marker &C0
B801 STA hazel_fcb_addr_lo,y ; Mark first channel as active
B804 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
B805 .attr_to_chan_index←5← 93FE JSR← 9414 JSR← 9F5B JSR← 9F99 JSR← BADC JSR
PHP ; Save flags
B806 SEC ; Prepare subtraction
B807 SBC #&20 ; Subtract &20 to get table index
B809 BMI error_chan_out_of_range ; Negative: out of valid range
B80B CMP #&10 ; Above maximum channel index &0F?
B80D BCC return_chan_index ; In range: valid index
B80F .error_chan_out_of_range←1← B809 BMI
LDA #&ff ; Out of range: return &FF (invalid)
B811 .return_chan_index←1← B80D BCC
PLP ; Restore flags
B812 TAX ; X = channel index (or &FF)
B813 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
B814 .check_chan_char←2← A084 JSR← B88C JSR
CMP #&20 ; Below space?
B816 BCC err_net_chan_invalid ; Yes: invalid channel character
B818 CMP #&30 ; Below '0'?
B81A 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)
B81C .err_net_chan_invalid←2← 9EC2 JMP← B816 BCC
PHA ; Save channel character
B81D .error_chan_not_found←1← B84F BEQ
LDA #&de ; Error code &DE
B81F .err_net_chan_not_found
JSR error_inline_log ; Generate 'Net channel' error
B822 EQUS "Net channel."
B82E JSR false_ref_6f6e ; Error string continuation (unreachable)
B831 STZ tx_buffer_scratch,x ; Clear tx_buffer_scratch+X scratch
B833 EQUS "on this file server"
B846 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
B847 .lookup_chan_by_char←2← A1F3 JSR← B81A BCC
PHA ; Save channel character
B848 SEC ; Prepare subtraction
B849 SBC #&20 ; Convert char to table index
B84B TAX ; X = channel table index
B84C LDA hazel_fcb_slot_attr,x ; Look up network number for channel
B84F BEQ error_chan_not_found ; Zero: channel not found, raise error
B851 JSR match_station_net ; Check station/network matches current
B854 BNE error_chan_not_here ; No match: build detailed error msg
B856 PLA ; Discard saved channel character
B857 LDA hazel_fcb_status,x ; Load channel status flags
B85A RTS ; Return; A = channel flags
B85B .error_chan_not_here←1← B854 BNE
LDA #&de ; Error code &DE
B85D STA error_text ; Store error code in error block
B860 LDA #0 ; BRK opcode
B862 STA error_block ; Store BRK at start of error block
B865 TAX ; X=0: copy index
B866 .loop_copy_chan_err_str←1← B86D BNE
INX ; Advance copy position
B867 LDA net_chan_err_strings,x ; Load 'Net channel' string byte
B86A STA error_text,x ; Copy to error text
B86D BNE loop_copy_chan_err_str ; Continue until NUL terminator
B86F STX fs_load_addr_2 ; Save end-of-string position
B871 STX fs_work_4 ; Save for suffix append
B873 PLA ; Retrieve channel character
B874 JSR append_space_and_num ; Append ' N' (channel number)
B877 LDY fs_work_4 ; Load 'Net channel' end position
B879 .loop_append_err_suffix←1← B881 BNE
INY ; Skip past NUL to suffix string
B87A INX ; Advance destination position
B87B LDA net_chan_err_strings,y ; Load ' not on this...' suffix byte
B87E STA error_text,x ; Append to error message
B881 BNE loop_append_err_suffix ; Continue until NUL
B883 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
B886 .store_result_check_dir←2← BB6D JSR← BBF3 JSR
LDA hazel_chan_attr ; Load current channel attribute
B889 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)
B88C .check_not_dir←2← 9EF5 JSR← A170 JSR
JSR check_chan_char ; Validate and look up channel
B88F AND #2 ; Test directory flag (bit 1)
B891 BEQ return_7 ; Not a directory: return OK
B893 LDA #&a8 ; Error code &A8
B895 JSR error_inline_log ; Generate 'Is a dir.' error
B898 EQUS "Is a directory."
B8A7 .return_7←1← B891 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
B8A8 .alloc_fcb_slot←7← A02A JSR← A05F JSR← A5C6 JSR← A663 JSR← A68E JSR← A6C5 JSR← B8DF JSR
PHA ; Save channel attribute
B8A9 LDX #&20 ; Start scanning from FCB slot &20
B8AB .loop_scan_fcb_slots←1← B8B3 BNE
LDA hazel_fcb_addr_mid,x ; Load FCB station byte
B8AE BEQ done_found_free_slot ; Zero: slot is free, use it
B8B0 INX ; Try next slot
B8B1 CPX #&30 ; Past last FCB slot &2F?
B8B3 BNE loop_scan_fcb_slots ; No: check next slot
B8B5 PLA ; No free slot: discard saved attribute
B8B6 LDA #0 ; A=0: return failure (Z set)
B8B8 RTS ; Return
B8B9 .done_found_free_slot←1← B8AE BEQ
PLA ; Restore channel attribute
B8BA STA hazel_fcb_addr_mid,x ; Store attribute in FCB slot
B8BD LDA #0 ; A=0: clear value
B8BF STA hazel_fcb_addr_lo_minus20,x ; Clear FCB transfer count low
B8C2 STA hazel_fcb_addr_mid_minus20,x ; Clear FCB transfer count mid
B8C5 STA hazel_fcb_addr_lo,x ; Clear FCB transfer count high
B8C8 LDA hazel_fs_station ; Load current station number
B8CB STA hazel_fcb_addr_hi,x ; Store station in FCB
B8CE LDA hazel_fs_network ; Load current network number
B8D1 STA hazel_fcb_slot_attr,x ; Store network in FCB
B8D4 TXA ; Get FCB slot index
B8D5 PHA ; Save slot index
B8D6 SEC ; Prepare subtraction
B8D7 SBC #&20 ; Convert slot to channel index (0-&0F)
B8D9 TAX ; X = channel index
B8DA PLA ; Restore A = FCB slot index
B8DB 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
B8DC .alloc_fcb_or_error←2← 9FD9 JSR← A522 JSR
PHA ; Save argument
B8DD LDA #0 ; A=0: allocate any available slot
B8DF JSR alloc_fcb_slot ; Try to allocate an FCB slot
B8E2 BNE return_alloc_success ; Success: slot allocated
B8E4 LDA #&c0 ; Error code &C0
B8E6 JSR error_inline_log ; Generate 'No more FCBs' error
B8E9 EQUS "No more FCBs."
B8F6 .return_alloc_success←1← B8E2 BNE
PLA ; Restore argument
B8F7 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
B8F8 .close_all_net_chans←3← 9785 JSR← 9813 JSR← A6D5 JSR
CLC ; C=0: close all matching channels
B8F9 .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)
B8FC .scan_fcb_flags←1← A099 JSR
LDX #&10 ; Start from FCB slot &10
B8FE .loop_scan_fcb_down←4← B90A BVC← B914 BVS← B919 BNE← B923 BEQ
DEX ; Previous FCB slot
B8FF BPL skip_if_slots_done ; More slots to check
B901 RTS ; All FCB slots processed, return
B902 .skip_if_slots_done←1← B8FF BPL
LDA hazel_fcb_status,x ; Load channel flags for this slot
B905 TAY ; Save flags in Y
B906 AND #2 ; Test active flag (bit 1)
B908 BEQ done_check_station ; Not active: check station match
B90A BVC loop_scan_fcb_down ; V clear (close all): next slot
B90C BCC done_check_station ; C clear: check station match
B90E TYA ; Restore original flags
B90F AND #&df ; Clear write-pending flag (bit 5)
B911 STA hazel_fcb_status,x ; Update channel flags
B914 BVS loop_scan_fcb_down ; Next slot (V always set here)
B916 .done_check_station←2← B908 BEQ← B90C BCC
JSR match_station_net ; Check if channel belongs to station
B919 BNE loop_scan_fcb_down ; No match: skip to next slot
B91B LDA #0 ; A=0: clear channel
B91D STA hazel_fcb_status,x ; Clear channel flags (close it)
B920 STA hazel_fcb_slot_attr,x ; Clear network number
B923 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
B925 .match_station_net←7← A64A JSR← A675 JSR← A6AC JSR← AA0A JSR← AFC2 JSR← B851 JSR← B916 JSR
LDA hazel_fcb_state_byte,x ; Load FCB station number
B928 EOR hazel_fs_station ; Compare with current station
B92B BNE return_from_match_stn ; Different: Z=0, no match
B92D LDA hazel_fcb_network,x ; Load FCB network number
B930 EOR hazel_fs_network ; Compare with current network
B933 .return_from_match_stn←1← B92B 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)
B934 .find_open_fcb←2← BA33 JSR← BB21 JSR
LDX hazel_cur_fcb_index ; Load current FCB index
B937 BIT always_set_v_byte ; Set V flag (first pass marker)
B93A .loop_find_fcb←4← B949 BVC← B94F BPL← B96C BVS← B973 BNE
INX ; Next FCB slot
B93B CPX #&10 ; Past end of table (&10)?
B93D BNE skip_if_no_wrap ; No: continue checking
B93F LDX #0 ; Wrap around to slot 0
B941 .skip_if_no_wrap←1← B93D BNE
CPX hazel_cur_fcb_index ; Back to starting slot?
B944 BNE done_check_fcb_status ; No: check this slot
B946 BVC loop_scan_empty_fcb ; V clear (second pass): scan empties
B948 CLV ; Clear V for second pass
B949 BVC loop_find_fcb ; Continue scanning
B94B .done_check_fcb_status←1← B944 BNE
LDA hazel_fcb_flags,x ; Load FCB status flags
B94E ROL ; Shift bit 7 (in-use) into carry
B94F BPL loop_find_fcb ; Not in use: skip
B951 AND #4 ; Test bit 2 (modified flag)
B953 BNE skip_if_modified_fcb ; Modified: check further conditions
B955 .done_select_fcb←1← B975 BEQ
DEX ; Adjust for following INX
B956 .loop_scan_empty_fcb←2← B946 BVC← B961 BPL
INX ; Next FCB slot
B957 CPX #&10 ; Past end of table?
B959 BNE done_test_empty_slot ; No: continue
B95B LDX #0 ; Wrap around to slot 0
B95D .done_test_empty_slot←1← B959 BNE
LDA hazel_fcb_flags,x ; Load FCB status flags
B960 ROL ; Shift bit 7 into carry
B961 BPL loop_scan_empty_fcb ; Not in use: continue scanning
B963 SEC ; Set carry
B964 ROR ; Restore original flags
B965 STA hazel_fcb_flags,x ; Save flags back (mark as found)
B968 LDX hazel_cur_fcb_index ; Restore original FCB index
B96B RTS ; Return with found slot in X
B96C .skip_if_modified_fcb←1← B953 BNE
BVS loop_find_fcb ; V set (first pass): skip modified
B96E LDA hazel_fcb_flags,x ; Load FCB status flags
B971 AND #&20 ; Test bit 5 (offset pending)
B973 BNE loop_find_fcb ; Bit 5 set: skip this slot
B975 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 in the 4.21 build.

On ExitXsmall loop counter (last DEX value)
Y0 (cleared by the TYA path)
B977 .init_wipe_counters←2← B9BA JSR← BA65 JSR
LDY #1 ; Initial pass count = 1
B979 STY hazel_pass_counter ; Store pass counter
B97C DEY ; Y=0
B97D STY hazel_byte_counter_lo ; Clear byte counter low
B980 STY hazel_offset_counter ; Clear offset counter
B983 STY hazel_transfer_flag ; Clear transfer flag
B986 TYA ; A=0
B987 LDX #2 ; Clear 3 counter bytes
B989 .loop_clear_counters←1← B98D BPL
STA hazel_xfer_init_zeros,x ; Clear counter byte
B98C DEX ; Next byte
B98D BPL loop_clear_counters ; Loop for indices 2, 1, 0
B98F STX hazel_sentinel_cd ; Store &FF as sentinel in xfer_sentinel_1
B992 STX hazel_sentinel_ce ; Store &FF as sentinel in xfer_sentinel_2
B995 LDX #&ca ; X=&CA: workspace offset
B997 LDY #&c2 ; Y=&10: page &10
B999 RTS ; Return; X/Y point to &10CA

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
B99A .start_wipe_pass←2← BA2A JSR← BB54 JSR
JSR verify_ws_checksum ; Verify workspace checksum integrity
B99D STX hazel_cur_fcb_index ; Save current FCB index
B9A0 LDA hazel_fcb_flags,x ; Load FCB status flags
B9A3 ROR ; Shift bit 0 (active) into carry
B9A4 BCC done_clear_fcb_active ; Not active: clear status and return
B9A6 LDA hazel_station_lo ; Save current station low to stack
B9A9 PHA ; Push station low
B9AA LDA hazel_station_hi ; Save current station high
B9AD PHA ; Push station high
B9AE LDA hazel_fcb_station_lo,x ; Load FCB station low
B9B1 STA hazel_station_lo ; Set as working station low
B9B4 LDA hazel_fcb_station_hi,x ; Load FCB station high
B9B7 STA hazel_station_hi ; Set as working station high
B9BA JSR init_wipe_counters ; Reset transfer counters
B9BD DEC hazel_offset_counter ; Set offset to &FF (no data yet)
B9C0 DEC hazel_pass_counter ; Set pass counter to 0 (flush mode)
B9C3 LDX hazel_cur_fcb_index ; Reload FCB index
B9C6 TXA ; Transfer to A
B9C7 CLC ; Prepare addition
B9C8 ADC #&c3 ; Add &11 for buffer page offset
B9CA STA hazel_buf_addr_hi ; Store buffer address high byte
B9CD LDA hazel_fcb_flags,x ; Load FCB status flags
B9D0 AND #&20 ; Test bit 5 (has saved offset)
B9D2 BEQ done_restore_offset ; No offset: skip restore
B9D4 LDA hazel_fcb_offset_save,x ; Load saved byte offset
B9D7 STA hazel_offset_counter ; Restore offset counter
B9DA .done_restore_offset←1← B9D2 BEQ
LDA hazel_fcb_attr_ref,x ; Load FCB attribute reference
B9DD STA hazel_chan_ref ; Store as current reference
B9E0 TAX ; Transfer to X
B9E1 JSR read_rx_attribute ; Read saved receive attribute
B9E4 PHA ; Push to stack
B9E5 TXA ; Restore attribute to A
B9E6 STA (net_rx_ptr),y ; Set attribute in receive buffer
B9E8 LDX #&ca ; X=&CA: workspace offset
B9EA LDY #&c2 ; Y=&10: page &10
B9EC LDA #0 ; A=0: standard transfer mode
B9EE JSR send_and_receive ; Send data and receive response
B9F1 LDX hazel_cur_fcb_index ; Reload FCB index
B9F4 PLA ; Restore saved receive attribute
B9F5 JSR store_rx_attribute ; Restore receive attribute
B9F8 PLA ; Restore station high
B9F9 STA hazel_station_hi ; Store station high
B9FC PLA ; Restore station low
B9FD STA hazel_station_lo ; Store station low
BA00 .done_clear_fcb_active←1← B9A4 BCC
LDA #&dc ; Mask &DC: clear bits 0, 1, 5
BA02 AND hazel_fcb_flags,x ; Clear active and offset flags
BA05 STA hazel_fcb_flags,x ; Update FCB status
BA08 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)
BA09 .save_fcb_context←2← BACC JSR← BC85 JSR
LDX #&0c ; Copy 13 bytes (indices 0 to &0C)
BA0B .loop_save_tx_context←1← BA15 BPL
LDA hazel_txcb_port,x ; Load TX buffer byte
BA0E STA hazel_ctx_buffer,x ; Save to context buffer at &10D9
BA11 LDA fs_load_addr,x ; Load workspace byte from fs_load_addr
BA13 PHA ; Save to stack
BA14 DEX ; Next byte down
BA15 BPL loop_save_tx_context ; Loop for all 13 bytes
BA17 CPY #0 ; Y=0? (no FCB to process)
BA19 BNE done_save_context ; Non-zero: scan and process FCBs
BA1B JMP loop_restore_workspace ; Y=0: skip to restore workspace
BA1E .done_save_context←1← BA19 BNE
PHP ; Save flags
BA1F LDX #&ff ; X=&FF: start scanning from -1
BA21 .loop_find_pending_fcb←2← BA25 BPL← BA28 BPL
INX ; Next FCB slot
BA22 LDA hazel_fcb_flags,x ; Load FCB status flags
BA25 BPL loop_find_pending_fcb ; Bit 7 clear: not pending, skip
BA27 ASL ; Shift bit 6 to bit 7
BA28 BPL loop_find_pending_fcb ; Bit 6 clear: skip
BA2A JSR start_wipe_pass ; Flush this FCB's pending data
BA2D LDA #&40 ; Pending marker &40
BA2F STA hazel_fcb_flags,x ; Mark FCB as pending-only
BA32 PHP ; Save flags
BA33 JSR find_open_fcb ; Find next available FCB slot
BA36 PLP ; Restore flags
BA37 LDA hazel_chan_attr ; Load current channel attribute
BA3A STA hazel_chan_ref ; Store as current reference
BA3D PHA ; Save attribute
BA3E SEC ; Prepare attribute-to-channel conversion
BA3F SBC #&20 ; Convert attribute (&20+) to channel index
BA41 TAY ; Y = attribute index
BA42 LDA hazel_fcb_slot_attr,y ; Load station for this attribute
BA45 STA hazel_txcb_data ; Store station in TX buffer
BA48 PLA ; Restore attribute
BA49 STA hazel_fcb_attr_ref,x ; Store attribute in FCB slot
BA4C LDA hazel_station_lo ; Load working station low
BA4F STA hazel_fcb_station_lo,x ; Store in TX buffer
BA52 LDA hazel_station_hi ; Load working station high
BA55 STA hazel_fcb_station_hi,x ; Store in TX buffer
BA58 TXA ; Get FCB slot index
BA59 CLC ; Prepare addition
BA5A ADC #&c3 ; Add &11 for buffer page offset
BA5C STA hazel_buf_addr_hi ; Store buffer address high byte
BA5F PLP ; Restore flags
BA60 BVC done_init_wipe ; V clear: skip directory request
BA62 JSR flush_fcb_if_station_known ; Command byte = 0
BA65 .done_init_wipe←1← BA60 BVC
JSR init_wipe_counters ; Reset transfer counters
BA68 JSR read_rx_attribute ; Read saved receive attribute
BA6B PHA ; Function code &0D
BA6C LDA hazel_chan_ref ; Load current reference
BA6F STA (net_rx_ptr),y ; Set in receive buffer
BA71 LDY #&c2 ; Y=&10: page &10
BA73 LDA #2 ; A=2: transfer mode 2
BA75 JSR send_and_receive ; Send and receive data
BA78 PLA ; Restore receive attribute
BA79 JSR store_rx_attribute ; Restore receive attribute
BA7C LDX hazel_cur_fcb_index ; Reload FCB index
BA7F LDA hazel_pass_counter ; Load pass counter
BA82 BNE done_calc_offset ; Non-zero: data received, calc offset
BA84 LDA hazel_offset_counter ; Load offset counter
BA87 BEQ done_set_fcb_active ; Zero: no data received at all
BA89 .done_calc_offset←1← BA82 BNE
LDA hazel_offset_counter ; Load offset counter
BA8C EOR #&ff ; Negate (ones complement)
BA8E CLC ; Clear carry for add
BA8F ADC #1 ; Complete twos complement negation
BA91 STA hazel_fcb_offset_save,x ; Store negated offset in FCB
BA94 LDA #&20 ; Set bit 5 (has saved offset)
BA96 ORA hazel_fcb_flags,x ; Add to FCB flags
BA99 STA hazel_fcb_flags,x ; Update FCB status
BA9C LDA hazel_buf_addr_hi ; Load buffer address high byte
BA9F STA fs_load_addr_3 ; Set pointer high byte
BAA1 LDA #0 ; A=0: pointer low byte and clear val
BAA3 STA fs_load_addr_2 ; Set pointer low byte
BAA5 LDY hazel_fcb_offset_save,x ; Load negated offset (start of clear)
BAA8 .loop_clear_buffer←1← BAAB BNE
STA (fs_load_addr_2),y ; Clear buffer byte
BAAA INY ; Next byte
BAAB BNE loop_clear_buffer ; Loop until page boundary
BAAD .done_set_fcb_active←1← BA87 BEQ
LDA #2 ; Set bit 1 (active flag)
BAAF ORA hazel_fcb_flags,x ; Add active flag to status
BAB2 STA hazel_fcb_flags,x ; Update FCB status
BAB5 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 &BA1B (close-and-restore exit) and the BNE retry at &BABE.

BAB7 .loop_restore_workspace←2← BA1B JMP← BABE BNE
PLA ; Restore workspace byte from stack
BAB8 STA fs_load_addr,y ; Store to fs_load_addr workspace
BABB INY ; Next byte
BABC CPY #&0d ; Restored all 13 bytes?
BABE 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. (Pre-HAZEL ROMs read from &10D9 and wrote to &0F00; the 4.21 build relocates both to HAZEL.)

BAC0 .restore_catalog_entry←1← BCB5 JSR
LDY #&0c ; Copy 13 bytes (indices 0 to &0C)
BAC2 .loop_restore_tx_buf←1← BAC9 BPL
LDA hazel_ctx_buffer,y ; Load saved catalog byte from &10D9
BAC5 STA hazel_txcb_port,y ; Restore to TX buffer
BAC8 DEY ; Next byte down
BAC9 BPL loop_restore_tx_buf ; Loop for all bytes
BACB 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.

BACC .loop_save_before_match←1← BAEB 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
BACF .find_matching_fcb←3← A115 JSR← BB88 JSR← BC25 JSR
LDX #&ff ; X=&FF: start scanning from -1
BAD1 .loop_reload_attr←2← BB0D BNE← BB15 BNE
LDY hazel_chan_attr ; Load channel attribute to match
BAD4 .loop_next_fcb_slot←2← BAF3 BEQ← BAF9 BNE
INX ; Next FCB slot
BAD5 CPX #&10 ; Past end of table (&10)?
BAD7 BNE done_test_fcb_active ; No: check this slot
BAD9 LDA hazel_chan_attr ; Load channel attribute
BADC JSR attr_to_chan_index ; Convert to channel index
BADF LDA hazel_fcb_addr_hi,x ; Load station for this channel
BAE2 STA hazel_station_hi ; Store as match target station high
BAE5 LDA hazel_fcb_addr_mid,x ; Load port for this channel
BAE8 STA hazel_station_lo ; Store as match target station low
BAEB JMP loop_save_before_match ; Save context and rescan from start
BAEE .done_test_fcb_active←1← BAD7 BNE
LDA hazel_fcb_flags,x ; Load FCB status flags
BAF1 AND #2 ; Test active flag (bit 1)
BAF3 BEQ loop_next_fcb_slot ; Not active: skip to next
BAF5 TYA ; Get attribute to match
BAF6 CMP hazel_fcb_attr_ref,x ; Compare with FCB attribute ref
BAF9 BNE loop_next_fcb_slot ; No attribute match: skip
BAFB STX hazel_cur_fcb_index ; Save matching FCB index
BAFE PHP ; Save flags from attribute compare
BAFF SEC ; Prepare subtraction
BB00 SBC #&20 ; Convert attribute to channel index
BB02 PLP ; Restore flags from attribute compare
BB03 TAY ; Y = channel index
BB04 LDX hazel_cur_fcb_index ; Reload FCB index
BB07 LDA hazel_fcb_addr_mid,y ; Load channel station byte
BB0A CMP hazel_fcb_station_lo,x ; Compare with FCB station
BB0D BNE loop_reload_attr ; Station mismatch: try next
BB0F LDA hazel_fcb_addr_hi,y ; Load channel network byte
BB12 CMP hazel_fcb_station_hi,x ; Compare with FCB network
BB15 BNE loop_reload_attr ; Network mismatch: try next
BB17 LDA hazel_fcb_flags,x ; Load FCB flags
BB1A BPL return_test_offset ; Bit 7 clear: no pending flush
BB1C AND #&7f ; Clear pending flag (bit 7)
BB1E STA hazel_fcb_flags,x ; Update FCB status
BB21 JSR find_open_fcb ; Find new open FCB slot
BB24 LDA hazel_fcb_flags,x ; Reload FCB flags
BB27 .return_test_offset←1← BB1A BPL
AND #&20 ; Test bit 5 (has offset data)
BB29 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
BB2A .inc_fcb_byte_count←2← BBCD JSR← BC65 JSR
INC hazel_fcb_addr_lo,x ; Increment byte count low
BB2D BNE return_from_inc_fcb_count ; No overflow: done
BB2F INC hazel_fcb_addr_mid,x ; Increment byte count mid
BB32 BNE return_from_inc_fcb_count ; No overflow: done
BB34 INC hazel_fcb_addr_hi,x ; Increment byte count high
BB37 .return_from_inc_fcb_count←2← BB2D BNE← BB32 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)
BB38 .process_all_fcbs←9← 8D9D JSR← 9078 JSR← 9778 JSR← 9EC9 JSR← 9F0E JSR← 9FB1 JSR← A06B JSR← A175 JSR← A9DF JSR
PHX ; Save X on entry
BB39 PHY ; Save Y across the body
BB3A LDX #&f7 ; X=&F7: save 9 workspace bytes (&F7..&FF)
BB3C .loop_save_fcb_workspace←1← BB41 BMI
LDA fcb_workspace_idx_base,x ; Load workspace byte
BB3F PHA ; Push fs_options
BB40 INX ; Next byte
BB41 BMI loop_save_fcb_workspace ; X<0: more bytes to save
BB43 LDX #&0f ; Start from FCB slot &0F
BB45 STX hazel_cur_fcb_index ; Store as current FCB index
BB48 .loop_process_fcb←1← BB5B BPL
LDX hazel_cur_fcb_index ; Load current FCB index
BB4B TYA ; Get filter attribute
BB4C BEQ done_flush_fcb ; Zero: process all FCBs
BB4E CMP hazel_fcb_attr_ref,x ; Compare with FCB attribute ref
BB51 BNE done_advance_fcb ; No match: skip this FCB
BB53 .done_flush_fcb←1← BB4C BEQ
PHA ; Save filter attribute
BB54 JSR start_wipe_pass ; Flush pending data for this FCB
BB57 PLY
BB58 .done_advance_fcb←1← BB51 BNE
DEC hazel_cur_fcb_index ; Previous FCB index
BB5B BPL loop_process_fcb ; More slots: continue loop
BB5D LDX #8 ; X=8: restore 9 workspace bytes
BB5F .loop_restore_fcb_workspace←1← BB63 BPL
PLA ; Restore fs_block_offset
BB60 STA fs_work_4,x ; Restore workspace byte
BB62 DEX ; Next byte down
BB63 BPL loop_restore_fcb_workspace ; More bytes: continue restoring
BB65 PLY
BB66 PLX
BB67 RTS ; Return

BGETV vector handler: read byte from open file

Reached via the BGETV vector at &021A, 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
BB68 .bgetv_handler
STY hazel_chan_attr ; Save channel attribute
BB6B TXA ; Save caller's X
BB6C PHA ; Push X
BB6D JSR store_result_check_dir ; Store result and check not directory
BB70 LDA hazel_fcb_status,x ; Load channel flags
BB73 AND #&20 ; Test write-only flag (bit 5)
BB75 BEQ done_read_fcb_byte ; Not write-only: proceed with read
BB77 LDA #&d4 ; Error code &D4
BB79 JSR error_inline_log ; Generate 'Write only' error
BB7C EQUS "Write only."
BB87 .done_read_fcb_byte←1← BB75 BEQ
CLV ; Clear V (first-pass matching)
BB88 JSR find_matching_fcb ; Find FCB matching this channel
BB8B BEQ done_load_from_buf ; No offset: read byte from buffer
BB8D LDA hazel_fcb_addr_lo,y ; Load byte count for matching FCB
BB90 CMP hazel_fcb_offset_save,x ; Compare with buffer offset limit
BB93 BCC done_load_from_buf ; Below offset: data available
BB95 LDA hazel_fcb_status,y ; Load channel flags for FCB
BB98 TAX ; Transfer to X for testing
BB99 AND #&40 ; Test bit 6 (EOF already signalled)
BB9B BNE error_end_of_file ; EOF already set: raise error
BB9D TXA ; Restore flags
BB9E ORA #&40 ; Set EOF flag (bit 6)
BBA0 STA hazel_fcb_status,y ; Update channel flags with EOF
BBA3 LDA #0 ; A=0: clear receive attribute
BBA5 JSR store_rx_attribute ; Clear receive attribute (A=0)
BBA8 PLA ; Restore caller's X
BBA9 TAX ; X restored
BBAA LDA #&fe ; A=&FE: EOF marker byte
BBAC LDY hazel_chan_attr ; Restore channel attribute
BBAF SEC ; C=1: end of file
BBB0 RTS ; Return
BBB1 .error_end_of_file←1← BB9B BNE
LDA #&df ; Error code &DF
BBB3 JSR error_inline_log ; Generate 'End of file' error
BBB6 EQUS "End of file."
BBC2 .done_load_from_buf←2← BB8B BEQ← BB93 BCC
LDA hazel_fcb_addr_lo,y ; Load current byte count (= offset)
BBC5 PHA ; Save byte count
BBC6 TYA ; Get FCB slot index
BBC7 TAX ; X = FCB slot for byte count inc
BBC8 LDA #0 ; A=0: clear receive attribute
BBCA JSR store_rx_attribute ; Clear receive attribute (A=0)
BBCD JSR inc_fcb_byte_count ; Increment byte count for this FCB
BBD0 PLA ; Restore byte count (= buffer offset)
BBD1 TAY ; Y = offset into data buffer
BBD2 LDA hazel_cur_fcb_index ; Load current FCB index
BBD5 CLC ; Prepare addition
BBD6 ADC #&c3 ; Add &11 for buffer page offset
BBD8 STA fs_load_addr_3 ; Set pointer high byte
BBDA LDA #0 ; A=0: pointer low byte
BBDC STA fs_load_addr_2 ; Set pointer low byte
BBDE PLA ; Restore caller's X
BBDF TAX ; X restored
BBE0 LDA (fs_load_addr_2),y ; Read data byte from buffer
BBE2 LDY hazel_chan_attr ; Restore channel attribute
BBE5 CLC ; C=0: byte read successfully
BBE6 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
BBE7 .bputv_handler
STY hazel_chan_attr ; Save channel attribute
BBEA PHA ; Save data byte
BBEB TAY ; Y = data byte
BBEC TXA ; Save caller's X
BBED PHA ; Push X
BBEE TYA ; Restore data byte to A
BBEF PHA ; Push data byte for later
BBF0 STA hazel_saved_byte ; Save data byte in workspace
BBF3 JSR store_result_check_dir ; Store result and check not directory
BBF6 LDA hazel_fcb_status,x ; Load channel flags
BBF9 BMI done_test_write_flag ; Bit 7 set: channel open, proceed
BBFB LDA #&c1 ; Error &C1: Not open for update
BBFD JSR error_inline_log ; Raise error with inline string
BC00 EQUS "Not open for update."
BC14 .done_test_write_flag←1← BBF9 BMI
AND #&20 ; Test write flag (bit 5)
BC16 BEQ done_find_write_fcb ; Not write-capable: use buffer path
BC18 LDY hazel_fcb_slot_attr,x ; Load reply port for this channel
BC1B PLA ; Restore data byte
BC1C JSR send_wipe_request ; Send byte directly to server
BC1F JMP done_inc_byte_count ; Update byte count and return
BC22 .done_find_write_fcb←1← BC16 BEQ
BIT always_set_v_byte ; Set V flag (alternate match mode)
BC25 JSR find_matching_fcb ; Find matching FCB for channel
BC28 LDA hazel_fcb_addr_lo,y ; Load byte count for FCB
BC2B CMP #&ff ; Buffer full (&FF bytes)?
BC2D BNE done_check_buf_offset ; No: store byte in buffer
BC2F JSR flush_fcb_with_init ; Save X
BC32 .done_check_buf_offset←1← BC2D BNE
CMP hazel_fcb_offset_save,x ; Push Y
BC35 BCC done_set_dirty_flag ; Below offset: skip offset update
BC37 ADC #0 ; Carry set from BCS/BCC above
BC39 STA hazel_fcb_offset_save,x ; Update buffer offset in FCB
BC3C BNE done_set_dirty_flag ; Non-zero: keep offset flag
BC3E LDA #&df ; Mask &DF: clear bit 5
BC40 AND hazel_fcb_flags,x ; Clear offset flag
BC43 STA hazel_fcb_flags,x ; Update FCB status
BC46 .done_set_dirty_flag←2← BC35 BCC← BC3C BNE
LDA #1 ; Set bit 0 (dirty/active)
BC48 ORA hazel_fcb_flags,x ; Add to FCB flags
BC4B STA hazel_fcb_flags,x ; Update FCB status
BC4E LDA hazel_fcb_addr_lo,y ; Load byte count (= write position)
BC51 PHA ; Save count
BC52 TYA ; Get FCB slot index
BC53 TAX ; X = FCB slot
BC54 PLA ; Restore byte count
BC55 TAY ; Y = buffer write offset
BC56 LDA hazel_cur_fcb_index ; Load current FCB index
BC59 CLC ; Prepare addition
BC5A ADC #&c3 ; Add &11 for buffer page offset
BC5C STA fs_load_addr_3 ; Set pointer high byte
BC5E LDA #0 ; A=0: pointer low byte
BC60 STA fs_load_addr_2 ; Set pointer low byte
BC62 PLA ; Restore data byte
BC63 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).

BC65 .done_inc_byte_count←1← BC1F JMP
JSR inc_fcb_byte_count ; Increment byte count for this FCB
BC68 LDA #0 ; A=0: clear receive attribute
BC6A JSR store_rx_attribute ; Clear receive attribute (A=0)
BC6D PLA ; Restore caller's X
BC6E TAX ; X restored
BC6F PLA ; Discard saved data byte
BC70 LDY hazel_chan_attr ; Restore channel attribute
BC73 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
BC74 .flush_fcb_if_station_known←1← BA62 JSR
PHA ; Save A
BC75 PHX ; Save X
BC76 PHY
BC77 LDA hazel_fcb_slot_attr,y ; Read FCB slot attribute byte
BC7A 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
BC7C .flush_fcb_with_init←1← BC2F JSR
PHA ; Save attribute byte (saved-station-test path)
BC7D PHX ; Save X again
BC7E PHY ; Save Y
BC7F LDA hazel_fcb_slot_attr,y ; Load station for this channel
BC82 PHA ; Save station on stack
BC83 LDY #0 ; Y=0: reset index
BC85 JSR save_fcb_context ; Save current FCB context
BC88 PLA ; Restore station from stack
BC89 .store_station_and_flush←1← BC7A BNE
STA hazel_txcb_data ; Store station in command buffer
BC8C PLY
BC8D PHY ; Save Y again for the next iteration
BC8E PHA ; Save station for later restore
BC8F LDX #0 ; X=0
BC91 STX hazel_txcb_flag ; Clear function code
BC94 LDA hazel_fcb_addr_lo,y ; Load byte count lo from FCB
BC97 STA hazel_txcb_count ; Store as data byte count
BC9A LDA hazel_fcb_addr_mid,y ; Load byte count mid from FCB
BC9D STA hazel_txcb_result ; Store as reply command byte
BCA0 LDA hazel_fcb_addr_hi,y ; Load byte count hi from FCB
BCA3 STA hazel_exec_addr ; Store as load vector field
BCA6 LDY #&0d ; Y=&0D: TX command byte offset
BCA8 LDX #5 ; X=5: send 5 bytes
BCAA JSR save_net_tx_cb ; Send flush request to server
BCAD PLA ; Restore station from stack
BCAE TAY ; Y=station for wipe request
BCAF LDA hazel_saved_byte ; Load saved data byte
BCB2 JSR send_wipe_request ; Send close/wipe request to server
BCB5 JSR restore_catalog_entry ; Restore catalog state after flush
BCB8 PLY
BCB9 PLX
BCBA PLA ; Restore A
BCBB 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
BCBC .send_wipe_request←2← BC1C JSR← BCB2 JSR
STY hazel_net_reply_buf_2 ; Store reply port
BCBF STA hazel_net_reply_buf_3 ; Store data byte
BCC2 TYA ; Save Y
BCC3 PHA ; Push Y to stack
BCC4 TXA ; Save X
BCC5 PHA ; Push X to stack
BCC6 LDA #&90 ; Function code &90
BCC8 STA hazel_net_reply_buf_0 ; Store in send buffer
BCCB JSR init_txcb ; Initialise TX control block
BCCE LDA #&dc ; TX start address low = &DC
BCD0 STA txcb_start ; Set TX start in control block
BCD2 LDA #&e0 ; TX end address low = &E0
BCD4 STA txcb_end ; Set TX end in control block
BCD6 LDA #9 ; Expected reply port = 9
BCD8 STA hazel_net_reply_buf_1 ; Store reply port in buffer
BCDB LDX #&c0 ; TX control = &C0
BCDD LDY #0 ; Y=0: no timeout
BCDF LDA hazel_net_reply_buf_2 ; Load reply port for addressing
BCE2 JSR send_disconnect_reply ; Send packet to server
BCE5 LDA hazel_net_reply_buf_1 ; Load reply status
BCE8 BEQ done_toggle_station ; Zero: success
BCEA STA hazel_fs_last_error ; Store error code
BCED LDX #0 ; X=0: copy index
BCEF .loop_copy_wipe_err_msg←1← BCFA BNE
LDA hazel_net_reply_buf_0,x ; Load error message byte
BCF2 STA error_block,x ; Copy to error block
BCF5 CMP #&0d ; Is it CR (end of message)?
BCF7 BEQ done_terminate_wipe_err ; Yes: terminate string
BCF9 INX ; Next byte
BCFA BNE loop_copy_wipe_err_msg ; Continue copying error message
BCFC .done_terminate_wipe_err←1← BCF7 BEQ
LDA #0 ; NUL terminator
BCFE STA error_block,x ; Terminate error string in block
BD01 DEX ; Back up position for error check
BD02 JMP check_net_error_code ; Process and raise network error
BD05 .done_toggle_station←1← BCE8 BEQ
LDX hazel_chan_attr ; Load channel attribute index
BD08 LDA hazel_fcb_state_byte,x ; Load station number for channel
BD0B EOR #1 ; Toggle bit 0 (alternate station)
BD0D STA hazel_fcb_state_byte,x ; Update station number
BD10 PLA ; Restore X
BD11 TAX ; X restored
BD12 PLA ; Restore Y
BD13 TAY ; Y restored
BD14 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
BD15 .send_and_receive←2← B9EE JSR← BA75 JSR
JSR set_options_ptr ; Set up FS options pointer
BD18 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
BD1B .read_rx_attribute←4← 99DF JSR← 99F2 JSR← B9E1 JSR← BA68 JSR
LDY #&0a ; Y=&0A: receive attribute offset
BD1D LDA (net_rx_ptr),y ; Read byte from receive buffer
BD1F 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
BD20 .store_rx_attribute←10← 8B96 JSR← 9EF9 JSR← 9FB9 JSR← 9FFD JSR← B889 JSR← B9F5 JSR← BA79 JSR← BBA5 JSR← BBCA JSR← BC6A JSR
LDY #&0a ; Y=&0A: receive attribute offset
BD22 STA (net_rx_ptr),y ; Store byte to receive buffer
BD24 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.

BD25 .abort_if_escape←1← BD59 JSR
BIT escape_flag ; Test bit 7 of escape flag
BD27 BMI error_escape_pressed ; Escape pressed: handle abort
BD29 RTS ; No escape: return
BD2A .error_escape_pressed←1← BD27 BMI
JSR close_ws_file ; Close the open file
BD2D .escape_error_close←1← 9722 JMP
JSR osnewl ; Write newline (characters 10 and 13)
BD30 LDA #osbyte_acknowledge_escape ; Acknowledge escape condition
BD32 JSR osbyte ; Clear escape condition and perform escape effects
BD35 LDA #&11 ; Error number &11
BD37 JSR error_inline ; Generate 'Escape' BRK error
BD3A EQUS "Escape."
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
BD41 .cmd_dump
JSR open_file_for_read ; Open the file (handle stored in ws_page)
BD44 LDX #&14 ; X=&14: 21-byte stack buffer for dump line state 21 bytes to push (0-&14)
BD46 LDA #0 ; A=0: zero-fill
BD48 .loop_push_zero_buf←1← BD4A BPL
PHA ; Push zero
BD49 DEX ; Step counter
BD4A BPL loop_push_zero_buf ; Loop while X >= 0 (21 zeros)
BD4C TSX ; Capture stack pointer for later restore
BD4D JSR init_dump_buffer ; Parse address range and validate against file extent Set up buffer pointer and parse args
BD50 LDA (work_ae),y ; Read low nibble of starting address
BD52 AND #&f0 ; Mask high nibble (top 4 bits)
BD54 BEQ loop_dump_line ; Aligned (high nibble zero): skip the header print Skip header if 16-byte aligned
BD56 JSR print_dump_header ; Print 'Address: 00 01 ... 0F: ASCII data' header Print column header for offset start
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 &BDF9.

BD59 .loop_dump_line←2← BD54 BEQ← BDF9 JMP
JSR abort_if_escape ; Test escape and abort if pressed
BD5C LDA #&ff ; A=&FF: count counter starts here so first INC -> 0
BD5E STA osword_flag ; Save counter (-1)
BD60 .loop_read_dump_byte←1← BD6F BNE
LDY ws_page ; Y = file handle
BD62 JSR osbget ; Read one byte via OSBGET (C set on EOF)
BD65 BCS done_check_dump_eof ; EOF: finish off this line then exit
BD67 INC osword_flag ; Increment count counter
BD69 LDY osword_flag ; Y = current count (also buffer offset)
BD6B STA (work_ae),y ; Store byte in 16-byte line buffer at (work_ae)+Y
BD6D CPY #&0f ; Done all 16 bytes?
BD6F BNE loop_read_dump_byte ; No: read next byte
BD71 CLC ; C clear: not EOF (clean line)
BD72 .done_check_dump_eof←1← BD65 BCS
PHP ; Save the EOF/clean flag
BD73 LDA osword_flag ; Reload counter byte
BD75 BPL done_check_boundary ; Bit 7 clear (counter is 0..&7F): bytes were read
BD77 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 &BD7B and the fall-through tail at &BDFE.

On EntryXstack-byte count - 1 (caller sets it to &14 or &15)
BD79 .loop_pop_stack_buf←2← BD7B BPL← BDFE JMP
PLA ; Restore one stack byte
BD7A DEX ; Step
BD7B BPL loop_pop_stack_buf ; Loop while X >= 0 (22 pulls)
BD7D JMP close_ws_file ; Tail-jump to close_ws_file
BD80 .done_check_boundary←1← BD75 BPL
LDY #&10 ; Y=&10: read displayed-address byte 0
BD82 LDA (work_ae),y ; Read low byte
BD84 AND #&f0 ; Top nibble
BD86 BNE done_start_dump_addr ; Non-zero: not a 256-byte boundary, skip header Non-zero: header already current
BD88 JSR print_dump_header ; Boundary: print column header
BD8B .done_start_dump_addr←1← BD86 BNE
LDY #&13 ; Y=&13: highest byte of 4-byte address
BD8D .loop_print_addr_byte←1← BD97 BNE
LDA (work_ae),y ; Read address byte (highest first)
BD8F PHA ; Save it (print_hex_byte clobbers A)
BD90 JSR print_hex_byte ; Print as 2 hex digits
BD93 PLA ; Restore A
BD94 DEY ; Step backwards
BD95 CPY #&0f ; Reached low byte (offset &0F)?
BD97 BNE loop_print_addr_byte ; No: continue printing
BD99 INY ; Y=&10: low byte of address
BD9A CLC ; Clear C
BD9B ADC #&10 ; Bump address by 16 bytes for next line Add 16 to lowest address byte
BD9D PHP ; Save C from the add
BD9E .loop_inc_dump_addr←1← BDA9 BNE
PLP ; Restore C from previous step
BD9F STA (work_ae),y ; Store updated address byte
BDA1 INY ; Step Y up
BDA2 LDA (work_ae),y ; Read next byte
BDA4 ADC #0 ; Add carry from below
BDA6 PHP ; Save C
BDA7 CPY #&14 ; Done all 4 bytes (Y=&14)?
BDA9 BNE loop_inc_dump_addr ; No: continue propagating
BDAB PLP ; Restore final C
BDAC JSR print_inline ; Print ' : ' separator before hex byte field Print address/data separator
BDAF EQUS " : "
BDB2 LDY #0 ; Y=0: start of buffer
BDB4 LDX osword_flag ; X = byte counter (-1 initially, INC'd to 0..&0F) X = bytes read (counter for display)
BDB6 .loop_print_dump_hex←1← BDC1 BPL
LDA (work_ae),y ; Read byte from buffer
BDB8 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 &BDCC after short-line padding).

On EntryXremaining bytes - 1
Ybuffer offset
BDBB .loop_next_dump_col←1← BDCC JMP
INY ; Step buffer offset
BDBC CPY #&10 ; Done all 16?
BDBE BEQ done_print_separator ; Yes: print separator before ASCII field
BDC0 DEX ; Step counter (Y was off-by-one from line read) Decrement remaining data bytes
BDC1 BPL loop_print_dump_hex ; Have a real byte? Print it
BDC3 PHY ; End of partial line: pad with 3 spaces
BDC4 JSR print_inline ; Print ' ' inline
BDC7 EQUS " "
BDCA NOP ; NOP -- bit-7 terminator + harmless resume opcode
BDCB PLY ; Restore Y
BDCC JMP loop_next_dump_col ; Continue padding the rest of the hex column
BDCF .done_print_separator←1← BDBE BEQ
DEX ; Counter has finished -- step it once more for the ASCII test
BDD0 JSR print_inline ; Print ': ' inline (ASCII field separator)
BDD3 EQUS ": "
BDD5 LDY #0 ; Y=0: rewind to start of line buffer
BDD7 JSR advance_x_by_8 ; Skip 8 padding spaces if needed (advance_x_by_8)
BDDA .loop_print_dump_ascii←1← BDF1 BPL
LDA (work_ae),y ; Read line buffer byte
BDDC AND #&7f ; Mask off bit 7 (DEL/inverted)
BDDE CMP #&20 ; Below ' '? (control char)
BDE0 BCS done_test_del ; Yes: skip to substitution
BDE2 .skip_non_printable←1← BDE6 BEQ
LDA #&2e ; Substitute '.' for non-printables
BDE4 .done_test_del←1← BDE0 BCS
CMP #&7f ; Compare with DEL
BDE6 BEQ skip_non_printable ; Equal: also non-printable, substitute '.'
BDE8 JSR osasci ; Print the (possibly substituted) character
BDEB INY ; Step Y
BDEC CPY #&10 ; Done 16 chars?
BDEE BEQ done_end_dump_line ; Yes: end this line
BDF0 DEX ; Step counter back
BDF1 BPL loop_print_dump_ascii ; Loop while X >= 0
BDF3 .done_end_dump_line←1← BDEE BEQ
JSR osnewl ; Print newline at end of line
BDF6 PLP ; Restore EOF flag
BDF7 BCS done_dump_eof ; EOF: tidy up and exit
BDF9 JMP loop_dump_line ; More to dump: jump to next line
BDFC .done_dump_eof←1← BDF7 BCS
LDX #&14 ; X=&14: balance the loop_pop_stack_buf counter
BDFE 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)
BE01 .print_dump_header←2← BD56 JSR← BD88 JSR
LDA (work_ae),y ; Read low nibble of starting address from (work_ae),Y
BE03 PHA ; Save it (we'll print it 16 times incrementing each iteration)
BE04 JSR print_inline ; Print 'CR>Address : ' header via inline string
BE07 EQUS ".Address : " ; *Dump column header
BE13 LDX #&0f ; X=&0F: print 16 column-number digits
BE15 PLA ; Pull the starting low nibble back into A
BE16 .loop_print_hex_row←1← BE1F BPL
JSR print_hex_and_space ; Print A as two hex digits + space
BE19 SEC ; Set C ready for the increment
BE1A ADC #0 ; A += 1 (column index increments, with C set on entry)
BE1C AND #&0f ; Wrap to nibble (0..15)
BE1E DEX ; Step column counter
BE1F BPL loop_print_hex_row ; Loop while X >= 0 (16 iterations)
BE21 JSR print_inline ; Print ': ASCII dataCR>CR>' trailer via inline
BE24 EQUS ": ASCII data.." ; *Dump trailer
BE35 NOP ; NOP -- bit-7 terminator + harmless resume opcode
BE36 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
BE37 .print_hex_and_space←2← BDB8 JSR← BE16 JSR
PHA ; Save A so the caller can re-use the value
BE38 JSR print_hex_byte ; Print A as two hex digits
BE3B LDA #&20 ; A=' ': trailing column separator
BE3D JSR osasci ; Print the space via OSASCI
BE40 .done_print_hex_space
PLA ; Restore caller's A
BE41 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)
BE42 .parse_dump_range←2← BEB2 JSR← BF3E JSR
TYA ; Move command-line offset Y into A for the X copy Save command line offset to X
BE43 TAX ; X = current command-line offset (live cursor) X tracks current position
BE44 LDA #0 ; A=0: zero-fill value
BE46 TAY ; Y=0: accumulator index
BE47 .loop_clear_hex_accum←1← BE4C BNE
STA (work_ae),y ; Zero accumulator byte at (work_ae)+Y
BE49 INY ; Step accumulator
BE4A CPY #4 ; Done all 4 bytes?
BE4C 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
BE4E .loop_parse_hex_digit←1← BE95 JMP
TXA ; Reload command-line offset
BE4F INX ; Step cursor
BE50 TAY ; Y = stepped cursor (for the indirect read)
BE51 LDA (os_text_ptr),y ; Read next command-line byte
BE53 CMP #&0d ; CR? (end of address)
BE55 BEQ done_test_hex_space ; Yes: range parsed -- exit via space-skip
BE57 CMP #&20 ; Space?
BE59 BEQ done_test_hex_space ; Yes: also a separator -- exit
BE5B CMP #&30 ; Below '0'?
BE5D BCC error_bad_hex_value ; Yes: not hex -- raise 'Bad hex'
BE5F CMP #&3a ; Above '9'?
BE61 BCC done_mask_hex_digit ; No: it's '0'-'9' -- skip the letter handling Yes: is a decimal digit
BE63 AND #&5f ; Force uppercase via AND #&5F
BE65 ADC #&b8 ; Add &B8: 'A' (=&41) becomes &F9 with C set; 'F' becomes &FE; this maps 'A'-'F' to &FA-&FF in C Map 'A'-'F' → &FA-&FF (C=0 here)
BE67 BCS error_bad_hex_value ; Carry out of ADC: digit was below 'A' -> bad hex Carry set: char > 'F', error
BE69 CMP #&fa ; Below &FA? (i.e. before 'A' in mapped range) Below &FA? (i.e. was < 'A')
BE6B BCC error_bad_hex_value ; Yes (out of [&FA,&FF]): bad hex
BE6D .done_mask_hex_digit←1← BE61 BCC
AND #&0f ; Keep low nibble (0-15)
BE6F PHA ; Push the new nibble
BE70 TXA ; Push X (current command-line offset)
BE71 PHA ; Preserve on stack
BE72 LDX #4 ; X=4: rotate the 4-byte accumulator left 4 times 4 bits to shift in
BE74 .loop_shift_nibble←1← BE8A BNE
LDY #0 ; Y=0: byte index for the rotate
BE76 TYA ; A=0 (and C clear from TYA's flags)
BE77 .loop_rotate_hex_accum←1← BE83 BNE
PHA ; Save A onto stack so we can use PHP/PLP to round-trip carry through the rotate Transfer carry bit to flags via stack
BE78 PLP ; Pull flags (effectively C clear from the TYA above; on later iterations C carries the bit shifted out) C = bit shifted out of prev iter
BE79 LDA (work_ae),y ; Read next accumulator byte
BE7B ROL ; Shift in C from below, shift out top bit to C Rotate left through carry
BE7C STA (work_ae),y ; Write back
BE7E PHP ; Save the new C
BE7F PLA ; Pull A back (PHA earlier)
BE80 INY ; Step accumulator byte
BE81 CPY #4 ; Done all 4 bytes?
BE83 BNE loop_rotate_hex_accum ; No: rotate next byte
BE85 PHA ; PHA/PLP: bring saved C into flag register
BE86 PLP ; C = overflow bit
BE87 BCS error_hex_overflow ; C set: a bit fell off the top -- overflow Overflow: address too large
BE89 DEX ; Step rotate counter
BE8A BNE loop_shift_nibble ; Loop while X != 0 (4 rotates total)
BE8C PLA ; Pull saved X (command-line offset)
BE8D TAX ; Restore X
BE8E PLA ; Pull saved nibble into A
BE8F LDY #0 ; Y=0: low byte of accumulator
BE91 ORA (work_ae),y ; OR new nibble into accumulator[0]
BE93 STA (work_ae),y ; Write back
BE95 JMP loop_parse_hex_digit ; Loop for next hex digit
BE98 .error_hex_overflow←1← BE87 BCS
PLA ; Discard saved nibble
BE99 PLA ; Discard saved X
BE9A SEC ; Set C: signal overflow to caller
BE9B RTS ; Return with C=1
BE9C .error_bad_hex_value←3← BE5D BCC← BE67 BCS← BE6B BCC
JSR close_ws_file ; Close the dump file before raising the error Close open file before error
BE9F JMP err_bad_hex ; Raise 'Bad hex' error; never returns
BEA2 .loop_skip_hex_spaces←1← BEA7 BEQ
INY ; Step past current space
BEA3 .done_test_hex_space←2← BE55 BEQ← BE59 BEQ
LDA (os_text_ptr),y ; Read next byte
BEA5 CMP #&20 ; Still a space?
BEA7 BEQ loop_skip_hex_spaces ; Yes: keep skipping
BEA9 CLC ; Clear C: signal success
BEAA 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
BEAB .init_dump_buffer←1← BD4D JSR
INX ; Step Y past the *Dump command name into the argument X+1: first byte of buffer
BEAC STX work_ae ; Save the cursor offset
BEAE LDX #1 ; Set bit 0 of addr_work to 1 -- 'mode' flag for parse_dump_range below Buffer is on stack in page 1
BEB0 STX addr_work ; Save mode flag
BEB2 JSR parse_dump_range ; Parse the start address (max 4 hex digits) Parse start offset from command line
BEB5 BCS error_outside_file ; Overflow: too many digits
BEB7 TYA ; Save current Y (cursor after start address) A = command line offset after parse
BEB8 PHA ; Push it
BEB9 LDY ws_page ; Y = file handle saved in ws_page
BEBB LDX #&aa ; X=&AA: zero-page address for OSARGS result
BEBD LDA #2 ; A=2: OSARGS sub-fn 2 = read sequential file extent A=2: read file extent (length)
BEBF JSR osargs ; Get file size into 4 bytes at &AA
BEC2 LDY #3 ; Y=3: compare 4-byte values (high to low)
BEC4 .loop_cmp_file_length←1← BECC BPL
LDA osword_flag,y ; Read file size byte at &AA+Y
BEC7 CMP (work_ae),y ; Compare with parsed start address (work_ae+Y) Compare with start offset byte
BEC9 BNE done_check_outside ; Mismatch: branch decides which is bigger
BECB DEY ; Step to next byte
BECC BPL loop_cmp_file_length ; Loop while Y >= 0 (covers indices 3, 2, 1, 0) More bytes to compare
BECE BMI done_advance_start ; All bytes equal: start = extent (allowed); jump to the post-validation path All equal: start = length, within file
BED0 .done_check_outside←1← BEC9 BNE
BCC error_outside_file ; C clear: parsed_start > file_size -- reject Length < start: outside file
BED2 LDY #&ff ; Y=&FF: signal 'no copy needed' to the loop below Y=&FF: length > start, flag for later
BED4 BNE done_advance_start ; Always taken: skip directly to advance phase Continue to copy start address
BED6 .error_outside_file←2← BEB5 BCS← BED0 BCC
JSR close_ws_file ; Close the file before raising
BED9 LDA #&b7 ; A=&B7: 'Outside file' error code
BEDB JSR error_inline ; Raise via inline string; never returns
BEDE EQUS "Outside file." ; *Dump range error
BEEB .loop_copy_osword_data←1← BEF3 BNE
.loop_copy_start_addr←1← BEF3 BNE
LDA (work_ae),y ; Copy file-extent byte from osword_flag to (work_ae) Load start address byte from buffer
BEED STA osword_flag,y ; Store it (used as default end address)
BEF0 .done_advance_start←2← BECE BMI← BED4 BNE
INY ; Step Y
BEF1 CPY #4 ; Done all 4 bytes?
BEF3 BNE loop_copy_osword_data ; No: continue copying
BEF5 LDX #&aa ; X=&AA: zero-page source for the OSARGS write-back
BEF7 LDY ws_page ; Y = file handle
BEF9 LDA #1 ; A=1: OSARGS sub-fn 1 = write sequential file pointer A=1: write file pointer
BEFB JSR osargs ; Set the file's read pointer to the parsed start OSARGS: set file pointer
BEFE PLA ; Pull saved cursor offset
BEFF TAY ; Restore into Y
BF00 LDA (os_text_ptr),y ; Read next command-line byte
BF02 CMP #&0d ; CR (end of args)?
BF04 BNE done_parse_disp_base ; No: there's a second arg -- handle below
BF06 LDY #1 ; Y=1: copy os_text_ptr (2 bytes) to work_ae as a displacement-base hint Copy 2 bytes: os_text_ptr to buffer
BF08 .loop_copy_osfile_ptr←1← BF0E BPL
LDA os_text_ptr,y ; Read os_text_ptr+Y
BF0B STA (work_ae),y ; Save in work_ae+Y
BF0D DEY ; Step backwards
BF0E BPL loop_copy_osfile_ptr ; Loop while Y >= 0
BF10 LDA #osfile_read_catalogue_info ; A=5: OSFILE sub-fn 5 = read catalogue info
BF12 LDX work_ae ; X = filename pointer low (work_ae)
BF14 LDY addr_work ; Y = filename pointer high (addr_work)
BF16 JSR osfile ; Read load address into work_ae+0..3
BF19 LDY #2 ; Y=2: shift 3 bytes down 2 positions to drop the first 2 bytes (action code + a flag) Start at OSFILE +2 (load addr byte 0)
BF1B .loop_shift_osfile_data←1← BF26 BNE
LDA (work_ae),y ; Read source byte
BF1D DEY ; Y -= 2 (destination)
BF1E DEY ; Continue decrement
BF1F STA (work_ae),y ; Store at destination
BF21 INY ; Y += 3 to advance to next source
BF22 INY ; (continued)
BF23 INY ; (continued)
BF24 CPY #6 ; Done 6 bytes shifted?
BF26 BNE loop_shift_osfile_data ; No: continue
BF28 DEY ; Y -= 2: position at high byte of load address Y=6 after loop exit
BF29 DEY ; Y=4: check from buf[4] downward
BF2A .loop_check_ff_addr←1← BF31 BNE
LDA (work_ae),y ; Read load-address byte at Y
BF2C CMP #&ff ; Is it &FF (signals no real load address)?
BF2E BNE done_add_disp_base ; No: have a real load address; add it as displacement No: valid load address, use it
BF30 DEY ; Yes: step back to next higher byte
BF31 BNE loop_check_ff_addr ; Loop until Y=0
BF33 LDY #3 ; All four bytes were &FF: zero out the load address Clear all 4 bytes
BF35 LDA #0 ; A=0
BF37 .loop_zero_load_addr←1← BF3A BPL
STA (work_ae),y ; Zero work_ae+Y
BF39 DEY ; Step backwards
BF3A BPL loop_zero_load_addr ; Loop while Y >= 0
BF3C BMI done_add_disp_base ; Always taken (after BPL drops out): skip second-arg path Continue to compute display address
BF3E .done_parse_disp_base←1← BF04 BNE
JSR parse_dump_range ; Parse end-address argument
BF41 BCC done_add_disp_base ; Success: continue with displacement-add
BF43 JSR close_ws_file ; Parse error: close file then raise 'Bad address' Invalid: close file before error
BF46 LDA #&fc ; A=&FC: 'Bad address' error code
BF48 JSR error_bad_inline ; Raise; never returns
BF4B EQUS "address."
BF53 .done_add_disp_base←3← BF2E BNE← BF3C BMI← BF41 BCC
LDY #0 ; Y=0: start of work_ae
BF55 LDX #4 ; X=4: 4-byte add
BF57 CLC ; Clear C for the add
BF58 .loop_add_disp_bytes←1← BF62 BNE
LDA (work_ae),y ; Read low byte of address from (work_ae)+Y
BF5A ADC osword_flag,y ; Add osword_flag+Y (low byte of length, with carry propagating) Add start offset byte
BF5D STA osword_flag,y ; Store sum back to osword_flag+Y
BF60 INY ; Advance to next byte
BF61 DEX ; Decrement byte counter
BF62 BNE loop_add_disp_bytes ; Loop until 4 bytes added
BF64 LDY #&14 ; Y=&14: target offset = workspace+&13 (top of end-addr field, stored hi-byte-first) Point past end of address area
BF66 LDX #3 ; X=3: source = osword_flag+3 (top byte of sum)
BF68 .loop_store_disp_addr←1← BF6E BPL
DEY ; Pre-decrement Y (so first store is to offset &13)
BF69 LDA osword_flag,x ; Read sum byte from osword_flag+X
BF6B STA (work_ae),y ; Store at (work_ae)+Y
BF6D DEX ; Decrement source index
BF6E BPL loop_store_disp_addr ; Loop until X wraps below 0
BF70 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)
BF71 .close_ws_file←5← BD2A JSR← BD7D JMP← BE9C JSR← BED6 JSR← BF43 JSR
LDY ws_page ; Y = saved file handle from ws_page
BF73 LDA #osfind_close ; A=0: OSFIND close
BF75 JMP osfind ; Tail-call OSFIND to close the handle

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
BF78 .open_file_for_read←1← BD41 JSR
PHP ; Save flags so caller's NZC survive
BF79 TYA ; Move command-line offset Y into A for the add A=filename offset from Y
BF7A CLC ; Clear C for the 16-bit add
BF7B ADC os_text_ptr ; A = os_text_ptr_lo + Y (filename address low byte) Add text pointer low byte
BF7D PHA ; Push it (we need to restore os_text_ptr after OSFIND) Save filename address low
BF7E TAX ; Move filename low into X (OSFIND wants the address in X/Y) X=filename address low (for OSFIND)
BF7F LDA #0 ; A=0: zero high byte before the carry-add
BF81 ADC os_text_ptr_hi ; Add os_text_ptr_hi with carry from the low add Add text pointer high byte + carry
BF83 PHA ; Push filename high byte for the restore
BF84 TAY ; Move filename high into Y
BF85 LDA #osfind_open_input ; A=&40: OSFIND open-for-input mode
BF87 JSR osfind ; Open the file; returns handle in A (zero on failure)
BF8A TAY ; Copy returned handle into Y (also sets Z if zero)
BF8B STA ws_page ; Stash the handle in ws_page for later close
BF8D BNE restore_text_ptr ; Non-zero: open succeeded, skip error path
BF8F LDA #&d6 ; A=&D6: 'Not found' error code
BF91 JSR error_inline ; Raise the error with the inline string below; never returns Raise 'Not found' error
BF94 EQUS "Not found."
BF9E .restore_text_ptr←1← BF8D 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 Restore text pointer high from stack
BF9F STA os_text_ptr_hi ; Store as os_text_ptr_hi
BFA1 PLA ; Restore filename low byte into os_text_ptr_lo (so (os_text_ptr) now points at the filename) Restore text pointer low from stack
BFA2 STA os_text_ptr ; Store as os_text_ptr lo
BFA4 LDY #0 ; Y=0: scan from start of filename
BFA6 .loop_skip_filename←1← BFAF BNE
INY ; Step to next byte
BFA7 LDA (os_text_ptr),y ; Read filename byte
BFA9 CMP #&0d ; Hit CR? End of command line
BFAB BEQ done_skip_filename ; Yes: filename ended at CR (no trailing spaces) Yes: finished parsing filename
BFAD CMP #&20 ; Hit space? End of filename
BFAF BNE loop_skip_filename ; No (still inside filename): keep scanning
BFB1 .loop_skip_fn_spaces←1← BFB6 BEQ
INY ; Step past spaces
BFB2 LDA (os_text_ptr),y ; Read next byte
BFB4 CMP #&20 ; Still a space?
BFB6 BEQ loop_skip_fn_spaces ; Yes: keep skipping
BFB8 .done_skip_filename←1← BFAB BEQ
PLP ; Done: Y points just past the filename and any spaces Restore caller flags
BFB9 RTS ; Restore caller's flags

Advance X by 16 via nested JSR + fall-through

Note: the name is historical and misleading -- this routine actually advances X by 16, not 8.

JSR advance_x_by_4 followed by no RTS, so control falls through into advance_x_by_4 itself for a second pass. Each pass through advance_x_by_4 runs inx4 twice (8 INXs), so two passes give 16 INXs in total.

On EntryXvalue to advance
On ExitXinput + 16
A, Ypreserved
BFBA .advance_x_by_8←3← 9EDA JSR← AC32 JSR← BDD7 JSR
JSR advance_x_by_4 ; First INX-by-4 via JSR; falls into advance_x_by_4 for the second four JSR+fall-through: 8+8=16 INXs total
fall through ↓

Advance X by 8 via JSR and fall-through

Note: the name is historical and misleading -- this routine actually advances X by 8, not 4.

JSR inx4 (4 INXs); the JSR's RTS lands at &BFC0 which is inx4's entry, so a second pass runs by fall- through (4 more INXs). Total: 8 INXs.

On EntryXvalue to advance
On ExitXinput + 8
A, Ypreserved
BFBD .advance_x_by_4←1← BFBA JSR
JSR inx4 ; JSR inx4 (4 INX); RTS returns here, then falls into inx4 again for the implicit second four JSR+fall-through: 4+4=8 INXs
fall through ↓

Increment X four times

Four consecutive INX instructions. Used as a building block by advance_x_by_4 and advance_x_by_8 via JSR/fall-through chaining.

On EntryXvalue to advance
On ExitXinput + 4
A, Ypreserved
N, Z FLAGSreflect new X
BFC0 .inx4←1← BFBD JSR
INX ; X += 4
BFC1 INX ; (continued)
BFC2 INX ; (continued)
BFC3 INX ; (continued)
BFC4 RTS ; Return; caller is either an explicit JSR (so X has advanced by 4) or advance_x_by_8's fall-through (so X has advanced by 8 total) Return

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).

BFC5 EQUB &FF, &FF ; ROM-tail padding (2 bytes &FF)
BFC7 EQUB &FF ; ROM-tail padding (1 byte &FF; on its own line for annotation) Padding; next byte is reloc_p5_src
BFC8 EQUB &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, ; ROM-tail &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, ; padding (30 &FF, &FF, &FF, &FF, &FF, &FF, &FF, &FF, ; bytes &FF) &FF, &FF, &FF, &FF, &FF, &FF

HAZEL Y-indexed access bases (3 labels at the ROM tail)

Three labels positioned &1A, 2, and 1 bytes before &C000 (the start of HAZEL), used as indexing bases for reads and writes into HAZEL.

The trick: HAZEL begins at &C000, so an LDA hazel_minus_2,Y / STA hazel_minus_2,Y instruction with Y >= 2 lands at &BFFE + Y >= &C000 -- inside HAZEL. ANFS exploits this in several places to copy fixed-size blocks between HAZEL workspace and other buffers without burning a separate two-byte zero-page pointer:

Site / routine instruction base Y range Effective range
loop_copy_fs_ctx STA hazel_minus_2,Y hazel_minus_2 9..2 &C007..&C000
loop_restore_ctx LDA hazel_minus_2,Y hazel_minus_2 9..2 &C007..&C000
loop_copy_ws_to_pb LDA hazel_minus_2,Y hazel_minus_2 4..6 &C002..&C004
loop_copy_station LDA hazel_minus_1,Y hazel_minus_1 2..1 &C001..&C000
osword_13_set_station_body STA hazel_minus_1,Y hazel_minus_1 2..1 &C001..&C000
loop_copy_txcb_init LDA hazel_minus_1a,Y hazel_minus_1a >= &1A spans into HAZEL from &C000

Each loop's CPY/BNE guard stops Y before it would land inside the ROM tail, so the actual workspace data lives entirely in HAZEL. The labels themselves never have their own bytes read -- the &FF byte at each label address is incidental, only the address matters.

BFE6 .hazel_minus_1a←1← AC5A LDA
EQUB &FF, &FF, &FF, &FF, &FF, ; Base for hazel_minus_1a,Y &FF, &FF, &FF, &FF, &FF, ; reads in &FF, &FF, &FF, &FF, &FF, ; loop_copy_txcb_init -- &FF, &FF, &FF, &FF, &FF, ; &BFE6 + Y reaches into &FF, &FF, &FF, &FF ; HAZEL for Y >= &1A
BFFE .hazel_minus_2←3← 8B67 STA← 9066 LDA← AC85 LDA
EQUB &FF ; Base for hazel_minus_2,Y reads/writes -- &BFFE + Y reaches into HAZEL for Y >= 2 (used by loop_copy_fs_ctx, loop_restore_ctx, loop_copy_ws_to_pb)
BFFF .hazel_minus_1←2← A9D1 LDA← A9E6 STA
EQUB &FF ; Base for hazel_minus_1,Y reads/writes -- &BFFF + Y reaches into HAZEL for Y >= 1 (used by loop_copy_station, osword_13_set_station)