Acorn ANFS 4.18

Updated 31 Mar 2026

← All Acorn NFS and Acorn ANFS versions

; Sideways ROM header
; ANFS ROM 4.08.53 disassembly (Acorn Advanced Network Filing ; System)
; ============================================================== ; =====
8000 .rom_header←1← 04E2 LDA
.language_entry←1← 04E2 LDA
.pydis_start←1← 04E2 LDA
EQUB &00, &42, &43
8003 .service_entry←1← 04F1 LDY
JMP service_handler ; JMP service_handler
8006 .rom_type←1← 04D6 AND
EQUB &82 ; ROM type: service + language
8007 .copyright_offset←1← 04DE LDX
EQUB copyright - rom_header
8008 .binary_version
EQUB &04
8009 .title
EQUS "Acorn ANFS 4.18"
8018 .version
EQUB &00
8019 .copyright
EQUB &00 ; Null terminator before copyright
801A .copyright_string
EQUS "(C)1985 Acorn."

Service 5: unrecognised interrupt (SR dispatch)

Tests IFR bit 2 (SR complete) to check for a shift register transfer complete. If SR is not set, returns A=5 to pass the service call on. If SR is set, saves registers, reads the VIA ACR, clears and restores the SR mode bits from ws_0d64, then dispatches the TX completion callback via the operation type stored in tx_op_type. The indexed handler performs the completion action (e.g. resuming background print spooling) before returning with A=0 to claim the service call.

On EntryA5 (service call number)
XROM slot
Yparameter
8028 .svc5_irq_check
LDA #4 ; A=4: SR bit mask for IFR test
802A BIT system_via_ifr ; Test IFR bit 2: SR complete
802D BNE save_registers ; SR set: shift register complete
802F LDA #5 ; A=5: not our interrupt, pass on
8031 RTS ; Return service code 5 to MOS
8032 .save_registers←1← 802D BNE
TXA ; Save X on stack
8033 PHA ; Push saved X
8034 TYA ; Save Y on stack
8035 PHA ; Push saved Y
8036 LDA system_via_acr ; Read ACR for shift register restore
8039 AND #&e3 ; Clear SR mode bits (2-4)
803B ORA ws_0d64 ; Restore saved SR mode from ws_0d64
803E STA system_via_acr ; Write restored ACR to system VIA
8041 LDA system_via_sr ; Read SR to clear shift register IRQ
8044 LDA #4 ; A=4: SR bit mask
8046 STA system_via_ifr ; Clear SR interrupt flag in IFR
8049 STA system_via_ier ; Disable SR interrupt in IER
804C LDY tx_op_type ; Load TX operation type for dispatch
804F TYA ; Copy to A for sign test
8050 BMI set_jsr_protection ; Bit 7 set: dispatch via table
8052 LDA #&fe ; A=&FE: Econet receive event
8054 JSR generate_event ; Call event vector handler
8057 JMP tx_done_exit ; Fire event (enable: *FX52,150)

Generate event via event vector

Dispatches through the event vector (EVNTV) to notify event handlers. Called with the event number in A.

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

Set JSR protection and dispatch via table

Validates the TX operation type in Y against the dispatch table range, saves the current JSR protection mask, sets protection bits 2-4, then dispatches through the PHA/RTS trampoline using the table at set_rx_buf_len_hi. If Y >= &86, skips the protection setup and dispatches directly.

On EntryYTX operation type (dispatch index)
805D .set_jsr_protection←1← 8050 BMI
CPY #&86 ; Y >= &86: above dispatch range
805F BCS dispatch_svc5 ; Out of range: skip protection
8061 LDA ws_0d68 ; Save current JSR protection mask
8064 STA ws_0d69 ; Backup to saved_jsr_mask
8067 ORA #&1c ; Set protection bits 2-4
8069 STA ws_0d68 ; Apply protection during dispatch
806C .dispatch_svc5←1← 805F BCS
LDA #&85 ; Push return addr high (&85)
806E PHA ; High byte on stack for RTS
806F LDA set_rx_buf_len_hi,y ; Load dispatch target low byte
8072 PHA ; Low byte on stack for RTS
8073 .svc_5_unknown_irq
RTS ; RTS = dispatch to PHA'd address

ADLC initialisation

Initialise ADLC hardware and Econet workspace. Reads station ID via &FE18 (INTOFF side effect), performs a full ADLC reset (adlc_full_reset), then checks for Tube co-processor via OSBYTE &EA and stores the result in l0d63. Issues NMI claim service request (OSBYTE &8F, X=&0C). Falls through to init_nmi_workspace to copy the NMI shim to RAM.

8074 .adlc_init←1← 8F5D JSR
BIT station_id_disable_net_nmis ; INTOFF: read station ID, disable NMIs
8077 JSR adlc_full_reset ; Full ADLC hardware reset
807A LDA #&ea ; OSBYTE &EA: check Tube co-processor
807C LDX #0 ; X=0 for OSBYTE
807E STX ws_0d62 ; Clear Econet init flag before setup
8081 JSR osbyte_x0 ; Check Tube presence via OSBYTE &EA
8084 STX tube_present ; Store Tube presence flag from OSBYTE &EA
8087 LDA #&8f ; OSBYTE &8F: issue service request
8089 LDX #&0c ; X=&0C: NMI claim service
808B JSR osbyte_yff ; Issue NMI claim service request
808E LDY #5 ; Y=5: NMI claim service number
8090 .econet_restore
CPY #5 ; Check if NMI service was claimed (Y changed)
8092 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 &0D00, then patches the current ROM bank number into the self-modifying code at &0D07. Clears tx_src_net, need_release_tube, and tx_op_type to zero. Reads station ID into tx_src_stn (&0D22). Sets ws_0d60 and ws_0d62 to &80 to mark TX complete and Econet initialised. Finally re-enables NMIs via INTON (&FE20 read).

8094 .init_nmi_workspace
LDY #&20 ; Copy 32 bytes of NMI shim from ROM to &0D00
8096 .copy_nmi_shim←1← 809D BNE
LDA listen_jmp_hi,y ; Read byte from NMI shim ROM source
8099 STA nmi_code_base,y ; Write to NMI shim RAM at &0D00
809C DEY ; Next byte (descending)
809D BNE copy_nmi_shim ; Loop until all 32 bytes copied
809F LDA romsel_copy ; Patch current ROM bank into NMI shim
80A1 STA nmi_romsel ; Self-modifying code: ROM bank at &0D07
80A4 STY tx_src_net ; Clear source network (Y=0 from copy loop)
80A7 STY prot_flags ; Clear Tube release flag
80A9 STY tx_op_type ; Clear TX operation type
80AC LDY station_id_disable_net_nmis ; Read station ID (and disable NMIs)
80AF STY tx_src_stn ; Set own station as TX source
80B2 LDA #&80 ; &80 = Econet initialised
80B4 STA ws_0d60 ; Mark TX as complete (ready)
80B7 STA ws_0d62 ; Mark Econet as initialised
80BA BIT video_ula_control ; INTON: re-enable NMIs (&FE20 read side effect)
80BD .adlc_init_done←1← 8092 BNE
RTS ; Return

NMI RX scout handler (initial byte)

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

80BE .nmi_rx_scout←1← 89B2 JMP
LDA #1 ; A=&01: mask for SR2 bit0 (AP = Address Present)
80C0 BIT econet_control23_or_status2 ; BIT SR2: Z = A AND SR2 -- tests if AP is set
80C3 BEQ scout_error ; AP not set, no incoming data -- check for errors
80C5 LDA econet_data_continue_frame ; Read first RX byte (destination station address)
80C8 CMP station_id_disable_net_nmis ; Compare to our station ID (&FE18 read = INTOFF, disables NMIs)
80CB BEQ accept_frame ; Match -- accept frame
80CD CMP #&ff ; Check for broadcast address (&FF)
80CF BNE scout_reject ; Neither our address nor broadcast -- reject frame
80D1 LDA #&40 ; Flag &40 = broadcast frame
80D3 STA rx_src_net ; Store broadcast flag in rx_src_net
80D6 .accept_frame←1← 80CB BEQ
LDA #&db ; Install nmi_rx_scout_net NMI handler
80D8 JMP install_nmi_handler ; Install next handler and RTI

RX scout second byte handler

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

80DB .nmi_rx_scout_net
BIT econet_control23_or_status2 ; BIT SR2: test for RDA (bit7 = data available)
80DE BPL scout_error ; No RDA -- check errors
80E0 LDA econet_data_continue_frame ; Read destination network byte
80E3 BEQ accept_local_net ; Network = 0 -- local network, accept
80E5 EOR #&ff ; EOR &FF: test if network = &FF (broadcast)
80E7 BEQ accept_scout_net ; Broadcast network -- accept
80E9 .scout_reject←1← 80CF BNE
LDA #&a2 ; Reject: wrong network. CR1=&A2: RIE|RX_DISCONTINUE
80EB STA econet_control1_or_status1 ; Write CR1 to discontinue RX
80EE JMP set_nmi_rx_scout ; Return to idle scout listening
80F1 .accept_local_net←1← 80E3 BEQ
STA rx_src_net ; Network = 0 (local): clear tx_flags
80F4 .accept_scout_net←1← 80E7 BEQ
STA port_buf_len ; Store Y offset for scout data buffer
80F6 LDA #&0d ; Install scout data handler (&8102)
80F8 LDY #&81 ; High byte of scout data handler
80FA JMP set_nmi_vector ; Install scout data loop and RTI

Scout error/discard handler

Handles scout reception errors and end-of-frame conditions. Reads SR2 and tests AP|RDA (bits 0|7): if neither set, the frame ended cleanly and is simply discarded. If unexpected data is present, performs 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.

80FD .scout_error←5← 80C3 BEQ← 80DE BPL← 8112 BPL← 8146 BEQ← 8148 BPL
LDA econet_control23_or_status2 ; Read SR2
8100 AND #&81 ; Test AP (b0) | RDA (b7)
8102 BEQ scout_discard ; Neither set -- clean end, discard frame
8104 JSR adlc_full_reset ; Unexpected data/status: full ADLC reset
8107 JMP set_nmi_rx_scout ; Discard and return to idle
810A .scout_discard←1← 8102 BEQ
JMP reset_adlc_rx_listen ; Gentle discard: RX_DISCONTINUE
810D LDY port_buf_len ; Y = buffer offset
810F LDA econet_control23_or_status2 ; Read SR2
8112 .scout_loop_rda←1← 8132 BNE
BPL scout_error ; No RDA -- error handler
8114 LDA econet_data_continue_frame ; Read data byte from RX FIFO
8117 STA scout_buf,y ; Store at &0D3D+Y (scout buffer)
811A INY ; Advance buffer index
811B LDA econet_control23_or_status2 ; Read SR2 again (FV detection point)
811E BMI scout_loop_second ; RDA set -- more data, read second byte
8120 BNE scout_complete ; SR2 non-zero (FV or other) -- scout completion
8122 .scout_loop_second←1← 811E BMI
LDA econet_data_continue_frame ; Read second byte of pair
8125 STA scout_buf,y ; Store at &0D3D+Y
8128 INY ; Advance and check buffer limit
8129 CPY #&0c ; Copied all 12 scout bytes?
812B BEQ scout_complete ; Buffer full (Y=12) -- force completion
812D STY port_buf_len ; Save final buffer offset
812F LDA econet_control23_or_status2 ; Read SR2 for next pair
8132 BNE scout_loop_rda ; SR2 non-zero -- loop back for more bytes
8134 JMP nmi_rti ; SR2 = 0 -- RTI, 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.

8137 .scout_complete←2← 8120 BNE← 812B BEQ
LDA #0 ; Save Y for next iteration
8139 STA econet_control1_or_status1 ; Write CR1
813C LDA #&84 ; CR2=&84: disable PSE, enable RDA_SUPPRESS_FV
813E STA econet_control23_or_status2 ; Write CR2
8141 LDA #2 ; A=&02: FV mask for SR2 bit1
8143 BIT econet_control23_or_status2 ; BIT SR2: test FV (Z) and RDA (N)
8146 BEQ scout_error ; No FV -- not a valid frame end, error
8148 BPL scout_error ; FV set but no RDA -- missing last byte, error
814A LDA econet_data_continue_frame ; Read last byte from RX FIFO
814D STA scout_buf,y ; Store last byte at &0D3D+Y
8150 LDA #&44 ; CR1=&44: RX_RESET | TIE (switch to TX for ACK)
8152 STA econet_control1_or_status1 ; Write CR1: switch to TX mode
8155 SEC ; Set bit7 of need_release_tube flag
8156 ROR prot_flags ; Rotate C=1 into bit7: mark Tube release needed
8158 LDA scout_port ; Check port byte: 0 = immediate op, non-zero = data transfer
815B BNE scout_match_port ; Port non-zero -- look for matching receive block
815D .scout_no_match
JMP immediate_op ; Port = 0 -- immediate operation handler
8160 .scout_match_port←1← 815B BNE
BIT rx_src_net ; Check if broadcast (bit6 of tx_flags)
8163 BVC scan_port_list ; Not broadcast -- skip CR2 setup
8165 LDA #7 ; CR2=&07: broadcast prep
8167 STA econet_control23_or_status2 ; Write CR2: broadcast frame prep
816A .scan_port_list←1← 8163 BVC
BIT econet_flags ; Check if RX port list active (bit7)
816D BPL try_nfs_port_list ; No active ports -- try NFS workspace
816F LDA #&c0 ; Start scanning port list at page &C0
8171 LDY #0 ; Y=0: start offset within each port slot
8173 .scan_nfs_port_list←1← 81B8 BNE
STA port_ws_offset ; Store page to workspace pointer low
8175 STY rx_buf_offset ; Store page high byte for slot scanning
8177 .check_port_slot←1← 81AA BCC
LDY #0 ; Y=0: read control byte from start of slot
8179 .scout_ctrl_check
LDA (port_ws_offset),y ; Read port control byte from slot
817B BEQ discard_no_match ; Zero = end of port list, no match
817D CMP #&7f ; &7F = any-port wildcard
817F BNE next_port_slot ; Not wildcard -- check specific port match
8181 INY ; Y=1: advance to port byte in slot
8182 LDA (port_ws_offset),y ; Read port number from slot (offset 1)
8184 BEQ check_station_filter ; Zero port in slot = match any port
8186 CMP scout_port ; Check if port matches this slot
8189 BNE next_port_slot ; Port mismatch -- try next slot
818B .check_station_filter←1← 8184 BEQ
INY ; Y=2: advance to station byte
818C LDA (port_ws_offset),y ; Read station filter from slot (offset 2)
818E BEQ port_match_found ; Zero station = match any station, accept
8190 CMP scout_buf ; Check if source station matches
8193 BNE next_port_slot ; Station mismatch -- try next slot
8195 .scout_port_match
INY ; Y=3: advance to network byte
8196 LDA (port_ws_offset),y ; Read network filter from slot (offset 3)
8198 BEQ port_match_found ; Zero = accept any network
819A CMP scout_src_net ; Check if source network matches
819D BEQ port_match_found ; Network matches or zero = accept
819F .next_port_slot←3← 817F BNE← 8189 BNE← 8193 BNE
LDA rx_buf_offset ; Check if NFS workspace search pending
81A1 BEQ try_nfs_port_list ; No NFS workspace -- try fallback path
81A3 LDA port_ws_offset ; Load current slot base address
81A5 CLC ; CLC for 12-byte slot advance
81A6 ADC #&0c ; Advance to next 12-byte port slot
81A8 STA port_ws_offset ; Update workspace pointer to next slot
81AA BCC check_port_slot ; Always branches (page &C0 won't overflow)
81AC .discard_no_match←2← 817B BEQ← 81B2 BVC
JMP nmi_error_dispatch ; No match found -- discard frame
81AF .try_nfs_port_list←2← 816D BPL← 81A1 BEQ
BIT econet_flags ; Try NFS workspace if paged list exhausted
81B2 BVC discard_no_match ; No NFS workspace RX (bit6 clear) -- discard
81B4 LDA #0 ; NFS workspace starts at offset 0 in page
81B6 LDY nfs_workspace_hi ; NFS workspace high byte for port list
81B8 BNE scan_nfs_port_list ; Scan NFS workspace port list
81BA .port_match_found←4← 818E BEQ← 8198 BEQ← 819D BEQ← 84B6 JMP
LDA #3 ; Match found: set scout_status = 3
81BC STA rx_port ; Record match for completion handler
81BF JSR tx_calc_transfer ; Calculate transfer parameters
81C2 BCC nmi_error_dispatch ; C=0: no Tube claimed -- discard
81C4 BIT rx_src_net ; Check broadcast flag for ACK path
81C7 BVC send_data_rx_ack ; Not broadcast -- normal ACK path
81C9 JMP copy_scout_to_buffer ; Broadcast: different completion path
81CC .send_data_rx_ack←2← 81C7 BVC← 84AB JMP
LDA #&44 ; CR1=&44: RX_RESET | TIE
81CE STA econet_control1_or_status1 ; Write CR1: TX mode for ACK
81D1 LDA #&a7 ; CR2=&A7: RTS | CLR_TX_ST | FC_TDRA | PSE
81D3 STA econet_control23_or_status2 ; Write CR2: enable TX with PSE
81D6 LDA #&dd ; Install data_rx_setup at &81DD
81D8 LDY #&81 ; High byte of data_rx_setup handler
81DA JMP ack_tx_write_dest ; Send ACK with data_rx_setup as next NMI
81DD .data_rx_setup
LDA #&82 ; CR1=&82: TX_RESET | RIE (switch to RX for data frame)
81DF STA econet_control1_or_status1 ; Write CR1: switch to RX for data frame
81E2 LDA #&e7 ; Install nmi_data_rx at &81E7
81E4 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: &81E7 (AP+addr check) -> &81FB (net=0 check) -> &8211 (skip ctrl+port) -> &8239 (bulk data read) -> &8278 (completion)

81E7 .nmi_data_rx
LDA #1 ; A=1: AP mask for SR2 bit test
81E9 BIT econet_control23_or_status2 ; BIT SR2: test AP bit
81EC BEQ nmi_error_dispatch ; No AP: wrong frame or error
81EE LDA econet_data_continue_frame ; Read first byte (dest station)
81F1 CMP station_id_disable_net_nmis ; Compare to our station ID (INTOFF)
81F4 BNE nmi_error_dispatch ; Not for us: error path
81F6 LDA #&fb ; Install net check handler at &81FB
81F8 JMP install_nmi_handler ; Set NMI vector via RAM shim
81FB .nmi_data_rx_net
BIT econet_control23_or_status2 ; Validate source network = 0
81FE BPL nmi_error_dispatch ; SR2 bit7 clear: no data ready -- error
8200 LDA econet_data_continue_frame ; Read dest network byte
8203 BNE nmi_error_dispatch ; Network != 0: wrong network -- error
8205 LDA #&11 ; Install skip handler at &8211
8207 LDY #&82 ; High byte of &8211 handler
8209 BIT econet_control1_or_status1 ; SR1 bit7: IRQ, data already waiting
820C BMI nmi_data_rx_skip ; Data ready: skip directly, no RTI
820E JMP set_nmi_vector ; Install handler and return via RTI
8211 .nmi_data_rx_skip←1← 820C BMI
BIT econet_control23_or_status2 ; Skip control and port bytes (already known from scout)
8214 BPL nmi_error_dispatch ; SR2 bit7 clear: error
8216 LDA econet_data_continue_frame ; Discard control byte
8219 LDA econet_data_continue_frame ; Discard port byte
fall through ↓

Install data RX bulk or Tube handler

Selects between the normal bulk RX handler (&8239) and the Tube RX handler based on bit 1 of rx_src_net (tx_flags). If normal mode, loads the handler address &8239 and 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 and returns via RTI.

821C .install_data_rx_handler←1← 88C6 JMP
LDA #2 ; A=2: Tube transfer flag mask
821E BIT rx_src_net ; Check if Tube transfer active
8221 BNE install_tube_rx ; Tube active: use Tube RX path
8223 LDA #&44 ; Install bulk read at &8239
8225 LDY #&82 ; High byte of &8239 handler
8227 BIT econet_control1_or_status1 ; SR1 bit7: more data already waiting?
822A BMI nmi_data_rx_bulk ; Yes: enter bulk read directly
822C JMP set_nmi_vector ; No: install handler and RTI
822F .install_tube_rx←1← 8221 BNE
LDA #&a1 ; Tube: install Tube RX at &8296
8231 LDY #&82 ; High byte of &8296 handler
8233 JMP set_nmi_vector ; Install Tube handler and RTI

NMI error handler dispatch

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

8236 .nmi_error_dispatch←12← 81AC JMP← 81C2 BCC← 81EC BEQ← 81F4 BNE← 81FE BPL← 8203 BNE← 8214 BPL← 8257 BEQ← 8289 BEQ← 828F BEQ← 834C JMP← 8485 JMP
LDA rx_src_net ; Check tx_flags for error path
8239 BPL rx_error_reset ; Bit7 clear: RX error path
823B JMP tx_result_fail ; Bit7 set: TX result = not listening
823E .rx_error_reset←1← 8239 BPL
JSR adlc_full_reset ; Full ADLC reset on RX error
8241 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) -> frame completion at &8278. SR2 = 0 -> RTI, wait for next NMI to continue.

8244 .nmi_data_rx_bulk←1← 822A BMI
LDY port_buf_len ; Y = buffer offset, resume from last position
8246 LDA econet_control23_or_status2 ; Read SR2 for next pair
8249 .data_rx_loop←1← 8273 BNE
BPL data_rx_complete ; SR2 bit7 clear: frame complete (FV)
824B LDA econet_data_continue_frame ; Read first byte of pair from RX FIFO
824E STA (open_port_buf),y ; Store byte to buffer
8250 INY ; Advance buffer offset
8251 BNE read_sr2_between_pairs ; Y != 0: no page boundary crossing
8253 INC open_port_buf_hi ; Crossed page: increment buffer high byte
8255 DEC port_buf_len_hi ; Decrement remaining page count
8257 BEQ nmi_error_dispatch ; No pages left: handle as complete
8259 .read_sr2_between_pairs←1← 8251 BNE
LDA econet_control23_or_status2 ; Read SR2 between byte pairs
825C BMI read_second_rx_byte ; SR2 bit7 set: more data available
825E BNE data_rx_complete ; SR2 non-zero, bit7 clear: frame done
8260 .read_second_rx_byte←1← 825C BMI
LDA econet_data_continue_frame ; Read second byte of pair from RX FIFO
8263 STA (open_port_buf),y ; Store byte to buffer
8265 INY ; Advance buffer offset
8266 STY port_buf_len ; Save updated buffer position
8268 BNE check_sr2_loop_again ; Y != 0: no page boundary crossing
826A INC open_port_buf_hi ; Crossed page: increment buffer high byte
826C DEC port_buf_len_hi ; Decrement remaining page count
826E BEQ data_rx_complete ; No pages left: frame complete
8270 .check_sr2_loop_again←1← 8268 BNE
LDA econet_control23_or_status2 ; Read SR2 for next iteration
8273 BNE data_rx_loop ; SR2 non-zero: more data, loop back
8275 JMP nmi_rti ; SR2=0: no more data yet, wait for NMI

Data frame completion

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

8278 .data_rx_complete←3← 8249 BPL← 825E BNE← 826E BEQ
LDA #&84 ; CR1=&00: disable all interrupts
827A STA econet_control23_or_status2 ; Write CR2: disable PSE for bit testing
827D LDA #0 ; CR2=&84: disable PSE for individual bit testing
827F STA econet_control1_or_status1 ; Write CR1: disable all interrupts
8282 STY port_buf_len ; Save Y (byte count from data RX loop)
8284 LDA #2 ; A=&02: FV mask
8286 BIT econet_control23_or_status2 ; BIT SR2: test FV (Z) and RDA (N)
8289 BEQ nmi_error_dispatch ; No FV -- error
828B BPL send_ack ; FV set, no RDA -- proceed to ACK
828D LDA port_buf_len_hi ; Check if buffer space remains
828F .read_last_rx_byte←3← 82AC BEQ← 82D3 BEQ← 82DF BEQ
BEQ nmi_error_dispatch ; No buffer space: error/discard frame
8291 LDA econet_data_continue_frame ; FV+RDA: read and store last data byte
8294 LDY port_buf_len ; Y = current buffer write offset
8296 STA (open_port_buf),y ; Store last byte in port receive buffer
8298 INC port_buf_len ; Advance buffer write offset
829A BNE send_ack ; No page wrap: proceed to send ACK
829C INC open_port_buf_hi ; Page boundary: advance buffer page
829E .send_ack←2← 828B BPL← 829A BNE
JMP ack_tx ; Send ACK frame to complete handshake
82A1 .nmi_data_rx_tube
LDA econet_control23_or_status2 ; Read SR2 for Tube data receive path
82A4 .rx_tube_data←1← 82BF BNE
BPL data_rx_tube_complete ; RDA clear: no more data, frame complete
82A6 LDA econet_data_continue_frame ; Read data byte from ADLC RX FIFO
82A9 JSR advance_buffer_ptr ; Check buffer limits and transfer size
82AC BEQ read_last_rx_byte ; Zero: buffer full, handle as error
82AE STA tube_data_register_3 ; Send byte to Tube data register 3
82B1 LDA econet_data_continue_frame ; Read second data byte (paired transfer)
82B4 STA tube_data_register_3 ; Send second byte to Tube
82B7 JSR advance_buffer_ptr ; Check limits after byte pair
82BA BEQ data_rx_tube_complete ; Zero: Tube transfer complete
82BC LDA econet_control23_or_status2 ; Re-read SR2 for next byte pair
82BF BNE rx_tube_data ; More data available: continue loop
82C1 .data_rx_tube_error
JMP nmi_rti ; Unexpected end: return from NMI
82C4 .data_rx_tube_complete←2← 82A4 BPL← 82BA BEQ
LDA #0 ; CR1=&00: disable all interrupts
82C6 STA econet_control1_or_status1 ; Write CR1 for individual bit testing
82C9 LDA #&84 ; CR2=&84: disable PSE
82CB STA econet_control23_or_status2 ; Write CR2: same pattern as main path
82CE LDA #2 ; A=&02: FV mask for Tube completion
82D0 BIT econet_control23_or_status2 ; BIT SR2: test FV (Z) and RDA (N)
82D3 BEQ read_last_rx_byte ; No FV: incomplete frame, error
82D5 BPL ack_tx ; FV set, no RDA: proceed to ACK
82D7 LDA port_buf_len ; Check if any buffer was allocated
82D9 ORA port_buf_len_hi ; OR all 4 buffer pointer bytes together
82DB ORA open_port_buf ; Check buffer low byte
82DD ORA open_port_buf_hi ; Check buffer high byte
82DF BEQ read_last_rx_byte ; All zero (null buffer): error
82E1 LDA econet_data_continue_frame ; Read extra trailing byte from FIFO
82E4 STA rx_extra_byte ; Save extra byte at &0D5D for later use
82E7 LDA #&20 ; Bit5 = extra data byte available flag
82E9 ORA rx_src_net ; Set extra byte flag in tx_flags
82EC 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. If bit7 of &0D4A is set, this is a final ACK -> completion (&88C6). Otherwise, configures for TX (CR1=&44, CR2=&A7) and sends the ACK frame (dst_stn, dst_net from &0D3D, src_stn from &FE18, src_net=0). The ACK frame has no data payload -- just address bytes.

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

82EF .ack_tx←2← 829E JMP← 82D5 BPL
LDA rx_src_net ; Load TX flags to check ACK type
82F2 BPL ack_tx_configure ; Bit7 clear: normal scout ACK
82F4 JSR advance_rx_buffer_ptr ; Final ACK: call completion handler
82F7 JMP tx_result_ok ; Jump to TX success result
82FA .ack_tx_configure←1← 82F2 BPL
LDA #&44 ; CR1=&44: RX_RESET | TIE (switch to TX mode)
82FC STA econet_control1_or_status1 ; Write CR1: switch to TX mode
82FF LDA #&a7 ; CR2=&A7: RTS|CLR_TX_ST|FC_TDRA|2_1_BYTE|PSE
8301 STA econet_control23_or_status2 ; Write CR2: enable TX with status clear
8304 LDA #&96 ; Install saved next handler (&8396 for scout ACK)
8306 LDY #&83 ; High byte of post-ACK handler
8308 .ack_tx_write_dest←2← 81DA JMP← 84F3 JMP
STA saved_nmi_lo ; Store next handler low byte
830B STY saved_nmi_hi ; Store next handler high byte
830E LDA scout_buf ; Load dest station from RX scout buffer
8311 BIT econet_control1_or_status1 ; BIT SR1: test TDRA (V=bit6)
8314 BVC dispatch_nmi_error ; TDRA not ready -- error
8316 STA econet_data_continue_frame ; Write dest station to TX FIFO
8319 LDA scout_src_net ; Write dest network to TX FIFO
831C STA econet_data_continue_frame ; Write dest net byte to FIFO
831F LDA #&26 ; Install handler at &8326 (write src addr)
8321 LDY #&83 ; High byte of nmi_ack_tx_src
8323 JMP set_nmi_vector ; Set NMI vector to ack_tx_src handler

ACK TX continuation

Continuation of ACK frame transmission. Reads our station ID from &FE18 (INTOFF side effect), tests TDRA via SR1, and writes station + network=0 to the TX FIFO, completing the 4-byte ACK address header. Then checks rx_src_net bit 7: if set, branches to start_data_tx to begin the data phase. Otherwise writes CR2=&3F (TX_LAST_DATA) and falls through to post_ack_scout for scout processing.

8326 .nmi_ack_tx_src
LDA station_id_disable_net_nmis ; Load our station ID (also INTOFF)
8329 BIT econet_control1_or_status1 ; BIT SR1: test TDRA
832C BVC dispatch_nmi_error ; TDRA not ready -- error
832E STA econet_data_continue_frame ; Write our station to TX FIFO
8331 LDA #0 ; Write network=0 to TX FIFO
8333 STA econet_data_continue_frame ; Write network=0 (local) to TX FIFO
8336 LDA rx_src_net ; Check tx_flags for data phase
8339 BMI start_data_tx ; bit7 set: start data TX phase
833B LDA #&3f ; CR2=&3F: TX_LAST_DATA | CLR_RX_ST | FLAG_IDLE | FC_TDRA | 2_1_BYTE | PSE
fall through ↓

Post-ACK scout processing

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

833D .post_ack_scout
STA econet_control23_or_status2 ; Write CR2 to clear status after ACK TX
8340 LDA saved_nmi_lo ; Install saved handler from &0D4B/&0D4C
8343 LDY saved_nmi_hi ; Load saved next handler high byte
8346 JMP set_nmi_vector ; Install next NMI handler
8349 .start_data_tx←1← 8339 BMI
JMP data_tx_begin ; Jump to start data TX phase
834C .dispatch_nmi_error←2← 8314 BVC← 832C BVC
JMP nmi_error_dispatch ; Jump to error handler

Advance RX buffer pointer after transfer

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

834F .advance_rx_buffer_ptr←2← 82F4 JSR← 83A5 JSR
LDA #2 ; A=2: test bit1 of tx_flags
8351 BIT rx_src_net ; BIT tx_flags: check data transfer bit
8354 BEQ return_rx_complete ; Bit1 clear: no transfer -- return
8356 CLC ; CLC: init carry for 4-byte add
8357 PHP ; Save carry on stack for loop
8358 LDY #8 ; Y=8: RXCB high pointer offset
835A .add_rxcb_ptr←1← 8366 BCC
LDA (port_ws_offset),y ; Load RXCB[Y] (buffer pointer byte)
835C PLP ; Restore carry from stack
835D ADC net_tx_ptr,y ; Add transfer count byte
8360 STA (port_ws_offset),y ; Store updated pointer back to RXCB
8362 INY ; Next byte
8363 PHP ; Save carry for next iteration
8364 CPY #&0c ; Done 4 bytes? (Y reaches &0C)
8366 BCC add_rxcb_ptr ; No: continue adding
8368 PLP ; Discard final carry
8369 LDA #&20 ; A=&20: test bit5 of tx_flags
836B BIT rx_src_net ; BIT tx_flags: check Tube bit
836E BEQ skip_tube_update ; No Tube: skip Tube update
8370 TXA ; Save X on stack
8371 PHA ; Push X
8372 LDA #8 ; A=8: offset for Tube address
8374 CLC ; CLC for address calculation
8375 ADC port_ws_offset ; Add workspace base offset
8377 TAX ; X = address low for Tube claim
8378 LDY rx_buf_offset ; Y = address high for Tube claim
837A LDA #1 ; A=1: Tube claim type (read)
837C JSR tube_addr_data_dispatch ; Claim Tube address for transfer
837F LDA rx_extra_byte ; Load extra RX data byte
8382 STA tube_data_register_3 ; Send to Tube via R3
8385 SEC ; SEC: init carry for increment
8386 LDY #8 ; Y=8: start at high pointer
8388 .inc_rxcb_ptr←1← 838F BCS
LDA #0 ; A=0: add carry only (increment)
838A ADC (port_ws_offset),y ; Add carry to pointer byte
838C STA (port_ws_offset),y ; Store back to RXCB
838E INY ; Next byte
838F BCS inc_rxcb_ptr ; Keep going while carry propagates
8391 PLA ; Restore X from stack
8392 TAX ; Transfer to X register
8393 .skip_tube_update←1← 836E BEQ
LDA #&ff ; A=&FF: return value (transfer done)
8395 .return_rx_complete←1← 8354 BEQ
RTS ; Return
8396 LDA scout_port ; Load received port byte
8399 BNE rx_complete_update_rxcb ; Port != 0: data transfer frame
839B LDY scout_ctrl ; Port=0: load control byte
839E CPY #&82 ; Ctrl = &82 (POKE)?
83A0 BEQ rx_complete_update_rxcb ; Yes: POKE also needs data transfer
83A2 JMP imm_op_build_reply ; Other port-0 ops: immediate dispatch

Complete RX and update RXCB

Finalises a received data transfer. Calls advance_rx_buffer_ptr to update the 4-byte buffer pointer with the transfer count (and handle Tube re-claim if needed). Adds the buffer bytes remaining to the base address, then subtracts 8 from the RXCB buffer length to account for the scout overhead. Clears the RXCB flag byte and sends the final ACK via ack_tx. On Tube transfers, releases the Tube claim before resetting to idle listen.

83A5 .rx_complete_update_rxcb←3← 8399 BNE← 83A0 BEQ← 8434 JMP
JSR advance_rx_buffer_ptr ; Update buffer pointer and check for Tube
83A8 BNE skip_buf_ptr_update ; Transfer not done: skip buffer update
83AA .add_buf_to_base
LDA port_buf_len ; Load buffer bytes remaining
83AC CLC ; CLC for address add
83AD ADC open_port_buf ; Add to buffer base address
83AF BCC store_buf_ptr_lo ; No carry: skip high byte increment
83B1 .inc_rxcb_buf_hi
INC open_port_buf_hi ; Carry: increment buffer high byte
83B3 .store_buf_ptr_lo←1← 83AF BCC
LDY #8 ; Y=8: store updated buffer position
83B5 .store_rxcb_buf_ptr
STA (port_ws_offset),y ; Store updated low byte to RXCB
83B7 INY ; Y=9: buffer high byte offset
83B8 LDA open_port_buf_hi ; Load updated buffer high byte
83BA .store_rxcb_buf_hi
STA (port_ws_offset),y ; Store high byte to RXCB
83BC .skip_buf_ptr_update←1← 83A8 BNE
LDA scout_port ; Check port byte again
83BF BEQ discard_reset_rx ; Port=0: immediate op, discard+listen
83C1 LDA scout_src_net ; Load source network from scout buffer
83C4 LDY #3 ; Y=3: RXCB source network offset
83C6 STA (port_ws_offset),y ; Store source network to RXCB
83C8 DEY ; Y=2: source station offset Y=&02
83C9 LDA scout_buf ; Load source station from scout buffer
83CC STA (port_ws_offset),y ; Store source station to RXCB
83CE DEY ; Y=1: port byte offset Y=&01
83CF LDA scout_port ; Load port byte
83D2 STA (port_ws_offset),y ; Store port to RXCB
83D4 DEY ; Y=0: control/flag byte offset Y=&00
83D5 LDA scout_ctrl ; Load control byte from scout
83D8 ORA #&80 ; Set bit7 = reception complete flag
83DA STA (port_ws_offset),y ; Store to RXCB (marks CB as complete)
83DC LDA fs_flags ; Load callback event flags
83DF ROR ; Shift bit 0 into carry
83E0 BCC discard_reset_rx ; Bit 0 clear: no callback, skip to reset
83E2 LDA port_ws_offset ; Set carry for subtraction Load RXCB workspace pointer low byte
83E4 .loop_count_rxcb_slot←1← 83E7 BCS
INY ; Count slots
83E5 SBC #&0c ; Subtract 12 bytes per RXCB slot
83E7 BCS loop_count_rxcb_slot ; Loop until pointer exhausted
83E9 DEY ; Adjust for off-by-one
83EA CPY #3 ; Check slot index >= 3
83EC BCC discard_reset_rx ; Slot < 3: no callback, skip to reset
83EE JSR discard_reset_listen ; Discard scout and reset listen state
83F1 TYA ; Pass slot index as callback parameter
83F2 JMP setup_sr_tx ; Jump to TX completion with slot index
83F5 .discard_reset_rx←6← 8241 JMP← 83BF BEQ← 83E0 BCC← 83EC BCC← 8875 JMP← 88DF JMP
JSR discard_reset_listen ; Discard scout and reset RX listen
83F8 .reset_adlc_rx_listen←3← 810A JMP← 8470 BCS← 852C JMP
JSR adlc_rx_listen ; Reset ADLC and return to RX listen
83FB .set_nmi_rx_scout←2← 80EE JMP← 8107 JMP
LDA #&be ; A=&BE: low byte of nmi_rx_scout
83FD LDY #&80 ; Y=&80: high byte of nmi_rx_scout
83FF 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 l0d63 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.

8402 .discard_reset_listen←2← 83EE JSR← 83F5 JSR
LDA #2 ; Tube flag bit 1 AND tx_flags bit 1
8404 AND tube_present ; Check if Tube transfer active
8407 .imm_op_jump_table←1← 8478 LDA
BIT rx_src_net ; Test tx_flags for Tube transfer
840A BEQ return_from_discard_reset ; No Tube transfer active -- skip release
840C JSR release_tube ; Release Tube claim before discarding
840F .return_from_discard_reset←1← 840A BEQ
RTS ; Return

Copy scout data to port buffer

Copies scout data bytes (offsets 4-11) from the RX scout buffer at &0D3D into the open port buffer. Checks bit 1 of rx_src_net (tx_flags) to select the write path: direct memory store via (open_port_buf),Y for normal transfers, or Tube data register 3 write for Tube transfers. Calls advance_buffer_ptr after each byte. Falls through to release_tube on completion. Handles page overflow (Y wrap) by branching to scout_page_overflow.

8410 .copy_scout_to_buffer←1← 81C9 JMP
TXA ; Save X on stack
8411 PHA ; Push X
8412 LDX #4 ; X=4: start at scout byte offset 4
8414 LDA #2 ; A=2: Tube transfer check mask
8416 .copy_scout_select
BIT rx_src_net ; BIT tx_flags: check Tube bit
8419 BNE copy_scout_via_tube ; Tube active: use R3 write path
841B LDY port_buf_len ; Y = current buffer position
841D .copy_scout_bytes←1← 8430 BNE
LDA scout_buf,x ; Load scout data byte
8420 STA (open_port_buf),y ; Store to port buffer
8422 INY ; Advance buffer pointer
8423 BNE next_scout_byte ; No page crossing
8425 INC open_port_buf_hi ; Page crossing: inc buffer high byte
8427 DEC port_buf_len_hi ; Decrement remaining page count
8429 BEQ scout_page_overflow ; No pages left: overflow
842B .next_scout_byte←1← 8423 BNE
INX ; Next scout data byte
842C STY port_buf_len ; Save updated buffer position
842E CPX #&0c ; Done all scout data? (X reaches &0C)
8430 BNE copy_scout_bytes ; No: continue copying
8432 .scout_copy_done←2← 8447 BEQ← 8481 BEQ
PLA ; Restore X from stack
8433 TAX ; Transfer to X register
8434 JMP rx_complete_update_rxcb ; Jump to completion handler
8437 .copy_scout_via_tube←2← 8419 BNE← 8445 BNE
LDA scout_buf,x ; Tube path: load scout data byte
843A STA tube_data_register_3 ; Send byte to Tube via R3
843D JSR advance_buffer_ptr ; Increment buffer position counters
8440 BEQ check_scout_done ; Counter overflow: handle end of buffer
8442 INX ; Next scout data byte
8443 CPX #&0c ; Done all scout data?
8445 BNE copy_scout_via_tube ; No: continue Tube writes
8447 BEQ scout_copy_done ; ALWAYS branch

Release Tube co-processor claim

Tests need_release_tube (&98) bit 7: if set, the Tube has already been released and the subroutine just clears the flag. If clear (Tube claim held), calls tube_addr_data_dispatch with A=&82 to release the claim, then clears the release flag via LSR (which shifts bit 7 to 0). Called after completed RX transfers and during discard paths to ensure no stale Tube claims persist.

8449 .release_tube←2← 840C JSR← 893E JSR
BIT prot_flags ; Check if Tube needs releasing
844B BMI clear_release_flag ; Bit7 set: already released
844D LDA #&82 ; A=&82: Tube release claim type
844F JSR tube_addr_data_dispatch ; Release Tube address claim
8452 .clear_release_flag←1← 844B BMI
LSR prot_flags ; Clear release flag (LSR clears bit7)
8454 RTS ; Return

Immediate operation handler (port = 0)

Checks the control byte at l0d30 for immediate operation codes (&81-&88). Codes below &81 or above &88 are out of range and discarded. Codes &87-&88 (HALT/CONTINUE) bypass the protection mask check. For &81-&86, converts to a 0-based index and tests against the immediate operation mask at &0D61 to determine if this station accepts the operation. If accepted, dispatches via the immediate operation table. Builds the reply by storing data length, station/network, and control byte into the RX buffer.

8455 .immediate_op←1← 815D JMP
LDY scout_ctrl ; Control byte &81-&88 range check
8458 CPY #&81 ; Below &81: not an immediate op
845A BCC imm_op_out_of_range ; Out of range low: jump to discard
845C CPY #&89 ; Above &88: not an immediate op
845E BCS imm_op_out_of_range ; Out of range high: jump to discard
8460 CPY #&87 ; HALT(&87)/CONTINUE(&88) skip protection
8462 BCS dispatch_imm_op ; Ctrl >= &87: dispatch without mask check
8464 TYA ; Convert ctrl byte to 0-based index for mask
8465 SEC ; SEC for subtract
8466 SBC #&81 ; A = ctrl - &81 (0-based operation index)
8468 TAY ; Y = index for mask rotation count
8469 LDA ws_0d68 ; Load protection mask from LSTAT
846C .rotate_prot_mask←1← 846E BPL
ROR ; Rotate mask right by control byte index
846D DEY ; Decrement rotation counter
846E BPL rotate_prot_mask ; Loop until bit aligned
8470 BCS reset_adlc_rx_listen ; Bit set = operation disabled, discard
8472 .dispatch_imm_op←1← 8462 BCS
LDY scout_ctrl ; Reload ctrl byte for dispatch table
8475 LDA #&84 ; Hi byte: all handlers are in page &84
8477 PHA ; Push hi byte for PHA/PHA/RTS dispatch
8478 LDA imm_op_jump_table,y ; Load handler low byte from jump table
847B PHA ; Push handler low byte
847C RTS ; RTS dispatches to handler
847D .scout_page_overflow←1← 8429 BEQ
INC port_buf_len ; Increment port buffer length
847F .check_scout_done←1← 8440 BEQ
CPX #&0b ; Check if scout data index reached 11
8481 BEQ scout_copy_done ; Yes: loop back to continue reading
8483 PLA ; Restore A from stack
8484 TAX ; Transfer to X
8485 .imm_op_out_of_range←2← 845A BCC← 845E BCS
JMP nmi_error_dispatch ; Jump to discard handler
8488 .imm_op_dispatch_lo
EQUB <(rx_imm_peek-1)
8489 EQUB <(rx_imm_poke-1)
848A EQUB <(rx_imm_exec-1)
848B EQUB <(rx_imm_exec-1)
848C EQUB <(rx_imm_exec-1)
848D EQUB <(rx_imm_halt_cont-1)
848E EQUB <(rx_imm_halt_cont-1)
848F EQUB <(rx_imm_machine_type-1)

RX immediate: JSR/UserProc/OSProc setup

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

8490 .rx_imm_exec
LDA #0 ; A=0: port buffer lo at page boundary
8492 STA open_port_buf ; Set port buffer lo
8494 LDA #&82 ; Buffer length lo = &82
8496 STA port_buf_len ; Set buffer length lo
8498 LDA #1 ; Buffer length hi = 1
849A STA port_buf_len_hi ; Set buffer length hi
849C LDA net_rx_ptr_hi ; Load RX page hi for buffer
849E STA open_port_buf_hi ; Set port buffer hi
84A0 LDY #1 ; Y=1: copy 2 bytes (1 down to 0)
84A2 .copy_addr_loop←1← 84A9 BPL
LDA scout_data,y ; Load remote address byte
84A5 STA exec_addr_lo,y ; Store to exec address workspace
84A8 DEY ; Next byte (descending)
84A9 BPL copy_addr_loop ; Loop until all 4 bytes copied
84AB .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, then jumps to the common data-receive path at c81af.

84AE .svc5_dispatch_lo
.rx_imm_poke
LDA #&2e ; Port workspace offset = &3D
84B0 STA port_ws_offset ; Store workspace offset lo
84B2 LDA #&0d ; RX buffer page = &0D
84B4 STA rx_buf_offset ; Store workspace offset hi
84B6 JMP port_match_found ; Enter POKE data-receive path

RX immediate: machine type query

Sets up a buffer at &88C1 (length #&01FC) for the machine type query response. Falls through to set_rx_buf_len_hi to configure buffer dimensions, then branches to set_tx_reply_flag.

84B9 .rx_imm_machine_type
LDA #1 ; Buffer length hi = 1
84BB .set_rx_buf_len_hi←1← 806F LDA
STA port_buf_len_hi ; Set buffer length hi
84BD LDA #&fc ; Buffer length lo = &FC
84BF STA port_buf_len ; Set buffer length lo
84C1 LDA #&cb ; Buffer start lo = &25
84C3 STA open_port_buf ; Set port buffer lo
84C5 LDA #&88 ; Buffer hi = &7F (below screen)
84C7 STA open_port_buf_hi ; Set port buffer hi
84C9 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.

84CB .rx_imm_peek
LDA #&2e ; Port workspace offset = &3D
84CD STA port_ws_offset ; Store workspace offset lo
84CF LDA #&0d ; RX buffer page = &0D
84D1 STA rx_buf_offset ; Store workspace offset hi
84D3 LDA #2 ; Scout status = 2 (PEEK response)
84D5 STA rx_port ; Store scout status
84D8 JSR tx_calc_transfer ; Calculate transfer size for response
84DB BCC imm_op_discard ; C=0: transfer not set up, discard
84DD .set_tx_reply_flag←1← 84C9 BNE
LDA rx_src_net ; Mark TX flags bit 7 (reply pending)
84E0 ORA #&80 ; Set reply pending flag
84E2 STA rx_src_net ; Store updated TX flags
84E5 .rx_imm_halt_cont
LDA #&44 ; CR1=&44: TIE | TX_LAST_DATA
84E7 STA econet_control1_or_status1 ; Write CR1: enable TX interrupts
84EA .tx_cr2_setup
LDA #&a7 ; CR2=&A7: RTS|CLR_RX_ST|FC_TDRA|PSE
84EC STA econet_control23_or_status2 ; Write CR2 for TX setup
84EF .tx_nmi_setup
LDA #&0c ; NMI handler lo byte (self-modifying)
84F1 LDY #&85 ; Y=&85: NMI handler high byte
84F3 JMP ack_tx_write_dest ; Acknowledge and write TX dest

Build immediate operation reply header

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

84F6 .imm_op_build_reply←1← 83A2 JMP
LDA port_buf_len ; Get buffer position for reply header
84F8 CLC ; Clear carry for offset addition
84F9 ADC #&80 ; Data offset = buf_len + &80 (past header)
84FB LDY #&7f ; Y=&7F: reply data length slot
84FD STA (net_rx_ptr),y ; Store reply data length in RX buffer
84FF LDY #&80 ; Y=&80: source station slot
8501 LDA scout_buf ; Load requesting station number
8504 STA (net_rx_ptr),y ; Store source station in reply header
8506 INY ; Y=&81
8507 LDA scout_src_net ; Load requesting network number
850A STA (net_rx_ptr),y ; Store source network in reply header
850C LDA scout_ctrl ; Load control byte from received frame
850F .setup_sr_tx←1← 83F2 JMP
STA tx_op_type ; Save TX operation type for SR dispatch
8512 LDA #&84 ; IER bit 2: disable SR interrupt
8514 STA system_via_ier ; Write IER to disable SR
8517 LDA system_via_acr ; Read ACR for shift register config
851A AND #&1c ; Isolate shift register mode bits (2-4)
851C STA ws_0d64 ; Save original SR mode for later restore
851F LDA system_via_acr ; Reload ACR for modification
8522 AND #&e3 ; Clear SR mode bits (keep other bits)
8524 ORA #8 ; SR mode 2: shift in under φ2
8526 STA system_via_acr ; Apply new shift register mode
8529 BIT system_via_sr ; Read SR to clear pending interrupt
852C .imm_op_discard←1← 84DB BCC
JMP reset_adlc_rx_listen ; Return to idle listen mode

Increment 4-byte receive buffer pointer

Adds one to the counter at &A2-&A5 (port_buf_len low/high, open_port_buf low/high), 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.

852F .advance_buffer_ptr←3← 82A9 JSR← 82B7 JSR← 843D JSR
INC port_buf_len ; Increment buffer length low byte
8531 BNE return_from_advance_buf ; No overflow: done
8533 INC port_buf_len_hi ; Increment buffer length high byte
8535 BNE return_from_advance_buf ; No overflow: done
8537 INC open_port_buf ; Increment buffer pointer low byte
8539 BNE return_from_advance_buf ; No overflow: done
853B INC open_port_buf_hi ; Increment buffer pointer high byte
853D .return_from_advance_buf←3← 8531 BNE← 8535 BNE← 8539 BNE
RTS ; Return
; TX done dispatch table (lo bytes)
; Low bytes of PHA/PHA/RTS dispatch targets for TX
; operation types &83-&87. Read by the dispatch at
; &8064 via LDA set_rx_buf_len_hi,Y (base &84BB
; + Y). High byte is always &85, so targets are
; &85xx+1. Entries for Y < &83 read from preceding
; code bytes and are not valid operation types.
853E .tx_done_dispatch_lo
EQUB <(tx_done_jsr-1)
853F EQUB <(tx_done_econet_event-1)
8540 EQUB <(tx_done_os_proc-1)
8541 EQUB <(tx_done_halt-1)
8542 EQUB <(tx_done_continue-1)

TX done: remote JSR execution

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

8543 .tx_done_jsr
LDA #&85 ; Hi byte of tx_done_exit-1
8545 PHA ; Push hi byte on stack
8546 LDA #&84 ; Push lo of (tx_done_exit-1)
8548 PHA ; Push lo byte on stack
8549 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 l0d66/l0d67 into X/A and sets Y=8 (Econet event number), then falls through to tx_done_fire_event to call OSEVEN.

854C .tx_done_econet_event
LDX exec_addr_lo ; X = remote address lo from l0d66
854F LDA exec_addr_hi ; A = remote address hi from l0d67
8552 LDY #event_network_error ; Y = 8: Econet event number
8554 .tx_done_fire_event
JSR oseven ; Generate event Y='Network error'
8557 JMP tx_done_exit ; Exit TX done handler

TX done: OSProc call

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

855A .tx_done_os_proc
LDX exec_addr_lo ; X = remote address lo
855D LDY exec_addr_hi ; Y = remote address hi
8560 JSR dir_op_dispatch ; Call ROM entry point at &8000
8563 JMP tx_done_exit ; Exit TX done handler

TX done: HALT

Sets bit 2 of rx_flags (&0D61), 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.

8566 .tx_done_halt
LDA #4 ; A=&04: bit 2 mask for rx_flags
8568 BIT econet_flags ; Test if already halted
856B BNE tx_done_exit ; Already halted: skip to exit
856D ORA econet_flags ; Set bit 2 in rx_flags
8570 STA econet_flags ; Store halt flag
8573 LDA #4 ; A=4: re-load halt bit mask
8575 CLI ; Enable interrupts during halt wait
8576 .halt_spin_loop←1← 8579 BNE
BIT econet_flags ; Test halt flag
8579 BNE halt_spin_loop ; Still halted: keep spinning
857B BEQ tx_done_exit ; ALWAYS branch

TX done: CONTINUE

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

857D .tx_done_continue
LDA econet_flags ; Load current RX flags
8580 AND #&fb ; Clear bit 2: release halted station
8582 STA econet_flags ; Store updated flags
8585 .tx_done_exit←5← 8057 JMP← 8557 JMP← 8563 JMP← 856B BNE← 857B BEQ
PLA ; Restore Y from stack
8586 TAY ; Transfer to Y register
8587 PLA ; Restore X from stack
8588 TAX ; Transfer to X register
8589 LDA #0 ; A=0: success status
858B RTS ; Return with A=0 (success)

Begin TX operation

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

858C .tx_begin←3← 98D6 JSR← A5BE JMP← A89A JSR
TXA ; Save X on stack
858D PHA ; Push X
858E LDY #2 ; Y=2: TXCB offset for dest station
8590 LDA (nmi_tx_block),y ; Load dest station from TX control block
8592 STA tx_dst_stn ; Store to TX scout buffer
8595 INY ; Y=&03
8596 LDA (nmi_tx_block),y ; Load dest network from TX control block
8598 STA tx_dst_net ; Store to TX scout buffer
859B LDY #0 ; Y=0: first byte of TX control block
859D LDA (nmi_tx_block),y ; Load control/flag byte
859F BMI tx_imm_op_setup ; Bit7 set: immediate operation ctrl byte
85A1 JMP tx_active_start ; Bit7 clear: normal data transfer
85A4 .tx_imm_op_setup←1← 859F BMI
STA tx_ctrl_byte ; Store control byte to TX scout buffer
85A7 TAX ; X = control byte for range checks
85A8 INY ; Y=1: port byte offset
85A9 LDA (nmi_tx_block),y ; Load port byte from TX control block
85AB STA tx_port ; Store port byte to TX scout buffer
85AE BNE tx_line_idle_check ; Port != 0: skip immediate op setup
85B0 CPX #&83 ; Ctrl < &83: PEEK/POKE need address calc
85B2 BCS tx_ctrl_range_check ; Ctrl >= &83: skip to range check
85B4 SEC ; SEC: init borrow for 4-byte subtract
85B5 PHP ; Save carry on stack for loop
85B6 LDY #8 ; Y=8: high pointer offset in TXCB
85B8 .calc_peek_poke_size←1← 85CC BCC
LDA (nmi_tx_block),y ; Load TXCB[Y] (end addr byte)
85BA DEY ; Y -= 4: back to start addr offset
85BB DEY ; (continued)
85BC DEY ; (continued)
85BD DEY ; (continued)
85BE PLP ; Restore borrow from stack
85BF SBC (nmi_tx_block),y ; end - start = transfer size byte
85C1 STA tx_data_start,y ; Store result to tx_data_start
85C4 INY ; Y += 5: advance to next end byte
85C5 INY ; (continued)
85C6 INY ; (continued)
85C7 INY ; (continued)
85C8 INY ; (continued)
85C9 PHP ; Save borrow for next byte
85CA CPY #&0c ; Done all 4 bytes? (Y reaches &0C)
85CC BCC calc_peek_poke_size ; No: next byte pair
85CE PLP ; Discard final borrow
85CF .tx_ctrl_range_check←1← 85B2 BCS
CPX #&81 ; Ctrl < &81: not an immediate op
85D1 BCC tx_active_start ; Below range: normal data transfer
85D3 .check_imm_range
CPX #&89 ; Ctrl >= &89: out of immediate range
85D5 BCS tx_active_start ; Above range: normal data transfer
85D7 LDY #&0c ; Y=&0C: start of extra data in TXCB
85D9 .copy_imm_params←1← 85E1 BCC
LDA (nmi_tx_block),y ; Load extra parameter byte from TXCB
85DB STA imm_param_base,y ; Copy to NMI shim workspace at &0D1A+Y
85DE INY ; Next byte
85DF CPY #&10 ; Done 4 bytes? (Y reaches &10)
85E1 BCC copy_imm_params ; No: continue copying
85E3 .tx_line_idle_check←1← 85AE BNE
LDA #&20 ; A=&20: mask for SR2 INACTIVE bit
85E5 BIT econet_control23_or_status2 ; BIT SR2: test if line is idle
85E8 BNE tx_no_clock_error ; Line not idle: handle as line jammed
85EA LDA #&fd ; A=&FD: high byte of timeout counter
85EC PHA ; Push timeout high byte to stack
85ED LDA #6 ; Scout frame = 6 address+ctrl bytes
85EF STA rx_ctrl ; Store scout frame length
85F2 LDA #0 ; A=0: init low byte of timeout counter
fall through ↓

INACTIVE polling loop

Entry point for the Econet line idle detection loop. Saves the TX index in rx_remote_addr, pushes two timeout counter bytes onto the stack, and loads Y=&E7 (CR2 value for TX preparation). Loads the INACTIVE bit mask (&04) into A and falls through to intoff_test_inactive to begin polling SR2 with interrupts disabled.

85F4 .inactive_poll
STA rx_remote_addr ; Save TX index
85F7 PHA ; Push timeout byte 1 on stack
85F8 PHA ; Push timeout byte 2 on stack
85F9 LDY #&e7 ; Y=&E7: CR2 value for TX prep (RTS|CLR_TX_ST|CLR_RX_ST|FC_TDRA|2_1_ BYTE|PSE)
85FB .reload_inactive_mask←3← 8621 BNE← 8626 BNE← 862B BNE
LDA #4 ; A=&04: INACTIVE bit mask for SR2 test
85FD .test_inactive_retry
PHP ; Save interrupt state
85FE SEI ; Disable interrupts for ADLC access
fall through ↓

Disable NMIs and test INACTIVE

Disables NMIs via two reads of &FE18 (INTOFF), then polls SR2 for the INACTIVE bit (bit 2). If INACTIVE is detected, reads SR1 and writes CR2=&67 to clear status, then tests CTS (SR1 bit 4): if CTS is present, branches to tx_prepare to begin transmission. If INACTIVE is not set, re-enables NMIs via &FE20 (INTON) and decrements the 3-byte timeout counter on the stack. On timeout, falls through to tx_line_jammed.

85FF .intoff_test_inactive
BIT station_id_disable_net_nmis ; INTOFF -- disable NMIs
8602 BIT station_id_disable_net_nmis ; INTOFF again (belt-and-braces)
8605 .test_line_idle
BIT econet_control23_or_status2 ; BIT SR2: Z = &04 AND SR2 -- tests INACTIVE
8608 BEQ inactive_retry ; INACTIVE not set -- re-enable NMIs and loop
860A LDA econet_control1_or_status1 ; Read SR1 (acknowledge pending interrupt)
860D LDA #&67 ; CR2=&67: CLR_TX_ST|CLR_RX_ST|FC_ TDRA|2_1_BYTE|PSE
860F STA econet_control23_or_status2 ; Write CR2: clear status, prepare TX
8612 LDA #&10 ; A=&10: CTS mask for SR1 bit4
8614 BIT econet_control1_or_status1 ; BIT SR1: tests CTS present
8617 BNE tx_prepare ; CTS set -- clock hardware detected, start TX
8619 .inactive_retry←1← 8608 BEQ
BIT video_ula_control ; INTON -- re-enable NMIs (&FE20 read)
861C PLP ; Restore interrupt state
861D TSX ; 3-byte timeout counter on stack
861E INC error_text,x ; Increment timeout counter byte 1
8621 BNE reload_inactive_mask ; Not overflowed: retry INACTIVE test
8623 INC stack_page_2,x ; Increment timeout counter byte 2
8626 BNE reload_inactive_mask ; Not overflowed: retry INACTIVE test
8628 INC stack_page_3,x ; Increment timeout counter byte 3
862B BNE reload_inactive_mask ; Not overflowed: retry INACTIVE test
862D BEQ tx_line_jammed ; ALWAYS branch
862F .tx_active_start←3← 85A1 JMP← 85D1 BCC← 85D5 BCS
LDA #&44 ; CR1=&44: TIE | TX_LAST_DATA
8631 BNE store_tx_error ; ALWAYS branch

TX timeout error handler (Line Jammed)

Reached when the INACTIVE polling loop times out without detecting a quiet line. Writes CR2=&07 (FC_TDRA|2_1_BYTE|PSE) to abort the TX attempt, pulls the 3-byte timeout state from the stack, and stores error code &40 ('Line Jammed') in the TX control block via store_tx_error.

8633 .tx_line_jammed←1← 862D BEQ
LDA #7 ; CR2=&07: FC_TDRA | 2_1_BYTE | PSE (abort TX)
8635 STA econet_control23_or_status2 ; Write CR2 to abort TX
8638 PLA ; Clean 3 bytes of timeout loop state
8639 PLA ; Pop saved register
863A PLA ; Pop saved register
863B LDA #&40 ; Error &40 = 'Line Jammed'
863D BNE store_tx_error ; ALWAYS branch to shared error handler ALWAYS branch
863F .tx_no_clock_error←1← 85E8 BNE
LDA #&43 ; Error &43 = 'No Clock'
8641 .store_tx_error←2← 8631 BNE← 863D BNE
LDY #0 ; Offset 0 = error byte in TX control block
8643 STA (nmi_tx_block),y ; Store error code in TX CB byte 0
8645 LDA #&80 ; &80 = TX complete flag
8647 STA ws_0d60 ; Signal TX operation complete
864A PLA ; Restore X saved by caller
864B TAX ; Move to X register
864C RTS ; Return to TX caller

TX preparation

Configures the ADLC for frame transmission. Writes CR2=Y (&E7: RTS|CLR_TX_ST|CLR_RX_ST|FC_TDRA| 2_1_BYTE|PSE) and CR1=&44 (RX_RESET|TIE) to enable TX with interrupts. Installs the nmi_tx_data handler at &86E0. Sets need_release_tube flag via SEC/ROR. Writes the 4-byte destination address (dst_stn, dst_net, src_stn, src_net=0) to the TX FIFO. For Tube transfers, claims the Tube address; for direct transfers, sets up the buffer pointer from the TXCB.

864D .tx_prepare←1← 8617 BNE
STY econet_control23_or_status2 ; Write CR2 = Y (&E7: RTS|CLR_TX_ST|CLR_RX_ST| FC_TDRA|2_1_BYTE|PSE)
8650 LDX #&44 ; CR1=&44: RX_RESET | TIE (TX active, TX interrupts enabled)
8652 STX econet_control1_or_status1 ; Write to ADLC CR1
8655 LDX #&ea ; Install NMI handler at &86E0 (TX data handler)
8657 LDY #&86 ; High byte of NMI handler address
8659 STX nmi_jmp_lo ; Write NMI vector low byte directly
865C STY nmi_jmp_hi ; Write NMI vector high byte directly
865F SEC ; Set need_release_tube flag (SEC/ROR = bit7)
8660 ROR prot_flags ; Rotate carry into bit 7 of flag
8662 BIT video_ula_control ; INTON -- NMIs now fire for TDRA (&FE20 read)
8665 LDA tx_port ; Load destination port number
8668 BNE setup_data_xfer ; Port != 0: standard data transfer
866A LDY tx_ctrl_byte ; Port 0: load control byte for table lookup
866D LDA tube_tx_sr1_operand,y ; Look up tx_flags from table
8670 STA rx_src_net ; Store operation flags
8673 LDA tube_tx_inc_operand,y ; Look up tx_length from table
8676 STA rx_ctrl ; Store expected transfer length
8679 LDA #&86 ; Push high byte of return address (&9C)
867B PHA ; Push high byte for PHA/PHA/RTS dispatch
867C LDA intoff_disable_nmi_op,y ; Look up handler address low from table
867F PHA ; Push low byte for PHA/PHA/RTS dispatch
8680 RTS ; RTS dispatches to control-byte handler
; TX ctrl dispatch table (lo bytes)
; Low bytes of PHA/PHA/RTS dispatch targets for TX
; control byte types &81-&88. Read by the dispatch
; at &867C via LDA intoff_disable_nmi_op,Y (base
; intoff_test_inactive+1). High byte is always &86,
; so targets are &86xx+1. Last entry dispatches to
; tx_ctrl_machine_type at &867F, immediately after
; the table.
8681 .tx_ctrl_dispatch_lo
EQUB <(tx_ctrl_peek-1)
8682 EQUB <(tx_ctrl_poke-1)
8683 EQUB <(proc_op_status2-1)
8684 EQUB <(proc_op_status2-1)
8685 EQUB <(proc_op_status2-1)
8686 EQUB <(tx_ctrl_exit-1)
8687 EQUB <(tx_ctrl_exit-1)
8688 EQUB <(tx_ctrl_machine_type-1)

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

8689 .tx_ctrl_machine_type
LDA #3 ; scout_status=3 (machine type query)
868B BNE store_status_copy_ptr ; Skip address addition, store status ALWAYS branch

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.

868D .tx_ctrl_peek
LDA #3 ; A=3: scout_status for PEEK op
868F 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.

8691 .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). Stores A as the scout status byte at rx_port (&0D40), then performs a 4-byte addition with carry propagation, adding bytes from the TXCB (nmi_tx_block+&0C to +&0F) into the transfer address workspace at &0D1E-&0D21. 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)
8693 .tx_ctrl_store_and_add←1← 868F BNE
STA rx_port ; Store scout status
8696 CLC ; Clear carry for 4-byte addition
8697 PHP ; Save carry on stack
8698 LDY #&0c ; Y=&0C: start at offset 12
869A .add_bytes_loop←1← 86A7 BCC
LDA tx_addr_base,y ; Load workspace address byte
869D PLP ; Restore carry from previous byte
869E ADC (nmi_tx_block),y ; Add TXCB address byte
86A0 STA tx_addr_base,y ; Store updated address byte
86A3 INY ; Next byte
86A4 PHP ; Save carry for next addition
fall through ↓

TX ctrl: JSR/UserProc/OSProc setup

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

86A5 .tx_ctrl_proc
CPY #&10 ; Compare Y with 16-byte boundary
86A7 BCC add_bytes_loop ; Below boundary: continue addition
86A9 PLP ; Restore processor flags
86AA BNE skip_buf_setup ; Skip buffer setup if transfer size is zero
86AC .setup_data_xfer←1← 8668 BNE
LDA tx_dst_stn ; Load dest station for broadcast check
86AF AND tx_dst_net ; AND with dest network
86B2 CMP #&ff ; Both &FF = broadcast address?
86B4 BNE setup_unicast_xfer ; Not broadcast: unicast path
86B6 LDA #&0e ; Broadcast scout: 14 bytes total
86B8 STA rx_ctrl ; Store broadcast scout length
86BB LDA #&40 ; A=&40: broadcast flag
86BD STA rx_src_net ; Set broadcast flag in tx_flags
86C0 LDY #4 ; Y=4: start of address data in TXCB
86C2 .copy_bcast_addr←1← 86CA BCC
LDA (nmi_tx_block),y ; Copy TXCB address bytes to scout buffer
86C4 STA tx_src_stn,y ; Store to TX source/data area
86C7 INY ; Next byte
86C8 CPY #&0c ; Done 8 bytes? (Y reaches &0C)
86CA BCC copy_bcast_addr ; No: continue copying
86CC BCS tx_ctrl_exit ; ALWAYS branch
86CE .setup_unicast_xfer←1← 86B4 BNE
LDA #0 ; A=0: clear flags for unicast
86D0 STA rx_src_net ; Clear tx_flags
86D3 .proc_op_status2
LDA #2 ; scout_status=2: data transfer pending
86D5 .store_status_copy_ptr←1← 868B BNE
STA rx_port ; Store scout status
86D8 .skip_buf_setup←1← 86AA BNE
LDA nmi_tx_block ; Copy TX block pointer to workspace ptr
86DA STA port_ws_offset ; Store low byte
86DC LDA nmi_tx_block_hi ; Copy TX block pointer high byte
86DE STA rx_buf_offset ; Store high byte
86E0 JSR tx_calc_transfer ; Calculate transfer size from RXCB
86E3 .tx_ctrl_exit←1← 86CC BCS
PLP ; Restore processor status from stack
86E4 PLA ; Restore stacked registers (4 PLAs)
86E5 PLA ; Second PLA
86E6 PLA ; Third PLA
86E7 PLA ; Fourth PLA
86E8 TAX ; Restore X from A
86E9 RTS ; Return to caller

NMI TX data handler

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

86EA .nmi_tx_data
LDY rx_remote_addr ; Load TX buffer index
86ED BIT econet_control1_or_status1 ; BIT SR1: V=bit6(TDRA), N=bit7(IRQ)
86F0 .tx_fifo_write←1← 870B BMI
BVC tx_fifo_not_ready ; TDRA not set -- TX error
86F2 LDA tx_dst_stn,y ; Load byte from TX buffer
86F5 STA econet_data_continue_frame ; Write to TX_DATA (continue frame)
86F8 INY ; Next TX buffer byte
86F9 LDA tx_dst_stn,y ; Load second byte from TX buffer
86FC INY ; Advance TX index past second byte
86FD STY rx_remote_addr ; Save updated TX buffer index
8700 STA econet_data_continue_frame ; Write second byte to TX_DATA
8703 CPY rx_ctrl ; Compare index to TX length
8706 BCS tx_last_data ; Frame complete -- go to TX_LAST_DATA
8708 BIT econet_control1_or_status1 ; Check if we can send another pair
870B BMI tx_fifo_write ; IRQ set -- send 2 more bytes (tight loop)
870D JMP nmi_rti ; RTI -- wait for next NMI
8710 .tx_error←1← 8753 BEQ
LDA #&42 ; Error &42
8712 BNE tx_store_error ; ALWAYS branch
8714 .tx_fifo_not_ready←1← 86F0 BVC
LDA #&67 ; CR2=&67: clear status, return to listen
8716 STA econet_control23_or_status2 ; Write CR2: clear status, idle listen
8719 LDA #&41 ; Error &41 (TDRA not ready)
871B .tx_store_error←1← 8712 BNE
LDY station_id_disable_net_nmis ; INTOFF (also loads station ID)
871E .delay_nmi_disable←1← 8721 BNE
PHA ; PHA/PLA delay loop (256 iterations for NMI disable)
871F PLA ; PHA/PLA delay (~7 cycles each)
8720 INY ; Increment delay counter
8721 BNE delay_nmi_disable ; Loop 256 times for NMI disable
8723 JMP tx_store_result ; Store error and return to idle

TX_LAST_DATA and frame completion

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

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

TX completion: switch to RX mode

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

8732 .nmi_tx_complete
LDA #&82 ; Jump to error handler
8734 STA econet_control1_or_status1 ; Write CR1 to switch from TX to RX
8737 BIT rx_src_net ; Test workspace flags
873A BVC check_handshake_bit ; bit6 not set -- check bit0
873C JMP tx_result_ok ; bit6 set -- TX completion
873F .check_handshake_bit←1← 873A BVC
LDA #1 ; A=1: mask for bit0 test
8741 BIT rx_src_net ; Test tx_flags bit0 (handshake)
8744 BEQ install_reply_scout ; bit0 clear: install reply handler
8746 JMP handshake_await_ack ; bit0 set -- four-way handshake data phase
8749 .install_reply_scout←1← 8744 BEQ
LDA #&4e ; Install RX reply handler at &8744
874B JMP install_nmi_handler ; Install handler and RTI

RX reply scout handler

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

874E .nmi_reply_scout
LDA #1 ; A=&01: AP mask for SR2
8750 BIT econet_control23_or_status2 ; BIT SR2: test AP (Address Present)
8753 BEQ tx_error ; No AP -- error
8755 LDA econet_data_continue_frame ; Read first RX byte (destination station)
8758 CMP station_id_disable_net_nmis ; Compare to our station ID (INTOFF side effect)
875B BNE reject_reply ; Not our station -- error/reject
875D LDA #&62 ; Install next handler at &8758 (reply continuation)
875F JMP install_nmi_handler ; Install continuation handler

RX reply continuation handler

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

8762 .nmi_reply_cont
BIT econet_control23_or_status2 ; Read RX byte (destination station)
8765 BPL reject_reply ; No RDA -- error
8767 LDA econet_data_continue_frame ; Read destination network byte
876A BNE reject_reply ; Non-zero -- network mismatch, error
876C LDA #&79 ; Install next handler at &8779 (reply validation)
876E BIT econet_control1_or_status1 ; BIT SR1: test IRQ (N=bit7) -- more data ready?
8771 BMI nmi_reply_validate ; IRQ set -- fall through to &8779 without RTI
8773 JMP install_nmi_handler ; IRQ not set -- install handler and RTI
8776 .reject_reply←7← 875B BNE← 8765 BPL← 876A BNE← 877C BPL← 8784 BNE← 878C BNE← 8793 BEQ
JMP tx_result_fail ; Store error and return to idle

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

Reads the source station and source network from the reply scout and validates them against the original TX destination (&0D20/&0D21). Sequence: 1. Check SR2 bit7 (RDA) at &8779 -- must see data available 2. Read source station at &877E, compare to &0D20 (tx_dst_stn) 3. Read source network at &877C, compare to &0D21 (tx_dst_net) 4. Check SR2 bit1 (FV) at &8786 -- 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).

8779 .nmi_reply_validate←1← 8771 BMI
BIT econet_control23_or_status2 ; BIT SR2: test RDA (bit7). Must be set for valid reply.
877C BPL reject_reply ; No RDA -- error (FV masking RDA via PSE would cause this)
877E LDA econet_data_continue_frame ; Read source station
8781 CMP tx_dst_stn ; Compare to original TX destination station (&0D20)
8784 BNE reject_reply ; Mismatch -- not the expected reply, error
8786 LDA econet_data_continue_frame ; Read source network
8789 CMP tx_dst_net ; Compare to original TX destination network (&0D21)
878C BNE reject_reply ; Mismatch -- error
878E LDA #2 ; A=&02: FV mask for SR2 bit1
8790 BIT econet_control23_or_status2 ; BIT SR2: test FV -- frame must be complete
8793 BEQ reject_reply ; No FV -- incomplete frame, error
8795 LDA #&a7 ; CR2=&A7: RTS|CLR_TX_ST|FC_TDRA|2_ 1_BYTE|PSE (TX in handshake)
8797 STA econet_control23_or_status2 ; Write CR2: enable RTS for TX handshake
879A LDA #&44 ; CR1=&44: RX_RESET | TIE (TX active for scout ACK)
879C STA econet_control1_or_status1 ; Write CR1: reset RX, enable TX interrupt
879F LDA #&78 ; Install next handler at &8878 (four-way data phase) into &0D43/&0D44
87A1 LDY #&88 ; High byte &88 of next handler address
87A3 STA saved_nmi_lo ; Store low byte to nmi_next_lo
87A6 STY saved_nmi_hi ; Store high byte to nmi_next_hi
87A9 LDA tx_dst_stn ; Load dest station for scout ACK TX
87AC BIT econet_control1_or_status1 ; BIT SR1: test TDRA (V=bit6)
87AF BVC tx_check_tdra_ready ; TDRA not ready -- error
87B1 STA econet_data_continue_frame ; Write dest station to TX FIFO
87B4 LDA tx_dst_net ; Write dest network to TX FIFO
87B7 STA econet_data_continue_frame ; Write dest network to TX FIFO
87BA LDA #&c1 ; Install handler at &87B7 (write src addr for scout ACK)
87BC LDY #&87 ; High byte &87 of handler address
87BE 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 &FE18 (INTOFF), tests TDRA via SR1, and writes station + network=0 to the TX FIFO. Then checks bit 1 of rx_src_net to select between the immediate-op data NMI handler and the normal nmi_data_tx handler at &87EE. Installs the chosen handler via set_nmi_vector. Shares the tx_check_tdra entry at &87C7 with ack_tx.

87C1 .nmi_scout_ack_src
LDA station_id_disable_net_nmis ; Load our station ID (also INTOFF)
87C4 BIT econet_control1_or_status1 ; BIT SR1: test TDRA
87C7 .tx_check_tdra_ready←1← 87AF BVC
BVC data_tx_check_fifo ; TDRA not ready -- error
87C9 STA econet_data_continue_frame ; Write our station to TX FIFO
87CC LDA #0 ; Write network=0 to TX FIFO
87CE STA econet_data_continue_frame ; Write network byte to TX FIFO
87D1 .data_tx_begin←1← 8349 JMP
LDA #2 ; Test bit 1 of tx_flags
87D3 BIT rx_src_net ; Check if immediate-op or data-transfer
87D6 BNE install_imm_data_nmi ; Bit 1 set: immediate op, use alt handler
87D8 LDA #&ee ; Install nmi_data_tx at &87EE
87DA LDY #&87 ; High byte of handler address
87DC JMP set_nmi_vector ; Install and return via set_nmi_vector
87DF .install_imm_data_nmi←1← 87D6 BNE
LDA #&37 ; Install nmi_imm_data at &8837
87E1 LDY #&88 ; High byte of handler address
87E3 JMP set_nmi_vector ; Install and return via set_nmi_vector

TX data phase: send payload

Transmits the data payload of a four-way handshake. Loads bytes from (open_port_buf),Y or from Tube R3 depending on the transfer mode, writing pairs to the TX FIFO. After each pair, decrements the byte count (port_buf_len). If the count reaches zero, branches to tx_last_data to signal end of frame. Otherwise tests SR1 bit 7 (IRQ): if still asserted, writes another pair without returning from NMI (tight loop optimisation). If IRQ clears, returns via RTI.

87E6 .nmi_data_tx←1← 87F0 BEQ
LDY port_buf_len_hi ; Y = buffer offset, resume from last position
87E8 BEQ data_tx_last ; No pages left: send final partial page
87EA LDY port_buf_len ; Load remaining byte count
87EC BEQ check_tdra_status ; Zero bytes left: skip to TDRA check
87EE LDY port_buf_len ; Load remaining byte count (alt entry)
87F0 BEQ nmi_data_tx ; Zero: loop back to top of handler
87F2 .check_tdra_status←1← 87EC BEQ
BIT econet_control1_or_status1 ; BIT SR1: test TDRA (V=bit6)
87F5 .data_tx_check_fifo←2← 87C7 BVC← 8818 BMI
BVC tube_tx_fifo_write ; TDRA not ready -- error
87F7 LDA (open_port_buf),y ; Write data byte to TX FIFO
87F9 STA econet_data_continue_frame ; Write first byte of pair to FIFO
87FC INY ; Advance buffer offset
87FD BNE write_second_tx_byte ; No page crossing
87FF DEC port_buf_len_hi ; Page crossing: decrement page count
8801 BEQ data_tx_last ; No pages left: send last data
8803 INC open_port_buf_hi ; Increment buffer high byte
8805 .write_second_tx_byte←1← 87FD BNE
LDA (open_port_buf),y ; Load second byte of pair
8807 STA econet_data_continue_frame ; Write second byte to FIFO
880A INY ; Advance buffer offset
880B STY port_buf_len ; Save updated buffer position
880D BNE check_irq_loop ; No page crossing
880F DEC port_buf_len_hi ; Page crossing: decrement page count
8811 BEQ data_tx_last ; No pages left: send last data
8813 INC open_port_buf_hi ; Increment buffer high byte
8815 .check_irq_loop←1← 880D BNE
BIT econet_control1_or_status1 ; BIT SR1: test IRQ (N=bit7) for tight loop
8818 BMI data_tx_check_fifo ; IRQ still set: write 2 more bytes
881A JMP nmi_rti ; No IRQ: return, wait for next NMI
881D .data_tx_last←5← 87E8 BEQ← 8801 BEQ← 8811 BEQ← 8850 BEQ← 8866 BEQ
LDA #&3f ; CR2=&3F: TX_LAST_DATA (close data frame)
881F STA econet_control23_or_status2 ; Write CR2 to close frame
8822 LDA rx_src_net ; Check tx_flags for next action
8825 BPL install_saved_handler ; Bit7 clear: error, install saved handler
8827 LDA #&f5 ; Install discard_reset_listen at &83F5
8829 LDY #&83 ; High byte of &83F5 handler
882B JMP set_nmi_vector ; Set NMI vector and return
882E .install_saved_handler←1← 8825 BPL
LDA saved_nmi_lo ; Load saved next handler low byte
8831 LDY saved_nmi_hi ; Load saved next handler high byte
8834 JMP set_nmi_vector ; Install saved handler and return
8837 .nmi_data_tx_tube
BIT econet_control1_or_status1 ; Tube TX: BIT SR1 test TDRA
883A .tube_tx_fifo_write←2← 87F5 BVC← 886B BMI
BVC tx_tdra_error ; TDRA not ready -- error
883C LDA tube_data_register_3 ; Read byte from Tube R3
883F STA econet_data_continue_frame ; Write to TX FIFO
8842 INC port_buf_len ; Increment 4-byte buffer counter
8844 BNE write_second_tube_byte ; Low byte didn't wrap
8846 INC port_buf_len_hi ; Carry into second byte
8848 BNE write_second_tube_byte ; No further carry
884A INC open_port_buf ; Carry into third byte
884C BNE write_second_tube_byte ; No further carry
884E INC open_port_buf_hi ; Carry into fourth byte
8850 BEQ data_tx_last ; Counter wrapped to zero: last data
8852 .write_second_tube_byte←3← 8844 BNE← 8848 BNE← 884C BNE
LDA tube_data_register_3 ; Read second Tube byte from R3
8855 STA econet_data_continue_frame ; Write second byte to TX FIFO
8858 INC port_buf_len ; Increment 4-byte counter (second byte)
885A BNE check_tube_irq_loop ; Low byte didn't wrap
885C .tube_tx_inc_byte2
INC port_buf_len_hi ; Carry into second byte
885E BNE check_tube_irq_loop ; No further carry
8860 .tube_tx_inc_byte3
INC open_port_buf ; Carry into third byte
8862 BNE check_tube_irq_loop ; No further carry
8864 .tube_tx_inc_byte4
INC open_port_buf_hi ; Carry into fourth byte
8866 BEQ data_tx_last ; Counter wrapped to zero: last data
8868 .check_tube_irq_loop←3← 885A BNE← 885E BNE← 8862 BNE
BIT econet_control1_or_status1 ; BIT SR1: test IRQ for tight loop
886B BMI tube_tx_fifo_write ; IRQ still set: write 2 more bytes
886D JMP nmi_rti ; No IRQ: return, wait for next NMI
8870 .tx_tdra_error←1← 883A BVC
LDA rx_src_net ; TX error: check flags for path
8873 BPL tx_result_fail ; Bit7 clear: TX result = not listening
8875 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 &0D4A 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 the nmi_final_ack handler at &887A via set_nmi_vector.

8878 .handshake_await_ack←1← 8746 JMP
LDA #&82 ; CR1=&82: TX_RESET | RIE (switch to RX for final ACK)
887A STA econet_control1_or_status1 ; Write to ADLC CR1
887D LDA #&84 ; Install nmi_final_ack handler
887F LDY #&88 ; High byte of handler address
8881 JMP set_nmi_vector ; Install and return via set_nmi_vector

RX final ACK handler

Receives the final ACK in a four-way handshake. Same validation pattern as the reply scout handler (&874E-&8779): &887A: Check AP, read dest_stn, compare to our station &888E: Check RDA, read dest_net, validate = 0 &88A2: Check RDA, read src_stn/net, compare to TX dest &88C1: Check FV for frame completion On success, stores result=0 at tx_result_ok. On failure, error &41.

8884 .nmi_final_ack
LDA #1 ; A=&01: AP mask
8886 BIT econet_control23_or_status2 ; BIT SR2: test AP
8889 BEQ tx_result_fail ; No AP -- error
888B LDA econet_data_continue_frame ; Read dest station
888E CMP station_id_disable_net_nmis ; Compare to our station (INTOFF side effect)
8891 BNE tx_result_fail ; Not our station -- error
8893 LDA #&98 ; Install nmi_final_ack_net handler
8895 JMP install_nmi_handler ; Install continuation handler
8898 .nmi_final_ack_net
BIT econet_control23_or_status2 ; BIT SR2: test RDA
889B BPL tx_result_fail ; No RDA -- error
889D LDA econet_data_continue_frame ; Read dest network
88A0 BNE tx_result_fail ; Non-zero -- network mismatch, error
88A2 LDA #&ac ; Install nmi_final_ack_validate handler
88A4 BIT econet_control1_or_status1 ; BIT SR1: test IRQ -- more data ready?
88A7 BMI nmi_final_ack_validate ; IRQ set -- fall through to validate
88A9 JMP install_nmi_handler ; Install handler and RTI

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 (&0D20) and tx_dst_net (&0D21). 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.

88AC .nmi_final_ack_validate←1← 88A7 BMI
BIT econet_control23_or_status2 ; BIT SR2: test RDA
88AF BPL tx_result_fail ; No RDA -- error
88B1 LDA econet_data_continue_frame ; Read source station
88B4 CMP tx_dst_stn ; Compare to TX dest station (&0D20)
88B7 BNE tx_result_fail ; Mismatch -- error
88B9 LDA econet_data_continue_frame ; Read source network
88BC CMP tx_dst_net ; Compare to TX dest network (&0D21)
88BF BNE tx_result_fail ; Mismatch -- error
88C1 LDA rx_src_net ; Load TX flags for next action
88C4 BPL check_fv_final_ack ; bit7 clear: no data phase
88C6 JMP install_data_rx_handler ; Install data RX handler
88C9 .check_fv_final_ack←1← 88C4 BPL
LDA #2 ; A=&02: FV mask for SR2 bit1
88CB BIT econet_control23_or_status2 ; BIT SR2: test FV -- frame must be complete
88CE 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). This two-instruction entry point exists so that JMP sites can target the success path without needing to set A. Called from ack_tx (&82EC) for final-ACK completion and from nmi_tx_complete (&8732) for immediate-op completion where no ACK is expected.

88D0 .tx_result_ok←2← 82F7 JMP← 873C JMP
LDA #0 ; A=0: success result code
88D2 BEQ tx_store_result ; BEQ: always taken (A=0) ALWAYS branch

TX failure: not listening

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

88D4 .tx_result_fail←11← 823B JMP← 8776 JMP← 8873 BPL← 8889 BEQ← 8891 BNE← 889B BPL← 88A0 BNE← 88AF BPL← 88B7 BNE← 88BF BNE← 88CE 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 ws_0d60 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)
88D6 .tx_store_result←2← 8723 JMP← 88D2 BEQ
LDY #0 ; Y=0: index into TX control block
88D8 STA (nmi_tx_block),y ; Store result/error code at (nmi_tx_block),0
88DA LDA #&80 ; &80: completion flag for &0D3A
88DC STA ws_0d60 ; Signal TX complete
88DF JMP discard_reset_rx ; Full ADLC reset and return to idle listen
; Unreferenced dead data (16 bytes)
; 16 bytes between JMP discard_reset_rx (&88DF) and
; tx_calc_transfer (&88F2). Unreachable as code (after
; an unconditional JMP) and unreferenced as data. No
; label, index, or indirect pointer targets any address
; in the &88E2-&88F1 range. Likely unused remnant from
; development.
88E2 EQUB &0E ; Dead data: &0E
88E3 EQUB &0E ; Dead data: &0E
88E4 EQUB &0A ; Dead data: &0A
88E5 EQUB &0A ; Dead data: &0A
88E6 EQUB &0A ; Dead data: &0A
88E7 EQUB &06 ; Dead data: &06
88E8 EQUB &06 ; Dead data: &06
88E9 EQUB &0A ; Dead data: &0A
88EA EQUB &81 ; Dead data: &81
88EB EQUB &00 ; Dead data: &00
88EC EQUB &00 ; Dead data: &00
88ED EQUB &00 ; Dead data: &00
88EE EQUB &00 ; Dead data: &00
88EF EQUB &01 ; Dead data: &01
88F0 EQUB &01 ; Dead data: &01
88F1 EQUB &81 ; Dead data: &81

Calculate transfer size

Computes the data transfer byte count from the RXCB buffer pointers. Reads the 4-byte buffer end address from (port_ws_offset) and checks for Tube addresses (&FExx/&FFxx). For Tube transfers, claims the Tube address and sets the transfer flag in rx_src_net. Subtracts the buffer start from the buffer end to compute the byte count, storing it in port_buf_len/port_buf_len_hi. Also copies the buffer start address to open_port_buf for the RX/TX handlers to use as their working pointer.

88F2 .tx_calc_transfer←3← 81BF JSR← 84D8 JSR← 86E0 JSR
LDY #7 ; Y=7: offset to RXCB buffer addr byte 3
88F4 LDA (port_ws_offset),y ; Read RXCB[7] (buffer addr high byte)
88F6 CMP #&ff ; Compare to &FF
88F8 BNE check_tx_in_progress ; Not &FF: normal buffer, skip Tube check
88FA DEY ; Y=&06
88FB LDA (port_ws_offset),y ; Read RXCB[6] (buffer addr byte 2)
88FD CMP #&fe ; Check if addr byte 2 >= &FE (Tube range)
88FF BCS fallback_calc_transfer ; Tube/IO address: use fallback path
8901 .check_tx_in_progress←1← 88F8 BNE
LDA tube_present ; Transmit in progress?
8904 BEQ fallback_calc_transfer ; No: fallback path
8906 LDA rx_src_net ; Load TX flags for transfer setup
8909 ORA #2 ; Set bit 1 (transfer complete)
890B STA rx_src_net ; Store with bit 1 set (Tube xfer)
890E SEC ; Init borrow for 4-byte subtract
890F PHP ; Save carry on stack
8910 LDY #4 ; Y=4: start at RXCB offset 4
8912 .calc_transfer_size←1← 8924 BCC
LDA (port_ws_offset),y ; Load RXCB[Y] (current ptr byte)
8914 INY ; Y += 4: advance to RXCB[Y+4]
8915 INY ; (continued)
8916 INY ; (continued)
8917 INY ; (continued)
8918 PLP ; Restore borrow from previous byte
8919 SBC (port_ws_offset),y ; Subtract RXCB[Y+4] (start ptr byte)
891B STA net_tx_ptr,y ; Store result byte
891E DEY ; Y -= 3: next source byte
891F DEY ; (continued)
8920 DEY ; (continued)
8921 PHP ; Save borrow for next byte
8922 CPY #8 ; Done all 4 bytes?
8924 BCC calc_transfer_size ; No: next byte pair
8926 PLP ; Discard final borrow
8927 TXA ; Save X
8928 PHA ; Save X
8929 LDA #4 ; Compute address of RXCB+4
892B CLC ; CLC for base pointer addition
892C ADC port_ws_offset ; Add RXCB base to get RXCB+4 addr
892E TAX ; X = low byte of RXCB+4
892F LDY rx_buf_offset ; Y = high byte of RXCB ptr
8931 LDA #&c2 ; Tube claim type &C2
8933 JSR tube_addr_data_dispatch ; Claim Tube transfer address
8936 BCC restore_x_and_return ; No Tube: skip reclaim
8938 LDA rx_port ; Tube: reclaim with scout status
893B JSR tube_addr_data_dispatch ; Reclaim with scout status type
893E JSR release_tube ; Release Tube claim after reclaim
8941 SEC ; C=1: Tube address claimed
8942 .restore_x_and_return←1← 8936 BCC
PLA ; Restore X
8943 TAX ; Restore X from stack
8944 RTS ; Return with C = transfer status
8945 .fallback_calc_transfer←2← 88FF BCS← 8904 BEQ
LDY #4 ; Y=4: RXCB current pointer offset
8947 LDA (port_ws_offset),y ; Load RXCB[4] (current ptr lo)
8949 LDY #8 ; Y=8: RXCB start address offset
894B SEC ; Set carry for subtraction
894C SBC (port_ws_offset),y ; Subtract RXCB[8] (start ptr lo)
894E STA port_buf_len ; Store transfer size lo
8950 LDY #5 ; Y=5: current ptr hi offset
8952 LDA (port_ws_offset),y ; Load RXCB[5] (current ptr hi)
8954 SBC #0 ; Propagate borrow only
8956 STA open_port_buf_hi ; Temp store of adjusted hi byte
8958 LDY #8 ; Y=8: start address lo offset
895A LDA (port_ws_offset),y ; Copy RXCB[8] to open port buffer lo
895C STA open_port_buf ; Store to scratch (side effect)
895E LDY #9 ; Y=9: start address hi offset
8960 LDA (port_ws_offset),y ; Load RXCB[9]
8962 SEC ; Set carry for subtraction
8963 SBC open_port_buf_hi ; Subtract adjusted hi byte
8965 STA port_buf_len_hi ; Store transfer size hi
8967 SEC ; Return with C=1
8968 .nmi_shim_rom_src
RTS ; Return with C=1 (success)

ADLC full reset

Performs a full ADLC hardware reset. Writes CR1=&C1 (TX_RESET|RX_RESET|AC) to put both TX and RX sections in reset with address control enabled. Then configures CR4=&1E (8-bit RX word, abort extend, NRZ encoding) and CR3=&00 (no loopback, no AEX, NRZ, no DTR). Falls through to adlc_rx_listen to re-enter RX listen mode.

8969 .adlc_full_reset←3← 8077 JSR← 8104 JSR← 823E JSR
LDA #&c1 ; CR1=&C1: TX_RESET | RX_RESET | AC (both sections in reset, address control set)
896B STA econet_control1_or_status1 ; Write CR1 to ADLC register 0
896E LDA #&1e ; CR4=&1E (via AC=1): 8-bit RX word length, abort extend enabled, NRZ encoding
8970 STA econet_data_terminate_frame ; Write CR4 to ADLC register 3
8973 LDA #0 ; CR3=&00 (via AC=1): no loop-back, no AEX, NRZ, no DTR
8975 STA econet_control23_or_status2 ; Write CR3 to ADLC register 1
fall through ↓

Enter RX listen mode

Configures the ADLC for passive RX listen mode. Writes CR1=&82 (TX_RESET|RIE): TX section held in reset, RX interrupts enabled. Writes CR2=&67 (CLR_TX_ST|CLR_RX_ST|FC_TDRA|2_1_BYTE|PSE) to clear all pending status and enable prioritised status. This is the idle state where the ADLC listens for incoming scout frames via NMI.

8978 .adlc_rx_listen←2← 83F8 JSR← 89A4 JMP
LDA #&82 ; CR1=&82: TX_RESET | RIE (TX in reset, RX interrupts enabled)
897A STA econet_control1_or_status1 ; Write to ADLC CR1
897D LDA #&67 ; CR2=&67: CLR_TX_ST | CLR_RX_ST | FC_TDRA | 2_1_BYTE | PSE
897F STA econet_control23_or_status2 ; Write to ADLC CR2
8982 RTS ; Return; ADLC now in RX listen mode

Wait for idle NMI state and reset Econet

Service 12 handler: NMI release. Checks ws_0d62 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 &0D0C/&0D0D against the address of nmi_rx_scout (&80BE). When the NMI handler returns to idle, falls through to save_econet_state to clear the initialised flags and re-enter RX listen mode.

8983 .wait_idle_and_reset
BIT ws_0d62 ; Check if Econet has been initialised
8986 BPL reset_enter_listen ; Not initialised: skip to RX listen
8988 .poll_nmi_idle←2← 898D BNE← 8994 BNE
LDA nmi_jmp_lo ; Read current NMI handler low byte
898B CMP #&be ; Expected: &B3 (nmi_rx_scout low)
898D BNE poll_nmi_idle ; Not idle: spin and wait
898F LDA nmi_jmp_hi ; Read current NMI handler high byte
8992 EOR #&80 ; Test if high byte = &80 (page of nmi_rx_scout)
8994 BNE poll_nmi_idle ; Not idle: spin and wait
fall through ↓

Reset Econet flags and enter RX listen

Disables NMIs via two reads of &FE18 (INTOFF), then clears ws_0d60 (TX complete) and ws_0d62 (Econet initialised) 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 NMI release (service 12) to safely tear down the Econet state before another ROM can claim the NMI workspace.

8996 .save_econet_state
BIT station_id_disable_net_nmis ; INTOFF: disable NMIs
8999 BIT station_id_disable_net_nmis ; INTOFF again (belt-and-braces)
899C STA ws_0d60 ; TX not in progress
899F STA ws_0d62 ; Econet not initialised
89A2 LDY #5 ; Y=5: service call workspace page
89A4 .reset_enter_listen←1← 8986 BPL
JMP adlc_rx_listen ; Set ADLC to RX listen mode

Bootstrap NMI entry point (in ROM)

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

89A7 .nmi_bootstrap_entry
BIT station_id_disable_net_nmis ; INTOFF: disable NMIs while switching ROM
89AA PHA ; Save A
89AB TYA ; Transfer Y to A
89AC PHA ; Save Y (via A)
89AD LDA #0 ; ROM bank 0 (patched during init for actual bank)
89AF STA romsel ; Select Econet ROM bank via ROMSEL
89B2 JMP nmi_rx_scout ; Jump to scout handler in ROM

ROM copy of set_nmi_vector + nmi_rti

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

89B5 .rom_set_nmi_vector
STY nmi_jmp_hi ; Store handler high byte at &0D0D
89B8 STA nmi_jmp_lo ; Store handler low byte at &0D0C
89BB LDA romsel_copy ; Restore NFS ROM bank
89BD STA romsel ; Page in via hardware latch
89C0 PLA ; Restore Y from stack
89C1 TAY ; Transfer ROM bank to Y
89C2 PLA ; Restore A from stack
89C3 BIT video_ula_control ; INTON: re-enable NMIs
89C6 RTI ; Return from interrupt
; Unreferenced dead data (3 bytes)
; 3 bytes between the RTI at &89C6 (end of the NMI
; shim ROM source) and svc_dispatch_lo at &89CA.
; The init copy loop (Y=1..&20) copies &89A7-&89C6
; to &0D00-&0D1F; these bytes are outside that range
; and unreferenced. Likely unused development remnant.
89C7 EQUB &01 ; Dead data: &01
89C8 EQUB &00 ; Dead data: &00
89C9 EQUB &18
; Service dispatch table (37 entries, split lo/hi).
; PHA/PHA/RTS dispatch used by svc_dispatch.
; Indices 0-14: service calls (index = service + 1).
; Indices 15-36: FS command and OSWORD routing.
; Indices 1, 7, 11 point to return_4 (no-op RTS).
89CA .svc_dispatch_lo←1← 8E52 LDA
EQUB &04 ; lo - dummy entry (outside ROM range)
89CB EQUB <(dispatch_rts-1) ; lo - Svc 0: already claimed (no-op)
89CC EQUB <(svc_1_abs_workspace-1) ; lo - Svc 1: absolute workspace
89CD EQUB <(svc_2_private_workspace-1) ; lo - Svc 2: private workspace
89CE EQUB <(svc_3_autoboot-1) ; lo - Svc 3: auto-boot
89CF EQUB <(svc_4_star_command-1) ; lo - Svc 4: unrecognised star command
89D0 EQUB <(svc5_irq_check-1) ; lo - Svc 5: unrecognised interrupt
89D1 EQUB <(dispatch_rts-1) ; lo - Svc 6: BRK (no-op)
89D2 EQUB <(svc_7_osbyte-1) ; lo - Svc 7: unrecognised OSBYTE
89D3 EQUB <(svc_8_osword-1) ; lo - Svc 8: unrecognised OSWORD
89D4 EQUB <(svc_9_help-1) ; lo - Svc 9: *HELP
89D5 EQUB <(dispatch_rts-1) ; lo - Svc 10: static workspace (no-op)
89D6 EQUB <(econet_restore-1) ; lo - Svc 11: NMI release (reclaim NMIs)
89D7 EQUB <(wait_idle_and_reset-1) ; lo - Svc 12: NMI claim (save NMI state)
89D8 EQUB <(svc_18_fs_select-1) ; lo - Svc 18: filing system selection
89D9 EQUB <(lang_0_insert_remote_key-1) ; lo - Lang 0: no language / Tube
89DA EQUB <(lang_1_remote_boot-1) ; lo - Lang 1: normal startup
89DB EQUB <(lang_2_save_palette_vdu-1) ; lo - Lang 2: softkey byte (Electron)
89DC EQUB <(lang_3_execute_at_0100-1) ; lo - Lang 3: softkey length (Electron)
89DD EQUB <(lang_4_remote_validated-1) ; lo - Lang 4: remote validated
89DE EQUB <(fscv_0_opt_entry-1) ; lo - FSCV 0: *OPT
89DF EQUB <(fscv_1_eof-1) ; lo - FSCV 1: EOF check
89E0 EQUB <(fscv_2_star_run-1) ; lo - FSCV 2: */ (run)
89E1 EQUB <(fscv_3_star_cmd-1) ; lo - FSCV 3: unrecognised star command
89E2 EQUB <(fscv_2_star_run-1) ; lo - FSCV 4: *RUN
89E3 EQUB <(fscv_5_cat-1) ; lo - FSCV 5: *CAT
89E4 EQUB <(fscv_6_shutdown-1) ; lo - FSCV 6: shutdown
89E5 EQUB <(fscv_7_read_handles-1) ; lo - FSCV 7: read handle range
89E6 EQUB <(fsreply_0_print_dir-1) ; lo - FS reply: print directory name
89E7 EQUB <(fsreply_1_copy_handles_boot-1) ; lo - FS reply: copy handles + boot
89E8 EQUB <(fsreply_2_copy_handles-1) ; lo - FS reply: copy handles
89E9 EQUB <(fsreply_3_set_csd-1) ; lo - FS reply: set CSD handle
89EA EQUB <(fscv_2_star_run-1) ; lo - FS reply: notify + execute
89EB EQUB <(fsreply_5_set_lib-1) ; lo - FS reply: set library handle
89EC EQUB <(net_1_read_handle-1) ; lo - *NET1: read handle from packet
89ED EQUB <(net_2_read_handle_entry-1) ; lo - *NET2: read handle from workspace
89EE EQUB <(net_3_close_handle-1) ; lo - *NET3: close handle
89EF .svc_dispatch_hi←1← 8E4E LDA
EQUB &D3 ; hi - dummy entry (outside ROM range)
89F0 EQUB >(dispatch_rts-1) ; hi - Svc 0: already claimed (no-op)
89F1 EQUB >(svc_1_abs_workspace-1) ; hi - Svc 1: absolute workspace
89F2 EQUB >(svc_2_private_workspace-1) ; hi - Svc 2: private workspace
89F3 EQUB >(svc_3_autoboot-1) ; hi - Svc 3: auto-boot
89F4 EQUB >(svc_4_star_command-1) ; hi - Svc 4: unrecognised star command
89F5 EQUB >(svc5_irq_check-1) ; hi - Svc 5: unrecognised interrupt
89F6 EQUB >(dispatch_rts-1) ; hi - Svc 6: BRK (no-op)
89F7 EQUB >(svc_7_osbyte-1) ; hi - Svc 7: unrecognised OSBYTE
89F8 EQUB >(svc_8_osword-1) ; hi - Svc 8: unrecognised OSWORD
89F9 EQUB >(svc_9_help-1) ; hi - Svc 9: *HELP
89FA EQUB >(dispatch_rts-1) ; hi - Svc 10: static workspace (no-op)
89FB EQUB >(econet_restore-1) ; hi - Svc 11: NMI release (reclaim NMIs)
89FC EQUB >(wait_idle_and_reset-1) ; hi - Svc 12: NMI claim (save NMI state)
89FD EQUB >(svc_18_fs_select-1) ; hi - Svc 18: filing system selection
89FE EQUB >(lang_0_insert_remote_key-1) ; hi - Lang 0: no language / Tube
89FF EQUB >(lang_1_remote_boot-1) ; hi - Lang 1: normal startup
8A00 EQUB >(lang_2_save_palette_vdu-1) ; hi - Lang 2: softkey byte (Electron)
8A01 EQUB >(lang_3_execute_at_0100-1) ; hi - Lang 3: softkey length (Electron)
8A02 EQUB >(lang_4_remote_validated-1) ; hi - Lang 4: remote validated
8A03 EQUB >(fscv_0_opt_entry-1) ; hi - FSCV 0: *OPT
8A04 EQUB >(fscv_1_eof-1) ; hi - FSCV 1: EOF check
8A05 EQUB >(fscv_2_star_run-1) ; hi - FSCV 2: */ (run)
8A06 EQUB >(fscv_3_star_cmd-1) ; hi - FSCV 3: unrecognised star command
8A07 EQUB >(fscv_2_star_run-1) ; hi - FSCV 4: *RUN
8A08 EQUB >(fscv_5_cat-1) ; hi - FSCV 5: *CAT
8A09 EQUB >(fscv_6_shutdown-1) ; hi - FSCV 6: shutdown
8A0A EQUB >(fscv_7_read_handles-1) ; hi - FSCV 7: read handle range
8A0B EQUB >(fsreply_0_print_dir-1) ; hi - FS reply: print directory name
8A0C EQUB >(fsreply_1_copy_handles_boot-1) ; hi - FS reply: copy handles + boot
8A0D EQUB >(fsreply_2_copy_handles-1) ; hi - FS reply: copy handles
8A0E EQUB >(fsreply_3_set_csd-1) ; hi - FS reply: set CSD handle
8A0F EQUB >(fscv_2_star_run-1) ; hi - FS reply: notify + execute
8A10 EQUB >(fsreply_5_set_lib-1) ; hi - FS reply: set library handle
8A11 EQUB >(net_1_read_handle-1) ; hi - *NET1: read handle from packet
8A12 EQUB >(net_2_read_handle_entry-1) ; hi - *NET2: read handle from workspace
8A13 EQUB >(net_3_close_handle-1) ; hi - *NET3: close handle
8A14 EQUB &8A

Service call dispatch

Handles service calls 1, 4, 8, 9, 13, 14, and 15. Service 1: absolute workspace claim. Service 4: unrecognised star command. Service 8: unrecognised OSWORD. Service 9: *HELP. Service 13: ROM initialisation. Service 14: ROM initialisation complete. Service 15: vectors claimed.

On EntryAservice call number
XROM slot
Yparameter
8A15 .service_handler←1← 8003 JMP
PHA ; Save service call number
8A16 CMP #&0f ; Is it service 15 (vectors claimed)?
8A18 BNE dispatch_service ; No: skip vectors-claimed handling
8A1A TYA ; Save Y parameter
8A1B PHA ; Save Y on stack
8A1C LDA #osbyte_read_os_version ; OSBYTE 0: read OS version
8A1E LDX #1 ; X=1 to request version number
8A20 JSR osbyte ; Read OS version number into X
; X is the OS version number:
; X=0, OS 1.00 (Early BBC B or Electron OS 1.00)
; X=1, OS 1.20 or American OS
; X=2, OS 2.00 (BBC B+)
; X=3, OS 3.2/3.5 (Master 128)
; X=4, OS 4.0 (Master Econet Terminal)
; X=5, OS 5.0 (Master Compact)
8A23 CPX #1 ; OS 1.20?
8A25 BEQ restore_rom_slot ; Yes: skip workspace setup
8A27 CPX #2 ; OS 2.00 (BBC B+)?
8A29 BEQ restore_rom_slot ; Yes: skip workspace setup
8A2B TXA ; Transfer OS version to A
8A2C PHP ; Save flags (Z set if OS 1.00)
8A2D LDX romsel_copy ; Get current ROM slot number
8A2F PLP ; Restore flags
8A30 BEQ clear_workspace_byte ; OS 1.00: skip INX
8A32 INX ; Adjust index for OS 3+ workspace
8A33 .clear_workspace_byte←1← 8A30 BEQ
LDA #0 ; A=0
8A35 STA rom_type_table,x ; Clear workspace byte for this ROM
8A38 .restore_rom_slot←2← 8A25 BEQ← 8A29 BEQ
LDX romsel_copy ; Restore ROM slot to X
8A3A PLA ; Restore Y parameter
8A3B TAY ; Transfer to Y
8A3C .dispatch_service←1← 8A18 BNE
PLA ; Restore service call number
8A3D JSR tube_vdu_dispatch ; Check relocated code service dispatch
8A40 PHA ; Save service call number
8A41 CMP #1 ; Service 1 (workspace claim)?
8A43 BNE check_adlc_flag ; No: skip ADLC check
8A45 LDA econet_control1_or_status1 ; Read ADLC status register 1
8A48 AND #&ed ; Mask relevant status bits
8A4A BNE set_adlc_absent ; Non-zero: ADLC absent, set flag
8A4C LDA econet_control23_or_status2 ; Read ADLC status register 2
8A4F AND #&db ; Mask relevant status bits
8A51 BEQ check_adlc_flag ; Zero: ADLC present, skip
8A53 .set_adlc_absent←1← 8A4A BNE
ROL rom_ws_pages,x ; Shift bit 7 into carry
8A56 SEC ; Set carry to mark ADLC absent
8A57 ROR rom_ws_pages,x ; Rotate carry into bit 7 of slot flag
8A5A .check_adlc_flag←2← 8A43 BNE← 8A51 BEQ
LDA rom_ws_pages,x ; Load ROM slot flag byte
8A5D ASL ; Shift bit 7 (ADLC absent) into carry
8A5E PLA ; Restore service call number
8A5F BCC handle_vectors_claimed ; ADLC present: continue dispatch
8A61 RTS ; ADLC absent: decline service, return
8A62 .handle_vectors_claimed←1← 8A5F BCC
CMP #&0f ; Service 15 (vectors claimed)?
8A64 BNE dispatch_svc_with_state ; No: handle other services
8A66 LDX ws_0d6a ; Already initialised?
8A69 BNE start_rom_scan ; Yes: skip first-time init
8A6B INX ; X=1 (mark as initialised)
8A6C .sub_c8a6c
STX last_break_type ; Set ROM present flag
8A6F .start_rom_scan←1← 8A69 BNE
STA ws_ptr_hi ; A=service call number; use as ROM counter
8A71 .loop_scan_net_roms←1← 8A9A BPL
LDA #&80 ; Point to ROM header copyright offset
8A73 STA osrdsc_ptr_hi ; Set high byte of OSRDSC pointer
8A75 LDA #&0c ; Offset &0C: copyright string offset
8A77 STA osrdsc_ptr ; Set low byte of OSRDSC pointer
8A79 JSR read_paged_rom ; Read next ROM title char
8A7C CMP #&4e ; First char 'N'?
8A7E BNE next_rom_slot ; No: not a NET ROM, try next
8A80 JSR read_paged_rom ; Read next ROM title char
8A83 CMP #&45 ; Second char 'E'?
8A85 BNE next_rom_slot ; No: not a NET ROM, try next
8A87 JSR read_paged_rom ; Read next ROM title char
8A8A CMP #&54 ; Third char 'T'?
8A8C BNE next_rom_slot ; No: not a NET ROM, try next
8A8E LDX ws_ptr_hi ; X=ROM slot for indexed store
8A90 LDA rom_ws_pages,x ; Load its slot flag byte
8A93 ORA #&80 ; Set bit 7 to mark as NET ROM
8A95 STA rom_ws_pages,x ; Store updated flag
8A98 .next_rom_slot←3← 8A7E BNE← 8A85 BNE← 8A8C BNE
DEC ws_ptr_hi ; Previous ROM slot
8A9A BPL loop_scan_net_roms ; More ROMs to check: loop
8A9C LDA #&0f ; A=&0F: restore service call number
8A9E BNE restore_romsel_rts ; ALWAYS branch

Read next byte from paged ROM via OSRDSC

Increments the read pointer at osrdsc_ptr (&F6) first, then calls OSRDSC (&FFB9) with the ROM number from error_block (&0100) in Y. Called three times by service_handler during ROM identification to read the copyright string and ROM type byte.

On ExitAbyte read from ROM
8AA0 .read_paged_rom←3← 8A79 JSR← 8A80 JSR← 8A87 JSR
INC osrdsc_ptr ; Advance read pointer to next byte
8AA2 LDY ws_ptr_hi ; Y=ROM number
8AA4 JMP osrdsc ; Read byte from ROM Y or screen
8AA7 .dispatch_svc_with_state←1← 8A64 BNE
TAX ; Transfer service number to X
8AA8 LDA svc_state ; Save current service state
8AAA PHA ; Push old state
8AAB TXA ; Restore service number to A
8AAC STA svc_state ; Store as current service state
8AAE CMP #&0d ; Service < 13?
8AB0 BCC dispatch_svc_index ; Yes: use as dispatch index directly
8AB2 SBC #5 ; Subtract 5 (map 13-17 to 8-12)
8AB4 CMP #&0d ; Mapped value = 13? (original was 18)
8AB6 BEQ dispatch_svc_index ; Yes: valid service 18 (FS select)
8AB8 LDA #0 ; Unknown service: set index to 0
8ABA .dispatch_svc_index←2← 8AB0 BCC← 8AB6 BEQ
TAX ; Transfer dispatch index to X
8ABB BEQ restore_svc_state ; Index 0: unhandled service, skip
8ABD LDA ws_page ; Save current workspace page
8ABF PHA ; Push old page
8AC0 STY ws_page ; Set workspace page from Y parameter
8AC2 TYA ; Transfer Y to A
8AC3 LDY #0 ; Y=0 for dispatch offset
8AC5 JSR svc_dispatch ; Dispatch to service handler via table
8AC8 PLA ; Restore old workspace page
8AC9 STA ws_page ; Store it back
8ACB .restore_svc_state←1← 8ABB BEQ
LDX svc_state ; Get service state (return code)
8ACD PLA ; Restore old service state
8ACE STA svc_state ; Store it back
8AD0 TXA ; Transfer return code to A
8AD1 .restore_romsel_rts←1← 8A9E BNE
LDX romsel_copy ; Restore ROM slot to X
8AD3 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.

8AD4 .cmd_roff
LDY #0 ; Offset 0 in receive block
8AD6 LDA (net_rx_ptr),y ; Load remote operation flag
8AD8 BEQ clear_svc_and_ws ; Zero: already off, skip to cleanup
8ADA LDA #0 ; A=0
8ADC TAX ; X=&00
8ADD STA (net_rx_ptr),y ; Clear remote operation flag
8ADF TAY ; Y=&00
8AE0 LDA #osbyte_read_write_econet_keyboard_disable ; OSBYTE &C9: keyboard disable
8AE2 JSR osbyte ; Enable keyboard (for Econet)
8AE5 LDA #&0a ; A=&0A: workspace init parameter
8AE7 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, which is also used directly by cmd_roff. Called by check_escape.

8AEA .scan_remote_keys←1← 959F JSR
STX nfs_workspace ; Save X in workspace
8AEC LDA #&ce ; A=&CE: start of key range
8AEE .loop_scan_key_range←1← 8AF9 BEQ
LDX nfs_workspace ; Restore X from workspace
8AF0 LDY #&7f ; Y=&7F: OSBYTE scan parameter
8AF2 JSR osbyte ; OSBYTE: scan keyboard
8AF5 ADC #1 ; Advance to next key code
8AF7 CMP #&d0 ; Reached &D0?
8AF9 BEQ loop_scan_key_range ; No: loop back (scan &CE and &CF)
8AFB .clear_svc_and_ws←1← 8AD8 BEQ
LDA #0 ; A=0
8AFD STA svc_state ; Clear service state
8AFF STA nfs_workspace ; Clear workspace byte
8B01 RTS ; Return

Save OS text pointer for later retrieval

Copies &F2/&F3 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
8B02 .save_text_ptr←3← 8C52 JSR← 8C7D JSR← A2BF JSR
PHA ; Save A
8B03 LDA os_text_ptr ; Copy OS text pointer low
8B05 STA fs_crc_lo ; to fs_crc_lo
8B07 LDA os_text_ptr_hi ; Copy OS text pointer high
8B09 STA fs_crc_hi ; to fs_crc_hi
8B0B PLA ; Restore A
8B0C .return_from_save_text_ptr←2← 8B0F BNE← 8B18 BMI
RTS ; Return

Service 18: filing system selection request

Checks if Y=5 (Econet filing system number); returns unclaimed if not. Also returns if bit 7 of &0D6C is already set, indicating the FS is already selected. Otherwise falls through to cmd_net_fs to perform the full network filing system selection sequence.

On EntryYfiling system number requested
8B0D .svc_18_fs_select
CPY #5 ; Y=5 (Econet filing system)?
8B0F BNE return_from_save_text_ptr ; No: not ours, return unclaimed
8B11 LDA #0 ; A=0: clear service state
8B13 STA svc_state ; Reset service processing state
8B15 BIT fs_flags ; Already selected?
8B18 BMI return_from_save_text_ptr ; Yes (bit 7 set): return unclaimed
fall through ↓

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, notifies the OS via FSCV reason 6, copies the FS context block from the receive block to &0DFA, installs 7 filing system vectors (FILEV etc.) from fs_vector_table, initialises the ADLC and extended vectors, sets up the channel table, and copies the workspace page to &1000 as a shadow. Sets bit 7 of &0D6C to mark the FS as selected, then issues service call 15.

8B1A .cmd_net_fs←1← 8CE0 JSR
JSR get_ws_page ; Get workspace page for this ROM slot
8B1D STA fs_load_addr_hi ; Store as high byte of load address
8B1F LDA #0 ; A=0
8B21 STA fs_load_addr ; Clear low byte of load address
8B23 CLC ; Clear carry for addition
8B24 LDY #&76 ; Y=&76: checksum range end
8B26 .loop_sum_rom_bytes←1← 8B29 BPL
ADC (fs_load_addr),y ; Add byte to running checksum
8B28 DEY ; Decrement index
8B29 BPL loop_sum_rom_bytes ; Loop until all bytes summed
8B2B LDY #&77 ; Y=&77: checksum storage offset
8B2D EOR (fs_load_addr),y ; Compare with stored checksum
8B2F BEQ done_rom_checksum ; Match: checksum valid
8B31 JMP error_net_checksum ; Mismatch: raise checksum error
8B34 .done_rom_checksum←1← 8B2F BEQ
JSR notify_new_fs ; Call FSCV with A=6 (new FS)
8B37 LDY #9 ; Y=9: end of FS context block
8B39 .loop_copy_fs_ctx←1← 8B41 BNE
LDA (net_rx_ptr),y ; Load byte from receive block
8B3B STA osword_ws_base,y ; Store into FS workspace
8B3E DEY ; Decrement index
8B3F CPY #1 ; Reached offset 1?
8B41 BNE loop_copy_fs_ctx ; No: continue copying
8B43 ROL fs_flags ; Shift bit 7 of FS flags into carry
8B46 CLC ; Clear carry
8B47 ROR fs_flags ; Clear bit 7 of FS flags
8B4A LDY #&0d ; Y=&0D: vector table size - 1
8B4C .loop_set_vectors←1← 8B53 BPL
LDA fs_vector_table,y ; Load FS vector address
8B4F STA filev,y ; Store into FILEV vector table
8B52 DEY ; Decrement index
8B53 BPL loop_set_vectors ; Loop until all vectors installed
8B55 JSR init_adlc_and_vectors ; Initialise ADLC and NMI workspace
8B58 LDY #&1b ; Y=&1B: extended vector offset
8B5A LDX #7 ; X=7: two more vectors to set up
8B5C JSR write_vector_entry ; Set up extended vectors
8B5F LDA #0 ; A=0
8B61 STA fs_eof_flags ; Clear FS state byte
8B64 STA cur_chan_attr ; Clear workspace byte
8B67 STA fs_lib_flags ; Clear workspace byte
8B6A STA svc_state ; Clear service state
8B6C JSR store_rx_attribute ; Clear receive attribute byte
8B6F STA l0e08 ; Clear workspace byte
8B72 JSR setup_ws_ptr ; Set up workspace pointers
8B75 JSR init_channel_table ; Initialise FS state
8B78 LDY #&77 ; Y=&77: workspace block size - 1
8B7A .loop_copy_ws_page←1← 8B80 BPL
LDA (fs_ws_ptr),y ; Load byte from source workspace
8B7C STA fcb_count_lo,y ; Store to page &10 shadow copy
8B7F DEY ; Decrement index
8B80 BPL loop_copy_ws_page ; Loop until all bytes copied
8B82 LDA #&80 ; A=&80: FS selected flag
8B84 ORA fs_flags ; Set bit 7 of FS flags
8B87 STA fs_flags ; Store updated flags
8B8A JMP issue_svc_15 ; Issue service 15 (FS initialised)
8B8D .help_print_nfs_cmds←1← 8C7A JMP
LDX #&4a ; X=&4A: NFS command table offset
8B8F 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.

8B92 .help_utils
LDX #0 ; X=0: utility command table offset
8B94 BEQ print_cmd_table ; ALWAYS branch

*HELP NET topic handler

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

8B96 .help_net
LDX #&4a ; X=&4A: NFS command table offset
fall through ↓

Print *HELP command listing with optional header

If V flag is set, saves X/Y, calls print_version_header to show the ROM version string and station number, then restores X/Y. If V flag is clear, outputs 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
8B98 .print_cmd_table←2← 8B8F JSR← 8B94 BEQ
BVC print_table_newline ; V clear: need to print header first
8B9A TXA ; Save X (table offset)
8B9B PHA ; Push it
8B9C TYA ; Save Y
8B9D PHA ; Push it
8B9E JSR print_version_header ; Print version string header
8BA1 PLA ; Restore Y
8BA2 TAY ; Transfer to Y
8BA3 PLA ; Restore X
8BA4 TAX ; Transfer to X
8BA5 CLV ; Clear overflow flag
8BA6 BVC print_cmd_table_loop ; ALWAYS branch
8BA8 .print_table_newline←1← 8B98 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. Entries with bit 7 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
8BAB .print_cmd_table_loop←2← 8BA6 BVC← 8C6D JSR
TYA ; Save Y (command line offset)
8BAC PHA ; Push it
8BAD PHP ; Save processor status
8BAE .loop_next_entry←1← 8C2C JMP
LDA cmd_table_fs,x ; Load byte from command table
8BB1 BPL print_indent ; Bit 7 clear: valid entry, continue
8BB3 JMP done_print_table ; End of table: finish up
8BB6 .print_indent←1← 8BB1 BPL
JSR print_inline ; Print two-space indent
8BB9 EQUS " "
8BBB LDY #9 ; Y=9: max command name length
8BBD LDA cmd_table_fs,x ; Load first byte of command name
8BC0 .loop_print_name←1← 8BC8 BPL
JSR osasci ; Write character
8BC3 INX ; Advance table pointer
8BC4 DEY ; Decrement padding counter
8BC5 LDA cmd_table_fs,x ; Load next character
8BC8 BPL loop_print_name ; Bit 7 clear: more chars, continue
8BCA .loop_pad_spaces←1← 8BD0 BPL
LDA #&20 ; Pad with spaces
8BCC JSR osasci ; Write character 32
8BCF DEY ; Decrement remaining pad count
8BD0 BPL loop_pad_spaces ; More padding needed: loop
8BD2 LDA cmd_table_fs,x ; Load syntax descriptor byte
8BD5 AND #&1f ; Mask to get syntax string index
8BD7 CMP #&0e ; Index &0E: shared commands?
8BD9 BEQ print_shared_prefix ; Yes: handle shared commands list
8BDB TAY ; Use index as Y
8BDC LDA cmd_syntax_table,y ; Look up syntax string offset
8BDF TAY ; Transfer offset to Y
8BE0 .loop_print_syntax←2← 8BED JMP← 8BF3 JMP
INY ; Advance to next character
8BE1 LDA cmd_syntax_strings,y ; Load syntax string character
8BE4 BEQ done_entry_newline ; Zero terminator: end of syntax
8BE6 CMP #&0d ; Carriage return: line continuation
8BE8 BNE print_syntax_char ; No: print the character
8BEA JSR help_wrap_if_serial ; Handle line wrap in syntax output
8BED JMP loop_print_syntax ; Continue with next character
8BF0 .print_syntax_char←1← 8BE8 BNE
JSR osasci ; Write character
8BF3 JMP loop_print_syntax ; Continue with next character
8BF6 .print_shared_prefix←1← 8BD9 BEQ
TXA ; Save table pointer
8BF7 PHA ; Push it
8BF8 JSR print_inline ; Print opening parenthesis
8BFB EQUS "("
8BFC LDY #0 ; Y=0: shared command counter
8BFE LDX #&d3 ; X=&D3: shared command table start
8C00 .loop_next_shared←1← 8C22 BNE
LDA cmd_table_fs,x ; Load byte from shared command table
8C03 BMI done_shared_cmds ; Bit 7 set: end of shared commands
8C05 DEX ; Back up one position
8C06 .loop_print_shared←1← 8C0F JMP
INX ; Advance to next character
8C07 LDA cmd_table_fs,x ; Load command name character
8C0A BMI print_last_char ; Bit 7 set: end of this name
8C0C JSR osasci ; Write character
8C0F JMP loop_print_shared ; Print more characters of name
8C12 .print_last_char←1← 8C0A BMI
AND #&7f ; Strip bit 7 from final character
8C14 JSR osasci ; Write character
8C17 INY ; Count this shared command
8C18 CPY #4 ; Printed 4 commands?
8C1A BNE skip_syntax_bytes ; No: continue on same line
8C1C JSR help_wrap_if_serial ; Handle line wrap after 4 commands
8C1F .skip_syntax_bytes←1← 8C1A BNE
INX ; X += 3: skip syntax descriptor and address
8C20 INX ; (continued)
8C21 INX ; (continued)
8C22 BNE loop_next_shared ; Loop for more shared commands
8C24 .done_shared_cmds←1← 8C03 BMI
PLA ; Restore original table pointer
8C25 TAX ; Transfer to X
8C26 .done_entry_newline←1← 8BE4 BEQ
JSR osnewl ; Write newline (characters 10 and 13)
8C29 INX ; X += 3: skip syntax descriptor and address
8C2A INX ; (continued)
8C2B INX ; (continued)
8C2C JMP loop_next_entry ; Loop for next command
8C2F .done_print_table←1← 8BB3 JMP
PLP ; Restore processor status
8C30 PLA ; Restore Y
8C31 TAY ; Transfer to Y
8C32 RTS ; Return

Wrap *HELP syntax lines for serial output

Checks the output destination via &0355. Returns immediately for VDU (stream 0) or printer (stream 3) output. For serial streams, outputs a newline followed by 12 spaces of indentation to align continuation lines with the syntax description column.

On ExitYpreserved
8C33 .help_wrap_if_serial←2← 8BEA JSR← 8C1C JSR
LDA vdu_mode ; Read output stream type
8C36 BEQ return_from_help_wrap ; Stream 0 (VDU): no wrapping
8C38 CMP #3 ; Stream 3 (printer)?
8C3A BEQ return_from_help_wrap ; Yes: no wrapping
8C3C TYA ; Save Y
8C3D PHA ; Push it
8C3E JSR osnewl ; Write newline (characters 10 and 13)
8C41 LDY #&0b ; Y=&0B: indent width - 1
8C43 LDA #&20 ; Space character
8C45 .loop_indent_spaces←1← 8C49 BPL
JSR osasci ; Write character 32
8C48 DEY ; Decrement indent counter
8C49 BPL loop_indent_spaces ; More spaces needed: loop
8C4B PLA ; Restore Y
8C4C TAY ; Transfer to Y
8C4D .return_from_help_wrap←2← 8C36 BEQ← 8C3A 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
8C4E .svc_4_star_command
LDX #0 ; X=0: start of utility command table
8C50 LDY ws_page ; Get command line offset
8C52 JSR save_text_ptr ; Save text pointer to fs_crc
8C55 JSR match_fs_cmd ; Try to match command in table
8C58 BCS svc_return_unclaimed ; No match: return to caller
8C5A 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 &C4. 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).

8C5D .svc_9_help
JSR check_credits_easter_egg ; Check for credits Easter egg
8C60 LDY ws_page ; Get command line offset
8C62 LDA (os_text_ptr),y ; Load character at offset
8C64 CMP #&0d ; Is it CR (bare *HELP)?
8C66 BNE check_help_topic ; No: check for specific topic
8C68 JSR print_version_header ; Print version string
8C6B LDX #&c4 ; X=&C4: start of help command list
8C6D JSR print_cmd_table_loop ; Print command list from table
8C70 .svc_return_unclaimed←3← 8C58 BCS← 8C9D BEQ← 8CD8 BNE
LDY ws_page ; Restore Y (command line offset)
8C72 RTS ; Return unclaimed
8C73 .check_help_topic←1← 8C66 BNE
BIT bit_test_ff ; Test for topic match (sets flags)
8C76 CMP #&2e ; Is first char '.' (abbreviation)?
8C78 BNE match_help_topic ; No: try topic-specific help
8C7A JMP help_print_nfs_cmds ; '.' found: show full command list
8C7D .match_help_topic←1← 8C78 BNE
JSR save_text_ptr ; Save text pointer to fs_crc
8C80 .loop_dispatch_help←1← 8C9B BNE
PHP ; Save flags
8C81 LDX #&c4 ; X=&C4: help command table start
8C83 JSR match_fs_cmd ; Try to match help topic in table
8C86 BCS skip_if_no_match ; No match: try next topic
8C88 PLP ; Restore flags
8C89 LDA #&8c ; Push return address high (&8C)
8C8B PHA ; Push it for RTS dispatch
8C8C LDA #&7f ; Push return address low (&74)
8C8E PHA ; Push it for RTS dispatch
8C8F LDA cmd_table_fs_hi,x ; Load dispatch address high
8C92 PHA ; Push dispatch high for RTS
8C93 LDA cmd_table_fs_lo,x ; Load dispatch address low
8C96 PHA ; Push dispatch low for RTS
8C97 RTS ; Dispatch via RTS (returns to &8C80)
8C98 .skip_if_no_match←1← 8C86 BCS
PLP ; Restore flags from before match
8C99 CMP #&0d ; End of command line?
8C9B BNE loop_dispatch_help ; No: try matching next topic
8C9D BEQ svc_return_unclaimed ; ALWAYS branch

Print ANFS version string and station number

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

8C9F .print_version_header←2← 8B9E JSR← 8C68 JSR
JSR print_inline ; Print version string via inline
8CA2 .version_string_cr
EQUS ".Advanced NFS 4.18."
8CB5 NOP ; NOP (string terminator)
8CB6 JMP print_station_id ; Print station number after version

Read workspace page number for current ROM slot

Indexes into the MOS per-ROM workspace table at &0DF0 using romsel_copy (&F4) as the ROM slot. Returns the allocated page number in both A and Y for caller convenience.

On ExitAworkspace page number
Yworkspace page number (same as A)
8CB9 .get_ws_page←4← 8B1A JSR← 8CC0 JSR← 8F87 JSR← AFE2 JSR
LDY romsel_copy ; Get current ROM slot number
8CBB LDA rom_ws_pages,y ; Load workspace page for this slot
8CBE TAY ; Transfer to Y
8CBF 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
8CC0 .setup_ws_ptr←2← 8B72 JSR← 8EE7 JSR
JSR get_ws_page ; Get workspace page for ROM slot
8CC3 STY nfs_temp ; Store page in nfs_temp
8CC5 LDA #0 ; A=0
8CC7 STA fs_ws_ptr ; Clear low byte of pointer
8CC9 .return_from_setup_ws_ptr←1← 8CEB 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.

8CCA .svc_3_autoboot
LDA #osbyte_scan_keyboard_from_16 ; OSBYTE &7A: scan keyboard from key 16
8CCC JSR osbyte ; Keyboard scan starting from key 16
8CCF TXA ; X is key number if key is pressed, or &ff otherwise
8CD0 BMI select_net_fs ; No key pressed: select Net FS
8CD2 CMP #&19 ; Key &19 (N)?
8CD4 BEQ write_key_state ; Yes: write key state and boot
8CD6 EOR #&55 ; EOR with &55: maps to zero if 'N'
8CD8 BNE svc_return_unclaimed ; Not N key: return unclaimed
8CDA .write_key_state←1← 8CD4 BEQ
TAY ; Y=key
8CDB LDA #osbyte_write_keys_pressed ; OSBYTE &78: write keys pressed
8CDD JSR osbyte ; Write current keys pressed (X and Y)
8CE0 .select_net_fs←1← 8CD0 BMI
JSR cmd_net_fs ; Select NFS as current filing system
8CE3 JSR print_station_id ; Print station number
8CE6 JSR osnewl ; Write newline (characters 10 and 13)
8CE9 LDX ws_page ; Get workspace page
8CEB BNE return_from_setup_ws_ptr ; Non-zero: already initialised, return
8CED LDA fs_lib_flags ; Load boot flags
8CF0 ORA #4 ; Set bit 2 (auto-boot in progress)
8CF2 STA fs_lib_flags ; Store updated boot flags
8CF5 LDX #&0f ; X=&0F: boot filename address low
8CF7 LDY #&8d ; Y=&8D: boot filename address high
8CF9 JMP fscv_3_star_cmd ; Execute boot file

Notify OS of filing system selection

Calls FSCV with A=6 to announce the FS change, then issues paged ROM service call 10 via OSBYTE &8F to inform other ROMs. Sets X=&0A and branches to issue_svc_osbyte which falls through from the call_fscv subroutine.

8CFC .notify_new_fs←1← 8B34 JSR
LDA #6 ; A=6: notify new filing system
8CFE JSR call_fscv ; Call FSCV
8D01 LDX #&0a ; X=&0A: service 10 parameter
8D03 BNE issue_svc_osbyte ; ALWAYS branch

Dispatch to filing system control vector (FSCV)

Indirect JMP through FSCV at &021E, 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
8D05 .call_fscv←1← 8CFE JSR
JMP (fscv) ; Dispatch via FSCV
8D08 .issue_svc_15←1← 8B8A JMP
LDX #&0f ; X=&0F: service 15 parameter
8D0A .issue_svc_osbyte←1← 8D03 BNE
LDA #osbyte_issue_service_request ; OSBYTE &8F: issue service request
8D0C JMP osbyte ; Issue paged ROM service call
8D0F EQUS "i .boot"
8D16 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 string: B Cockburn, J Dunn, B Robertson, and J Wills, each terminated by CR.

8D17 .check_credits_easter_egg←1← 8C5D JSR
LDY ws_page ; Get command line offset
8D19 LDX #5 ; X=5: start of credits keyword
8D1B .loop_match_credits←1← 8D24 BNE
LDA (os_text_ptr),y ; Load character from command line
8D1D CMP credits_keyword_start,x ; Compare with credits keyword
8D20 BNE done_credits_check ; Mismatch: check if keyword complete
8D22 INY ; Advance command line pointer
8D23 INX ; Advance keyword pointer
8D24 BNE loop_match_credits ; Continue matching
8D26 .done_credits_check←1← 8D20 BNE
CPX #&0d ; Reached end of keyword (X=&0D)?
8D28 BNE return_from_credits_check ; No: keyword not fully matched, return
8D2A LDX #0 ; X=0: start of credits text
8D2C .loop_emit_credits←1← 8D35 BNE
LDA credits_keyword_start,x ; Load character from credits string
8D2F BEQ return_from_credits_check ; Zero terminator: done printing
8D31 JSR osasci ; Write character
8D34 INX ; Advance string pointer
8D35 BNE loop_emit_credits ; Continue printing
8D37 .return_from_credits_check←2← 8D28 BNE← 8D2F BEQ
RTS ; Return
8D38 .credits_keyword_start←2← 8D1D CMP← 8D2C LDA
EQUB &0D ; CR
8D39 .credits_string
EQUS "The authors of ANFS are;"
8D51 EQUB &0D ; CR
8D52 EQUS "B Cockburn"
8D5C EQUB &0D ; CR
8D5D EQUS "J Du"
8D61 .ps_template_base←1← B01B LDA
EQUS "nn"
8D63 EQUB &0D ; CR
8D64 EQUS "B Robertson"
8D6F EQUB &0D ; CR
8D70 EQUS "J Wills"
8D77 EQUB &0D ; CR
8D78 EQUB &00 ; String terminator

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

8D79 .cmd_iam
TYA ; Save Y (command line offset)
8D7A PHA ; Push it
8D7B LDA #osbyte_close_spool_exec ; OSBYTE &77: close SPOOL/EXEC
8D7D STA fs_eof_flags ; Store as pending operation marker
8D80 JSR osbyte ; Close any *SPOOL and *EXEC files
8D83 LDY #0 ; Y=0
8D85 STY fs_work_4 ; Clear password entry flag
8D87 JSR process_all_fcbs ; Reset FS connection state
8D8A LDA #0 ; A=0
8D8C STA fs_eof_flags ; Clear pending operation marker
8D8F PLA ; Restore command line offset
8D90 TAY ; Transfer to Y
8D91 LDA (fs_options),y ; Load first option byte
8D93 JSR is_decimal_digit ; Parse station number if present
8D96 BCC cmd_pass ; Not a digit: skip to password entry
8D98 JSR parse_addr_arg ; Parse user ID string
8D9B BCS skip_no_fs_addr ; No user ID: go to password
8D9D STA fs_server_net ; Store file server station low
8DA0 JSR clear_if_station_match ; Check and store FS network
8DA3 INY ; Skip separator
8DA4 JSR parse_addr_arg ; Parse next argument
8DA7 .skip_no_fs_addr←1← 8D9B BCS
BEQ cmd_pass ; No FS address: skip to password
8DA9 STA fs_server_stn ; Store file server station high
8DAC LDX #&ff ; X=&FF: pre-decrement for loop
8DAE .loop_copy_logon_cmd←1← 8DB5 BPL
INX ; Advance index
8DAF LDA cmd_table_nfs_iam,x ; Load logon command template byte
8DB2 STA fs_cmd_data,x ; Store into transmit buffer
8DB5 BPL loop_copy_logon_cmd ; Bit 7 clear: more bytes, loop
8DB7 JSR copy_arg_validated ; Send logon with file server lookup
8DBA 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.

8DBC .cmd_pass←2← 8D96 BCC← 8DA7 BEQ
JSR copy_arg_to_buf_x0 ; Build FS command packet
8DBF .scan_pass_prompt←1← 8DBA BEQ
LDY #&ff ; Y=&FF: pre-increment for loop
8DC1 .loop_scan_colon←1← 8DCB BNE
INY ; Advance to next byte
8DC2 LDA fs_cmd_data,y ; Load byte from reply buffer
8DC5 CMP #&0d ; Is it CR (end of prompt)?
8DC7 BEQ send_pass_to_fs ; Yes: no colon found, skip to send
8DC9 CMP #&3a ; Is it ':' (password prompt)?
8DCB BNE loop_scan_colon ; No: keep scanning
8DCD JSR oswrch ; Write character
8DD0 STY fs_work_4 ; Save position of colon
8DD2 .read_pw_char←4← 8DE2 BNE← 8DE6 BEQ← 8DE9 BNE← 8DF5 BNE
LDA #&ff ; A=&FF: mark as escapable
8DD4 STA need_release_tube ; Set escape flag
8DD6 JSR check_escape ; Check for escape condition
8DD9 JSR osrdch ; Read a character from the current input stream
8DDC CMP #&15 ; A=character read
8DDE BNE check_pw_special ; Not NAK (&15): check other chars
8DE0 LDY fs_work_4 ; Restore colon position
8DE2 BNE read_pw_char ; Non-zero: restart from colon
8DE4 .loop_erase_pw←1← 8DED BEQ
CPY fs_work_4 ; At colon position?
8DE6 BEQ read_pw_char ; Yes: restart password input
8DE8 DEY ; Backspace: move back one character
8DE9 BNE read_pw_char ; If not at start: restart input
8DEB .check_pw_special←1← 8DDE BNE
CMP #&7f ; Delete key (&7F)?
8DED BEQ loop_erase_pw ; Yes: handle backspace
8DEF STA fs_cmd_data,y ; Store character in password buffer
8DF2 INY ; Advance buffer pointer
8DF3 CMP #&0d ; Is it CR (end of password)?
8DF5 BNE read_pw_char ; No: read another character
8DF7 JSR osnewl ; Write newline (characters 10 and 13)
8DFA .send_pass_to_fs←1← 8DC7 BEQ
TYA ; Transfer string length to A
8DFB PHA ; Save string length
8DFC JSR init_txcb ; Set up transmit control block
8DFF JSR init_tx_ptr_for_pass ; Send to file server and get reply
8E02 PLA ; Restore string length
8E03 TAX ; Transfer to X (byte count)
8E04 INX ; Include terminator
8E05 LDY #0 ; Y=0
8E07 BEQ send_cmd_and_dispatch ; ALWAYS branch

Clear stored station if parsed argument matches

Parses a station number from the command line via init_bridge_poll and compares it with the expected station at &0E01 using EOR. If the parsed value matches (EOR result is zero), clears &0E01. Called by cmd_iam when processing a file server address in the logon command.

On ExitA0 if matched, non-zero if different
8E09 .clear_if_station_match←2← 8DA0 JSR← A687 JSR
JSR init_bridge_poll ; Parse station number from cmd line
8E0C EOR fs_server_net ; Compare with expected station
8E0F BNE return_from_station_match ; Different: return without clearing
8E11 STA fs_server_net ; Same: clear station byte
8E14 .return_from_station_match←1← 8E0F BNE
RTS ; Return
8E15 .check_urd_prefix←1← 9462 JMP
LDY #0 ; Y=0: first character offset
8E17 LDA (fs_crc_lo),y ; Load first character of command text
8E19 CMP #&26 ; Is it '&' (URD prefix)?
8E1B BNE pass_send_cmd ; No: send as normal FS command
8E1D JMP fscv_2_star_run ; Yes: route via *RUN for URD prefix handling
8E20 .pass_send_cmd←1← 8E1B BNE
JSR copy_arg_to_buf_x0 ; Build FS command packet
8E23 TAY ; Transfer result to Y
8E24 .send_cmd_and_dispatch←2← 8E07 BEQ← 9324 JMP
JSR save_net_tx_cb ; Set up command and send to FS
8E27 LDX fs_cmd_csd ; Load reply function code
8E2A BEQ dispatch_rts ; Zero: no reply, return
8E2C LDA fs_cmd_data ; Load first reply byte
8E2F LDY #&17 ; Y=&17: logon dispatch offset
8E31 BNE svc_dispatch ; ALWAYS branch
8E33 JSR set_xfer_params ; Parse reply as decimal number
8E36 CMP #8 ; Result >= 8?
8E38 BCS dispatch_rts ; Yes: out of range, return
8E3A TAX ; Transfer handle to X
8E3B JSR mask_owner_access ; Look up in open files table
8E3E TYA ; Transfer result to A
8E3F LDY #&13 ; Y=&13: handle dispatch offset
8E41 BNE svc_dispatch ; ALWAYS branch

Dispatch directory operation via PHA/PHA/RTS

Validates X < 5 and sets Y=&0E as the directory dispatch offset, then falls through to svc_dispatch for PHA/PHA/RTS table dispatch. Called by tx_done_os_proc to handle directory operations (e.g. FILEV, ARGSV) from the remote JSR service.

On EntryXdirectory operation code (0-4)
8E43 .dir_op_dispatch←1← 8560 JSR
CPX #5 ; Handle >= 5?
8E45 BCS dispatch_rts ; Yes: out of range, return
8E47 LDY #&0e ; Y=&0E: directory dispatch offset
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/hi tables onto the stack, loads fs_options into X, then returns via RTS to dispatch to the target subroutine. Used for all service dispatch, FS command execution, and OSBYTE handler routing.

On EntryXbase dispatch index
Yadditional offset
On ExitXfs_options value
8E49 .svc_dispatch←5← 8AC5 JSR← 8E31 BNE← 8E41 BNE← 8E4B BPL← 8EA2 JMP
INX ; Advance X to target index
8E4A DEY ; Decrement Y offset counter
8E4B BPL svc_dispatch ; Y still positive: continue counting
8E4D TAY ; Y=&FF: will be ignored by caller
8E4E LDA svc_dispatch_hi,x ; Load dispatch address high byte
8E51 PHA ; Push high byte for RTS dispatch
8E52 .push_dispatch_lo
LDA svc_dispatch_lo,x ; Load dispatch address low byte
8E55 PHA ; Push low byte for RTS dispatch
8E56 LDX fs_options ; Load FS options pointer
8E58 .dispatch_rts←3← 8E2A BEQ← 8E38 BCS← 8E45 BCS
RTS ; Dispatch via RTS
; 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 =
; &8E59). 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.
8E59 .ps_template_data
EQUS "PRINT " ; PS template: default name "PRINT "
8E5F EQUB &01
8E60 EQUB &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.

8E61 .fs_vector_table←1← 8B4C LDA
EQUW &FF1B ; FILEV dispatch (&FF1B)
8E63 EQUW &FF1E ; ARGSV dispatch (&FF1E)
8E65 EQUW &FF21 ; BGETV dispatch (&FF21)
8E67 EQUW &FF24 ; BPUTV dispatch (&FF24)
8E69 EQUW &FF27 ; GBPBV dispatch (&FF27)
8E6B EQUW &FF2A ; FINDV dispatch (&FF2A)
8E6D EQUW &FF2D ; FSCV dispatch (&FF2D)
8E6F EQUW &9935 ; FILEV handler (&9935)
8E71 EQUB &4A ; (ROM bank — not read)
8E72 EQUW &9BBE ; ARGSV handler (&9BBE)
8E74 EQUB &44 ; (ROM bank — not read)
8E75 EQUW &B7CE ; BGETV handler (&B7CE)
8E77 EQUB &57 ; (ROM bank — not read)
8E78 EQUW &B84D ; BPUTV handler (&B84D)
8E7A EQUB &42 ; (ROM bank — not read)
8E7B EQUW &9E2F ; GBPBV handler (&9E2F)
8E7D EQUB &41 ; (ROM bank — not read)
8E7E EQUW &9D4E ; FINDV handler (&9D4E)
8E80 EQUB &52 ; (ROM bank — not read)
8E81 EQUW &8E33 ; FSCV handler (&8E33)

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
8E83 .osbyte_x0←3← 8081 JSR← 8F62 JSR← 970E JSR
LDX #0 ; X=0
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
8E85 .osbyte_yff←1← 808B JSR
LDY #&ff ; Y=&FF
8E87 .jmp_osbyte←1← 8E90 BEQ
JMP osbyte ; Execute OSBYTE and return
; NETV handler address
; 2-byte handler address for the NETV extended
; vector, read by write_vector_entry at Y=&36
; from svc_dispatch_lo_offset. Points to
; netv_handler which dispatches OSWORDs
; 0-8 to Econet handlers. Interleaved with the
; OSBYTE wrapper code in the data area.
8E8A .netv_handler_addr
EQUW netv_handler

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 at &8E87.

On EntryAOSBYTE number
On ExitX0
Y0
8E8C .osbyte_x0_y0←1← 9722 JSR
LDX #0 ; X=0
8E8E LDY #0 ; Y=0
8E90 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)
8E92 .svc_7_osbyte
LDA osbyte_a_copy ; Get original OSBYTE A parameter
8E94 SBC #&31 ; Subtract &31 (map &32-&35 to 1-4)
8E96 CMP #4 ; In range 0-3?
8E98 BCS return_from_svc_1_workspace ; No: not ours, return unclaimed
8E9A TAX ; Transfer to X as dispatch index
8E9B LDA #0 ; A=0: claim the service call
8E9D STA svc_state ; Set return value to 0 (claimed)
8E9F TYA ; Transfer Y to A (OSBYTE Y param)
8EA0 LDY #&21 ; Y=&21: OSBYTE dispatch offset
8EA2 JMP svc_dispatch ; Dispatch to OSBYTE handler via table

Service 1: absolute workspace claim

Ensures the NFS workspace allocation is at least &16 pages by checking Y on entry. If Y < &16, sets Y = &16 to claim the required pages; otherwise returns Y unchanged. This is a passive claim — NFS only raises the allocation, never lowers it.

On EntryYcurrent highest workspace page claim
On ExitY>= &16 (NFS minimum requirement)
8EA5 .svc_1_abs_workspace
CPY #&16 ; Need at least &16 pages?
8EA7 BCS return_from_svc_1_workspace ; Already enough: return
8EA9 LDY #&16 ; Request &16 pages of workspace
8EAB .return_from_svc_1_workspace←2← 8E98 BCS← 8EA7 BCS
RTS ; Return

Record workspace page count (capped at &21)

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

On EntryYworkspace page count from service 1
8EAC .store_ws_page_count←1← 8EDF JSR
TYA ; Transfer Y to A
8EAD CMP #&21 ; Y >= &21?
8EAF BCC done_cap_ws_count ; No: use Y as-is
8EB1 LDA #&21 ; Cap at &21
8EB3 .done_cap_ws_count←1← 8EAF BCC
LDY #&0b ; Offset &0B in receive block
8EB5 STA (net_rx_ptr),y ; Store workspace page count
8EB7 RTS ; Return

Service 2: claim private workspace and initialise NFS

Handles MOS service call 2 (private workspace claim). Allocates two workspace pages starting at Y: the receive block page (net_rx_ptr_hi) and NFS workspace page (nfs_workspace_hi), plus a per-ROM workspace page stored at &0DF0+ROM slot. Zeroes all workspace, initialises the station ID from the Econet hardware register at &FE18, allocates FS handle pages, copies initial state to page &10, and falls through to init_adlc_and_vectors.

On EntryYfirst available private workspace page
8EB8 .svc_2_private_workspace
STY net_rx_ptr_hi ; Store Y as receive block page
8EBA INY ; Advance to next page
8EBB STY nfs_workspace_hi ; Store as NFS workspace page
8EBD INY ; Advance to next page
8EBE TYA ; Transfer page to A
8EBF LDY romsel_copy ; Get current ROM slot number
8EC1 STA rom_ws_pages,y ; Store workspace page for this slot
8EC4 LDY fdc_1770_data ; Load break type from hardware register
8EC7 LDA #0 ; A=0
8EC9 STA net_rx_ptr ; Clear receive block pointer low
8ECB STA nfs_workspace ; Clear NFS workspace pointer low
8ECD STA ws_page ; Clear workspace page counter
8ECF STA ws_0d60 ; Clear workspace byte
8ED2 LDY #0 ; Offset 0 in receive block
8ED4 STA (net_rx_ptr),y ; Clear remote operation flag
8ED6 LDA #osbyte_issue_service_request ; OSBYTE &8F: issue service request
8ED8 LDX #1 ; X=1: workspace claim service
8EDA LDY #&0e ; Y=&0E: requested pages
8EDC JSR osbyte ; Issue paged ROM service call, Reason X=1 - Absolute public workspace claim
8EDF JSR store_ws_page_count ; Record final workspace allocation
8EE2 LDA last_break_type ; Load ROM present flag
8EE5 BEQ read_station_id ; Zero: first ROM init, skip FS setup
8EE7 JSR setup_ws_ptr ; Set up workspace pointers
8EEA STA fs_flags ; Clear FS flags
8EED TAY ; A=0, transfer to Y
8EEE .loop_zero_workspace←1← 8EF3 BNE
STA (fs_ws_ptr),y ; Clear byte in FS workspace
8EF0 STA (nfs_workspace),y ; Clear byte in NFS workspace
8EF2 INY ; Advance index
8EF3 BNE loop_zero_workspace ; Loop until full page zeroed
8EF5 LDY #8 ; Offset 8 in receive block
8EF7 STA (net_rx_ptr),y ; Clear protection flags
8EF9 JSR copy_ps_data_y1c ; Initialise station identity block
8EFC LDY #2 ; Offset 2 in receive block
8EFE LDA #&fe ; A=&FE: default station ID marker
8F00 STA fs_server_stn ; Store default station low
8F03 STA (net_rx_ptr),y ; Store into receive block
8F05 LDA #0 ; A=0
8F07 STA fs_server_net ; Clear station high byte
8F0A INY ; Y=&03
8F0B STA (net_rx_ptr),y ; Store into receive block
8F0D LDY #3 ; Offset 3 in NFS workspace
8F0F STA (nfs_workspace),y ; Clear NFS workspace byte 3
8F11 DEY ; Y=&02
8F12 LDA #&eb ; A=&EB: default listen state
8F14 STA (nfs_workspace),y ; Store at NFS workspace offset 2
8F16 LDX #3 ; X=3: init data byte count
8F18 .loop_copy_init_data←1← 8F1F BNE
LDA ws_init_data,x ; Load initialisation data byte
8F1B STA fs_flags,x ; Store in workspace
8F1E DEX ; Decrement counter
8F1F BNE loop_copy_init_data ; More bytes: loop
8F21 STX ws_0d68 ; Clear workspace flag
8F24 STX fs_boot_option ; Clear workspace byte
8F27 JSR reset_spool_buf_state ; Initialise ADLC protection table
8F2A DEX ; X=&FF (underflow from X=0)
8F2B STX l0d71 ; Initialise workspace flag to &FF
8F2E .loop_alloc_handles←1← 8F3B BNE
LDA ws_page ; Get current workspace page
8F30 JSR byte_to_2bit_index ; Allocate FS handle page
8F33 BCS done_alloc_handles ; Allocation failed: finish init
8F35 LDA #&3f ; A=&3F: default handle permissions
8F37 STA (nfs_workspace),y ; Store handle permissions
8F39 INC ws_page ; Advance to next page
8F3B BNE loop_alloc_handles ; Continue allocating: loop
8F3D .done_alloc_handles←1← 8F33 BCS
JSR restore_fs_context ; Restore FS context from saved state
8F40 .read_station_id←1← 8EE5 BEQ
LDY station_id_disable_net_nmis ; Read station ID from hardware
8F43 TYA ; Transfer to A
8F44 BNE store_station_id ; Non-zero: station ID valid
8F46 .error_bad_station←1← 8F4D BEQ
JMP err_bad_station_num ; Station 0: report error
; Workspace init data
; 3 bytes read via LDA ws_init_data,X with X=3
; down to 1. ws_init_data at &8F2B overlaps the
; high byte of JMP err_bad_station_num; byte at
; &8F2B itself (&92) is never read (BNE exits
; when X=0). Stores to l0d6e, l0d6f, l0d70.
8F49 EQUB &FF ; l0d6e: init=&FF (retry count)
8F4A EQUB &28 ; l0d6f: init=&28 (40, receive poll count)
8F4B EQUB &0A ; l0d70: init=&0A (10, machine peek retries)
8F4C .store_station_id←1← 8F44 BNE
INY ; Increment station ID
8F4D BEQ error_bad_station ; Overflow to 0: report error
8F4F LDY #1 ; Offset 1: station ID in recv block
8F51 STA (net_rx_ptr),y ; Store station ID
8F53 LDX #&40 ; X=&40: Econet flag byte
8F55 STX econet_flags ; Store Econet control flag
8F58 LDA #3 ; A=3: protection level
8F5A JSR handle_spool_ctrl_byte ; Set up Econet protection
fall through ↓

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.

8F5D .init_adlc_and_vectors←1← 8B55 JSR
JSR adlc_init ; Initialise ADLC hardware
8F60 LDA #&a8 ; OSBYTE &A8: read ROM pointer table
8F62 JSR osbyte_x0 ; Read ROM pointer table address
8F65 STX fs_error_ptr ; Store table pointer low
8F67 STY fs_crflag ; Store table pointer high
8F69 LDY #&36 ; Y=&36: NETV vector offset
8F6B STY netv ; Set NETV address
8F6E 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 &0D72 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
8F70 .write_vector_entry←2← 8B5C JSR← 8F82 BNE
LDA svc_dispatch_lo_offset,y ; Load vector address low byte
8F73 STA (fs_error_ptr),y ; Store into extended vector table
8F75 INY ; Advance to high byte
8F76 LDA svc_dispatch_lo_offset,y ; Load vector address high byte
8F79 STA (fs_error_ptr),y ; Store into extended vector table
8F7B INY ; Advance to ROM ID byte
8F7C LDA romsel_copy ; Load current ROM slot number
8F7E STA (fs_error_ptr),y ; Store ROM ID in extended vector
8F80 INY ; Advance to next vector entry
8F81 DEX ; Decrement vector counter
8F82 BNE write_vector_entry ; More vectors to set: loop
8F84 JSR fscv_6_shutdown ; X=&FF Restore FS state if previously active
8F87 JSR get_ws_page ; Get workspace page for ROM slot
8F8A INY ; Advance Y past workspace page
8F8B RTS ; Return

Restore FS context from saved workspace

Copies 8 bytes (offsets 2 to 9) from the saved workspace at &0DFA back into the receive control block via (net_rx_ptr). This restores the station identity, directory handles, and library path after a filing system reselection. Called by svc_2_private_workspace during init, deselect_fs_if_active during FS teardown, and flip_set_station_boot.

8F8C .restore_fs_context←3← 8F3D JSR← 8FA8 JSR← A38E JMP
LDY #9 ; Y=9: end of FS context block
8F8E .loop_restore_ctx←1← 8F96 BNE
LDA osword_ws_base,y ; Load FS context byte
8F91 STA (net_rx_ptr),y ; Store into receive block
8F93 DEY ; Decrement index
8F94 CPY #1 ; Reached offset 1?
8F96 BNE loop_restore_ctx ; No: continue copying
8F98 RTS ; Return

Deselect filing system and save workspace

If the filing system is currently selected (bit 7 of &0D6C set), closes all open FCBs, closes SPOOL/EXEC files via OSBYTE &77, saves the FS workspace to page &10 shadow with checksum, and clears the selected flag.

8F99 .fscv_6_shutdown←1← 8F84 JSR
BIT fs_flags ; FS currently selected?
8F9C BPL return_from_fs_shutdown ; No (bit 7 clear): return
8F9E LDY #0 ; Y=0
8FA0 JSR process_all_fcbs ; Reset FS connection state
8FA3 LDA #osbyte_close_spool_exec ; OSBYTE &77: close SPOOL/EXEC
8FA5 JSR osbyte ; Close any *SPOOL and *EXEC files
8FA8 JSR restore_fs_context ; Restore FS context to receive block
8FAB LDY #&76 ; Y=&76: checksum range end
8FAD LDA #0 ; A=0: checksum accumulator
8FAF CLC ; Clear carry for addition
8FB0 .loop_checksum_byte←1← 8FB4 BPL
ADC fcb_count_lo,y ; Add byte from page &10 shadow
8FB3 DEY ; Decrement index
8FB4 BPL loop_checksum_byte ; Loop until all bytes summed
8FB6 LDY #&77 ; Y=&77: checksum storage offset
8FB8 BPL store_ws_byte ; ALWAYS branch
8FBA .loop_copy_to_ws←1← 8FC0 BPL
LDA fcb_count_lo,y ; Load byte from page &10 shadow
8FBD .store_ws_byte←1← 8FB8 BPL
STA (fs_ws_ptr),y ; Copy to FS workspace
8FBF DEY ; Decrement index
8FC0 BPL loop_copy_to_ws ; Loop until all bytes copied
8FC2 LDA fs_flags ; Load FS flags
8FC5 AND #&7f ; Clear bit 7 (FS no longer selected)
8FC7 STA fs_flags ; Store updated flags
8FCA .return_from_fs_shutdown←1← 8F9C BPL
RTS ; Return

Verify workspace checksum integrity

Sums bytes 0 to &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). The checksummed page holds open file information (preserved when NFS 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
Ypreserved
8FCB .verify_ws_checksum←5← 9BBE JSR← 9D4E JSR← 9DEE JSR← 9E2F JSR← B5FB JSR
PHP ; Save processor status
8FCC PHA ; Save A
8FCD TYA ; Transfer Y to A
8FCE PHA ; Save Y
8FCF LDY #&76 ; Y=&76: checksum range end
8FD1 LDA #0 ; A=0: checksum accumulator
8FD3 CLC ; Clear carry for addition
8FD4 .loop_sum_ws←1← 8FD7 BPL
ADC (fs_ws_ptr),y ; Add byte from FS workspace
8FD6 DEY ; Decrement index
8FD7 BPL loop_sum_ws ; Loop until all bytes summed
8FD9 LDY #&77 ; Y=&77: checksum storage offset
8FDB CMP (fs_ws_ptr),y ; Compare with stored checksum
8FDD BNE error_net_checksum ; Mismatch: raise checksum error
8FDF PLA ; Restore Y
8FE0 TAY ; Transfer to Y
8FE1 PLA ; Restore A
8FE2 PLP ; Restore processor status
8FE3 RTS ; Return (checksum valid)
8FE4 .error_net_checksum←2← 8B31 JMP← 8FDD BNE
LDA #&aa ; Error number &AA
8FE6 JSR error_bad_inline ; Raise 'net checksum' error
8FE9 EQUS "net sum."
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 (&FEA1) 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_auto_boot.

8FF1 .print_station_id←2← 8CB6 JMP← 8CE3 JSR
JSR print_inline ; Print 'Econet Station ' prefix
8FF4 EQUS "Econet Station " ; Print 'Econet Station ' via inline
9003 LDY #1 ; Y=1: station number offset in RX block
9005 LDA (net_rx_ptr),y ; Load station ID from receive block
9007 JSR print_num_no_leading ; Print station number as decimal
900A LDA #&20 ; Space character
900C BIT econet_control23_or_status2 ; Check ADLC status register 2
900F BEQ done_print_newline ; Clock present: skip warning
9011 JSR print_inline ; Print ' No Clock' via inline
9014 EQUS " No Clock"
901D NOP ; NOP (string terminator)
901E .done_print_newline←1← 900F BEQ
JSR osnewl ; Write newline (characters 10 and 13)
9021 .syntax_strings
RTS ; Return
; *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.
9022 .cmd_syntax_strings←1← 8BE1 LDA
.syn_opt_dir←1← 8BE1 LDA
EQUS "(<dir>)" ; Syn 1: *Dir, *LCat, *LEx, *Wipe
9029 EQUB &00 ; Null terminator
902A .syn_iam
EQUS "(<stn. id.>) <user id.> " ; Syn 2: *I Am (login)
9042 EQUB &0D ; Line break
9043 EQUS "((:<CR>)<password>)" ; Syn 2 continued: password clause
9056 EQUB &00 ; Null terminator
9057 .syn_object
EQUS "<object>" ; Syn 3: *Delete, *FS, *Remove
905F EQUB &00 ; Null terminator
9060 .syn_file_offset
EQUS "<filename> (<offset> " ; Syn 4: *Dump
9075 EQUB &0D ; Line break
9076 EQUS "(<address>))" ; Syn 4 continued: address clause
9082 EQUB &00 ; Null terminator
9083 .syn_dir
EQUS "<dir>" ; Syn 5: *Lib
9088 EQUB &00 ; Null terminator
9089 .syn_dir_num
EQUS "<dir> (<number>)" ; Syn 6: *CDir
9099 EQUB &00 ; Null terminator
909A .syn_password
EQUS "(:<CR>) <password> " ; Syn 7: *Pass
90AD EQUB &0D ; Line break
90AE EQUS "<new password>" ; Syn 7 continued: new password
90BC EQUB &00 ; Null terminator
90BD .syn_ps_type
EQUS "(<stn. id.>|<ps type>)" ; Syn 8: *PS, *Pollps
90D3 EQUB &00 ; Null terminator
90D4 .syn_access
EQUS "<object> (L)(W)(R)(/(W)(R))" ; Syn 9: *Access
90EF EQUB &00 ; Null terminator
90F0 .syn_rename
EQUS "<filename> <new filename>" ; Syn 10: *Rename
9109 EQUB &00 ; Null terminator
910A .syn_opt_stn
EQUS "(<stn. id.>)" ; Syn 11: (station id. argument)
9116 EQUB &00 ; Null terminator
9117 .syn_filename
EQUS "<filename>" ; Syn 12: *Print, *Type
9121 EQUB &00 ; Null terminator
; Command syntax string offset table
; 13 offsets into cmd_syntax_strings (&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.
9122 .cmd_syntax_table←1← 8BDC LDA
EQUB syn_iam - cmd_syntax_strings - 2 ; Idx 0: (no synta x)
9123 EQUB (syn_opt_dir - cmd_syntax_strings - 1) AND &FF ; Idx 1: "(< dir >)" (Y wraps via &FF)
9124 EQUB syn_iam - cmd_syntax_strings - 1 ; Idx 2: "(< stn. id.>) <user id .>... "
9125 EQUB syn_object - cmd_syntax_strings - 1 ; Idx 3: "< objec t>"
9126 EQUB syn_file_offset - cmd_syntax_strings - 1 ; Idx 4: "< filen ame> (< offse t >...) "
9127 EQUB syn_dir - cmd_syntax_strings - 1 ; Idx 5: "< dir>"
9128 EQUB syn_dir_num - cmd_syntax_strings - 1 ; Idx 6: "< dir> (< numbe r>)"
9129 EQUB syn_password - cmd_syntax_strings - 1 ; Idx 7: "(:< CR>) < passw ord >..."
912A EQUB syn_ps_type - cmd_syntax_strings - 1 ; Idx 8: "(< stn. id.>| <ps type >)"
912B EQUB syn_access - cmd_syntax_strings - 1 ; Idx 9: "< objec t> (L)( W)(R )..."
912C EQUB syn_rename - cmd_syntax_strings - 1 ; Idx 10: "< filen ame> <new filen ame>"
912D EQUB syn_opt_stn - cmd_syntax_strings - 1 ; Idx 11: "(< stn. id .>)"
912E EQUB syn_filename - cmd_syntax_strings - 1 ; Idx 12: "< filen ame>"

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. Called by print_5_hex_bytes, cmd_ex, cmd_dump, and print_dump_header.

On EntryAbyte to print
On ExitAoriginal byte value
912F .print_hex_byte←4← 9A66 JSR← AE11 JSR← BA6A JSR← BB04 JSR
PHA ; Save full byte
9130 LSR ; Shift high nybble to low
9131 LSR ; Continue shifting
9132 LSR ; Continue shifting
9133 LSR ; High nybble now in bits 0-3
9134 JSR print_hex_nybble ; Print high nybble as hex digit
9137 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: adds 7 for letters A-F (via ADC #6 with carry set from the CMP), then ADC #&30 for the final '0'-'F' character. Outputs via JMP OSASCI.

On EntryAvalue (low nybble used)
9138 .print_hex_nybble←1← 9134 JSR
AND #&0f ; Mask to low nybble
913A CMP #&0a ; Digit >= &0A?
913C BCC add_ascii_base ; No: skip letter adjustment
913E ADC #6 ; Add 7 to get 'A'-'F' (6 + carry)
9140 .add_ascii_base←1← 913C BCC
ADC #&30 ; Add &30 for ASCII '0'-'9' or 'A'-'F'
9142 JMP osasci ; Write character

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 are &EA (NOP) for fall-through and &B8 (CLV) followed by BVC for an unconditional forward branch.

On ExitAterminator byte (bit 7 set, also next opcode)
Xcorrupted (by OSASCI)
Y0
9145 .print_inline←35← 8BB6 JSR← 8BF8 JSR← 8C9F JSR← 8FF1 JSR← 9011 JSR← 953A JSR← ADC0 JSR← ADCA JSR← ADD8 JSR← ADE3 JSR← ADFF JSR← AE14 JSR← AE27 JSR← AE36 JSR← AF53 JSR← B0A2 JSR← B0AE JSR← B0C5 JSR← B0CF JSR← B0DA JSR← B1AA JSR← B24C JSR← B261 JSR← B284 JSR← B291 JSR← B2A0 JSR← B2B0 JSR← B2BF JSR← B3C4 JSR← B3E4 JSR← B431 JSR← BA86 JSR← BA9F JSR← BAAC JSR← BAE0 JSR
PLA ; Pop return address (low) — points to last byte of JSR
9146 STA fs_error_ptr ; Store as string pointer low
9148 PLA ; Pop return address (high)
9149 STA fs_crflag ; Store as string pointer high
914B LDY #0 ; Y=0: index for indirect loads
914D .loop_next_char←1← 9168 JMP
INC fs_error_ptr ; Advance pointer to next character
914F BNE load_char ; No page crossing
9151 INC fs_crflag ; Carry into high byte
9153 .load_char←1← 914F BNE
LDA (fs_error_ptr),y ; Load next byte from inline string
9155 BMI resume_caller ; Bit 7 set? Done — this byte is the next opcode
9157 LDA fs_error_ptr ; Save string pointer on stack
9159 PHA ; (push low byte)
915A LDA fs_crflag ; Save pointer high byte
915C PHA ; (push high byte)
915D LDA (fs_error_ptr),y ; Reload character (pointer may have been clobbered)
915F JSR osasci ; Print character via OSASCI Write character
9162 PLA ; Restore string pointer high
9163 STA fs_crflag ; Store pointer high
9165 PLA ; Restore string pointer low
9166 STA fs_error_ptr ; Store pointer low
9168 JMP loop_next_char ; Loop for next character
916B .resume_caller←1← 9155 BMI
JMP (fs_error_ptr) ; Jump to address of high-bit byte (resumes code)

Parse decimal or hex station address argument

Reads from the command argument at (&BE),Y. Supports '&' prefix for hex, '.' separator for net.station addresses, and plain decimal. Returns result in A. Raises errors for bad digits, overflow, or zero values.

916E .parse_addr_arg←5← 8D98 JSR← 8DA4 JSR← A0AD JSR← A0C2 JSR← AD24 JSR
LDA #0 ; Clear accumulator
9170 STA fs_load_addr_2 ; Initialise result to zero
9172 LDA (fs_crc_lo),y ; Get first character of argument
9174 CMP #&26 ; Is it '&' (hex prefix)?
9176 BNE next_dec_char ; No: try decimal path
9178 INY ; Skip '&' prefix
9179 LDA (fs_crc_lo),y ; Get first hex digit
917B BCS check_digit_range ; C always set from CMP: validate digit
917D .next_hex_char←1← 91AC BCC
INY ; Advance to next character
917E LDA (fs_crc_lo),y ; Get next character
9180 CMP #&2e ; Is it '.' (net.station separator)?
9182 BEQ handle_dot_sep ; Yes: handle dot separator
9184 CMP #&21 ; Below '!' (space/control)?
9186 BCC done_parse_num ; Yes: end of number
9188 .check_digit_range←1← 917B BCS
CMP #&30 ; Below '0'?
918A BCC skip_if_not_hex ; Not a digit: bad hex
918C CMP #&3a ; Above '9'?
918E BCC extract_digit_value ; Decimal digit: extract value
9190 AND #&5f ; Force uppercase
9192 ADC #&b8 ; Map 'A'-'F' to &FA-&FF
9194 BCS err_bad_hex ; Overflow: not A-F
9196 CMP #&fa ; Valid hex letter (A-F)?
9198 .skip_if_not_hex←1← 918A BCC
BCC err_bad_hex ; Below A: bad hex
919A .extract_digit_value←1← 918E BCC
AND #&0f ; Extract digit value (0-15)
919C STA fs_load_addr_3 ; Save current digit
919E LDA fs_load_addr_2 ; Load running result
91A0 CMP #&10 ; Would shift overflow a byte?
91A2 BCS error_overflow ; Yes: overflow error
91A4 ASL ; Shift result left 4 (x16)
91A5 ASL ; (shift 2)
91A6 ASL ; (shift 3)
91A7 ASL ; (shift 4)
91A8 ADC fs_load_addr_3 ; Add new hex digit
91AA STA fs_load_addr_2 ; Store updated result
91AC BCC next_hex_char ; Loop for next hex digit
91AE .next_dec_char←2← 9176 BNE← 91D8 BNE
LDA (fs_crc_lo),y ; Get current character
91B0 CMP #&2e ; Is it '.' (net.station separator)?
91B2 BEQ handle_dot_sep ; Yes: handle dot separator
91B4 CMP #&21 ; Below '!' (space/control)?
91B6 BCC done_parse_num ; Yes: end of number
91B8 JSR is_dec_digit_only ; Is it a decimal digit?
91BB BCC error_bad_number ; No: 'Bad number' error
91BD AND #&0f ; Extract digit value (0-9)
91BF STA fs_load_addr_3 ; Save current digit
91C1 ASL fs_load_addr_2 ; result * 2
91C3 BCS error_overflow ; Overflow
91C5 LDA fs_load_addr_2 ; Load result * 2
91C7 ASL ; result * 4
91C8 BCS error_overflow ; Overflow
91CA ASL ; result * 8
91CB BCS error_overflow ; Overflow
91CD ADC fs_load_addr_2 ; * 8 + * 2 = result * 10
91CF BCS error_overflow ; Overflow
91D1 ADC fs_load_addr_3 ; result * 10 + new digit
91D3 BCS error_overflow ; Overflow
91D5 STA fs_load_addr_2 ; Store updated result
91D7 INY ; Advance to next character
91D8 BNE next_dec_char ; Loop (always branches)
91DA .done_parse_num←2← 9186 BCC← 91B6 BCC
LDA fs_work_4 ; Check parsing mode
91DC BPL validate_station ; Bit 7 clear: net.station mode
91DE LDA fs_load_addr_2 ; Decimal-only mode: get result
91E0 BEQ error_bad_param ; Zero: 'Bad parameter'
91E2 RTS ; Return with result in A
91E3 .validate_station←1← 91DC BPL
LDA fs_load_addr_2 ; Get parsed station number
91E5 CMP #&ff ; Station 255 is reserved
91E7 BEQ err_bad_station_num ; 255: 'Bad station number'
91E9 LDA fs_load_addr_2 ; Reload result
91EB BNE return_parsed ; Non-zero: valid station
91ED LDA fs_work_4 ; Zero result: check if dot was seen
91EF BEQ err_bad_station_num ; No dot and zero: 'Bad station number'
91F1 DEY ; Check character before current pos
91F2 LDA (fs_crc_lo),y ; Load previous character
91F4 INY ; Restore Y
91F5 EOR #&2e ; Was previous char '.'?
91F7 BNE err_bad_station_num ; No: 'Bad station number'
91F9 .return_parsed←1← 91EB BNE
SEC ; C=1: number was parsed
91FA RTS ; Return (result in fs_load_addr_2)
91FB .handle_dot_sep←2← 9182 BEQ← 91B2 BEQ
LDA fs_work_4 ; Check if dot already seen
91FD BNE error_bad_number ; Already seen: 'Bad number'
91FF INC fs_work_4 ; Set dot-seen flag
9201 LDA fs_load_addr_2 ; Get network number (before dot)
9203 CMP #&ff ; Network 255 is reserved
9205 BEQ error_bad_net_num ; 255: 'Bad network number'
9207 RTS ; Return to caller with network part
9208 .err_bad_hex←3← 9194 BCS← 9198 BCC← BB6B JMP
LDA #&f1 ; Error code &F1
920A JSR error_bad_inline ; Generate 'Bad hex' error
920D EQUS "hex."
9211 .error_overflow←6← 91A2 BCS← 91C3 BCS← 91C8 BCS← 91CB BCS← 91CF BCS← 91D3 BCS
BIT fs_work_4 ; Test parsing mode
9213 BMI error_bad_param ; Decimal mode: 'Bad parameter'
9215 .err_bad_station_num←4← 8F46 JMP← 91E7 BEQ← 91EF BEQ← 91F7 BNE
LDA #&d0 ; Error code &D0
9217 JSR error_bad_inline ; Generate 'Bad station number' error
921A EQUS "station number."
9229 .error_bad_number←2← 91BB BCC← 91FD BNE
LDA #&f0 ; Error code &F0
922B JSR error_bad_inline ; Generate 'Bad number' error
922E EQUS "number."
9235 .error_bad_param←2← 91E0 BEQ← 9213 BMI
LDA #&94 ; Error code &94
9237 JSR error_bad_inline ; Generate 'Bad parameter' error
923A EQUS "parameter."
9244 .error_bad_net_num←1← 9205 BEQ
LDA #&d1 ; Error code &D1
9246 JSR error_bad_inline ; Generate 'Bad network number' error
9249 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_12 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
9258 .is_decimal_digit←3← 8D93 JSR← B005 JSR← B1DC JSR
CMP #&26 ; Is it '&' (hex prefix)?
925A BEQ return_from_digit_test ; Yes: return C set (not decimal)
925C CMP #&2e ; Is it '.' (separator)?
925E BEQ return_from_digit_test ; Yes: return C set (not decimal)
fall through ↓

Test for decimal digit '0'-'9'

Uses two CMPs to bracket-test A against the range &30-&39. CMP #&3A sets carry if A >= ':' (above digits), then 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
9260 .is_dec_digit_only←1← 91B8 JSR
CMP #&3a ; Above '9'?
9262 BCS not_a_digit ; Yes: not a digit
9264 CMP #&30 ; Below '0'? C clear if so
9266 .return_from_digit_test←2← 925A BEQ← 925E BEQ
RTS ; Return: C set if '0'-'9'
9267 .not_a_digit←1← 9262 BCS
CLC ; C=0: not a digit
9268 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 the protection bit encode table at &9286. Called by check_and_setup_txcb for owner and public access.

On ExitAencoded access flags
9269 .get_access_bits←2← 9B20 JSR← 9B4C JSR
LDY #&0e ; Offset &0E in directory entry
926B LDA (fs_options),y ; Load raw access byte
926D AND #&3f ; Mask to 6 access bits
926F LDX #4 ; X=4: start encoding at bit 4
9271 BNE begin_prot_encode ; ALWAYS branch to encoder ALWAYS branch

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 (&9286). Called by send_txcb_swap_addrs and check_and_setup_txcb.

On EntryAraw protection bits (low 5 used)
On ExitAencoded protection flags
9273 .get_prot_bits←2← 9A2A JSR← 9B69 JSR
AND #&1f ; Mask to 5 protection bits
9275 LDX #&ff ; X=&FF: start encoding at bit 0
9277 .begin_prot_encode←1← 9271 BNE
STA fs_error_ptr ; Save remaining bits
9279 LDA #0 ; Clear encoded result
927B .loop_encode_prot←1← 9283 BNE
INX ; Advance to next table position
927C LSR fs_error_ptr ; Shift out lowest source bit
927E BCC skip_clear_prot ; Bit clear: skip this position
9280 ORA prot_bit_encode_table,x ; Bit set: OR in encoded value
9283 .skip_clear_prot←1← 927E BCC
BNE loop_encode_prot ; More bits to process
9285 RTS ; Return encoded access in A
; Protection/access bit encode table
; 11-entry lookup table used by get_prot_bits and
; get_access_bits to remap attribute bits between
; the file server protocol format and the local
; representation. The encoding loop shifts out each
; source bit; for each set bit, the corresponding
; table entry is ORed into the result.
; Indices 0-4: used by get_prot_bits (5-bit input).
; Some entries set multiple output bits (expansion).
; Indices 5-10: used by get_access_bits (6-bit input
; from directory entry offset &0E). Each entry sets
; exactly one output bit (pure permutation).
9286 .prot_bit_encode_table←1← 9280 ORA
EQUB &50 ; Bit 0: &50 = %01010000 (bits 4,6)
9287 EQUB &20 ; Bit 1: &20 = %00100000 (bit 5)
9288 EQUB &05 ; Bit 2: &05 = %00000101 (bits 0,2)
9289 EQUB &02 ; Bit 3: &02 = %00000010 (bit 1)
928A EQUB &88 ; Bit 4: &88 = %10001000 (bits 3,7)
928B EQUB &04 ; Bit 0: &04 = %00000100 (bit 2)
928C EQUB &08 ; Bit 1: &08 = %00001000 (bit 3)
928D EQUB &80 ; Bit 2: &80 = %10000000 (bit 7)
928E EQUB &10 ; Bit 3: &10 = %00010000 (bit 4)
928F EQUB &01 ; Bit 4: &01 = %00000001 (bit 0)
9290 EQUB &02 ; Bit 5: &02 = %00000010 (bit 1)

Set OS text pointer then transfer parameters

Stores X/Y into the MOS text pointer at &F2/&F3, then falls through to set_xfer_params and set_options_ptr to configure the full FS transfer context. Called by byte_to_2bit_index.

On EntryXtext pointer low byte
Ytext pointer high byte
9291 .set_text_and_xfer_ptr←1← A114 JSR
STX os_text_ptr ; Set text pointer low
9293 STY os_text_ptr_hi ; Set text pointer high
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
9295 .set_xfer_params←5← 8E33 JSR← 9935 JSR← 9D51 JSR← 9E32 JSR← AD80 JSR
STA fs_last_byte_flag ; Store transfer byte count
9297 STX fs_crc_lo ; Store source pointer low
9299 STY fs_crc_hi ; Store source pointer high
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
929B .set_options_ptr←2← 9BC3 JSR← B984 JSR
STX fs_options ; Store options pointer low
929D STY fs_block_offset ; Store options pointer high
929F .clear_escapable←1← 9885 JMP
PHP ; Save processor flags
92A0 LSR need_release_tube ; Clear bit 0 of escape flag
92A2 PLP ; Restore processor flags
92A3 RTS ; Return

Compare 5-byte handle buffers for equality

Loops X from 4 down to 1, comparing each byte of l00af+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 all 5 bytes match
92A4 .cmp_5byte_handle←2← 9998 JSR← 9A9B JSR
LDX #4 ; Compare 5 bytes (indices 4 down to 1)
92A6 .loop_cmp_handle←1← 92AD BNE
LDA addr_work,x ; Load byte from handle buffer
92A8 EOR fs_load_addr_3,x ; Compare with channel handle
92AA BNE return_from_cmp_handle ; Mismatch: return Z=0
92AC DEX ; Next byte
92AD BNE loop_cmp_handle ; Loop until all compared
92AF .return_from_cmp_handle←1← 92AA BNE
RTS ; Return: Z=1 if all 5 matched
92B0 .fscv_7_read_handles
LDX #&20 ; Unreachable code
92B2 LDY #&2f ; (dead)
92B4 RTS ; (dead)

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. ORs bit 6 (&40) into the channel status byte at &1060+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
92B5 .set_conn_active←2← 9C48 JSR← 9E8F JSR
PHP ; Save processor flags
92B6 PHA ; Save A
92B7 TXA ; Transfer X to A
92B8 PHA ; Save original X
92B9 TSX ; Get stack pointer
92BA LDA stack_page_2,x ; Read original A from stack
92BD JSR attr_to_chan_index ; Convert to channel index
92C0 BMI done_conn_flag ; No channel found: skip
92C2 LDA #&40 ; Bit 6: connection active flag
92C4 ORA chan_status,x ; Set active flag in channel table
92C7 STA chan_status,x ; Store updated status
92CA BNE done_conn_flag ; ALWAYS branch to exit

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 ORing. 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
92CC .clear_conn_active←2← 9CA9 JSR← 9E8A JSR
PHP ; Save processor flags
92CD PHA ; Save A
92CE TXA ; Transfer X to A
92CF PHA ; Save original X
92D0 TSX ; Get stack pointer
92D1 LDA stack_page_2,x ; Read original A from stack
92D4 JSR attr_to_chan_index ; Convert to channel index
92D7 BMI done_conn_flag ; No channel found: skip
92D9 LDA #&bf ; Bit 6 clear mask (&BF = ~&40)
92DB AND chan_status,x ; Clear active flag in channel table
92DE STA chan_status,x ; Store updated status
92E1 .done_conn_flag←3← 92C0 BMI← 92CA BNE← 92D7 BMI
PLA ; Restore X
92E2 TAX ; Transfer back to X
92E3 PLA ; Restore A
92E4 PLP ; Restore processor flags
92E5 RTS ; Return

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.

92E6 .cmd_fs_operation
JSR copy_fs_cmd_name ; Copy command name to TX buffer
92E9 TXA ; Save buffer position
92EA PHA ; Push it
92EB JSR parse_quoted_arg ; Parse filename (handles quoting)
92EE JSR parse_access_prefix ; Parse owner/public access prefix
92F1 PLA ; Restore buffer position
92F2 TAX ; Transfer to X
92F3 JSR check_not_ampersand ; Reject '&' character in filename
92F6 CMP #&0d ; End of line?
92F8 BNE read_filename_char ; No: copy filename chars to buffer
92FA .error_bad_filename←3← 930E BEQ← 9409 JMP← AEDF JMP
LDA #&cc ; Error number &CC
92FC JSR error_bad_inline ; Raise 'Bad file name' error
92FF 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 &0F05, calling strip_token_prefix on each byte and terminating on CR. Used by cmd_fs_operation and cmd_rename.

9309 .check_not_ampersand←2← 92F3 JSR← 9311 JSR
LDA fs_filename_buf ; Load first parsed character
930C CMP #&26 ; Is it '&'?
930E BEQ error_bad_filename ; Yes: invalid filename
9310 RTS ; Return
9311 .read_filename_char←3← 92F8 BNE← 931F JMP← 93DA JMP
JSR check_not_ampersand ; Reject '&' in current char
9314 STA fs_cmd_data,x ; Store character in TX buffer
9317 INX ; Advance buffer pointer
9318 CMP #&0d ; End of line?
931A BEQ send_fs_request ; Yes: send request to file server
931C JSR strip_token_prefix ; Strip BASIC token prefix byte
931F JMP read_filename_char ; Continue reading filename chars
9322 .send_fs_request←2← 931A BEQ← 9402 JMP
LDY #0 ; Y=0: no extra dispatch offset
9324 JMP send_cmd_and_dispatch ; Send command and dispatch 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 &0F05 until the next bit-7 byte (end of name), then appends a space separator. Called by cmd_fs_operation and cmd_rename.

On ExitXTX buffer offset past name+space
Ycommand line offset (restored)
9327 .copy_fs_cmd_name←2← 92E6 JSR← 938B JSR
TYA ; Save command line offset
9328 PHA ; Push it
9329 .loop_scan_flag←1← 932D BPL
DEX ; Scan backwards in command table
932A LDA cmd_table_fs,x ; Load table byte
932D BPL loop_scan_flag ; Bit 7 clear: keep scanning
932F INX ; Point past flag byte to name start
9330 LDY #0 ; Y=0: TX buffer offset
9332 .loop_copy_name←1← 933C BNE
LDA cmd_table_fs,x ; Load command name character
9335 BMI append_space ; Bit 7 set: end of name
9337 STA fs_cmd_data,y ; Store character in TX buffer
933A INX ; Advance table pointer
933B INY ; Advance buffer pointer
933C BNE loop_copy_name ; Continue copying name
933E .append_space←1← 9335 BMI
LDA #&20 ; Space separator
9340 STA fs_cmd_data,y ; Append space after command name
9343 INY ; Advance buffer pointer
9344 TYA ; Transfer length to A
9345 TAX ; And to X (buffer position)
9346 PLA ; Restore command line offset
9347 TAY ; Transfer to Y
9348 .return_from_copy_cmd_name←1← 937D BEQ
RTS ; Return

Parse possibly-quoted filename argument

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

9349 .parse_quoted_arg←2← 92EB JSR← 9393 JSR
LDA #0 ; A=0: no quote mode
934B TAX ; X=&00
934C STA quote_mode ; Clear quote tracking flag
934F .loop_skip_spaces←1← 9356 BNE
LDA (fs_crc_lo),y ; Load char from command line
9351 CMP #&20 ; Space?
9353 BNE check_open_quote ; No: check for opening quote
9355 INY ; Skip leading space
9356 BNE loop_skip_spaces ; Continue skipping spaces
9358 .check_open_quote←1← 9353 BNE
CMP #&22 ; Double-quote character?
935A BNE loop_copy_arg_char ; No: start reading filename
935C INY ; Skip opening quote
935D EOR quote_mode ; Toggle quote mode flag
9360 STA quote_mode ; Store updated quote mode
9363 .loop_copy_arg_char←2← 935A BNE← 9378 BNE
LDA (fs_crc_lo),y ; Load char from command line
9365 CMP #&22 ; Double-quote?
9367 BNE store_arg_char ; No: store character as-is
9369 EOR quote_mode ; Toggle quote mode
936C STA quote_mode ; Store updated quote mode
936F LDA #&20 ; Replace closing quote with space
9371 .store_arg_char←1← 9367 BNE
STA fs_filename_buf,x ; Store character in parse buffer
9374 INY ; Advance command line pointer
9375 INX ; Advance buffer pointer
9376 CMP #&0d ; End of line?
9378 BNE loop_copy_arg_char ; No: continue parsing
937A LDA quote_mode ; Check quote balance flag
937D BEQ return_from_copy_cmd_name ; Balanced: return OK
937F LDA brk_ptr ; Unbalanced: use BRK ptr for error
9381 JSR error_bad_inline ; Raise 'Bad string' error
9384 EQUS "string."
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.

938B .cmd_rename
JSR copy_fs_cmd_name ; Copy 'Rename ' to TX buffer
938E TXA ; Save buffer position
938F PHA ; Push it
9390 JSR mask_owner_access ; Set owner-only access mask
9393 JSR parse_quoted_arg ; Parse first filename (quoted)
9396 JSR parse_access_prefix ; Parse access prefix
9399 PLA ; Restore buffer position
939A TAX ; Transfer to X
939B .loop_copy_rename←1← 93B9 JMP
LDA fs_filename_buf ; Load next parsed character
939E CMP #&0d ; End of line?
93A0 BNE store_rename_char ; No: store character
93A2 .error_bad_rename←1← 93D8 BNE
LDA #&b0 ; Error number &B0
93A4 JSR error_bad_inline ; Raise 'Bad rename' error
93A7 EQUS "rename."
93AE .store_rename_char←1← 93A0 BNE
STA fs_cmd_data,x ; Store character in TX buffer
93B1 INX ; Advance buffer pointer
93B2 CMP #&20 ; Space (name separator)?
93B4 BEQ skip_rename_spaces ; Yes: first name complete
93B6 JSR strip_token_prefix ; Strip BASIC token prefix byte
93B9 JMP loop_copy_rename ; Continue copying first filename
93BC .skip_rename_spaces←2← 93B4 BEQ← 93C4 BEQ
JSR strip_token_prefix ; Strip token from next char
93BF LDA fs_filename_buf ; Load next parsed character
93C2 CMP #&20 ; Still a space?
93C4 BEQ skip_rename_spaces ; Yes: skip multiple spaces
93C6 LDA fs_lib_flags ; Save current FS options
93C9 PHA ; Push them
93CA JSR mask_owner_access ; Reset access mask for second name
93CD TXA ; Save buffer position
93CE PHA ; Push it
93CF JSR parse_access_prefix ; Parse access prefix for second name
93D2 PLA ; Restore buffer position
93D3 TAX ; Transfer to X
93D4 PLA ; Restore original FS options
93D5 CMP fs_lib_flags ; Options changed (cross-FS)?
93D8 BNE error_bad_rename ; Yes: error (can't rename across FS)
93DA JMP read_filename_char ; Copy second filename and send

*Dir command handler

Handles three argument syntaxes: a plain path (delegates to pass_send_cmd), '&' alone for the root directory, and '&N.dir' for cross-filesystem directory changes. 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.

93DD .cmd_dir
LDA (fs_crc_lo),y ; Get first char of argument
93DF CMP #&26 ; Is it '&' (FS selector prefix)?
93E1 BNE dir_pass_simple ; No: simple dir change
93E3 INY ; Skip '&'
93E4 LDA (fs_crc_lo),y ; Get char after '&'
93E6 CMP #&0d ; End of line?
93E8 BEQ setup_fs_root ; Yes: '&' alone (root directory)
93EA CMP #&20 ; Space?
93EC BNE check_fs_dot ; No: check for '.' separator
93EE .setup_fs_root←1← 93E8 BEQ
LDY #&ff ; Y=&FF: pre-increment for loop
93F0 .loop_copy_fs_num←1← 93F8 BNE
INY ; Advance index
93F1 LDA (fs_crc_lo),y ; Load char from command line
93F3 STA fs_cmd_data,y ; Copy to TX buffer
93F6 CMP #&26 ; Is it '&' (end of FS path)?
93F8 BNE loop_copy_fs_num ; No: keep copying
93FA LDA #&0d ; Replace '&' with CR terminator
93FC STA fs_cmd_data,y ; Store CR in buffer
93FF INY ; Point past CR
9400 TYA ; Transfer length to A
9401 TAX ; And to X (byte count)
9402 JMP send_fs_request ; Send directory request to server
9405 .check_fs_dot←1← 93EC BNE
CMP #&2e ; Is char after '&' a dot?
9407 BEQ parse_fs_dot_dir ; Yes: &FS.dir format
9409 JMP error_bad_filename ; No: invalid syntax
940C .parse_fs_dot_dir←1← 9407 BEQ
INY ; Skip '.'
940D STY fs_load_addr ; Save dir path start position
940F LDA #4 ; FS command 4: examine directory
9411 STA fs_cmd_data ; Store in TX buffer
9414 LDA fs_lib_flags ; Load FS flags
9417 ORA #&40 ; Set bit 6 (FS selection active)
9419 STA fs_lib_flags ; Store updated flags
941C LDX #1 ; X=1: buffer offset
941E JSR copy_arg_validated ; Copy FS number to buffer
9421 LDY #&12 ; Y=&12: select FS command code
9423 JSR save_net_tx_cb ; Send FS selection command
9426 LDA fs_cmd_data ; Load reply status
9429 CMP #2 ; Status 2 (found)?
942B BEQ dir_found_send ; Yes: proceed to dir change
942D LDA #&d6 ; Error number &D6
942F JSR error_inline_log ; Raise 'Not found' error
9432 EQUS "Not found."
943C .dir_found_send←1← 942B BEQ
LDA fs_csd_handle ; Load current FS station byte
943F STA fs_cmd_data ; Store in TX buffer
9442 LDX #1 ; X=1: buffer offset
9444 LDY #7 ; Y=7: change directory command code
9446 JSR save_net_tx_cb ; Send directory change request
9449 LDX #1 ; X=1
944B STX fs_cmd_data ; Store start marker in buffer
944E STX fs_func_code ; Store start marker in buffer+1
9451 INX ; X=&02
9452 LDY fs_load_addr ; Restore dir path start position
9454 JSR copy_arg_validated ; Copy directory path to buffer
9457 LDY #6 ; Y=6: set directory command code
9459 JSR save_net_tx_cb ; Send set directory command
945C LDY fs_cmd_data ; Load reply handle
945F JMP fsreply_3_set_csd ; Select FS and return
9462 .dir_pass_simple←1← 93E1 BNE
JMP check_urd_prefix ; Simple: pass command to FS

Initialise TXCB for bye/receive on port &90

Loads A=&90 (the FS command port) and falls through to init_txcb_port, which initialises the TXCB from the template, sets the port, data start offset to 3, and decrements the control byte. Called by recv_and_process_reply.

9465 .init_txcb_bye←1← 94F1 JSR
LDA #&90 ; A=&90: bye command port
fall through ↓

Initialise TXCB with specified port number

Calls init_txcb to copy the 12-byte template into the TXCB workspace at &00C0, then stores A as the transmit port (txcb_port at &C1), sets txcb_start to 3 (data begins at offset 3 in the packet), and decrements txcb_ctrl. Called by check_and_setup_txcb.

On EntryAport number
9467 .init_txcb_port←1← 9AE0 JSR
JSR init_txcb ; Initialise TXCB from template
946A STA txcb_port ; Set transmit port
946C LDA #3 ; A=3: data start offset
946E STA txcb_start ; Set TXCB start offset
9470 DEC txcb_ctrl ; Decrement control byte
9472 RTS ; Return

Initialise TX control block from ROM template

Copies 12 bytes from txcb_init_template (&948B) 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.

9473 .init_txcb←5← 8DFC JSR← 9467 JSR← 94E0 JSR← A8D3 LDA← B93A JSR
PHA ; Save A
9474 LDY #&0b ; Y=&0B: template size - 1
9476 .loop_init_txcb←1← 9487 BPL
LDA txcb_init_template,y ; Load byte from TXCB template
9479 STA txcb_ctrl,y ; Store to TXCB workspace
947C CPY #2 ; Index >= 2?
947E BPL skip_txcb_dest ; Yes: skip dest station copy
9480 LDA fs_server_stn,y ; Load dest station byte
9483 STA txcb_dest,y ; Store to TXCB destination
9486 .skip_txcb_dest←1← 947E BPL
DEY ; Decrement index
9487 BPL loop_init_txcb ; More bytes: continue
9489 PLA ; Restore A
948A RTS ; Return
; TXCB initialisation template (12 bytes)
; Copied by init_txcb into the TXCB workspace at
; &00C0. For offsets 0-1, the destination station
; bytes are also copied from l0e00 into txcb_dest.
; The &FF byte at offset 6 (bit_test_ff, &9491)
; serves double duty: it is part of this template
; AND a BIT target used by 22 callers to set the
; V and N flags without clobbering A.
948B .txcb_init_template←1← 9476 LDA
EQUB &80 ; Offset 0: txcb_ctrl = &80 (transmit)
948C EQUB &99 ; Offset 1: txcb_port = &99 (FS reply)
948D EQUB &00 ; Offset 2: txcb_dest lo (overwritten)
948E EQUB &00 ; Offset 3: txcb_dest hi (overwritten)
948F EQUB &00 ; Offset 4: txcb_start = 0
9490 EQUB &0F ; Offset 5: buffer start hi (page &0F)
9491 .bit_test_ff←22← 8C73 BIT← 964E BIT← 977D BIT← 9B41 BIT← 9D0F BIT← A09E BIT← A19D BIT← A316 BIT← A341 BIT← A378 BIT← AA80 BIT← AF7F BIT← AF85 BIT← B025 BIT← B1A5 BIT← B205 BIT← B246 BIT← B2CE BIT← B55A BIT← B598 BIT← B888 BIT← B99D BIT
EQUB &FF ; Offset 6: BIT target / buffer end lo
9492 EQUB &FF ; Offset 7: txcb_pos = &FF
9493 EQUB &FF ; Offset 8: txcb_end = &FF
9494 EQUB &0F ; Offset 9: buffer end hi (page &0F)
9495 EQUB &FF ; Offset 10: extended addr fill (&FF)
9496 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.

9497 .send_request_nowrite←1← 9F14 JSR
PHA ; Save A
9498 SEC ; Set carry (read-only mode)
9499 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.

949B .send_request_write←2← 9958 JSR← 9A0C JSR
CLV ; Clear V
949C 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.

949E .cmd_bye
LDY #0 ; Y=0: close all files
94A0 JSR process_all_fcbs ; Process all file control blocks
94A3 LDA #osbyte_close_spool_exec ; OSBYTE &77: close spool/exec
94A5 JSR osbyte ; Close any *SPOOL and *EXEC files
94A8 JSR close_all_net_chans ; Close all network channels
94AB LDY #&17 ; Y=&17: *Bye function code
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, sends the packet, and waits for the reply. V is clear for standard mode.

94AD .save_net_tx_cb←25← 8E24 JSR← 9423 JSR← 9446 JSR← 9459 JSR← 9B5D JSR← 9C42 JSR← 9C52 JSR← 9CA0 JSR← 9D37 JSR← 9DB0 JSR← 9DE4 JSR← 9E7C JSR← 9E9F JSR← 9F6F JSR← A02A JSR← A1D6 JSR← A1FE JSR← A543 JSR← AD41 JMP← ADB8 JSR← ADF6 JSR← AE65 JSR← B381 JSR← B418 JSR← B917 JSR
CLV ; Clear V (standard mode)
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.

94AE .save_net_tx_cb_vset←3← 9B44 JSR← 9D15 JSR← AF82 JMP
LDA fs_urd_handle ; Copy FS station to TX control block
94B1 STA fs_cmd_urd ; Store in TXCB
94B4 .txcb_copy_carry_clr←1← 949C BVC
CLC ; Clear carry
94B5 .txcb_copy_carry_set←1← 9499 BCS
PHP ; Save flags (carry = mode)
94B6 STY fs_cmd_y_param ; Store function code in TXCB
94B9 LDY #1 ; Copy 2 bytes (indices 0-1)
94BB .loop_copy_vset_stn←1← 94C2 BPL
LDA fs_csd_handle,y ; Load source byte
94BE STA fs_cmd_csd,y ; Store to TXCB
94C1 DEY ; Next byte
94C2 BPL loop_copy_vset_stn ; Loop until all copied
94C4 BIT fs_lib_flags ; Test library flag bits 6-7
94C7 BVS use_lib_station ; Bit 6 set: use station as port
94C9 BPL done_vset_station ; Bit 7 clear: skip port override
94CB LDA fs_lib_handle ; Bit 7 set: load alternative port
94CE STA fs_cmd_csd ; Override TXCB port byte
94D1 BVC done_vset_station ; ALWAYS branch
94D3 .use_lib_station←1← 94C7 BVS
LDA fs_urd_handle ; Bit 6: load station byte
94D6 STA fs_cmd_csd ; Use station as TXCB port
94D9 .done_vset_station←2← 94C9 BPL← 94D1 BVC
PLP ; Restore flags (carry = mode)
fall through ↓

Build TXCB from scratch, send, and receive reply

Full send/receive cycle: saves flags, sets reply port &90, calls init_txcb to load the template, computes txcb_end from X+5, then dispatches based on carry: C set sends a disconnect via handle_disconnect, C clear calls init_tx_ptr_and_send and falls through to recv_and_process_reply. Called by setup_transfer_workspace.

94DA .prep_send_tx_cb←1← 9FC8 JSR
PHP ; Save flags
94DB LDA #&90 ; Port &90: FS command port
94DD STA txcb_reply_port ; Set reply port in TXCB
94E0 JSR init_txcb ; Initialise TXCB workspace
94E3 TXA ; Get TXCB data end offset
94E4 ADC #5 ; Add 5 for header size
94E6 STA txcb_end ; Set TXCB end pointer
94E8 PLP ; Restore flags
94E9 BCS handle_disconnect ; C set: send disconnect instead
94EB PHP ; Save flags
94EC JSR init_tx_ptr_and_send ; Initialise TX pointer and send
94EF PLP ; Restore flags
fall through ↓

Receive FS reply and dispatch on status codes

Calls init_txcb_bye to set up a receive TXCB on port &90, then wait_net_tx_ack to wait for the acknowledgment. Iterates over reply bytes: zero terminates, V-set codes are adjusted by +&2B, and non-zero codes 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.

94F0 .recv_and_process_reply←2← 9A1F JSR← 9F68 JSR
PHP ; Save flags
94F1 JSR init_txcb_bye ; Set up receive TXCB
94F4 JSR wait_net_tx_ack ; Wait for TX acknowledgment
94F7 PLP ; Restore flags
94F8 .loop_next_reply←1← 950C BCC
INY ; Advance to next reply byte
94F9 LDA (txcb_start),y ; Load reply byte
94FB TAX ; Save in X
94FC BEQ return_from_recv_reply ; Zero: no more replies, return
94FE BVC process_reply_code ; V clear: use code directly
9500 ADC #&2a ; V set: adjust reply code (+&2B)
9502 .process_reply_code←1← 94FE BVC
BNE store_reply_status ; Non-zero: process reply
9504 .return_from_recv_reply←2← 94FC BEQ← 9574 BPL
RTS ; Return
9505 .handle_disconnect←1← 94E9 BCS
PLA ; Discard saved flags
9506 LDX #&c0 ; X=&C0: disconnect command
9508 INY ; Advance reply offset
9509 JSR send_disconnect_reply ; Send disconnect reply
950C BCC loop_next_reply ; Successful: process next reply
950E .store_reply_status←1← 9502 BNE
STX fs_last_error ; Store reply status code
9511 LDA fs_eof_flags ; Load pending operation marker
9514 PHP ; Save pending operation flag (Z)
9515 BNE check_data_loss ; Pending: go to data loss check
9517 CPX #&bf ; Reply &BF (normal bye response)?
9519 BNE build_error_block ; No: build error from reply
951B .check_data_loss←1← 9515 BNE
LDA #&40 ; A=&40: initial data-loss flag
951D PHA ; Push data-loss accumulator
951E LDX #&f0 ; Scan 16 channel entries (15 to 0)
9520 .loop_scan_channels←1← 952E BMI
PLA ; Pop accumulator
9521 ORA l0fc8,x ; OR in channel status bits
9524 PHA ; Push updated accumulator
9525 LDA l0fc8,x ; Load channel status
9528 AND #&c0 ; Keep only bits 6-7 (close flags)
952A STA l0fc8,x ; Clear data bits, keep state flags
952D INX ; Advance to next channel slot
952E BMI loop_scan_channels ; Bit 7 set: more channels to scan
9530 STX fs_eof_flags ; Store last channel scanned
9533 JSR close_all_net_chans ; Close all network channels
9536 PLA ; Pop data-loss accumulator
9537 ROR ; Bit 0 to carry (data lost?)
9538 BCC reload_reply_status ; No data lost: skip message
953A JSR print_inline ; Print 'Data Lost' + CR
953D EQUS "Data Lost."
9547 .reload_reply_status←1← 9538 BCC
LDX fs_last_error ; Reload reply status code
954A PLP ; Restore pending operation flag
954B BEQ build_error_block ; No pending operation: build error
954D PLA ; No pending: build error from reply Pending: clean up stack (3 bytes)
954E PLA ; (second byte)
954F PLA ; (third byte)
9550 RTS ; Return to pending operation caller
9551 .build_error_block←2← 9519 BNE← 954B BEQ
LDY #1 ; Y=1: error code offset in reply
9553 CPX #&a8 ; Reply code >= &A8?
9555 BCS setup_error_copy ; Yes: keep server error code
9557 LDA #&a8 ; No: use minimum error code &A8
9559 STA (txcb_start),y ; Overwrite error code in reply
955B .setup_error_copy←1← 9555 BCS
LDY #&ff ; Y=&FF: pre-increment index
955D .loop_copy_error←1← 9565 BNE
INY ; Advance to next byte
955E LDA (txcb_start),y ; Load reply byte
9560 STA error_block,y ; Copy to error block
9563 EOR #&0d ; Is it CR (end of message)?
9565 BNE loop_copy_error ; No: copy next byte
9567 STA error_block,y ; Store null terminator (A=0 from EOR)
956A DEY ; Get message length
956B TYA ; Transfer to A
956C TAX ; Length in X
956D JMP check_net_error_code ; Go to error dispatch

Check for pending escape condition

ANDs the MOS escape flag (&FF) with the escapable flag at &97. If bit 7 of the result is clear (no escape or escape disabled), returns immediately. Otherwise enters raise_escape_error: acknowledges the escape via OSBYTE &7E, then jumps to classify_reply_error with A=6 to raise the Escape error. Called by cmd_pass and send_net_packet.

9570 .check_escape←2← 8DD6 JSR← 985B JSR
LDA escape_flag ; Load MOS escape flag
9572 AND need_release_tube ; Mask with escape-enabled flag
9574 BPL return_from_recv_reply ; No escape: return
9576 .raise_escape_error←1← B445 JMP
LDA #osbyte_acknowledge_escape ; OSBYTE &7E: acknowledge escape
9578 JSR osbyte ; Clear escape condition and perform escape effects
957B LDA #6 ; Error class 6: Escape
957D JMP classify_reply_error ; Classify as network error
9580 .lang_1_remote_boot
LDY #0 ; Offset 0: remote state byte
9582 LDA (net_rx_ptr),y ; Load remote state
9584 BEQ init_remote_session ; Zero: initialise remote session
9586 .done_commit_state←1← 95CC BNE
JMP commit_state_byte ; Non-zero: commit state and return
9589 .init_remote_session←2← 9584 BEQ← 95C2 BEQ
ORA #9 ; Set bits 0,3: remote active flags
958B STA (net_rx_ptr),y ; Store updated remote state
958D LDX #&80 ; X=&80: flag for vector setup
958F LDY #&80 ; Offset &80 in RX buffer
9591 LDA (net_rx_ptr),y ; Load remote station low
9593 PHA ; Save on stack
9594 INY ; Y=&81
9595 LDA (net_rx_ptr),y ; Load remote station high
9597 LDY #&0f ; Workspace offset &0F
9599 STA (nfs_workspace),y ; Store remote station high
959B DEY ; Y=&0E Y=&0e
959C PLA ; Restore remote station low
959D STA (nfs_workspace),y ; Store remote station low
959F JSR scan_remote_keys ; Set up remote keyboard scanning
95A2 JSR init_ws_copy_narrow ; Initialise workspace copy
95A5 LDX #1 ; X=1: disable keyboard
95A7 LDY #0 ; Y=0
95A9 LDA #osbyte_read_write_econet_keyboard_disable ; OSBYTE &C9: Econet keyboard disable
95AB JSR osbyte ; Disable keyboard (for Econet)
95AE .lang_3_execute_at_0100
JSR commit_state_byte ; Commit state change
95B1 LDA #0 ; Error code 0
95B3 JSR error_inline_log ; Generate 'Remoted' error
95B6 EQUS "Remoted."
95BE .lang_4_remote_validated
LDY #0 ; Offset 0: remote state byte
95C0 LDA (net_rx_ptr),y ; Load remote state
95C2 BEQ init_remote_session ; Zero: reinitialise session
95C4 LDY #&80 ; Offset &80: station low
95C6 LDA (net_rx_ptr),y ; Load station low from RX
95C8 LDY #&0e ; Workspace offset &0E
95CA CMP (nfs_workspace),y ; Compare with stored station
95CC BNE done_commit_state ; Different station: commit state
95CE .lang_0_insert_remote_key
LDY #&82 ; Offset &82: keypress byte
95D0 LDA (net_rx_ptr),y ; Load remote keypress
95D2 TAY ; Key code to Y
95D3 LDX #0 ; X=0: keyboard buffer
95D5 JSR commit_state_byte ; Commit state change
95D8 LDA #osbyte_insert_input_buffer ; OSBYTE &99: insert into buffer
95DA JMP osbyte ; Insert character Y into input buffer X

Wait for Econet TX completion with timeout

Saves the timeout counter from &0D6F and the TX control state from &0D61, then polls net_tx_ptr_hi (&9B) for completion. Uses a three-level nested loop: the outer counter comes from the configured timeout at &0D6F. On completion, restores both saved values. On timeout (all loops exhausted), branches to build_no_reply_error to raise 'No reply'. Called by 6 sites across the protocol stack.

95DD .wait_net_tx_ack←6← 94F4 JSR← 99B2 JSR← 9AEA JSR← A928 JMP← ABD1 JSR← AC73 JSR
LDA tx_retry_count ; Save TX timeout counter
95E0 PHA ; Push (used as outer loop counter)
95E1 LDA econet_flags ; Save TX control state
95E4 PHA ; Push (preserved during wait)
95E5 LDA net_tx_ptr_hi ; Check if TX in progress
95E7 BNE init_poll_counters ; Non-zero: skip force-wait
95E9 ORA #&80 ; Set bit 7 to force wait mode
95EB STA econet_flags ; Store updated control state
95EE .init_poll_counters←1← 95E7 BNE
LDA #0 ; A=0: initial counter values
95F0 PHA ; Push inner loop counter
95F1 PHA ; Push middle loop counter
95F2 TAY ; Y=&00
95F3 TSX ; X=SP for stack-relative DECs
95F4 .loop_poll_tx←3← 95FB BNE← 9600 BNE← 9605 BNE
LDA (net_tx_ptr),y ; Poll TX completion status
95F6 BMI done_poll_tx ; Bit 7 set: TX complete
95F8 DEC error_text,x ; Decrement inner counter
95FB BNE loop_poll_tx ; Not zero: keep polling
95FD DEC stack_page_2,x ; Decrement middle counter
9600 BNE loop_poll_tx ; Not zero: keep polling
9602 DEC stack_page_4,x ; Decrement outer counter
9605 BNE loop_poll_tx ; Not zero: keep polling
9607 .done_poll_tx←1← 95F6 BMI
PLA ; Discard inner counter
9608 PLA ; Discard middle counter
9609 PLA ; Restore l0d61 control state
960A STA econet_flags ; Write back TX control state
960D PLA ; Pop outer counter (0 if timed out)
960E BEQ build_no_reply_error ; Zero: TX timed out
9610 RTS ; Return (TX acknowledged)

Conditionally store error code to workspace

Tests bit 7 of &0D6C (FS selected flag). If clear, returns immediately. If set, stores A into &0E09 as the last error code. 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
9611 .cond_save_error_code←6← 9627 JSR← 9660 JSR← 967C JSR← 96A6 JSR← 96B8 JSR← 96D1 JSR
BIT fs_flags ; Test error logging flag
9614 BPL return_from_cond_save_err ; Bit 7 clear: skip save
9616 STA fs_last_error ; Save error code to workspace
9619 .return_from_cond_save_err←1← 9614 BPL
RTS ; Return
961A .build_no_reply_error←1← 960E BEQ
LDX #8 ; X=8: 'No reply' error index
961C LDY net_error_lookup_data,x ; Look up message table offset
961F LDX #0 ; X=0: error text start
9621 STX error_block ; Clear BRK byte in error block
9624 LDA error_msg_table,y ; Load error number from table
9627 JSR cond_save_error_code ; Conditionally save error code
962A .loop_copy_no_reply_msg←1← 9634 BNE
LDA error_msg_table,y ; Load message byte
962D STA error_text,x ; Store in error text buffer
9630 BEQ done_no_reply_msg ; Null terminator?
9632 INX ; Advance destination
9633 INY ; Advance source
9634 BNE loop_copy_no_reply_msg ; Loop until end of message
9636 .done_no_reply_msg←1← 9630 BEQ
JSR append_drv_dot_num ; Append ' net.station' to message
9639 LDA #0 ; A=0: null terminator
963B STA error_text,x ; Terminate error text
963E JMP check_net_error_code ; Check and raise network error
9641 .fixup_reply_status_a←1← 98C6 JMP
LDA (net_tx_ptr,x) ; Load first reply byte
9643 CMP #&41 ; Is it 'A' (status &41)?
9645 BNE skip_if_not_a ; No: keep original
9647 LDA #&42 ; Yes: change to 'B' (&42)
9649 .skip_if_not_a←1← 9645 BNE
CLV ; Clear V flag
964A BVC mask_error_class ; ALWAYS branch
964C .load_reply_and_classify←1← 987F JMP
LDA (net_tx_ptr,x) ; Load first reply byte
964E .classify_reply_error←2← 957D JMP← 9DDC JMP
BIT bit_test_ff ; Set V flag (via BIT &FF)
9651 .mask_error_class←1← 964A BVC
AND #7 ; Mask to error class (0-7)
9653 PHA ; Save error class on stack
9654 CMP #2 ; Class 2 (station error)?
9656 BNE build_simple_error ; No: build simple error message
9658 PHP ; Save flags (V state for suffix)
9659 TAX ; Error class to X
965A LDY net_error_lookup_data,x ; Look up message table offset
965D LDA error_msg_table,y ; Load error number from table
9660 JSR cond_save_error_code ; Conditionally save error code
9663 LDX #0 ; X=0: error text start
9665 STX error_block ; Clear BRK byte
9668 .loop_copy_station_msg←1← 9672 BNE
LDA error_msg_table,y ; Load message byte
966B STA error_text,x ; Store in error text
966E BEQ done_station_msg ; Null terminator?
9670 INY ; Advance source
9671 INX ; Advance destination
9672 BNE loop_copy_station_msg ; Loop until end of message
9674 .done_station_msg←1← 966E BEQ
JSR append_drv_dot_num ; Append ' net.station' suffix
9677 PLP ; Restore flags
9678 BVS suffix_not_listening ; V set: append 'not listening'
967A LDA #&a4 ; Error code &A4
967C JSR cond_save_error_code ; Conditionally save error code
967F STA error_text ; Replace error number in block
9682 LDY #&0b ; Y=&0B: 'not present' suffix index
9684 BNE load_suffix_offset ; ALWAYS branch
9686 .suffix_not_listening←1← 9678 BVS
LDY #9 ; Y=9: 'not listening' suffix index
9688 .load_suffix_offset←1← 9684 BNE
LDA net_error_lookup_data,y ; Look up suffix table offset
968B TAY ; Offset to Y for indexing
968C .loop_copy_suffix←1← 9696 BNE
LDA error_msg_table,y ; Load suffix byte
968F STA error_text,x ; Append to error text
9692 BEQ done_suffix ; Null terminator?
9694 INY ; Advance source
9695 INX ; Advance destination
9696 BNE loop_copy_suffix ; Loop until end of suffix
9698 .done_suffix←1← 9692 BEQ
BEQ check_msg_terminator ; ALWAYS branch to error dispatch
969A .build_simple_error←1← 9656 BNE
TAX ; Error class to X
969B LDY net_error_lookup_data,x ; Look up message table offset
969E LDX #0 ; X=0: error text start
96A0 STX error_block ; Clear BRK byte
96A3 LDA error_msg_table,y ; Load error number from table
96A6 JSR cond_save_error_code ; Conditionally save error code
96A9 .loop_copy_error_msg←1← 96B3 BNE
LDA error_msg_table,y ; Load message byte
96AC STA error_text,x ; Store in error text
96AF .check_msg_terminator←1← 9698 BEQ
BEQ check_net_error_code ; Null terminator? Go to error
96B1 INY ; Advance source
96B2 INX ; Advance destination
96B3 .bad_str_anchor
BNE loop_copy_error_msg ; Loop until end of message
96B5 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
96B8 .error_bad_inline←11← 8FE6 JSR← 920A JSR← 9217 JSR← 922B JSR← 9237 JSR← 9246 JSR← 92FC JSR← 9381 JSR← 93A4 JSR← A25F JSR← BC14 JSR
JSR cond_save_error_code ; Conditionally log error code to workspace
96BB TAY ; Save error number in Y
96BC PLA ; Pop return address (low) — points to last byte of JSR
96BD STA fs_load_addr ; Store return address low
96BF PLA ; Pop return address (high)
96C0 STA fs_load_addr_hi ; Store return address high
96C2 LDX #0 ; X=0: start of prefix string
96C4 .loop_copy_bad_prefix←1← 96CD BNE
INX ; Copy 'Bad ' prefix from lookup table
96C5 LDA bad_prefix,x ; Get next prefix character
96C8 STA error_text,x ; Store in error text buffer
96CB CMP #&20 ; Is it space (end of 'Bad ')?
96CD BNE loop_copy_bad_prefix ; No: copy next prefix character
96CF 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 sub_c95fb before building the error block.

On EntryAerror number
96D1 .error_inline_log←11← 942F JSR← 95B3 JSR← A276 JSR← AC00 JSR← AC12 JSR← B485 JSR← B4FB JSR← B547 JSR← B7DF JSR← B819 JSR← B863 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
96D4 .error_inline←4← A129 JSR← BA11 JSR← BBA7 JSR← BC5D JSR
TAY ; Save error number in Y
96D5 PLA ; Pop return address (low) — points to last byte of JSR
96D6 STA fs_load_addr ; Store return address low
96D8 PLA ; Pop return address (high)
96D9 STA fs_load_addr_hi ; Store return address high
96DB LDX #0 ; X=0: error text index
96DD .write_error_num_and_str←1← 96CF BEQ
STY error_text ; Store error number in error block
96E0 TYA ; Copy error number to A
96E1 PHA ; Push error number on stack
96E2 LDY #0 ; Y=0: inline string index
96E4 STY error_block ; Zero the BRK byte at &0100
96E7 .loop_copy_inline_str←1← 96EE BNE
INX ; Copy inline string into error block
96E8 INY ; Advance string index
96E9 LDA (fs_load_addr),y ; Read next byte from inline string
96EB STA error_text,x ; Store byte in error block
96EE BNE loop_copy_inline_str ; Loop until null terminator
96F0 .check_net_error_code←4← 956D JMP← 963E JMP← 96AF BEQ← B971 JMP
JSR read_rx_attribute ; Read receive attribute byte
96F3 BNE handle_net_error ; Non-zero: network returned an error
96F5 PLA ; Pop saved error number
96F6 CMP #&de ; Was it &DE (file server error)?
96F8 BEQ append_error_number ; Yes: append error number and trigger BRK
96FA .trigger_brk←1← 974B BEQ
JMP error_block ; Jump to BRK via error block
96FD .handle_net_error←1← 96F3 BNE
STA l0e08 ; Store error code in workspace
9700 PHA ; Push error code
9701 TXA ; Save X (error text index)
9702 PHA ; Push X
9703 JSR read_rx_attribute ; Read receive attribute byte
9706 STA fs_load_addr ; Save to fs_load_addr as spool handle
9708 LDA #0 ; A=0: clear error code in RX buffer
970A STA (net_rx_ptr),y ; Zero the error code byte in buffer
970C LDA #&c6 ; A=&C6: OSBYTE read spool handle
970E JSR osbyte_x0 ; Read current spool file handle
9711 CPY fs_load_addr ; Compare Y result with saved handle
9713 BEQ close_exec_via_y ; Match: close the spool file
9715 CPX fs_load_addr ; Compare X result with saved handle
9717 BNE done_close_files ; No match: skip spool close
9719 PHA ; Push A (preserved)
971A LDA #&c6 ; A=&C6: disable spool with OSBYTE
971C BNE close_spool_exec ; ALWAYS branch to close spool ALWAYS branch
971E .close_exec_via_y←1← 9713 BEQ
TYA ; Transfer Y to A for stack save
971F .close_exec_file
PHA ; Push A (preserved)
9720 LDA #&c7 ; A=&C7: disable exec with OSBYTE
9722 .close_spool_exec←1← 971C BNE
JSR osbyte_x0_y0 ; OSBYTE with X=0, Y=0 to close
9725 PLA ; Pull saved handle
9726 TAY ; Transfer to Y for OSFIND
9727 LDA #osfind_close ; A=0: close file
9729 JSR osfind ; Close the spool/exec file Close one or all files
972C .done_close_files←1← 9717 BNE
PLA ; Pull saved X (error text index)
972D TAX ; Restore X
972E LDY #&0a ; Y=&0A: lookup index for 'on channel'
9730 LDA net_error_lookup_data,y ; Load message offset from lookup table
9733 TAY ; Transfer offset to Y
9734 .loop_copy_channel_msg←1← 973E BNE
LDA error_msg_table,y ; Load error message byte
9737 STA error_text,x ; Append to error text buffer
973A BEQ append_error_number ; Null terminator: done copying
973C INX ; Advance error text index
973D INY ; Advance message index
973E BNE loop_copy_channel_msg ; Loop until full message copied
9740 .append_error_number←2← 96F8 BEQ← 973A BEQ
STX fs_load_addr_2 ; Save error text end position
9742 PLA ; Pull saved error number
9743 JSR append_space_and_num ; Append ' nnn' error number suffix
9746 LDA #0 ; A=0: null terminator
9748 STA stack_page_2,x ; Terminate error text string
974B BEQ trigger_brk ; ALWAYS branch to trigger BRK error ALWAYS branch

Append 'net.station' decimal string to error text

Reads network and station numbers from the TX control block at offsets 3 and 2. Writes a space separator then the network number (if non-zero), a dot, and 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
974D .append_drv_dot_num←2← 9636 JSR← 9674 JSR
LDA #&20 ; A=' ': space separator
974F STA error_text,x ; Append space to error text
9752 INX ; Advance error text index
9753 STX fs_load_addr_2 ; Save position for number formatting
9755 LDY #3 ; Y=3: offset to network number in TX CB
9757 LDA (net_tx_ptr),y ; Load network number
9759 BEQ append_station_num ; Zero: skip network part (local)
975B JSR append_decimal_num ; Append network number as decimal
975E LDX fs_load_addr_2 ; Reload error text position
9760 LDA #&2e ; A='.': dot separator
9762 STA error_text,x ; Append dot to error text
9765 INC fs_load_addr_2 ; Advance past dot
9767 .append_station_num←1← 9759 BEQ
LDY #2 ; Y=2: offset to station number in TX CB
9769 LDA (net_tx_ptr),y ; Load station number
976B JSR append_decimal_num ; Append station number as decimal
976E LDX fs_load_addr_2 ; Reload error text position
9770 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)
9771 .append_space_and_num←2← 9743 JSR← B4DA JSR
TAY ; Save number in Y
9772 LDA #&20 ; A=' ': space prefix
9774 LDX fs_load_addr_2 ; Load current error text position
9776 STA error_text,x ; Append space to error text
9779 INC fs_load_addr_2 ; Advance position past space
977B 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)
977C .append_decimal_num←2← 975B JSR← 976B JSR
TAY ; Save number in Y for division
977D BIT bit_test_ff ; Set V: suppress leading zeros
9780 LDA #&64 ; A=100: hundreds digit divisor
9782 JSR append_decimal_digit ; Extract and append hundreds digit
9785 LDA #&0a ; A=10: tens digit divisor
9787 JSR append_decimal_digit ; Extract and append tens digit
978A LDA #1 ; A=1: units digit (remainder)
978C 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
978D .append_decimal_digit←2← 9782 JSR← 9787 JSR
STA fs_load_addr_3 ; Store divisor
978F TYA ; Copy number to A for division
9790 LDX #&2f ; X='0'-1: digit counter (ASCII offset)
9792 PHP ; Save V flag (leading zero suppression)
9793 SEC ; Set carry for subtraction
9794 .loop_count_digit←1← 9797 BCS
INX ; Increment digit counter
9795 SBC fs_load_addr_3 ; Subtract divisor
9797 BCS loop_count_digit ; Not negative yet: continue counting
9799 ADC fs_load_addr_3 ; Add back divisor (restore remainder)
979B PLP ; Restore V flag
979C TAY ; Save remainder back to Y
979D TXA ; Digit counter to A (ASCII digit)
979E CMP #&30 ; Is digit '0'?
97A0 BNE store_digit ; Non-zero: always print
97A2 BVS return_from_store_digit ; V set (suppress leading zeros): skip
97A4 .store_digit←1← 97A0 BNE
CLV ; Clear V: first non-zero digit seen
97A5 LDX fs_load_addr_2 ; Load current text position
97A7 STA error_text,x ; Store ASCII digit in error text
97AA INC fs_load_addr_2 ; Advance text position
97AC .return_from_store_digit←1← 97A2 BVS
RTS ; Return
; Network error lookup table (12 bytes)
; Each byte is an offset into error_msg_table.
; Indices 0-7 are keyed by error class (reply AND 7).
; Index 8 is used by build_no_reply_error.
; Indices 9-11 point to suffix strings appended
; after the station address in compound errors.
97AD .net_error_lookup_data←5← 961C LDY← 965A LDY← 9688 LDA← 969B LDY← 9730 LDA
EQUB error_msg_table - error_msg_table ; Class 0: &A0 "Line jammed"
97AE EQUB msg_net_error - error_msg_table ; Class 1: &A1 "Net error"
97AF EQUB msg_station - error_msg_table ; Class 2: &A2 "Station"
97B0 EQUB msg_no_clock - error_msg_table ; Class 3: &A3 "No clock"
97B1 EQUB msg_escape - error_msg_table ; Class 4: &11 "Escape"
97B2 EQUB msg_escape - error_msg_table ; Class 5: &11 "Escape" (duplicate)
97B3 EQUB msg_escape - error_msg_table ; Class 6: &11 "Escape" (duplicate)
97B4 EQUB msg_bad_option - error_msg_table ; Class 7: &CB "Bad option"
97B5 EQUB msg_no_reply - error_msg_table ; Index 8: &A5 "No reply from station"
97B6 EQUB msg_not_listening - error_msg_table ; Index 9: " not listening" suffix
97B7 EQUB msg_on_channel - error_msg_table ; Index 10: " on channel" suffix
97B8 EQUB msg_not_present - error_msg_table ; Index 11: " not present" suffix
; Network error message table
; Each entry is [error_number][string...][null].
; The error number is the BRK error code stored in
; the error block at &0100. Entries 0-6 are complete
; error messages. The last 3 are suffix strings
; (no error number) appended to class 2 "Station"
; errors to form compound messages like
; "Station 1.254 not listening".
97B9 .error_msg_table←8← 9624 LDA← 962A LDA← 965D LDA← 9668 LDA← 968C LDA← 96A3 LDA← 96A9 LDA← 9734 LDA
EQUB &A0 ; Error &A0: Line jammed
97BA EQUS "Line jammed"
97C5 EQUB &00 ; Null terminator
97C6 .msg_net_error
EQUB &A1 ; Error &A1: Net error
97C7 EQUS "Net error"
97D0 EQUB &00 ; Null terminator
97D1 .msg_station
EQUB &A2 ; Error &A2: Station
97D2 EQUS "Station"
97D9 EQUB &00 ; Null terminator
97DA .msg_no_clock
EQUB &A3 ; Error &A3: No clock
97DB EQUB &4E
97DC EQUS "o clock"
97E3 EQUB &00 ; Null terminator
97E4 .msg_escape
EQUB &11 ; Error &11: Escape
97E5 EQUS "Escape"
97EB EQUB &00 ; Null terminator
97EC .msg_bad_option
EQUB &CB ; Error &CB: Bad option
97ED EQUS "Bad option"
97F7 EQUB &00 ; Null terminator
97F8 .msg_no_reply
EQUB &A5 ; Error &A5: No reply from station
97F9 EQUB &4E
97FA EQUS "o reply from station"
980E EQUB &00 ; Null terminator
980F .msg_not_listening
EQUS " not listening" ; Suffix: " not listening"
981D EQUB &00 ; Null terminator
981E .msg_on_channel
EQUS " on channel" ; Suffix: " on channel"
9829 EQUB &00 ; Null terminator
982A .msg_not_present
EQUS " not present" ; Suffix: " not present"
9836 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.

9837 .init_tx_ptr_and_send←2← 94EC JSR← 9ADB JSR
LDX #&c0 ; X=&C0: TX control block base (low)
9839 STX net_tx_ptr ; Set TX pointer low
983B LDX #0 ; X=0: TX control block base (high)
983D STX net_tx_ptr_hi ; Set TX pointer high (page 0)
fall through ↓

Transmit Econet packet with retry

Polls for line idle, starts transmission via the ADLC, and retries on failure with a configurable count and delay. Enables escape handling after the first retry phase exhausts its count.

983F .send_net_packet←6← A977 JMP← A9D4 JSR← ABC6 JSR← AC51 JSR← B05B JSR← B23A JSR
LDA net_context ; Load retry count from workspace
9842 BNE set_timeout ; Non-zero: use configured retry count
9844 LDA #&ff ; A=&FF: default retry count (255)
9846 .set_timeout←1← 9842 BNE
LDY #&60 ; Y=&60: timeout value
9848 PHA ; Push retry count
9849 TYA ; A=&60: copy timeout to A A=&60
984A PHA ; Push timeout
984B LDX #0 ; X=0: TX pointer index
984D LDA (net_tx_ptr,x) ; Load first byte of TX control block
984F .start_tx_attempt←1← 9871 BEQ
STA (net_tx_ptr,x) ; Write control byte back to CB
9851 PHA ; Push control byte
9852 JSR poll_adlc_tx_status ; Poll ADLC until line idle
9855 ASL ; Shift left: check bit 6 (success)
9856 BPL tx_success ; Bit 6 clear: transmission complete
9858 ASL ; Shift left: check bit 5 (fatal)
9859 BEQ tx_send_error ; Zero (bit 5 clear): fatal error
985B JSR check_escape ; Check for escape condition
985E PLA ; Pull control byte
985F TAX ; Restore to X
9860 PLA ; Pull timeout
9861 TAY ; Restore to Y
9862 PLA ; Pull retry count
9863 BEQ try_alternate_phase ; Zero retries remaining: try alternate
9865 .loop_retry_tx←1← 987C BNE
SBC #1 ; Decrement retry counter
9867 PHA ; Push updated retry count
9868 TYA ; Copy timeout to A
9869 PHA ; Push timeout for delay loop
986A TXA ; Copy control byte to A
986B .loop_tx_delay←2← 986C BNE← 986F BNE
DEX ; Inner delay: decrement X
986C BNE loop_tx_delay ; Loop until X=0
986E DEY ; Decrement outer counter Y
986F BNE loop_tx_delay ; Loop until Y=0
9871 BEQ start_tx_attempt ; ALWAYS branch: retry transmission ALWAYS branch
9873 .try_alternate_phase←1← 9863 BEQ
CMP net_context ; Compare retry count with alternate
9876 BNE tx_send_error ; Different: go to error handling
9878 LDA #&80 ; A=&80: set escapable flag
987A STA need_release_tube ; Mark as escapable for second phase
987C BNE loop_retry_tx ; ALWAYS branch: retry with escapable ALWAYS branch
987E .tx_send_error←2← 9859 BEQ← 9876 BNE
TAX ; Result code to X
987F JMP load_reply_and_classify ; Jump to classify reply and return
9882 .tx_success←1← 9856 BPL
PLA ; Pull control byte
9883 PLA ; Pull timeout
9884 PLA ; Pull retry count
9885 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.
; Offsets marked &FD are skipped, preserving the
; existing destination station and network. Buffer
; addresses point to &0D3A-&0D3E in NMI workspace.
; Original TX buffer values are pushed on the stack
; and restored after transmission.
9888 .pass_txbuf_init_table←2← 989E LDX← 98F8 LDX
EQUB &88 ; Offset 0: ctrl = &88 (immediate TX)
9889 EQUB &00 ; Offset 1: port = &00 (immediate op)
988A EQUB &FD ; Offset 2: &FD skip (preserve dest stn)
988B EQUB &FD ; Offset 3: &FD skip (preserve dest net)
988C EQUB &3A ; Offset 4: buf start lo (&3A)
988D EQUB &0D ; Offset 5: buf start hi (&0D) -> &0D3A
988E EQUB &FF ; Offset 6: extended addr fill (&FF)
988F EQUB &FF ; Offset 7: extended addr fill (&FF)
9890 EQUB &3E ; Offset 8: buf end lo (&3E)
9891 EQUB &0D ; Offset 9: buf end hi (&0D) -> &0D3E
9892 EQUB &FF ; Offset 10: extended addr fill (&FF)
9893 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.

9894 .init_tx_ptr_for_pass←1← 8DFF JSR
LDY #&c0 ; Y=&C0: TX control block base (low)
9896 STY net_tx_ptr ; Set TX pointer low byte
9898 LDY #0 ; Y=0: TX control block base (high)
989A 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.

989C .setup_pass_txbuf←1← ABC3 JSR
LDY #&0b ; Y=&0B: 12 bytes to process (0-11)
989E .loop_copy_template←1← 98AC BPL
LDX pass_txbuf_init_table,y ; Load template byte for this offset
98A1 CPX #&fd ; Is it &FD (skip marker)?
98A3 BEQ skip_template_byte ; Yes: skip this offset, don't modify
98A5 LDA (net_tx_ptr),y ; Load existing TX buffer byte
98A7 PHA ; Save original value on stack
98A8 TXA ; Copy template value to A
98A9 STA (net_tx_ptr),y ; Store template value to TX buffer
98AB .skip_template_byte←1← 98A3 BEQ
DEY ; Next offset (descending)
98AC BPL loop_copy_template ; Loop until all 12 bytes processed
98AE LDA rx_poll_count ; Load pass-through control value
98B1 PHA ; Push control value
98B2 TYA ; A=&FF (Y is &FF after loop)
98B3 PHA ; Push &FF as timeout
98B4 LDX #0 ; X=0: TX pointer index
98B6 LDA (net_tx_ptr,x) ; Load control byte from TX CB
98B8 .start_pass_tx←1← 98F1 BEQ
STA (net_tx_ptr,x) ; Write control byte to start TX
98BA PHA ; Save control byte on stack
98BB JSR poll_adlc_tx_status ; Poll ADLC until line idle
98BE ASL ; Shift result: check bit 6 (success)
98BF BPL pass_tx_success ; Bit 6 clear: transmission complete
98C1 ASL ; Shift result: check bit 5 (fatal)
98C2 BNE restore_retry_state ; Non-zero (not fatal): retry
98C4 .done_pass_retries←1← 98E3 BEQ
LDX #0 ; X=0: clear error status
98C6 JMP fixup_reply_status_a ; Jump to fix up reply status

Poll ADLC and start frame transmission

Shifts the workspace status byte left in a loop, then copies the TX pointer into the NMI TX block and calls tx_begin to start frame transmission. Returns the TX completion status in A.

98C9 .poll_adlc_tx_status←3← 9852 JSR← 98BB JSR← 98CC BCC
ASL ws_0d60 ; Shift ws_0d60 left to poll ADLC
98CC BCC poll_adlc_tx_status ; Bit not set: keep polling
98CE LDA net_tx_ptr ; Copy TX pointer low to NMI TX block
98D0 STA nmi_tx_block ; Store in NMI TX block low
98D2 LDA net_tx_ptr_hi ; Copy TX pointer high
98D4 STA nmi_tx_block_hi ; Store in NMI TX block high
98D6 JSR tx_begin ; Begin Econet frame transmission
98D9 .loop_poll_pass_tx←1← 98DB BMI
LDA (net_tx_ptr,x) ; Read TX status byte
98DB BMI loop_poll_pass_tx ; Bit 7 set: still transmitting
98DD RTS ; Return with result in A
98DE .restore_retry_state←1← 98C2 BNE
PLA ; Pull control byte
98DF TAX ; Restore to X
98E0 PLA ; Pull timeout
98E1 TAY ; Restore to Y
98E2 PLA ; Pull retry count
98E3 BEQ done_pass_retries ; Zero retries: go to error handling
98E5 SBC #1 ; Decrement retry counter
98E7 PHA ; Push updated retry count
98E8 TYA ; Copy timeout to A
98E9 PHA ; Push timeout
98EA TXA ; Copy control byte to A
98EB .loop_pass_tx_delay←2← 98EC BNE← 98EF BNE
DEX ; Inner delay loop: decrement X
98EC BNE loop_pass_tx_delay ; Loop until X=0
98EE DEY ; Decrement outer counter Y
98EF BNE loop_pass_tx_delay ; Loop until Y=0
98F1 BEQ start_pass_tx ; ALWAYS branch: retry transmission ALWAYS branch
98F3 .pass_tx_success←1← 98BF BPL
PLA ; Pull control byte (discard)
98F4 PLA ; Pull timeout (discard)
98F5 PLA ; Pull retry count (discard)
98F6 LDY #0 ; Y=0: start restoring from offset 0
98F8 .loop_restore_txbuf←1← 9905 BNE
LDX pass_txbuf_init_table,y ; Load template byte for this offset
98FB CPX #&fd ; Is it &FD (skip marker)?
98FD BEQ skip_restore_byte ; Yes: don't restore this offset
98FF PLA ; Pull original value from stack
9900 STA (net_tx_ptr),y ; Restore original TX buffer byte
9902 .skip_restore_byte←1← 98FD BEQ
INY ; Next offset (ascending)
9903 CPY #&0c ; Processed all 12 bytes?
9905 BNE loop_restore_txbuf ; No: continue restoring
9907 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.

9908 .load_text_ptr_and_parse←1← 9938 JSR
LDY #1 ; Y=1: start at second byte of pointer
990A .loop_copy_text_ptr←1← 9910 BPL
LDA (fs_options),y ; Load pointer byte from FS options
990C STA os_text_ptr,y ; Store in OS text pointer
990F DEY ; Decrement index
9910 BPL loop_copy_text_ptr ; Loop until both bytes copied
9912 INY ; Y=0: reset index for string reading
fall through ↓

Parse command line via GSINIT/GSREAD into &0E30

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

9913 .gsread_to_buf←1← AE94 JSR
LDX #&ff ; X=&FF: pre-increment for buffer index
9915 CLC ; C=0: initialise for string input
9916 JSR gsinit ; GSINIT: initialise string reading
9919 BEQ terminate_buf ; Z set (empty string): store terminator
991B .loop_gsread_char←1← 9924 BCC
JSR gsread ; GSREAD: read next character
991E BCS terminate_buf ; C set: end of string reached
9920 INX ; Advance buffer index
9921 STA fs_filename_buf,x ; Store character in l0e30 buffer
9924 BCC loop_gsread_char ; ALWAYS branch: read next character ALWAYS branch
9926 .terminate_buf←2← 9919 BEQ← 991E BCS
INX ; Advance past last character
9927 LDA #&0d ; A=CR: terminate filename
9929 STA fs_filename_buf,x ; Store CR terminator in buffer
992C LDA #&30 ; A=&30: low byte of l0e30 buffer
992E STA fs_crc_lo ; Set command text pointer low
9930 LDA #&0e ; A=&0E: high byte of l0e30 buffer
9932 STA fs_crc_hi ; Set command text pointer high
9934 RTS ; Return with buffer filled
9935 JSR set_xfer_params ; Set up transfer parameters
9938 JSR load_text_ptr_and_parse ; Load text pointer and parse filename
993B JSR mask_owner_access ; Set owner-only access mask
993E JSR parse_access_prefix ; Parse access prefix from filename
9941 LDA fs_last_byte_flag ; Load last byte flag
9943 BPL check_display_type ; Positive (not last): display file info
9945 CMP #&ff ; Is it &FF (last entry)?
9947 BEQ copy_arg_and_enum ; Yes: copy arg and iterate
9949 JMP return_with_last_flag ; Other value: return with flag
994C .copy_arg_and_enum←1← 9947 BEQ
JSR copy_arg_to_buf_x0 ; Copy argument to buffer at X=0
994F 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.

9951 .do_fs_cmd_iteration←1← A2D1 JSR
LDA #&92 ; A=&92: FS port number
9953 STA need_release_tube ; Set escapable flag to &92
9955 STA fs_cmd_urd ; Store port number in TX buffer
9958 JSR send_request_write ; Send request to file server
995B LDY #6 ; Y=6: offset to response cycle flag
995D LDA (fs_options),y ; Load cycle flag from FS options
995F BNE copy_ws_then_fsopts ; Non-zero: already initialised
9961 JSR copy_fsopts_to_zp ; Copy FS options to zero page first
9964 JSR copy_workspace_to_fsopts ; Then copy workspace to FS options
9967 BCC setup_txcb_addrs ; Branch to continue (C clear from JSR)
9969 .copy_ws_then_fsopts←1← 995F BNE
JSR copy_workspace_to_fsopts ; Copy workspace to FS options first
996C JSR copy_fsopts_to_zp ; Then copy FS options to zero page
996F .setup_txcb_addrs←1← 9967 BCC
LDY #4 ; Y=4: loop counter
9971 .loop_copy_addrs←1← 997C BNE
LDA fs_load_addr,x ; Load address byte from zero page
9973 STA txcb_end,x ; Save to TXCB end pointer
9975 ADC fs_file_len,x ; Add offset from buffer
9978 STA fs_work_4,x ; Store sum in fs_work area
997A INX ; Advance to next byte
997B DEY ; Decrement counter
997C BNE loop_copy_addrs ; Loop for all 4 bytes
997E SEC ; Set carry for subtraction
997F SBC fs_file_len_3 ; Subtract high offset
9982 STA fs_work_7 ; Store result in fs_work_7
9984 JSR format_filename_field ; Format filename for display
9987 JSR send_txcb_swap_addrs ; Send TXCB and swap addresses
998A LDX #2 ; X=2: copy 3 offset bytes
998C .loop_copy_offsets←1← 9993 BPL
LDA fs_file_len_3,x ; Load offset byte from l0f10
998F STA fs_cmd_data,x ; Store in l0f05 for next iteration
9992 DEX ; Decrement counter
9993 BPL loop_copy_offsets ; Loop until all bytes copied
9995 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.

9998 .send_txcb_swap_addrs←2← 9987 JSR← 9F52 JSR
JSR cmp_5byte_handle ; Compare 5-byte handle with current
999B BEQ return_from_txcb_swap ; Match: no need to send, return
999D LDA #&92 ; A=&92: FS reply port number
999F STA txcb_port ; Set TXCB port
99A1 .loop_swap_and_send←1← 99BD BNE
LDX #3 ; X=3: copy 4 bytes
99A3 .loop_copy_start_end←1← 99AC BPL
LDA txcb_end,x ; Load TXCB end pointer byte
99A5 STA txcb_start,x ; Store in TXCB start pointer
99A7 LDA fs_work_4,x ; Load new end address from fs_work
99A9 STA txcb_end,x ; Store in TXCB end pointer
99AB DEX ; Decrement counter
99AC BPL loop_copy_start_end ; Loop for all 4 bytes
99AE LDA #&7f ; A=&7F: control byte for data transfer
99B0 STA txcb_ctrl ; Set TXCB control byte
99B2 JSR wait_net_tx_ack ; Wait for network TX acknowledgement
99B5 LDY #3 ; Y=3: compare 4 bytes
99B7 .loop_verify_addrs←1← 99C0 BPL
LDA txcb_end,y ; Load TXCB end byte
99BA EOR fs_work_4,y ; Compare with expected end address
99BD BNE loop_swap_and_send ; Mismatch: resend from start
99BF DEY ; Decrement counter
99C0 BPL loop_verify_addrs ; Loop until all 4 bytes match
99C2 .return_from_txcb_swap←1← 999B BEQ
RTS ; Return (all bytes match)
99C3 .check_display_type←1← 9943 BPL
BEQ setup_dir_display ; Z set: directory entry display
99C5 JMP dispatch_osword_op ; Non-zero: jump to OSWORD dispatch
99C8 .setup_dir_display←2← 99C3 BEQ← 9AF8 JMP
LDX #4 ; X=4: loop counter for 4 iterations
99CA LDY #&0e ; Y=&0E: FS options offset for addresses
99CC SEC ; Set carry for subtraction
99CD .loop_compute_diffs←1← 99E7 BNE
LDA (fs_options),y ; Load address byte from FS options
99CF STA port_ws_offset,y ; Save to workspace (port_ws_offset)
99D2 JSR retreat_y_by_4 ; Y -= 4 to point to paired offset
99D5 SBC (fs_options),y ; Subtract paired value
99D7 STA fs_cmd_csd,y ; Store difference in l0f03 buffer
99DA PHA ; Push difference
99DB LDA (fs_options),y ; Load paired value from FS options
99DD STA port_ws_offset,y ; Save to workspace
99E0 PLA ; Pull difference back
99E1 STA (fs_options),y ; Store in FS options for display
99E3 JSR skip_one_and_advance5 ; Advance Y by 5 for next field
99E6 DEX ; Decrement loop counter
99E7 BNE loop_compute_diffs ; Loop for all 4 address pairs
99E9 LDY #9 ; Y=9: copy 9 bytes of options data
99EB .loop_copy_fs_options←1← 99F1 BNE
LDA (fs_options),y ; Load FS options byte
99ED STA fs_cmd_csd,y ; Store in l0f03 buffer
99F0 DEY ; Decrement index
99F1 BNE loop_copy_fs_options ; Loop until all 9 bytes copied
99F3 LDA #&91 ; A=&91: FS port for info request
99F5 STA need_release_tube ; Set escapable flag
99F7 STA fs_cmd_urd ; Store port in TX buffer
99FA STA fs_error_ptr ; Store in fs_error_ptr
99FC LDX #&0b ; X=&0B: copy argument at offset 11
99FE JSR copy_arg_to_buf ; Copy argument to TX buffer
9A01 LDY #1 ; Y=1: info sub-command
9A03 LDA fs_last_byte_flag ; Load last byte flag
9A05 CMP #7 ; Is it 7 (catalogue info)?
9A07 PHP ; Save comparison result
9A08 BNE send_info_request ; Not 7: keep Y=1
9A0A LDY #&1d ; Y=&1D: extended info command
9A0C .send_info_request←1← 9A08 BNE
JSR send_request_write ; Send request to file server
9A0F JSR format_filename_field ; Format filename for display
9A12 PLP ; Restore comparison flags
9A13 BNE setup_txcb_transfer ; Not catalogue info: show short format
9A15 LDX #0 ; X=0: start at first byte
9A17 BEQ store_result ; ALWAYS branch to store and display ALWAYS branch
9A19 .setup_txcb_transfer←1← 9A13 BNE
LDA fs_cmd_data ; Load file handle from l0f05
9A1C JSR check_and_setup_txcb ; Check and set up TXCB for transfer
9A1F .recv_reply←1← 9995 JMP
JSR recv_and_process_reply ; Receive and process reply
9A22 .store_result←1← 9A17 BEQ
STX fs_reply_cmd ; Store result byte in l0f08
9A25 LDY #&0e ; Y=&0E: protection bits offset
9A27 LDA fs_cmd_data ; Load access byte from l0f05
9A2A JSR get_prot_bits ; Extract protection bit flags
9A2D BEQ store_prot_byte ; Zero: use reply buffer data
9A2F .loop_copy_file_info←1← 9A37 BNE
LDA fs_reply_data,y ; Load file info byte from l0ef7
9A32 .store_prot_byte←1← 9A2D BEQ
STA (fs_options),y ; Store in FS options at offset Y
9A34 INY ; Advance to next byte
9A35 CPY #&12 ; Y=&12: end of protection fields?
9A37 BNE loop_copy_file_info ; No: copy next byte
9A39 LDY fs_messages_flag ; Load display flag from l0e06
9A3C BEQ return_from_advance_y ; Zero: skip display, return
9A3E LDY #&f4 ; Y=&F4: index into l0fff for filename
9A40 .loop_print_filename←1← 9A47 BNE
LDA l0fff,y ; Load filename character from l10f3
9A43 JSR osasci ; Print character via OSASCI Write character
9A46 INY ; Advance to next character
9A47 BNE loop_print_filename ; Printed all 12 characters? No: print next character
9A49 LDY #5 ; Y=5: offset for access string
9A4B JSR print_5_hex_bytes ; Print 5 hex bytes (access info)
9A4E JSR print_load_exec_addrs ; Print load and exec addresses
9A51 JSR osnewl ; Print newline Write newline (characters 10 and 13)
9A54 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.

9A57 .print_load_exec_addrs←1← 9A4E JSR
LDY #9 ; Y=9: offset for exec address
9A59 JSR print_5_hex_bytes ; Print 5 hex bytes (exec address)
9A5C LDY #&0c ; Y=&0C: offset for length (3 bytes)
9A5E LDX #3 ; X=3: print 3 bytes only
9A60 BNE loop_print_hex_byte ; ALWAYS branch to print routine ALWAYS branch

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)
9A62 .print_5_hex_bytes←2← 9A4B JSR← 9A59 JSR
LDX #4 ; X=4: print 5 bytes (4 to 0)
9A64 .loop_print_hex_byte←2← 9A60 BNE← 9A6B BNE
LDA (fs_options),y ; Load byte from FS options at offset Y
9A66 JSR print_hex_byte ; Print as 2-digit hex
9A69 DEY ; Decrement byte offset
9A6A DEX ; Decrement byte count
9A6B BNE loop_print_hex_byte ; Loop until all bytes printed
9A6D LDA #&20 ; A=' ': space separator
9A6F JMP osasci ; Print space via OSASCI and return Write character 32

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.

9A72 .copy_fsopts_to_zp←2← 9961 JSR← 996C JSR
LDY #5 ; Y=5: copy 4 bytes (offsets 2-5)
9A74 .loop_copy_fsopts_byte←1← 9A7C BCS
LDA (fs_options),y ; Load byte from FS options
9A76 STA work_ae,y ; Store in zero page at l00ae+Y
9A79 DEY ; Decrement index
9A7A CPY #2 ; Below offset 2?
9A7C 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.

9A7E .skip_one_and_advance5←1← 99E3 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
9A7F .advance_y_by_4←2← 9F2F JSR← B074 JSR
INY ; Y += 4
9A80 INY ; (continued)
9A81 INY ; (continued)
9A82 INY ; (continued)
9A83 .return_from_advance_y←1← 9A3C 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.

9A84 .copy_workspace_to_fsopts←2← 9964 JSR← 9969 JSR
LDY #&0d ; Y=&0D: copy bytes from offset &0D down
9A86 TXA ; Transfer X to A
9A87 .loop_copy_ws_byte←1← 9A8F BCS
STA (fs_options),y ; Store byte in FS options at offset Y
9A89 LDA fs_cmd_urd,y ; Load next workspace byte from l0f02+Y
9A8C DEY ; Decrement index
9A8D CPY #2 ; Below offset 2?
9A8F 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
9A91 .retreat_y_by_4←1← 99D2 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
9A92 .retreat_y_by_3←2← 9B0E JSR← 9F37 JSR
DEY ; Y -= 3
9A93 DEY ; (continued)
9A94 DEY ; (continued)
9A95 RTS ; Return
9A96 .discard_handle_match←2← 9A9E BEQ← 9AE4 BCS
PLA ; Discard stacked value
9A97 LDY fs_block_offset ; Restore Y from fs_block_offset
9A99 RTS ; Return (handle already matches)

Set up data transfer TXCB and dispatch reply

Compares the 5-byte handle; if unchanged, returns. Otherwise computes start/end addresses with overflow clamping, sets the port and control byte, sends the packet, and dispatches on the reply sub-operation code.

9A9A .check_and_setup_txcb←2← 9A1C JSR← 9F4D JSR
PHA ; Save port/sub-function on stack
9A9B JSR cmp_5byte_handle ; Compare 5-byte handle with current
9A9E BEQ discard_handle_match ; Match: discard port and return
9AA0 .init_transfer_addrs←1← 9AED BNE
LDX #0 ; X=0: loop start
9AA2 LDY #4 ; Y=4: copy 4 bytes
9AA4 STX fs_reply_cmd ; Clear l0f08 (transfer size low)
9AA7 STX fs_load_vector ; Clear l0f09 (transfer size high)
9AAA CLC ; Clear carry for addition
9AAB .loop_copy_addr_offset←1← 9AB8 BNE
LDA fs_load_addr,x ; Load address byte from zero page
9AAD STA txcb_start,x ; Store in TXCB start pointer
9AAF ADC fs_func_code,x ; Add offset from l0f06
9AB2 STA txcb_end,x ; Store sum in TXCB end pointer
9AB4 STA fs_load_addr,x ; Also update load address
9AB6 INX ; Advance to next byte
9AB7 DEY ; Decrement counter
9AB8 BNE loop_copy_addr_offset ; Loop for all 4 bytes
9ABA BCS clamp_end_to_limit ; Carry set: overflow, use limit
9ABC SEC ; Set carry for subtraction
9ABD .loop_check_vs_limit←1← 9AC5 BNE
LDA fs_load_addr,y ; Load computed end address
9AC0 SBC fs_work_4,y ; Subtract maximum from fs_work_4
9AC3 INY ; Advance to next byte
9AC4 DEX ; Decrement counter
9AC5 BNE loop_check_vs_limit ; Loop for all bytes
9AC7 BCC set_port_and_ctrl ; Below limit: keep computed end
9AC9 .clamp_end_to_limit←1← 9ABA BCS
LDX #3 ; X=3: copy 4 bytes of limit
9ACB .loop_copy_limit←1← 9AD0 BPL
LDA fs_work_4,x ; Load limit from fs_work_4
9ACD STA txcb_end,x ; Store as TXCB end
9ACF DEX ; Decrement counter
9AD0 BPL loop_copy_limit ; Loop for all 4 bytes
9AD2 .set_port_and_ctrl←1← 9AC7 BCC
PLA ; Pull port from stack
9AD3 PHA ; Push back (keep for later)
9AD4 PHP ; Save flags (carry = overflow state)
9AD5 STA txcb_port ; Set TXCB port number
9AD7 LDA #&80 ; A=&80: control byte for data request
9AD9 STA txcb_ctrl ; Set TXCB control byte
9ADB JSR init_tx_ptr_and_send ; Init TX pointer and send packet
9ADE LDA fs_error_ptr ; Load error pointer
9AE0 JSR init_txcb_port ; Init TXCB port from error pointer
9AE3 PLP ; Restore overflow flags
9AE4 BCS discard_handle_match ; Carry set: discard and return
9AE6 LDA #&91 ; A=&91: FS reply port
9AE8 STA txcb_port ; Set TXCB port for reply
9AEA JSR wait_net_tx_ack ; Wait for TX acknowledgement
9AED BNE init_transfer_addrs ; Non-zero (not done): retry send
9AEF .dispatch_osword_op←1← 99C5 JMP
STA fs_cmd_data ; Store sub-operation code
9AF2 CMP #7 ; Compare with 7
9AF4 BCC dispatch_ops_1_to_6 ; Below 7: handle operations 1-6
9AF6 BNE skip_if_error ; Above 7: jump to handle via finalise
9AF8 JMP setup_dir_display ; Equal to 7: jump to directory display
9AFB .dispatch_ops_1_to_6←1← 9AF4 BCC
CMP #6 ; Compare with 6
9AFD BEQ send_delete_request ; 6: delete file operation
9AFF CMP #5 ; Compare with 5
9B01 BEQ read_cat_info ; 5: read catalogue info
9B03 CMP #4 ; Compare with 4
9B05 BEQ setup_write_access ; 4: write file attributes
9B07 CMP #1 ; Compare with 1
9B09 BEQ setup_save_access ; 1: read file info
9B0B ASL ; Shift left twice: A*4
9B0C ASL ; A*4
9B0D TAY ; Copy to Y as index
9B0E JSR retreat_y_by_3 ; Y -= 3 to get FS options offset
9B11 LDX #3 ; X=3: copy 4 bytes
9B13 .loop_copy_fsopts_4←1← 9B1A BPL
LDA (fs_options),y ; Load byte from FS options at offset Y
9B15 STA fs_func_code,x ; Store in l0f06 buffer
9B18 DEY ; Decrement source offset
9B19 DEX ; Decrement byte count
9B1A BPL loop_copy_fsopts_4 ; Loop for all 4 bytes
9B1C LDX #5 ; X=5: copy arg to buffer at offset 5
9B1E BNE send_save_or_access ; ALWAYS branch to copy and send ALWAYS branch
9B20 .setup_save_access←1← 9B09 BEQ
JSR get_access_bits ; Get access bits for file
9B23 STA fs_file_attrs ; Store access byte in l0f0e
9B26 LDY #9 ; Y=9: source offset in FS options
9B28 LDX #8 ; X=8: copy 8 bytes to buffer
9B2A .loop_copy_fsopts_8←1← 9B31 BNE
LDA (fs_options),y ; Load FS options byte
9B2C STA fs_cmd_data,x ; Store in l0f05 buffer
9B2F DEY ; Decrement source offset
9B30 DEX ; Decrement byte count
9B31 BNE loop_copy_fsopts_8 ; Loop for all 8 bytes
9B33 LDX #&0a ; X=&0A: buffer offset for argument
9B35 .send_save_or_access←2← 9B1E BNE← 9B54 BNE
JSR copy_arg_to_buf ; Copy argument to buffer
9B38 LDY #&13 ; Y=&13: OSWORD &13 (NFS operation)
9B3A BNE send_request_vset ; ALWAYS branch to send request ALWAYS branch
9B3C .send_delete_request←1← 9AFD BEQ
JSR copy_arg_to_buf_x0 ; Copy argument to buffer at X=0
9B3F LDY #&14 ; Y=&14: delete file command
9B41 .send_request_vset←1← 9B3A BNE
BIT bit_test_ff ; Set V flag (no directory check)
9B44 JSR save_net_tx_cb_vset ; Send request with V set
9B47 .skip_if_error←1← 9AF6 BNE
BCS done_osword_op ; Carry set: error, jump to finalise
9B49 JMP return_with_last_flag ; No error: return with last flag
9B4C .setup_write_access←1← 9B05 BEQ
JSR get_access_bits ; Get access bits for file
9B4F STA fs_func_code ; Store in l0f06
9B52 LDX #2 ; X=2: buffer offset
9B54 BNE send_save_or_access ; ALWAYS branch to copy and send ALWAYS branch
9B56 .read_cat_info←1← 9B01 BEQ
LDX #1 ; X=1: buffer offset
9B58 JSR copy_arg_to_buf ; Copy argument to buffer
9B5B LDY #&12 ; Y=&12: open file command
9B5D JSR save_net_tx_cb ; Send open file request
9B60 LDA fs_obj_type ; Load reply handle from l0f11
9B63 STX fs_obj_type ; Clear l0f11
9B66 STX fs_len_clear ; Clear l0f14
9B69 JSR get_prot_bits ; Get protection bits
9B6C LDX fs_cmd_data ; Load file handle from l0f05
9B6F BEQ return_with_handle ; Zero: file not found, return
9B71 LDY #&0e ; Y=&0E: store access bits
9B73 STA (fs_options),y ; Store access byte in FS options
9B75 DEY ; Y=&0D Y=&0d
9B76 LDX #&0c ; X=&0C: copy 12 bytes of file info
9B78 .loop_copy_cat_info←1← 9B7F BNE
LDA fs_cmd_data,x ; Load reply byte from l0f05+X
9B7B STA (fs_options),y ; Store in FS options at offset Y
9B7D DEY ; Decrement destination offset
9B7E DEX ; Decrement source counter
9B7F BNE loop_copy_cat_info ; Loop for all 12 bytes
9B81 INX ; X=1 (INX from 0)
9B82 INX ; X=2
9B83 LDY #&11 ; Y=&11: FS options offset
9B85 .loop_copy_ext_info←1← 9B8C BPL
LDA fs_access_level,x ; Load extended info byte from l0f12
9B88 STA (fs_options),y ; Store in FS options
9B8A DEY ; Decrement destination offset
9B8B DEX ; Decrement source counter
9B8C BPL loop_copy_ext_info ; Loop until all copied
9B8E LDX fs_cmd_data ; Reload file handle
9B91 .return_with_handle←1← 9B6F BEQ
TXA ; Transfer to A
9B92 .done_osword_op←1← 9B47 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 &10F3 for directory listing output. Sources the name from either the command line or the l0f05 reply buffer depending on the value in l0f03. Truncates or pads to exactly 12 characters.

; Unreachable dead code (3 bytes)
; Duplicate of the JMP at &9B92 immediately above.
; Unreachable after the unconditional JMP and
; unreferenced. Likely a development remnant.
9B95 .format_filename_field←2← 9984 JSR← 9A0F JSR
LDY #0 ; Dead: duplicate JMP finalise_and_return Y=0: destination index
9B97 LDX fs_cmd_csd ; Load source offset from l0f03
9B9A BNE copy_from_buf_entry ; Non-zero: copy from l0f05 buffer
9B9C .loop_copy_cmdline_char←1← 9BA6 BNE
LDA (fs_crc_lo),y ; Load character from command line
9B9E CMP #&21 ; Below '!' (control/space)?
9BA0 BCC pad_with_spaces ; Yes: pad with spaces
9BA2 STA filename_buf,y ; Store printable character in l10f3
9BA5 INY ; Advance to next character
9BA6 BNE loop_copy_cmdline_char ; Loop for more characters
9BA8 .pad_with_spaces←2← 9BA0 BCC← 9BB0 BCC
LDA #&20 ; A=' ': space for padding
9BAA STA filename_buf,y ; Store space in display buffer
9BAD INY ; Advance index
9BAE CPY #&0c ; Filled all 12 characters?
9BB0 BCC pad_with_spaces ; No: pad more spaces
9BB2 RTS ; Return with field formatted
9BB3 .loop_copy_buf_char←1← 9BBB BPL
INX ; Advance source and destination
9BB4 INY ; INY
9BB5 .copy_from_buf_entry←1← 9B9A BNE
LDA fs_cmd_data,x ; Load byte from l0f05 buffer
9BB8 STA filename_buf,y ; Store in display buffer l10f3
9BBB BPL loop_copy_buf_char ; Bit 7 clear: more characters
9BBD RTS ; Return (bit 7 set = terminator)
9BBE JSR verify_ws_checksum ; Verify workspace checksum
9BC1 STA fs_last_byte_flag ; Store result as last byte flag
9BC3 JSR set_options_ptr ; Set FS options pointer
9BC6 ORA #0 ; OR with 0 to set flags
9BC8 BPL dispatch_osfind_op ; Positive: handle sub-operations
9BCA ASL ; Shift left to check bit 6
9BCB BEQ validate_chan_close ; Zero (was &80): close channel
9BCD JMP close_all_fcbs ; Other: process all FCBs first
9BD0 .validate_chan_close←1← 9BCB BEQ
TYA ; Transfer Y to A
9BD1 CMP #&20 ; Compare with &20 (space)
9BD3 BCS check_chan_range ; Above &20: check further
9BD5 .error_invalid_chan←1← 9BDA BCS
JMP err_net_chan_invalid ; Below &20: invalid channel char
9BD8 .check_chan_range←1← 9BD3 BCS
CMP #&30 ; Compare with '0'
9BDA BCS error_invalid_chan ; Above '0': invalid channel char
9BDC JSR process_all_fcbs ; Process all matching FCBs
9BDF TYA ; Transfer Y to A (FCB index)
9BE0 PHA ; Push FCB index
9BE1 TAX ; Copy to X
9BE2 LDY #0 ; Y=0: clear counter
9BE4 STY fs_last_byte_flag ; Clear last byte flag
9BE6 STY fs_block_offset ; Clear block offset
9BE8 .loop_copy_fcb_fields←1← 9BF3 BNE
LDA fcb_attr_or_count_mid,x ; Load channel data from l1010+X
9BEB STA (fs_options),y ; Store in FS options at Y
9BED JSR advance_x_by_8 ; Advance X by 8 (next FCB field)
9BF0 INY ; Advance destination index
9BF1 CPY #4 ; Copied all 4 channel fields?
9BF3 BNE loop_copy_fcb_fields ; No: copy next field
9BF5 PLA ; Pull saved FCB index
9BF6 STA fs_block_offset ; Restore to fs_block_offset
9BF8 .dispatch_osfind_op←1← 9BC8 BPL
CMP #5 ; Compare with 5
9BFA BCS done_return_flag ; 5 or above: return with last flag
9BFC CPY #0 ; Compare Y with 0
9BFE BNE osfind_with_channel ; Non-zero: handle OSFIND with channel
9C00 JMP osfind_close_or_open ; Y=0 (close): jump to OSFIND open
9C03 .osfind_with_channel←1← 9BFE BNE
PHA ; Push sub-function
9C04 TXA ; Transfer X to A
9C05 PHA ; Push X (FCB slot)
9C06 TYA ; Transfer Y to A
9C07 PHA ; Push Y (channel char)
9C08 JSR check_not_dir ; Check file is not a directory
9C0B PLA ; Pull channel char
9C0C JSR store_rx_attribute ; Store channel char as receive attribute
9C0F LDA fcb_net_or_port,x ; Load FCB flag byte from l1030
9C12 STA fs_cmd_data ; Store in l0f05
9C15 PLA ; Pull X (FCB slot)
9C16 TAX ; Restore X
9C17 PLA ; Pull sub-function
9C18 LSR ; Shift right: check bit 0
9C19 BEQ osargs_ptr_dispatch ; Zero (OSFIND close): handle close
9C1B PHP ; Save flags (carry from LSR)
9C1C PHA ; Push sub-function
9C1D LDX fs_options ; Load FS options pointer low
9C1F LDY fs_block_offset ; Load block offset
9C21 JSR process_all_fcbs ; Process all matching FCBs
9C24 LDA fcb_attr_or_count_mid,y ; Load updated data from l1010
9C27 STA fs_cmd_data ; Store in l0f05
9C2A PLA ; Pull sub-function
9C2B STA fs_func_code ; Store in l0f06
9C2E PLP ; Restore flags
9C2F TYA ; Transfer Y to A
9C30 PHA ; Push Y (offset)
9C31 BCC osargs_read_op ; Carry clear: read operation
9C33 LDY #3 ; Y=3: copy 4 bytes
9C35 .loop_copy_zp_to_buf←1← 9C3C BPL
LDA zp_work_3,x ; Load zero page data
9C37 STA fs_data_count,y ; Store in l0f07 buffer
9C3A DEX ; Decrement source
9C3B DEY ; Decrement counter
9C3C BPL loop_copy_zp_to_buf ; Loop for all 4 bytes
9C3E LDY #&0d ; Y=&0D: TX buffer size
9C40 LDX #5 ; X=5: argument offset
9C42 JSR save_net_tx_cb ; Send TX control block to server
9C45 STX fs_last_byte_flag ; Store X in last byte flag
9C47 PLA ; Pull saved offset
9C48 JSR set_conn_active ; Set connection active flag
9C4B .done_return_flag←1← 9BFA BCS
JMP return_with_last_flag ; Return with last flag
9C4E .osargs_read_op←1← 9C31 BCC
LDY #&0c ; Y=&0C: TX buffer size (smaller)
9C50 LDX #2 ; X=2: argument offset
9C52 JSR save_net_tx_cb ; Send TX control block
9C55 STA fs_last_byte_flag ; Store A in last byte flag
9C57 LDX fs_options ; Load FS options pointer low
9C59 LDY #2 ; Y=2: zero page offset
9C5B STA zp_work_3,x ; Store A in zero page
9C5D .loop_copy_reply_to_zp←1← 9C64 BPL
LDA fs_cmd_data,y ; Load buffer byte from l0f05+Y
9C60 STA zp_work_2,x ; Store in zero page at offset
9C62 DEX ; Decrement source X
9C63 DEY ; Decrement counter Y
9C64 BPL loop_copy_reply_to_zp ; Loop until all bytes copied
9C66 PLA ; Pull saved offset
9C67 JMP return_with_last_flag ; Return with last flag
9C6A .osargs_ptr_dispatch←1← 9C19 BEQ
BCS osargs_write_ptr ; Carry set: write file pointer
9C6C LDA fs_block_offset ; Load block offset
9C6E JSR attr_to_chan_index ; Convert attribute to channel index
9C71 LDY fs_options ; Load FS options pointer
9C73 LDA fcb_count_lo,x ; Load FCB low byte from l1000
9C76 STA zp_ptr_lo,y ; Store in zero page pointer low
9C79 LDA fcb_attr_or_count_mid,x ; Load FCB high byte from l1010
9C7C STA zp_ptr_hi,y ; Store in zero page pointer high
9C7F LDA fcb_station_or_count_hi,x ; Load FCB extent from l1020
9C82 STA zp_work_2,y ; Store in zero page work area
9C85 LDA #0 ; A=0: clear high byte
9C87 STA zp_work_3,y ; Store zero in work area high
9C8A BEQ return_with_last_flag ; ALWAYS branch to return with flag ALWAYS branch
9C8C .osargs_write_ptr←1← 9C6A BCS
STA fs_func_code ; Store write value in l0f06
9C8F TXA ; Transfer X to A
9C90 PHA ; Push X (zero page offset)
9C91 LDY #3 ; Y=3: copy 4 bytes
9C93 .loop_copy_ptr_to_buf←1← 9C9A BPL
LDA zp_work_3,x ; Load zero page data at offset
9C95 STA fs_data_count,y ; Store in l0f07 buffer
9C98 DEX ; Decrement source
9C99 DEY ; Decrement counter
9C9A BPL loop_copy_ptr_to_buf ; Loop for all 4 bytes
9C9C LDY #&0d ; Y=&0D: TX buffer size
9C9E LDX #5 ; X=5: argument offset
9CA0 JSR save_net_tx_cb ; Send TX control block
9CA3 STX fs_last_byte_flag ; Store X in last byte flag
9CA5 PLA ; Pull saved zero page offset
9CA6 TAY ; Transfer to Y
9CA7 LDA fs_block_offset ; Load block offset (attribute)
9CA9 JSR clear_conn_active ; Clear connection active flag
9CAC JSR attr_to_chan_index ; Convert attribute to channel index
9CAF LDA zp_ptr_lo,y ; Load zero page pointer low
9CB2 STA fcb_count_lo,x ; Store back to FCB l1000
9CB5 LDA zp_ptr_hi,y ; Load zero page pointer high
9CB8 STA fcb_attr_or_count_mid,x ; Store back to FCB l1010
9CBB LDA zp_work_2,y ; Load zero page work byte
9CBE STA fcb_station_or_count_hi,x ; Store back to FCB l1020
9CC1 JMP return_with_last_flag ; Return with last flag
9CC4 .close_all_fcbs←1← 9BCD JMP
JSR process_all_fcbs ; Process all matching FCBs first
9CC7 .return_with_last_flag←12← 9949 JMP← 9A54 JMP← 9B49 JMP← 9C4B JMP← 9C67 JMP← 9C8A BEQ← 9CC1 JMP← 9DBB JMP← 9E42 JMP← A2F7 JMP← A2FD JMP← A3B1 JMP
LDA fs_last_byte_flag ; Load last byte flag
9CC9 .finalise_and_return←6← 9B92 JMP← 9CDE BNE← 9CF1 BPL← 9D87 JMP← 9ECF JMP← A206 JMP
PHA ; Push result on stack
9CCA LDA #0 ; A=0: clear error flag
9CCC JSR store_rx_attribute ; Clear receive attribute (A=0)
9CCF PLA ; Pull result back
9CD0 LDX fs_options ; Restore X from FS options pointer
9CD2 LDY fs_block_offset ; Restore Y from block offset
9CD4 RTS ; Return to caller
9CD5 .osfind_close_or_open←1← 9C00 JMP
CMP #2 ; Compare with 2 (open for output)
9CD7 BCS done_file_open ; 2 or above: handle file open
9CD9 TAY ; Transfer to Y (Y=0 or 1)
9CDA BNE loop_copy_reply_data ; Non-zero (1 = read pointer): copy data
9CDC LDA #5 ; A=5: return code for close-all
9CDE BNE finalise_and_return ; ALWAYS branch to finalise ALWAYS branch
9CE0 .loop_copy_reply_data←2← 9CDA BNE← 9CE6 BPL
LDA fs_cmd_context,y ; Load reply data byte at Y
9CE3 STA (fs_options),y ; Store in FS options
9CE5 DEY ; Decrement index
9CE6 BPL loop_copy_reply_data ; Loop until all bytes copied
9CE8 STY zp_work_2,x ; Clear zero page work low
9CEA STY zp_work_3,x ; Clear zero page work high
9CEC .done_file_open←1← 9CD7 BCS
BEQ shift_and_finalise ; Z set: jump to clear A and return
9CEE .clear_result←2← 9CF5 BNE← A00C JMP
LDA #0 ; A=0: clear result
9CF0 .shift_and_finalise←1← 9CEC BEQ
LSR ; Shift right (always positive)
9CF1 BPL finalise_and_return ; Positive: jump to finalise
9CF3 .alloc_fcb_for_open←1← 9D61 BEQ
AND #&3f ; Mask to 6-bit access value
9CF5 BNE clear_result ; Non-zero: clear A and finalise
9CF7 TXA ; Transfer X to A (options pointer)
9CF8 JSR alloc_fcb_or_error ; Allocate FCB slot or raise error
9CFB EOR #&80 ; Toggle bit 7
9CFD ASL ; Shift left: build open mode
9CFE STA fs_cmd_data ; Store open mode in l0f05
9D01 ROL ; Rotate to complete mode byte
9D02 STA fs_func_code ; Store in l0f06
9D05 JSR parse_cmd_arg_y0 ; Parse command argument (Y=0)
9D08 LDX #2 ; X=2: buffer offset
9D0A JSR copy_arg_to_buf ; Copy argument to TX buffer
9D0D LDY #6 ; Y=6: open file command
9D0F BIT bit_test_ff ; Set V flag (skip directory check)
9D12 SEC ; Set carry
9D13 ROR need_release_tube ; Rotate carry into escapable flag bit 7
9D15 JSR save_net_tx_cb_vset ; Send open request with V set
9D18 BCS done_osfind ; Carry set (error): jump to finalise
9D1A LDA #&ff ; A=&FF: mark as newly opened
9D1C JSR store_rx_attribute ; Store &FF as receive attribute
9D1F LDA fs_cmd_data ; Load handle from l0f05
9D22 PHA ; Push handle
9D23 LDA #4 ; A=4: file info sub-command
9D25 STA fs_cmd_data ; Store sub-command
9D28 LDX #1 ; X=1: shift filename
9D2A .loop_shift_filename←1← 9D33 BNE
LDA fs_func_code,x ; Load filename byte from l0f06+X
9D2D STA fs_cmd_data,x ; Shift down to l0f05+X
9D30 INX ; Advance source index
9D31 CMP #&0d ; Is it CR (end of filename)?
9D33 BNE loop_shift_filename ; No: continue shifting
9D35 LDY #&12 ; Y=&12: file info request
9D37 JSR save_net_tx_cb ; Send file info request
9D3A LDA fs_last_byte_flag ; Load last byte flag
9D3C AND #&bf ; Clear bit 6 (read/write bits)
9D3E ORA fs_cmd_data ; OR with reply access byte
9D41 ORA #1 ; Set bit 0 (file is open)
9D43 TAY ; Transfer to Y (access flags)
9D44 AND #2 ; Check bit 1 (write access)
9D46 BEQ check_open_mode ; No write access: check read-only
9D48 PLA ; Pull handle from stack
9D49 JSR alloc_fcb_slot ; Allocate FCB slot for channel
9D4C BNE store_fcb_flags ; Non-zero: FCB allocated, store flags
9D4E JSR verify_ws_checksum ; Verify workspace checksum
9D51 JSR set_xfer_params ; Set up transfer parameters
9D54 TAX ; Transfer A to X
9D55 JSR mask_owner_access ; Set owner-only access mask
9D58 TXA ; Transfer X back to A
9D59 BEQ close_all_channels ; Zero: close file, process FCBs
9D5B JSR save_ptr_to_os_text ; Save text pointer for OS
9D5E LDY cur_dir_handle ; Load current directory handle
9D61 BEQ alloc_fcb_for_open ; Zero: allocate new FCB
9D63 TYA ; Transfer Y to A
9D64 LDX #0 ; X=0: clear directory handle
9D66 STX cur_dir_handle ; Store zero (clear handle)
9D69 BEQ done_osfind ; ALWAYS branch to finalise ALWAYS branch
9D6B .check_open_mode←1← 9D46 BEQ
LDA fs_func_code ; Load access/open mode byte
9D6E ROR ; Rotate right: check bit 0
9D6F BCS alloc_fcb_with_flags ; Carry set (bit 0): check read permission
9D71 ROR ; Rotate right: check bit 1
9D72 BCC alloc_fcb_with_flags ; Carry clear (no write): skip
9D74 BIT fs_data_count ; Test bit 7 of l0f07 (lock flag)
9D77 BPL alloc_fcb_with_flags ; Not locked: skip
9D79 TYA ; Transfer Y to A (flags)
9D7A ORA #&20 ; Set bit 5 (locked file flag)
9D7C TAY ; Transfer back to Y
9D7D .alloc_fcb_with_flags←3← 9D6F BCS← 9D72 BCC← 9D77 BPL
PLA ; Pull handle from stack
9D7E JSR alloc_fcb_slot ; Allocate FCB slot for channel
9D81 .store_fcb_flags←1← 9D4C BNE
TAX ; Transfer to X
9D82 TYA ; Transfer Y to A (flags)
9D83 STA fcb_flags,x ; Store flags in FCB table l1040
9D86 TXA ; Transfer X back to A (handle)
9D87 .done_osfind←2← 9D18 BCS← 9D69 BEQ
JMP finalise_and_return ; Jump to finalise and return
9D8A .close_all_channels←1← 9D59 BEQ
JSR process_all_fcbs ; Process all matching FCBs
9D8D TYA ; Transfer Y to A
9D8E BNE close_specific_chan ; Non-zero channel: close specific
9D90 LDA fs_options ; Load FS options pointer low
9D92 PHA ; Push (save for restore)
9D93 LDA #osbyte_close_spool_exec ; A=&77: OSBYTE close spool/exec files
9D95 JSR osbyte ; Close any *SPOOL and *EXEC files
9D98 PLA ; Pull saved options pointer
9D99 STA fs_options ; Restore FS options pointer
9D9B LDA #0 ; A=0: clear flags
9D9D STA fs_last_byte_flag ; Clear last byte flag
9D9F STA fs_block_offset ; Clear block offset
9DA1 BEQ send_close_request ; ALWAYS branch to send close request ALWAYS branch
9DA3 .close_specific_chan←1← 9D8E BNE
JSR check_chan_char ; Validate channel character
9DA6 LDA fcb_net_or_port,x ; Load FCB flag byte from l1030
9DA9 .send_close_request←1← 9DA1 BEQ
STA fs_cmd_data ; Store as l0f05 (file handle)
9DAC LDX #1 ; X=1: argument size
9DAE LDY #7 ; Y=7: close file command
9DB0 JSR save_net_tx_cb ; Send close file request
9DB3 LDY fs_block_offset ; Load block offset
9DB5 BNE clear_single_fcb ; Non-zero: clear single FCB
9DB7 CLV ; Clear V flag
9DB8 JSR scan_fcb_flags ; Scan and clear all FCB flags
9DBB .done_close←3← 9DC6 BEQ← 9DD8 BCC← 9DEC BPL
JMP return_with_last_flag ; Return with last flag
9DBE .clear_single_fcb←1← 9DB5 BNE
LDA #0 ; A=0: clear FCB entry
9DC0 STA fcb_attr_or_count_mid,y ; Clear l1010 (FCB high byte)
9DC3 STA fcb_flags,y ; Clear l1040 (FCB flags)
9DC6 BEQ done_close ; ALWAYS branch to return ALWAYS branch
9DC8 .fscv_0_opt_entry
BEQ store_display_flag ; Z set: handle OSARGS 0
9DCA CPX #4 ; Compare X with 4 (number of args)
9DCC BNE osargs_dispatch ; Not 4: check for error
9DCE CPY #4 ; Compare Y with 4
9DD0 BCC send_osargs_request ; Below 4: handle special OSARGS
9DD2 .osargs_dispatch←1← 9DCC BNE
DEX ; Decrement X
9DD3 BNE error_osargs ; X was 1: store display flag
9DD5 .store_display_flag←1← 9DC8 BEQ
STY fs_messages_flag ; Store Y in display control flag l0e06
9DD8 BCC done_close ; Carry clear: return with flag
9DDA .error_osargs←1← 9DD3 BNE
LDA #7 ; A=7: error code
9DDC JMP classify_reply_error ; Jump to classify reply error
9DDF .send_osargs_request←1← 9DD0 BCC
STY fs_cmd_data ; Store Y in l0f05
9DE2 LDY #&16 ; Y=&16: OSARGS save command
9DE4 JSR save_net_tx_cb ; Send OSARGS request
9DE7 LDY fs_block_offset ; Reload block offset
9DE9 STY fs_boot_option ; Store in l0e05
9DEC BPL done_close ; Positive: return with flag
9DEE .fscv_1_eof
JSR verify_ws_checksum ; Verify workspace checksum
9DF1 PHA ; Push result on stack
9DF2 LDA fs_block_offset ; Load block offset
9DF4 PHA ; Push block offset
9DF5 STX cur_chan_attr ; Store X in l10c9
9DF8 JSR find_matching_fcb ; Find matching FCB entry
9DFB BEQ mark_not_found ; Zero: no match found
9DFD LDA fcb_count_lo,y ; Load FCB low byte from l1000
9E00 CMP fcb_buf_offset,x ; Compare with stored offset l1098
9E03 BCC mark_not_found ; Below stored: no match
9E05 LDX #&ff ; X=&FF: mark as found (all bits set)
9E07 BMI restore_and_return ; ALWAYS branch (negative) ALWAYS branch
9E09 .mark_not_found←2← 9DFB BEQ← 9E03 BCC
LDX #0 ; X=0: mark as not found
9E0B .restore_and_return←1← 9E07 BMI
PLA ; Restore block offset from stack
9E0C TAY ; Transfer to Y
9E0D PLA ; Restore result from stack
9E0E 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).

9E0F .update_addr_from_offset9←1← 9F5A JSR
LDY #9 ; Y=9: FS options offset for high address
9E11 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
9E14 .update_addr_from_offset1←1← A053 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
9E16 .add_workspace_to_fsopts←1← 9E11 JSR
CLC ; Clear carry for addition
fall through ↓

Add or subtract 4 workspace bytes from FS options

Processes 4 consecutive bytes at (fs_options)+Y, adding or subtracting the corresponding workspace bytes from &0E0A-&0E0D. The direction is controlled by bit 7 of fs_load_addr_2: set for subtraction, clear for addition. Carry propagates across all 4 bytes for correct multi-byte arithmetic.

On EntryYFS options offset for first byte
Ccarry input for first byte
9E17 .adjust_fsopts_4bytes←2← 9F60 JSR← A05F JSR
LDX #&fc ; X=&FC: loop counter (-4 to -1)
9E19 .loop_adjust_byte←1← 9E2C BNE
LDA (fs_options),y ; Load FS options byte at offset Y
9E1B BIT fs_load_addr_2 ; Test fs_load_addr_2 bit 7 (add/subtract)
9E1D BMI subtract_ws_byte ; Bit 7 set: subtract instead
9E1F ADC fs_cmd_context,x ; Add workspace byte to FS options
9E22 JMP store_adjusted_byte ; Jump to store result
9E25 .subtract_ws_byte←1← 9E1D BMI
SBC fs_cmd_context,x ; Subtract workspace byte from FS options
9E28 .store_adjusted_byte←1← 9E22 JMP
STA (fs_options),y ; Store result back to FS options
9E2A INY ; Advance to next byte
9E2B INX ; Advance counter
9E2C BNE loop_adjust_byte ; Loop until 4 bytes processed
9E2E RTS ; Return
9E2F JSR verify_ws_checksum ; Verify workspace checksum
9E32 JSR set_xfer_params ; Set up transfer parameters
9E35 PHA ; Push transfer type on stack
9E36 JSR mask_owner_access ; Set owner-only access mask
9E39 PLA ; Pull transfer type
9E3A TAX ; Transfer to X
9E3B BEQ skip_if_out_of_range ; Zero: no valid operation, return
9E3D DEX ; Decrement (convert 1-based to 0-based)
9E3E CPX #8 ; Compare with 8 (max operation)
9E40 BCC valid_osgbpb_op ; Below 8: valid operation
9E42 .skip_if_out_of_range←1← 9E3B BEQ
JMP return_with_last_flag ; Out of range: return with flag
9E45 .valid_osgbpb_op←1← 9E40 BCC
TXA ; Transfer operation code to A
9E46 LDY #0 ; Y=0: buffer offset
9E48 PHA ; Push operation code
9E49 CMP #4 ; Compare with 4 (write operations)
9E4B BCC load_chan_handle ; Below 4: read operation
9E4D JMP write_block_entry ; 4 or above: write data block
9E50 .load_chan_handle←1← 9E4B BCC
LDA (fs_options),y ; Load channel handle from FS options
9E52 PHA ; Push handle
9E53 JSR check_not_dir ; Check file is not a directory
9E56 PLA ; Pull handle
9E57 TAY ; Transfer to Y
9E58 JSR process_all_fcbs ; Process all matching FCBs
9E5B LDA fcb_net_or_port,x ; Load FCB flag byte from l1030
9E5E STA fs_cmd_data ; Store file handle in l0f05
9E61 LDA #0 ; A=0: clear direction flag
9E63 STA fs_func_code ; Store in l0f06
9E66 LDA fcb_count_lo,x ; Load FCB low byte (position)
9E69 STA fs_data_count ; Store in l0f07
9E6C LDA fcb_attr_or_count_mid,x ; Load FCB high byte
9E6F STA fs_reply_cmd ; Store in l0f08
9E72 LDA fcb_station_or_count_hi,x ; Load FCB extent byte
9E75 STA fs_load_vector ; Store in l0f09
9E78 LDY #&0d ; Y=&0D: TX buffer size
9E7A LDX #5 ; X=5: argument count
9E7C JSR save_net_tx_cb ; Send TX control block to server
9E7F PLA ; Pull operation code
9E80 JSR setup_transfer_workspace ; Set up transfer workspace
9E83 PHP ; Save flags (carry from setup)
9E84 LDY #0 ; Y=0: index for channel handle
9E86 LDA (fs_options),y ; Load channel handle from FS options
9E88 BCS set_write_active ; Carry set (write): set active
9E8A JSR clear_conn_active ; Read: clear connection active
9E8D BPL setup_gbpb_request ; Branch to continue (always positive)
9E8F .set_write_active←1← 9E88 BCS
JSR set_conn_active ; Write: set connection active
9E92 .setup_gbpb_request←1← 9E8D BPL
STY fs_func_code ; Clear l0f06 (Y=0)
9E95 JSR lookup_cat_slot_data ; Look up channel slot data
9E98 STA fs_cmd_data ; Store flag byte in l0f05
9E9B LDY #&0c ; Y=&0C: TX buffer size (short)
9E9D LDX #2 ; X=2: argument count
9E9F JSR save_net_tx_cb ; Send TX control block
9EA2 JSR lookup_cat_entry_0 ; Look up channel entry at Y=0
9EA5 LDY #9 ; Y=9: FS options offset for position
9EA7 LDA fs_cmd_data ; Load new position low from l0f05
9EAA STA fcb_count_lo,x ; Update FCB low byte in l1000
9EAD STA (fs_options),y ; Store in FS options at Y=9
9EAF INY ; Y=&0A Y=&0a
9EB0 LDA fs_func_code ; Load new position high from l0f06
9EB3 STA fcb_attr_or_count_mid,x ; Update FCB high byte in l1010
9EB6 STA (fs_options),y ; Store in FS options at Y=&0A
9EB8 INY ; Y=&0B Y=&0b
9EB9 LDA fs_data_count ; Load new extent from l0f07
9EBC STA fcb_station_or_count_hi,x ; Update FCB extent in l1020
9EBF STA (fs_options),y ; Store in FS options at Y=&0B
9EC1 LDA #0 ; A=0: clear high byte of extent
9EC3 INY ; Y=&0C Y=&0c
9EC4 STA (fs_options),y ; Store zero in FS options at Y=&0C
9EC6 PLP ; Restore flags
9EC7 BCC return_success ; Carry clear: skip last-byte check
9EC9 LDA fs_last_byte_flag ; Load last-byte-of-transfer flag
9ECB CMP #3 ; Is transfer still pending (flag=3)?
9ECD .return_success←1← 9EC7 BCC
LDA #0 ; A=0: success
9ECF 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
9ED2 .lookup_cat_entry_0←2← 9EA2 JSR← 9EDE JSR
LDY #0 ; Y=0: offset for channel handle
9ED4 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 flag byte from &1030+X.

On EntryAchannel handle
On ExitAFCB flag byte
Xchannel slot index
9ED6 .lookup_cat_slot_data←1← 9E95 JSR
JSR lookup_chan_by_char ; Look up channel by character
9ED9 LDA fcb_net_or_port,x ; Load FCB flag byte from l1030
9EDC RTS ; Return with flag in A

Prepare workspace for OSGBPB data transfer

Orchestrates the setup for OSGBPB (get/put multiple bytes) operations. Looks up the channel, copies the 6-byte address structure from FS options (skipping the hole at offset 8), determines transfer direction from the operation code (even=read, odd=write), selects port &91 or &92 accordingly, and sends the FS request. Then configures the TXCB address pairs for the actual data transfer phase and dispatches to the appropriate handler.

9EDD .setup_transfer_workspace←2← 9E80 JSR← B987 JMP
PHA ; Push operation code on stack
9EDE JSR lookup_cat_entry_0 ; Look up channel entry at Y=0
9EE1 STA fs_cmd_data ; Store flag byte in l0f05
9EE4 LDY #&0b ; Y=&0B: source offset in FS options
9EE6 LDX #6 ; X=6: copy 6 bytes
9EE8 .loop_copy_opts_to_buf←1← 9EF4 BNE
LDA (fs_options),y ; Load FS options byte
9EEA STA fs_func_code,x ; Store in l0f06 buffer
9EED DEY ; Decrement source index
9EEE CPY #8 ; Skip offset 8?
9EF0 BNE skip_struct_hole ; No: continue copy
9EF2 DEY ; Skip offset 8 (hole in structure)
9EF3 .skip_struct_hole←1← 9EF0 BNE
DEX ; Decrement destination counter
9EF4 BNE loop_copy_opts_to_buf ; Loop until all 6 bytes copied
9EF6 PLA ; Pull operation code
9EF7 LSR ; Shift right: check bit 0 (direction)
9EF8 PHA ; Push updated code
9EF9 BCC store_direction_flag ; Carry clear: OSBGET (read)
9EFB INX ; Carry set: OSBPUT (write), X=1
9EFC .store_direction_flag←1← 9EF9 BCC
STX fs_func_code ; Store direction flag in l0f06
9EFF LDY #&0b ; Y=&0B: TX buffer size
9F01 LDX #&91 ; X=&91: port for OSBGET
9F03 PLA ; Pull operation code
9F04 PHA ; Push back (keep on stack)
9F05 BEQ store_port_and_send ; Zero (OSBGET): keep port &91
9F07 LDX #&92 ; X=&92: port for OSBPUT
9F09 DEY ; Y=&0A: adjusted buffer size Y=&0a
9F0A .store_port_and_send←1← 9F05 BEQ
STX fs_cmd_urd ; Store port in l0f02
9F0D STX fs_error_ptr ; Store port in fs_error_ptr
9F0F LDX #8 ; X=8: argument count
9F11 LDA fs_cmd_data ; Load file handle from l0f05
9F14 JSR send_request_nowrite ; Send request (no write data)
9F17 LDX #0 ; X=0: index
9F19 LDA (fs_options,x) ; Load channel handle from FS options
9F1B TAX ; Transfer to X as index
9F1C LDA fcb_flags,x ; Load FCB flags from l1040
9F1F EOR #1 ; Toggle bit 0 (transfer direction)
9F21 STA fcb_flags,x ; Store updated flags
9F24 CLC ; Clear carry for addition
9F25 LDX #4 ; X=4: process 4 address bytes
9F27 .loop_setup_addr_bytes←1← 9F3B BNE
LDA (fs_options),y ; Load FS options address byte
9F29 STA addr_work,y ; Store in zero page address area
9F2C STA txcb_pos,y ; Store in TXCB position
9F2F JSR advance_y_by_4 ; Advance Y by 4
9F32 ADC (fs_options),y ; Add offset from FS options
9F34 STA addr_work,y ; Store computed end address
9F37 JSR retreat_y_by_3 ; Retreat Y by 3 for next pair
9F3A DEX ; Decrement byte counter
9F3B BNE loop_setup_addr_bytes ; Loop for all 4 address bytes
9F3D INX ; X=1 (INX from 0)
9F3E .loop_copy_offset←1← 9F45 BPL
LDA fs_cmd_csd,x ; Load offset from l0f03
9F41 STA fs_func_code,x ; Copy to l0f06
9F44 DEX ; Decrement counter
9F45 BPL loop_copy_offset ; Loop until both bytes copied
9F47 PLA ; Pull operation code
9F48 BNE send_with_swap ; Non-zero (OSBPUT): swap addresses
9F4A LDA fs_cmd_urd ; Load port from l0f02
9F4D JSR check_and_setup_txcb ; Check and set up TXCB
9F50 BCS recv_and_update ; Carry set: skip swap
9F52 .send_with_swap←1← 9F48 BNE
JSR send_txcb_swap_addrs ; Send TXCB and swap start/end addresses
9F55 .recv_and_update←1← 9F50 BCS
JSR recv_reply_preserve_flags ; Receive and process reply
9F58 STX fs_load_addr_2 ; Store result in fs_load_addr_2
9F5A JSR update_addr_from_offset9 ; Update addresses from offset 9
9F5D DEC fs_load_addr_2 ; Decrement fs_load_addr_2
9F5F SEC ; Set carry for subtraction
9F60 JSR adjust_fsopts_4bytes ; Adjust FS options by 4 bytes
9F63 ASL fs_cmd_data ; Shift l0f05 left (update status)
9F66 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.

9F67 .recv_reply_preserve_flags←1← 9F55 JSR
PHP ; Save flags before reply processing
9F68 JSR recv_and_process_reply ; Process server reply
9F6B PLP ; Restore flags after reply processing
9F6C RTS ; Return
9F6D .send_osbput_data←1← 9F9D BEQ
LDY #&15 ; Y=&15: TX buffer size for OSBPUT data
9F6F JSR save_net_tx_cb ; Send TX control block
9F72 LDA fs_boot_option ; Load display flag from l0e05
9F75 STA fs_boot_data ; Store in l0f16
9F78 STX fs_load_addr ; Clear fs_load_addr (X=0)
9F7A STX fs_load_addr_hi ; Clear fs_load_addr_hi
9F7C LDA #&12 ; A=&12: byte count for data block
9F7E STA fs_load_addr_2 ; Store in fs_load_addr_2
9F80 BNE write_data_block ; ALWAYS branch to write data block ALWAYS branch
9F82 .write_block_entry←1← 9E4D JMP
LDY #4 ; Y=4: offset for station comparison
9F84 LDA tube_present ; Load stored station from l0d63
9F87 BEQ store_station_result ; Zero: skip station check
9F89 CMP (fs_options),y ; Compare with FS options station
9F8B BNE store_station_result ; Mismatch: skip subtraction
9F8D DEY ; Y=3 Y=&03
9F8E SBC (fs_options),y ; Subtract FS options value
9F90 .store_station_result←2← 9F87 BEQ← 9F8B BNE
STA svc_state ; Store result in svc_state
9F92 .loop_copy_opts_to_ws←1← 9F98 BNE
LDA (fs_options),y ; Load FS options byte at Y
9F94 STA fs_last_byte_flag,y ; Store in workspace at fs_last_byte_flag+Y
9F97 DEY ; Decrement index
9F98 BNE loop_copy_opts_to_ws ; Loop until all bytes copied
9F9A PLA ; Pull operation code
9F9B AND #3 ; Mask to 2-bit sub-operation
9F9D BEQ send_osbput_data ; Zero: send OSBPUT data
9F9F LSR ; Shift right: check bit 0
9FA0 BEQ handle_cat_update ; Zero (bit 0 clear): handle read
9FA2 BCS update_cat_position ; Carry set: handle catalogue update
9FA4 .handle_cat_update←1← 9FA0 BEQ
TAY ; Transfer to Y (Y=0)
9FA5 LDA fs_csd_handle,y ; Load data byte from l0e03
9FA8 STA fs_cmd_csd ; Store in l0f03
9FAB LDA fs_lib_handle ; Load high data byte from l0e04
9FAE STA fs_cmd_lib ; Store in l0f04
9FB1 LDA fs_urd_handle ; Load port from l0e02
9FB4 STA fs_cmd_urd ; Store in l0f02
9FB7 LDX #&12 ; X=&12: buffer size marker
9FB9 STX fs_cmd_y_param ; Store in l0f01
9FBC LDA #&0d ; A=&0D: count value
9FBE STA fs_func_code ; Store in l0f06
9FC1 STA fs_load_addr_2 ; Store in fs_load_addr_2
9FC3 LSR ; Shift right (A=6)
9FC4 STA fs_cmd_data ; Store in l0f05
9FC7 CLC ; Clear carry for addition
9FC8 JSR prep_send_tx_cb ; Prepare and send TX control block
9FCB STX fs_load_addr_hi ; Store X in fs_load_addr_hi (X=0)
9FCD INX ; X=1 (INX)
9FCE STX fs_load_addr ; Store X in fs_load_addr
fall through ↓

Write data block to destination or Tube

If no Tube present, copies directly from the l0f05 buffer via (fs_crc_lo). If Tube is active, claims the Tube, sets up the transfer address, and writes via R3.

9FD0 .write_data_block←2← 9F80 BNE← A048 JSR
LDA svc_state ; Load svc_state (tube flag)
9FD2 BNE tube_write_setup ; Non-zero: write via tube
9FD4 LDX fs_load_addr ; Load source index from fs_load_addr
9FD6 LDY fs_load_addr_hi ; Load destination index from fs_load_addr_hi
9FD8 .loop_copy_to_host←1← 9FE1 BNE
LDA fs_cmd_data,x ; Load data byte from l0f05 buffer
9FDB STA (fs_crc_lo),y ; Store to destination via fs_crc pointer
9FDD INX ; Advance source index
9FDE INY ; Advance destination index
9FDF DEC fs_load_addr_2 ; Decrement byte counter
9FE1 BNE loop_copy_to_host ; Loop until all bytes transferred
9FE3 BEQ tail_update_catalogue ; ALWAYS branch to update catalogue ALWAYS branch
9FE5 .tube_write_setup←1← 9FD2 BNE
JSR tube_claim_c3 ; Claim tube with call &C3
9FE8 LDA #1 ; A=1: tube transfer type (write)
9FEA LDX fs_options ; Load destination low from fs_options
9FEC LDY fs_block_offset ; Load destination high from fs_block_offset
9FEE INX ; Increment low byte
9FEF BNE set_tube_addr ; No wrap: skip high increment
9FF1 INY ; Carry: increment high byte
9FF2 .set_tube_addr←1← 9FEF BNE
JSR tube_addr_data_dispatch ; Set up tube transfer address
9FF5 LDX fs_load_addr ; Load source index
9FF7 .loop_write_to_tube←1← A005 BNE
LDA fs_cmd_data,x ; Load data byte from buffer
9FFA STA tube_data_register_3 ; Write to tube data register 3
9FFD INX ; Advance source index
9FFE LDY #6 ; Y=6: tube write delay
A000 .loop_tube_delay←1← A001 BNE
DEY ; Delay loop: decrement Y
A001 BNE loop_tube_delay ; Loop until delay complete
A003 DEC fs_load_addr_2 ; Decrement byte counter
A005 BNE loop_write_to_tube ; Loop until all bytes written to tube
A007 LDA #&83 ; A=&83: release tube claim
A009 JSR tube_addr_data_dispatch ; Release tube
A00C .tail_update_catalogue←2← 9FE3 BEQ← A070 JMP
JMP clear_result ; Jump to clear A and finalise return
A00F .update_cat_position←1← 9FA2 BCS
LDY #9 ; Y=9: offset for position byte
A011 LDA (fs_options),y ; Load position from FS options
A013 STA fs_func_code ; Store in l0f06
A016 LDY #5 ; Y=5: offset for extent byte
A018 LDA (fs_options),y ; Load extent byte from FS options
A01A STA fs_data_count ; Store in l0f07
A01D LDX #&0d ; X=&0D: byte count
A01F STX fs_reply_cmd ; Store in l0f08
A022 LDY #2 ; Y=2: command sub-type
A024 STY fs_load_addr ; Store in fs_load_addr
A026 STY fs_cmd_data ; Store in l0f05
A029 INY ; Y=3: TX buffer command byte Y=&03
A02A JSR save_net_tx_cb ; Send TX control block
A02D STX fs_load_addr_hi ; Store X (0) in fs_load_addr_hi
A02F LDA fs_func_code ; Load data offset from l0f06
A032 STA (fs_options,x) ; Store as first byte of FS options
A034 LDA fs_cmd_data ; Load data count from l0f05
A037 LDY #9 ; Y=9: position offset in FS options
A039 ADC (fs_options),y ; Add to current position
A03B STA (fs_options),y ; Store updated position
A03D LDA txcb_end ; Load TXCB end byte
A03F SBC #7 ; Subtract 7 (header overhead)
A041 STA fs_func_code ; Store remaining data size
A044 STA fs_load_addr_2 ; Store in fs_load_addr_2 (byte count)
A046 BEQ clear_buf_after_write ; Zero bytes: skip write
A048 JSR write_data_block ; Write data block to host/tube
A04B .clear_buf_after_write←1← A046 BEQ
LDX #2 ; X=2: clear 3 bytes (indices 0-2)
A04D .loop_clear_buf←1← A051 BPL
STA fs_data_count,x ; Clear l0f07+X
A050 DEX ; Decrement index
A051 BPL loop_clear_buf ; Loop until all cleared
A053 JSR update_addr_from_offset1 ; Update addresses from offset 1
A056 SEC ; Set carry for subtraction
A057 DEC fs_load_addr_2 ; Decrement fs_load_addr_2
A059 LDA fs_cmd_data ; Load data count from l0f05
A05C STA fs_func_code ; Copy to l0f06
A05F JSR adjust_fsopts_4bytes ; Adjust FS options by 4 bytes (subtract)
A062 LDX #3 ; X=3: check 4 bytes
A064 LDY #5 ; Y=5: starting offset
A066 SEC ; Set carry for comparison
A067 .loop_check_remaining←1← A06D BPL
LDA (fs_options),y ; Load FS options byte
A069 BNE done_write_block ; Non-zero: more data remaining
A06B INY ; Advance to next byte
A06C DEX ; Decrement counter
A06D BPL loop_check_remaining ; Loop until all bytes checked
A06F CLC ; All zero: clear carry (transfer complete)
A070 .done_write_block←1← A069 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.

A073 .tube_claim_c3←3← 9FE5 JSR← A078 BCC← A2E3 JSR
LDA #&c3 ; A=&C3: tube claim protocol
A075 JSR tube_addr_data_dispatch ; Dispatch tube address/data claim
A078 BCC tube_claim_c3 ; Carry clear: claim failed, retry
A07A 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
A07B .cmd_fs
LDA fs_server_stn ; Load current FS station high
A07E STA fs_work_5 ; Save to fs_work_5
A080 LDA fs_server_net ; Load current FS station low
A083 STA fs_work_6 ; Save to l00b6
A085 LDA (fs_crc_lo),y ; Get first character of argument
A087 CMP #&0d ; Is it CR (no argument)?
A089 BEQ print_current_fs ; No arg: print current FS info
A08B JSR parse_fs_ps_args ; Parse FS/PS station arguments
A08E LDA #1 ; A=1: write NFS info
A090 STA fs_work_4 ; Store OSWORD sub-function
A092 LDA #&13 ; OSWORD &13: NFS information
A094 LDX #<(fs_work_4) ; Parameter block low
A096 LDY #>(fs_work_4) ; Parameter block high
A098 JMP osword ; Read/Write NFS information (see https://beebwiki.mdfs.net/OSWORDs)
A09B .print_current_fs←1← A089 BEQ
JSR print_file_server_is ; Print 'File server '
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.

A09E .print_fs_info_newline←1← B0B6 JSR
BIT bit_test_ff ; Set V (suppress padding)
A0A1 JSR print_station_addr ; Print station address
A0A4 JMP osnewl ; Write newline (characters 10 and 13)

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.

A0A7 .parse_fs_ps_args←3← A08B JSR← B011 JSR← B1E8 JSR
TXA ; Save X on stack
A0A8 PHA ; Push X
A0A9 LDA #0 ; A=0: initialise dot-seen flag
A0AB STA fs_work_4 ; Clear dot-seen flag
A0AD JSR parse_addr_arg ; Parse first number (network)
A0B0 BCS skip_if_no_station ; C set: number found, check for dot
A0B2 TYA ; Save Y (command line offset)
A0B3 PHA ; Push Y
A0B4 JSR init_bridge_poll ; Initialise bridge polling
A0B7 EOR fs_load_addr_2 ; Compare bridge result with parsed value
A0B9 BEQ store_station_lo ; Same: keep bridge result
A0BB LDA fs_load_addr_2 ; Different: use parsed value
A0BD .store_station_lo←1← A0B9 BEQ
STA fs_work_6 ; Store station low byte
A0BF PLA ; Restore Y
A0C0 TAY ; Transfer back to Y
A0C1 INY ; Skip dot separator
A0C2 JSR parse_addr_arg ; Parse second number (station)
A0C5 .skip_if_no_station←1← A0B0 BCS
BEQ done_parse_fs_ps ; Zero result: skip store
A0C7 STA fs_work_5 ; Store station high byte
A0C9 .done_parse_fs_ps←1← A0C5 BEQ
PLA ; Restore X
A0CA TAX ; Transfer back to X
A0CB 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.

A0CC .get_pb_ptr_as_index←2← A0EA JSR← A0FA JSR
LDA osword_pb_ptr ; Load parameter block pointer
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)
A0CE .byte_to_2bit_index←4← 8F30 JSR← A5D1 JSR← A5EA JSR← B108 JSR
ASL ; Shift left (A * 2)
A0CF ASL ; Shift left (A * 4)
A0D0 PHA ; Save A * 4 on stack
A0D1 ASL ; Shift left (A * 8)
A0D2 TSX ; Get stack pointer
A0D3 PHP ; Save flags (carry from shift)
A0D4 ADC error_text,x ; A*8 + A*4 (from stack) = A*12
A0D7 ROR ; Divide by 2 with carry
A0D8 PLP ; Restore original flags
A0D9 ASL ; Shift left again
A0DA TAY ; Result to Y as index
A0DB PLA ; Pop saved A * 4
A0DC CMP #&48 ; A * 4 >= &48 (out of range)?
A0DE BCC return_from_2bit_index ; In range: return
A0E0 LDY #0 ; Out of range: Y=0
A0E2 TYA ; A=&00
A0E3 .return_from_2bit_index←1← A0DE BCC
RTS ; Return with A=index, Y=index
A0E4 .net_1_read_handle
LDY #&6f ; Y=&6F: source offset
A0E6 LDA (net_rx_ptr),y ; Load byte from RX buffer
A0E8 BCC store_pb_result ; C clear: store directly
A0EA .net_2_read_handle_entry
JSR get_pb_ptr_as_index ; Get index from PB pointer
A0ED BCS return_zero_uninit ; C set (out of range): clear value
A0EF LDA (nfs_workspace),y ; Load workspace byte at index
A0F1 CMP #&3f ; Is it '?' (uninitialised)?
A0F3 BNE store_pb_result ; No: use value from RX buffer
A0F5 .return_zero_uninit←1← A0ED BCS
LDA #0 ; A=0: return zero for uninitialised
A0F7 .store_pb_result←2← A0E8 BCC← A0F3 BNE
STA osword_pb_ptr ; Store result to PB pointer
A0F9 RTS ; Return
A0FA .net_3_close_handle
JSR get_pb_ptr_as_index ; Get index from PB pointer
A0FD BCC mark_ws_uninit ; C clear: store to workspace
A0FF ROR fs_flags ; Save carry to l0d6c bit 7
A102 LDA osword_pb_ptr ; Load PB pointer value
A104 ROL ; Shift carry back in
A105 ROL fs_flags ; Restore l0d6c bit 7
A108 RTS ; Return
A109 .mark_ws_uninit←1← A0FD BCC
ROR econet_flags ; Save carry to l0d61 bit 7
A10C LDA #&3f ; A='?': mark as uninitialised
A10E STA (nfs_workspace),y ; Store '?' to workspace
A110 ROL econet_flags ; Restore l0d61 bit 7
A113 RTS ; Return
A114 .fscv_3_star_cmd←1← 8CF9 JMP
JSR set_text_and_xfer_ptr ; Set text and transfer pointers
A117 LDY #&ff ; Y=&FF: prepare for INY to 0
A119 STY fs_spool_handle ; Clear spool handle (no spool active)
A11B STY need_release_tube ; Set escapable flag (&FF)
A11D INY ; Y=&00
A11E LDX #&4a ; X=&4A: FS command table offset
A120 JSR match_fs_cmd ; Match command in FS table
A123 BCS dispatch_fs_cmd ; C set: command found
A125 .cmd_fs_reentry←1← 8C5A JMP
BVC dispatch_fs_cmd ; V clear: syntax error
A127 .error_syntax←1← AF6F JMP
LDA #&dc ; Error code &DC
A129 JSR error_inline ; Generate 'Syntax' error
A12C EQUS "Syntax."
A133 .dispatch_fs_cmd←2← A123 BCS← A125 BVC
LDA #0 ; A=0: clear service state
A135 STA svc_state ; Store cleared service state
A137 LDA cmd_table_fs_hi,x ; Load command handler address high
A13A PHA ; Push high byte
A13B LDA cmd_table_fs_lo,x ; Load command handler address low
A13E PHA ; Push low byte
A13F RTS ; RTS dispatches to command handler

Match command name against FS command table

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

A140 .match_fs_cmd←5← 8C55 JSR← 8C83 JSR← A120 JSR← B320 JSR← B34D JSR
TYA ; Save Y (command line offset)
A141 PHA ; Push on stack
A142 .restart_table_scan←1← A168 BNE
PLA ; Restore saved Y
A143 PHA ; Push back (keep on stack)
A144 TAY ; Transfer to Y
A145 LDA cmd_table_fs,x ; Load table entry byte
A148 BMI check_char_type ; Bit 7 set: end of table names
A14A .loop_match_char←1← A157 BNE
LDA cmd_table_fs,x ; Load table byte
A14D BMI check_separator ; Bit 7 set: end of this name
A14F EOR (fs_crc_lo),y ; Compare with command line char
A151 AND #&df ; Case-insensitive compare
A153 BNE skip_entry_chars ; Mismatch: skip to next entry
A155 INY ; Match: advance command line
A156 INX ; Advance table pointer
A157 BNE loop_match_char ; Loop for next character
A159 .skip_entry_chars←2← A153 BNE← A15D BPL
INX ; Advance past remaining table chars
A15A LDA cmd_table_fs,x ; Load next table byte
A15D BPL skip_entry_chars ; Bit 7 clear: more chars to skip
A15F LDA (fs_crc_lo),y ; Check command line terminator
A161 CMP #&2e ; Is it '.' (abbreviation)?
A163 BEQ skip_dot_and_spaces ; Yes: skip spaces after dot
A165 .loop_skip_to_next←1← A17A BNE
INX ; X += 3: skip flags and address bytes
A166 INX ; (continued)
A167 INX ; (continued)
A168 BNE restart_table_scan ; Try next table entry
A16A .check_separator←1← A14D BMI
TYA ; Save Y (end of matched name)
A16B PHA ; Push position
A16C LDA (fs_crc_lo),y ; Load char after matched portion
A16E LDY #9 ; Y=9: check 10 separator chars
A170 .loop_check_sep_table←1← A176 BPL
CMP sep_table_data,y ; Compare with separator table
A173 BEQ separator_matched ; Match: valid command separator
A175 DEY ; Try next separator
A176 BPL loop_check_sep_table ; Loop through separator list
A178 PLA ; No separator match: restore Y
A179 TAY ; Transfer back to Y
A17A BNE loop_skip_to_next ; Try next table entry
; Command separator table (9 bytes)
; Characters that terminate a command name in the
; star command parser. loop_check_sep_table scans
; Y down from 8 to 0, comparing each input char
; against this table.
A17C .sep_table_data←1← A170 CMP
EQUB &20 ; Space
A17D EQUB &22 ; '"' double quote
A17E EQUB &23 ; '#' hash
A17F EQUB &24 ; '$' dollar
A180 EQUB &26 ; '&' ampersand
A181 EQUB &2A ; '*' asterisk
A182 EQUB &3A ; ':' colon
A183 EQUB &40 ; '@' at-sign
A184 EQUB &0D ; CR (carriage return)
A185 .separator_matched←1← A173 BEQ
PLA ; Restore saved Y
A186 TAY ; Transfer to Y
A187 .loop_skip_trail_spaces←1← A18E JMP
LDA (fs_crc_lo),y ; Load next char
A189 CMP #&20 ; Is it space?
A18B BNE check_cmd_flags ; No: done skipping
A18D .skip_dot_and_spaces←1← A163 BEQ
INY ; Advance past space
A18E JMP loop_skip_trail_spaces ; Loop for more spaces
A191 .check_cmd_flags←1← A18B BNE
LDA cmd_table_fs,x ; Load command flags byte
A194 ASL ; Shift: check 'no-arg' bit
A195 BPL clear_v_flag ; Bit clear: allow arguments
A197 LDA (fs_crc_lo),y ; Check if line ends here
A199 CMP #&0d ; Is it CR?
A19B BNE clear_v_flag ; No: argument present, V clear
A19D BIT bit_test_ff ; CR found: set V (no argument)
A1A0 BVS clear_c_flag ; V set: command is valid
A1A2 .clear_v_flag←2← A195 BPL← A19B BNE
CLV ; Clear V (argument present)
A1A3 .clear_c_flag←1← A1A0 BVS
CLC ; C=0: command not found
A1A4 .return_with_result←1← A1BF BCS
PLA ; Pop saved Y from stack
A1A5 LDA (fs_crc_lo),y ; Load command line char at Y
A1A7 RTS ; Return (C and V set per result)
A1A8 .loop_scan_past_word←1← A1B5 BNE
INY ; Advance past character
A1A9 .check_char_type←1← A148 BMI
LDA (fs_crc_lo),y ; Load current char
A1AB CMP #&0d ; Is it CR (end of line)?
A1AD BEQ set_c_and_return ; Yes: end of input
A1AF CMP #&2e ; Is it '.' (abbreviation dot)?
A1B1 BEQ skip_sep_spaces ; Yes: skip to next word
A1B3 CMP #&20 ; Is it space?
A1B5 BNE loop_scan_past_word ; No: keep scanning
A1B7 .skip_sep_spaces←2← A1B1 BEQ← A1BC BEQ
INY ; Skip past separator
A1B8 LDA (fs_crc_lo),y ; Load next char
A1BA CMP #&20 ; Is it space?
A1BC BEQ skip_sep_spaces ; Yes: skip consecutive spaces
A1BE .set_c_and_return←1← A1AD BEQ
SEC ; C=1: have more text to match
A1BF BCS return_with_result ; ALWAYS branch
A1C1 .fscv_2_star_run←1← 8E1D JMP
JSR save_ptr_to_os_text ; Save text pointer
A1C4 JSR mask_owner_access ; Set owner-only access mask
A1C7 JSR parse_cmd_arg_y0 ; Parse command argument (Y=0)
A1CA .open_file_for_run←1← A241 BNE
LDX #1 ; X=1: buffer index
A1CC JSR copy_arg_to_buf ; Copy argument to buffer
A1CF LDA #2 ; A=2: open for update
A1D1 STA fs_cmd_data ; Store open mode
A1D4 LDY #&12 ; Y=&12: open file command
A1D6 JSR save_net_tx_cb ; Send open request to server
A1D9 LDA fs_cmd_data ; Load reply status
A1DC CMP #1 ; Status 1 (success)?
A1DE BNE try_library_path ; No: file not found, try library
A1E0 LDX #3 ; X=3: check 4 handle bytes
A1E2 .loop_check_handles←1← A1EB BPL
INC fs_handle_check,x ; Increment handle byte
A1E5 BEQ alloc_run_fcb ; Was &FF (overflow to 0): try next
A1E7 JMP check_exec_addr ; Non-zero: handle valid, execute
A1EA .alloc_run_fcb←1← A1E5 BEQ
DEX ; Try next handle byte
A1EB BPL loop_check_handles ; Loop until all checked
A1ED JSR alloc_fcb_or_error ; Allocate new FCB or raise error
A1F0 LDX #1 ; X=1: open mode index
A1F2 STX fs_cmd_data ; Store in l0f05
A1F5 STX fs_func_code ; Store in l0f06
A1F8 INX ; X=2 X=&02
A1F9 JSR copy_arg_to_buf ; Copy argument to buffer
A1FC LDY #6 ; Y=6: re-open command
A1FE JSR save_net_tx_cb ; Send re-open request
A201 BCS done_run_dispatch ; C set: error on re-open
A203 JMP alloc_run_channel ; C clear: finalise file opening
A206 .done_run_dispatch←1← A201 BCS
JMP finalise_and_return ; Jump to finalise and return
A209 .try_library_path←1← A1DE BNE
LDA fs_filename_buf ; Load first char of filename
A20C CMP #&24 ; Is it '$' (root dir)?
A20E BEQ error_bad_command ; Yes: no library search, error
A210 LDA fs_lib_flags ; Load library flag byte
A213 BMI library_tried ; Bit 7 set: library already tried
A215 ROL ; Rotate bits to check library state
A216 ROL ; Rotate again
A217 BMI restore_filename ; Bit 7 set: restore from backup
A219 BCS error_bad_command ; Carry set: bad command
A21B LDX #&ff ; X=&FF: pre-increment for loop
A21D .loop_find_name_end←1← A223 BNE
INX ; Find end of filename
A21E LDA fs_filename_buf,x ; Load filename byte
A221 CMP #&0d ; Is it CR (end)?
A223 BNE loop_find_name_end ; No: continue scanning
A225 .loop_shift_name_right←1← A22C BPL
LDA fs_filename_buf,x ; Shift filename right by 8 bytes
A228 STA fs_filename_backup,x ; Store shifted byte
A22B DEX ; Previous byte
A22C BPL loop_shift_name_right ; Loop until all shifted
A22E LDX #7 ; X=7: 'Library.' is 8 bytes
A230 .loop_copy_lib_prefix←1← A237 BPL
LDA library_dir_prefix,x ; Copy 'Library.' prefix
A233 STA fs_filename_buf,x ; Store prefix byte
A236 DEX ; Previous byte
A237 BPL loop_copy_lib_prefix ; Loop until prefix copied
A239 LDA fs_lib_flags ; Load library flag
A23C ORA #&60 ; Set bits 5-6: library path active
A23E STA fs_lib_flags ; Store updated flag
A241 .retry_with_library←1← A258 BNE
BNE open_file_for_run ; Retry file open with library path
A243 .restore_filename←1← A217 BMI
LDX #&ff ; X=&FF: pre-increment for loop
A245 .loop_restore_name←1← A24E BNE
INX ; Restore original filename
A246 LDA fs_filename_backup,x ; Load backup byte
A249 STA fs_filename_buf,x ; Store to filename buffer
A24C EOR #&0d ; Is it CR (end)?
A24E BNE loop_restore_name ; No: continue restoring
A250 JSR mask_owner_access ; Set owner-only access mask
A253 ORA #&80 ; Set bit 7: library tried
A255 STA fs_lib_flags ; Store updated flag
A258 BNE retry_with_library ; ALWAYS branch
A25A .library_tried←1← A213 BMI
JSR mask_owner_access ; Set owner-only access mask
A25D .error_bad_command←3← A20E BEQ← A219 BCS← B332 JMP
LDA #&fe ; Error code &FE
A25F JSR error_bad_inline ; Generate 'Bad command' error
A262 EQUS "command."
A26A .check_exec_addr←1← A1E7 JMP
LDX #3 ; X=3: check 4 execution bytes
A26C .loop_check_exec_bytes←1← A272 BNE
INC fs_func_code,x ; Increment execution address byte
A26F BNE setup_oscli_arg ; Non-zero: valid, go to OSCLI
A271 DEX ; Try next byte
A272 BNE loop_check_exec_bytes ; Loop until all checked
A274 LDA #&93 ; Error code &93
A276 JSR error_inline_log ; Generate 'No!' error
A279 EQUS "No!."
A27D .alloc_run_channel←1← A203 JMP
LDA fs_cmd_data ; Load open mode result
A280 JSR alloc_fcb_slot ; Allocate FCB slot
A283 TAY ; Transfer to Y
A284 LDA #0 ; A=0: clear channel status
A286 STA chan_status,x ; Clear status in channel table
A289 STY cur_dir_handle ; Store handle in l1070
A28C LDY #3 ; Y=3: OSCLI execution
A28E JMP boot_cmd_oscli ; Execute via boot/OSCLI path
A291 .library_dir_prefix←1← A230 LDA
EQUS "Library."
A299 .setup_oscli_arg←1← A26F BNE
JSR copy_arg_to_buf_x0 ; Copy command argument to buffer
A29C LDY #0 ; Y=0
A29E CLC ; C=0 for GSINIT
A29F JSR gsinit ; Initialise GS string read
A2A2 .loop_read_gs_string←1← A2A5 BCC
JSR gsread ; Read next GS character
A2A5 BCC loop_read_gs_string ; C clear: more chars
A2A7 DEY ; Back up one position
A2A8 .loop_skip_trailing←1← A2AD BEQ
INY ; Skip trailing spaces
A2A9 LDA (os_text_ptr),y ; Load next char
A2AB CMP #&20 ; Is it space?
A2AD BEQ loop_skip_trailing ; Yes: skip it
A2AF EOR #&0d ; Check for CR (end of line)
A2B1 CLC ; C=0 for addition
A2B2 TYA ; Transfer Y offset to A
A2B3 ADC os_text_ptr ; Add to text pointer low
A2B5 STA fs_cmd_context ; Store as command tail pointer low
A2B8 LDA os_text_ptr_hi ; Load text pointer high
A2BA ADC #0 ; Add carry
A2BC STA fs_context_hi ; Store as command tail pointer high
A2BF JSR save_text_ptr ; Save text pointer for later
A2C2 LDX #&0e ; X=&0E: OSWORD parameter offset
A2C4 STX fs_block_offset ; Store as block offset high
A2C6 LDA #&0e ; A=&0E: OSWORD parameter size
A2C8 STA fs_options ; Store as options pointer
A2CA STA l0e14 ; Store to l0e16
A2CD LDX #&4a ; X=&4A: FS command table offset
A2CF LDY #5 ; Y=5
A2D1 JSR do_fs_cmd_iteration ; Execute FS command iteration
A2D4 LDA tube_present ; Load tube flag
A2D7 BEQ dispatch_via_vector ; Zero: no tube transfer needed
A2D9 AND fs_load_upper ; AND with l0f0b
A2DC AND fs_addr_check ; AND with l0f0c
A2DF CMP #&ff ; All &FF?
A2E1 BEQ dispatch_via_vector ; Yes: no tube transfer needed
A2E3 JSR tube_claim_c3 ; Claim tube for data transfer
A2E6 LDX #9 ; X=9: parameter count
A2E8 LDY #&0f ; Y=&0F: parameter offset
A2EA LDA #4 ; A=4: tube transfer type
A2EC JMP tube_addr_data_dispatch ; Dispatch tube address/data
A2EF .dispatch_via_vector←2← A2D7 BEQ← A2E1 BEQ
LDA #1 ; A=1
A2F1 JMP (fs_load_vector) ; Dispatch via indirect vector
A2F4 .fsreply_3_set_csd←1← 945F JMP
JSR find_station_bit3 ; Find station with bit 3 set
A2F7 JMP return_with_last_flag ; Return with last flag state
A2FA .fsreply_5_set_lib
JSR flip_set_station_boot ; Flip/set station boot config
A2FD JMP return_with_last_flag ; Return with last flag state

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.

A300 .find_station_bit2←1← A39F JSR
LDX #&10 ; X=&10: scan 16 slots (15 to 0)
A302 CLV ; Clear V
A303 .loop_search_stn_bit2←2← A309 BNE← A310 BEQ
DEX ; Try next slot
A304 BMI done_search_bit2 ; All slots checked: not found
A306 JSR match_station_net ; Compare station/network
A309 BNE loop_search_stn_bit2 ; No match: try next
A30B LDA chan_status,x ; Load slot status byte
A30E AND #4 ; Test bit 2 (PS active flag)?
A310 BEQ loop_search_stn_bit2 ; Not set: try next
A312 TYA ; Transfer Y to A
A313 STA fcb_net_or_port,x ; Store Y in slot data
A316 BIT bit_test_ff ; Set V (found match)
A319 .done_search_bit2←1← A304 BMI
STY fs_urd_handle ; Store Y to l0e02
A31C BVS set_flags_bit2 ; V set: found, skip allocation
A31E TYA ; Transfer Y to A
A31F JSR alloc_fcb_slot ; Allocate FCB slot
A322 STA handle_1_fcb ; Store allocation result
A325 BEQ jmp_restore_fs_ctx ; Zero: failed, restore context
A327 .set_flags_bit2←1← A31C BVS
LDA #&26 ; A=&26: station flags value
A329 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.

A32B .find_station_bit3←3← A2F4 JSR← A35D JSR← A3A5 JSR
LDX #&10 ; X=&10: scan 16 slots (15 to 0)
A32D CLV ; Clear V
A32E .loop_search_stn_bit3←2← A334 BNE← A33B BEQ
DEX ; Try next slot
A32F BMI done_search_bit3 ; All slots checked: not found
A331 JSR match_station_net ; Compare station/network
A334 BNE loop_search_stn_bit3 ; No match: try next
A336 LDA chan_status,x ; Load slot status byte
A339 AND #8 ; Test bit 3 (FS active flag)?
A33B BEQ loop_search_stn_bit3 ; Not set: try next
A33D TYA ; Transfer Y to A
A33E STA fcb_net_or_port,x ; Store Y in slot data
A341 BIT bit_test_ff ; Set V (found match)
A344 .done_search_bit3←1← A32F BMI
STY fs_csd_handle ; Store Y to l0e03
A347 BVS set_flags_bit3 ; V set: found, skip allocation
A349 TYA ; Transfer Y to A
A34A JSR alloc_fcb_slot ; Allocate FCB slot
A34D STA handle_2_fcb ; Store allocation result
A350 BEQ jmp_restore_fs_ctx ; Zero: failed, restore context
A352 .set_flags_bit3←1← A347 BVS
LDA #&2a ; A=&2A: station flags value
A354 BNE store_stn_flags_restore ; ALWAYS branch

*Flip command handler

Exchanges the CSD and CSL (library) handles. Saves the current CSD handle (&0E03), loads the library handle (&0E04) 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
A356 .cmd_flip
LDA fs_csd_handle ; Load current CSD handle
A359 PHA ; Save CSD handle
A35A LDY fs_lib_handle ; Load library handle into Y
A35D JSR find_station_bit3 ; Install library as new CSD
A360 PLA ; Restore original CSD handle
A361 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.

A362 .flip_set_station_boot←2← A2FA JSR← A3AB JSR
LDX #&10 ; X=&10: max 16 station entries
A364 CLV ; Clear V (no match found yet)
A365 .loop_search_stn_boot←2← A36B BNE← A372 BEQ
DEX ; Decrement station index
A366 BMI done_search_boot ; All searched: exit loop
A368 JSR match_station_net ; Check if station[X] matches
A36B BNE loop_search_stn_boot ; No match: try next station
A36D LDA chan_status,x ; Load station flags byte
A370 AND #&10 ; Test bit 4 (active flag)
A372 BEQ loop_search_stn_boot ; Not active: try next station
A374 TYA ; Transfer boot type to A
A375 STA fcb_net_or_port,x ; Store boot setting for station
A378 BIT bit_test_ff ; Set V flag (station match found)
A37B .done_search_boot←1← A366 BMI
STY fs_lib_handle ; Store boot type
A37E BVS set_flags_boot ; V set (matched): skip allocation
A380 TYA ; Boot type to A
A381 JSR alloc_fcb_slot ; Allocate FCB slot for new entry
A384 STA handle_3_fcb ; Store allocation result
A387 BEQ jmp_restore_fs_ctx ; Zero: allocation failed, exit
A389 .set_flags_boot←1← A37E BVS
LDA #&32 ; A=&32: station flags (active+boot)
A38B .store_stn_flags_restore←2← A329 BNE← A354 BNE
STA chan_status,x ; Store station flags
A38E .jmp_restore_fs_ctx←3← A325 BEQ← A350 BEQ← A387 BEQ
JMP restore_fs_context ; Restore FS context and return
A391 .fsreply_1_copy_handles_boot
JSR close_all_net_chans ; Close all network channels
A394 SEC ; Set carry flag
A395 LDA fs_reply_cmd ; Load reply boot type
A398 STA fs_boot_option ; Store as current boot type
A39B .fsreply_2_copy_handles
PHP ; Save processor status
A39C LDY fs_cmd_data ; Load station number from reply
A39F JSR find_station_bit2 ; Find station entry with bit 2
A3A2 LDY fs_func_code ; Load network number from reply
A3A5 JSR find_station_bit3 ; Find station entry with bit 3
A3A8 LDY fs_data_count ; Load boot type from reply
A3AB JSR flip_set_station_boot ; Set boot config for station
A3AE PLP ; Restore processor status
A3AF BCS check_auto_boot_flag ; Carry set: proceed with boot
A3B1 JMP return_with_last_flag ; Return with last flag
A3B4 .check_auto_boot_flag←1← A3AF BCS
LDA fs_lib_flags ; Load config flags
A3B7 TAX ; Save copy in X
A3B8 AND #4 ; Test bit 2 (auto-boot flag)
A3BA PHP ; Save bit 2 test result
A3BB TXA ; Restore full flags
A3BC AND #&fb ; Clear bit 2 (consume flag)
A3BE STA fs_lib_flags ; Store cleared flags
A3C1 PLP ; Restore bit 2 test result
A3C2 BNE load_boot_type ; Bit 2 was set: skip to boot cmd
A3C4 LDA #osbyte_scan_keyboard ; OSBYTE &79: scan keyboard
A3C6 LDX #(255 - inkey_key_ctrl) EOR 128 ; X=internal key number EOR 128
A3C8 JSR osbyte ; Test for 'CTRL' key pressed (X=129)
A3CB TXA ; X has top bit set if 'CTRL' pressed
A3CC BPL load_boot_type ; CTRL not pressed: proceed to boot
A3CE .boot_load_cmd←1← A3E6 BEQ
RTS ; CTRL pressed: cancel boot, return
A3CF EQUS "L.!BOOT"
A3D6 EQUB &0D
A3D7 .boot_exec_cmd
EQUS "E.!BOOT"
A3DE EQUB &0D
; Boot option OSCLI address table
; Low bytes of boot command string addresses,
; all in page &A3. Indexed by boot option 0-3
; (option 0 is never reached due to BEQ).
; Entry 2 reuses the tail of 'L.!BOOT' to
; get '!BOOT' (*RUN equivalent).
A3DF .boot_oscli_lo_table←1← A3E8 LDX
EQUB &DE
A3E0 EQUB &CF
A3E1 EQUB &D1
A3E2 EQUB &D7
A3E3 .load_boot_type←2← A3C2 BNE← A3CC BPL
LDY fs_boot_option ; Load boot type
A3E6 BEQ boot_load_cmd ; Type 0: no command, just return
A3E8 .boot_cmd_oscli←1← A28E JMP
LDX boot_oscli_lo_table,y ; Look up boot command address low
A3EB LDY #&a3 ; Boot command address high (&A3xx)
A3ED JMP oscli ; Execute boot command via OSCLI
; Star command table (4 interleaved sub-tables).
; Each entry: ASCII name + flag byte (&80+) +
; dispatch address word (PHA/PHA/RTS, addr-1).
; Sub-tables separated by &80 sentinel bytes.
; Flag byte: bit 7 = end of name marker,
; bit 6 = set V on return if no argument,
; bits 0-4 = *HELP syntax string index.
; 1: Utility cmds 2: NFS commands
; 3: Help topics 4: Copro/attributes
A3F0 .cmd_table_fs←12← 8BAE LDA← 8BBD LDA← 8BC5 LDA← 8BD2 LDA← 8C00 LDA← 8C07 LDA← 932A LDA← 9332 LDA← A145 LDA← A14A LDA← A15A LDA← A191 LDA
EQUS "C" ; *Close (first char)
A3F1 .cmd_table_fs_lo←3← 8C93 LDA← A13B LDA← B326 ORA
EQUS "l" ; *Close cont (dispatch lo base)
A3F2 .cmd_table_fs_hi←3← 8C8F LDA← A137 LDA← B353 AND
EQUS "ose" ; *Close cont (dispatch hi base)
A3F5 EQUB &80 ; No syntax
A3F6 EQUW cmd_close-1
A3F8 EQUS "Dump" ; *Dump
A3FC EQUB &C4
A3FD EQUW cmd_dump-1
A3FF EQUS "Net" ; *Net (select NFS)
A402 EQUB &80 ; No syntax
A403 EQUW cmd_net_fs-1
A405 EQUS "Pollps" ; *Pollps
A40B EQUB &88 ; Syn 8: (<stn. id.>|<ps type>)
A40C EQUW cmd_pollps-1
A40E EQUS "Print" ; *Print
A413 EQUB &CC ; V no arg; syn 12: <filename>
A414 EQUW cmd_print-1
A416 EQUS "Prot" ; *Prot
A41A EQUB &8E ; Syn 14: (attribute keywords)
A41B EQUW cmd_prot-1
A41D EQUS "PS" ; *PS; syn 8: (<stn. id.>|<ps type>)
A41F EQUB &88
A420 EQUW cmd_ps-1
A422 EQUS "Roff" ; *Roff
A426 EQUB &80 ; No syntax
A427 EQUW cmd_roff-1
A429 EQUS "Type" ; *Type
A42D EQUB &CC ; V no arg; syn 12: <filename>
A42E EQUW cmd_type-1
A430 EQUS "Unprot" ; *Unprot
A436 EQUB &8E ; Syn 14: (attribute keywords)
A437 EQUW cmd_unprot-1
A439 EQUB &80 ; End of utility sub-table
A43A .cmd_table_nfs
EQUS "Access" ; *Access
A440 EQUB &C9 ; V no arg; syn 9: <obj> (L)(W)(R)...
A441 EQUW cmd_fs_operation-1
A443 EQUS "Bye" ; *Bye
A446 EQUB &80 ; No syntax
A447 EQUW cmd_bye-1
A449 EQUS "Cdir" ; *Cdir
A44D EQUB &C6 ; V no arg; syn 6: <dir> (<number>)
A44E EQUW cmd_cdir-1
A450 EQUS "Delete" ; *Delete
A456 EQUB &C3 ; V no arg; syn 3: <object>
A457 EQUW cmd_fs_operation-1
A459 EQUS "Dir" ; *Dir
A45C EQUB &81 ; Syn 1: (<dir>)
A45D EQUW cmd_dir-1
A45F EQUS "Ex" ; *Ex; syn 1: (<dir>)
A461 EQUB &81
A462 EQUW cmd_ex-1
A464 EQUS "Flip" ; *Flip
A468 EQUB &80 ; No syntax
A469 EQUW cmd_flip-1
A46B EQUS "FS" ; *FS; syn 11: (<stn. id.>)
A46D EQUB &8B
A46E EQUW cmd_fs-1
A470 EQUS "Info" ; *Info
A474 EQUB &C3 ; V no arg; syn 3: <object>
A475 EQUW cmd_fs_operation-1
A477 .cmd_table_nfs_iam←1← 8DAF LDA
EQUS "I am" ; *I am
A47B EQUB &C2 ; V no arg; syn 2: (<stn>) <user>...
A47C EQUW cmd_iam-1
A47E EQUS "Lcat" ; *Lcat
A482 EQUB &81 ; Syn 1: (<dir>)
A483 EQUW cmd_lcat-1
A485 EQUS "Lex" ; *Lex
A488 EQUB &81 ; Syn 1: (<dir>)
A489 EQUW cmd_lex-1
A48B EQUS "Lib" ; *Lib
A48E EQUB &C5 ; V no arg; syn 5: <dir>
A48F EQUW cmd_fs_operation-1
A491 EQUS "Pass" ; *Pass
A495 EQUB &C7 ; V no arg; syn 7: <pass> ...
A496 EQUW cmd_pass-1
A498 EQUS "Remove" ; *Remove
A49E EQUB &C3
A49F EQUW cmd_remove-1
A4A1 EQUS "Rename" ; *Rename V no arg; syn 3: <object>
A4A7 EQUB &CA ; V no arg; syn 10: <file> <new file>
A4A8 EQUW cmd_rename-1
A4AA EQUS "Wipe" ; *Wipe
A4AE EQUB &81
A4AF EQUW cmd_wipe-1
A4B1 EQUB &80 ; End of NFS sub-table
A4B2 .cmd_table_help
EQUB &14, &8E
A4B4 EQUS "Net" ; *Net (local)
A4B7 EQUB &80 ; No syntax
A4B8 EQUW help_net-1
A4BA EQUS "Utils" ; *Utils
A4BF EQUB &80 ; No syntax
A4C0 EQUW help_utils-1
A4C2 EQUB &80 ; End of help topic sub-table
; Protection attribute keyword table. Each entry:
; ASCII name + flag byte (&80+) + OR mask + AND mask.
; Used by *Prot (ORA lo byte) and *Unprot (AND hi
; byte) to set/clear individual protection bits.
; Also listed by *HELP Prot/*HELP Unprot via the
; shared commands handler (syntax index 14).
; Bits: 0=Peek 1=Poke 2=JSR 3=Proc 4=Utils 5=Halt
A4C3 .cmd_table_copro
EQUS "Halt" ; Halt
A4C7 EQUB &FC ; Flag &FC: V no arg, syn 28 (unused)
A4C8 EQUB &20 ; *Prot OR mask: bit 5
A4C9 EQUB &DF ; *Unprot AND mask: ~bit 5
A4CA EQUS "JSR" ; JSR
A4CD EQUB &FC ; Flag &FC: V no arg, syn 28 (unused)
A4CE EQUB &04 ; *Prot OR mask: bit 2
A4CF EQUB &FB ; *Unprot AND mask: ~bit 2
A4D0 EQUS "Peek" ; Peek
A4D4 EQUB &FC ; Flag &FC: V no arg, syn 28 (unused)
A4D5 EQUB &01 ; *Prot OR mask: bit 0
A4D6 EQUB &FE ; *Unprot AND mask: ~bit 0
A4D7 EQUS "Poke" ; Poke
A4DB EQUB &FC ; Flag &FC: V no arg, syn 28 (unused)
A4DC EQUB &02 ; *Prot OR mask: bit 1
A4DD EQUB &FD ; *Unprot AND mask: ~bit 1
A4DE EQUS "Proc" ; Proc
A4E2 EQUB &FC ; Flag &FC: V no arg, syn 28 (unused)
A4E3 EQUB &08 ; *Prot OR mask: bit 3
A4E4 EQUB &F7 ; *Unprot AND mask: ~bit 3
A4E5 EQUS "Utils" ; Utils
A4EA EQUB &A9 ; Flag &A9: syn 9 (unused)
A4EB EQUB &10 ; *Prot OR mask: bit 4
A4EC EQUB &EF ; *Unprot AND mask: ~bit 4
A4ED EQUB &80 ; End of attribute keyword table

Filing system OSWORD entry

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.

A4EE .svc_8_osword
CLC ; CLC so SBC subtracts value+1
A4EF LDA osbyte_a_copy ; A = OSWORD number
A4F1 SBC #&0d ; A = OSWORD - &0E (CLC+SBC = -&0E)
A4F3 BMI return_from_osword_setup ; Below &0E: not ours, return
A4F5 CMP #7 ; Index >= 7? (OSWORD > &14)
A4F7 BCS return_from_osword_setup ; Above &14: not ours, return
A4F9 TAX ; X=OSWORD handler index (0-6)
A4FA LDY #6 ; Y=6: save 6 workspace bytes
A4FC .loop_ca4fc←1← A507 BNE
LDA svc_state,y ; Load current workspace byte
A4FF PHA ; Save on stack
A500 LDA l00ed,y ; Load OSWORD parameter byte
A503 STA svc_state,y ; Copy parameter to workspace
A506 DEY ; Next byte down
A507 BNE loop_ca4fc ; Loop for all 6 bytes
A509 JSR osword_setup_handler ; Set up dispatch and save state
A50C LDY #&fa ; Y=&FA: restore 6 workspace bytes
A50E .loop_ca50e←1← A513 BNE
PLA ; Restore saved workspace byte
A50F STA lffb0,y ; Store to osword_flag workspace
A512 INY ; Next byte
A513 BNE loop_ca50e ; Loop until all 6 restored
A515 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)
A516 .osword_setup_handler←1← A509 JSR
LDA osword_dispatch_hi_table,x ; X = OSWORD index (0-6) Load handler address high byte
A519 PHA ; Push high byte for RTS dispatch
A51A LDA osword_dispatch_lo_table,x ; Load handler address low byte
A51D PHA ; Push low byte for RTS dispatch
A51E LDA (ws_ptr_hi),y ; Copy 3 bytes (Y=2,1,0) Load PB byte 0 (OSWORD sub-code)
A520 STY svc_state ; Load from osword_flag workspace Clear service state
A522 .return_from_osword_setup←2← A4F3 BMI← A4F7 BCS
RTS ; RTS dispatches to pushed handler
; OSWORD dispatch table (7 entries, split lo/hi).
; PHA/PHA/RTS dispatch used by svc_8_osword.
; Maps OSWORD codes &0E-&14 to handler routines.
A523 .osword_dispatch_lo_table←1← A51A LDA
EQUB <(osword_0e_handler-1) ; lo-&0E: Read clock lo-&13: Misc operations Store to RX buffer
A524 EQUB <(return_from_osword_setup-1)
A525 EQUB <(osword_10_handler-1)
A526 EQUB <(osword_11_handler-1)
A527 EQUB <(osword_12_handler-1)
A528 EQUB <(osword_13_dispatch-1)
A529 EQUB <(osword_14_handler-1)
A52A .osword_dispatch_hi_table←1← A516 LDA
EQUB >(osword_0e_handler-1)
A52B EQUB >(return_from_osword_setup-1) ; hi-&0E: Read clock
A52C EQUB >(osword_10_handler-1) ; hi-&0F: (unimplemented)
A52D EQUB >(osword_11_handler-1) ; hi-&10: Transmit
A52E EQUB >(osword_12_handler-1)
A52F EQUB >(osword_13_dispatch-1) ; hi-&12: Read station info
A530 EQUB >(osword_14_handler-1)
A531 .osword_0e_handler
BIT fs_flags ; hi-&14: Bridge/net config Test station active flag
A534 BPL return_from_osword_0e ; Not active: just return
A536 CMP #4 ; Restore A (OSWORD sub-code) Sub-code = 4? (read clock)
A538 BEQ save_txcb_and_convert ; Yes: handle clock read
A53A LDA #8 ; Other sub-codes: set state = 8
A53C STA svc_state ; Store service state
A53E .return_from_osword_0e←1← A534 BPL
RTS ; Return
A53F .save_txcb_and_convert←1← A538 BEQ
LDX #0 ; X=0: start of TX control block
A541 LDY #&10 ; Y=&10: length of TXCB to save
A543 JSR save_net_tx_cb ; Save current TX control block
A546 LDA fs_load_vector ; Load seconds from clock workspace
A549 JSR bin_to_bcd ; Convert binary to BCD
A54C STA fs_load_upper ; Store BCD seconds
A54F LDA fs_reply_cmd ; Load minutes from clock workspace
A552 JSR bin_to_bcd ; Convert binary to BCD
A555 STA fs_handle_check ; Store BCD minutes
A558 LDA fs_data_count ; Load hours from clock workspace
A55B JSR bin_to_bcd ; Convert binary to BCD
A55E STA fs_load_vector ; Store BCD hours
A561 LDA #0 ; Clear hours high position
A563 STA fs_reply_cmd ; Store zero
A566 LDA fs_func_code ; Load day+month byte
A569 PHA ; Save for later high nibble extract
A56A LDA fs_cmd_data ; Load day value
A56D JSR bin_to_bcd ; Convert day to BCD
A570 STA fs_data_count ; Store BCD day
A573 PLA ; Restore day+month byte
A574 PHA ; Save again for month extract
A575 AND #&0f ; Mask low nibble (month low bits)
A577 JSR bin_to_bcd ; Convert to BCD
A57A STA fs_func_code ; Store BCD month
A57D PLA ; Restore day+month byte
A57E LSR ; Shift high nibble down
A57F LSR ; Continue shifting
A580 LSR ; Continue shifting
A581 LSR ; 4th shift: isolate high nibble
A582 ADC #&51 ; Add &51 for year offset + carry
A584 JSR bin_to_bcd ; Convert year to BCD
A587 STA fs_cmd_data ; Store BCD year
A58A LDY #6 ; Copy 7 bytes (Y=6 down to 0)
A58C .loop_copy_bcd_to_pb←1← A592 BPL
LDA fs_cmd_data,y ; Load BCD byte from workspace
A58F STA (ws_ptr_hi),y ; Store to parameter block
A591 DEY ; Next byte down
A592 BPL loop_copy_bcd_to_pb ; Loop for all 7 bytes
A594 RTS ; Return

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
A595 .bin_to_bcd←6← A549 JSR← A552 JSR← A55B JSR← A56D JSR← A577 JSR← A584 JSR
PHP ; Save processor flags (decimal mode)
A596 TAX ; X = binary count
A597 BEQ done_bcd_convert ; Zero: result is 0, skip loop
A599 SED ; Set decimal mode for BCD add
A59A LDA #0 ; Start BCD result at 0
A59C .loop_bcd_add←1← A5A0 BNE
CLC ; Clear carry for BCD add
A59D ADC #1 ; Add 1 in decimal mode
A59F DEX ; Count down binary value
A5A0 BNE loop_bcd_add ; Loop until zero
A5A2 .done_bcd_convert←1← A597 BEQ
PLP ; Restore flags (clears decimal mode)
A5A3 RTS ; Return with BCD result in A
A5A4 .osword_10_handler
ASL ws_0d60 ; Shift ws_0d60 left (status flag)
A5A7 TYA ; A = Y (saved index)
A5A8 BCS setup_ws_rx_ptrs ; C=1: transmit active path
A5AA STA (ws_ptr_hi),y ; C=0: store Y to parameter block
A5AC RTS ; Return (transmit not active)
A5AD .setup_ws_rx_ptrs←1← A5A8 BCS
LDA net_rx_ptr_hi ; Set workspace high byte
A5AF STA ws_ptr_lo ; Copy to ws_ptr_lo
A5B1 STA nmi_tx_block_hi ; Also set as NMI TX block high
A5B3 LDA #&6f ; Low byte = &6F
A5B5 STA osword_flag ; Set osword_flag
A5B7 STA nmi_tx_block ; Set NMI TX block low
A5B9 LDX #&0f ; X=&0F: byte count for copy
A5BB JSR copy_pb_byte_to_ws ; Copy data and begin transmission
A5BE JMP tx_begin ; Jump to begin Econet transmission
A5C1 .osword_11_handler
LDX nfs_workspace_hi ; Load NFS workspace page high byte
A5C3 STX ws_ptr_lo ; Set workspace pointer high
A5C5 STY osword_flag ; Set workspace pointer low from Y
A5C7 ROR econet_flags ; Rotate Econet flags (save interrupt state)
A5CA TAY ; Y=OSWORD flag (slot specifier)
A5CB STA work_ae ; Store OSWORD flag
A5CD BNE use_specified_slot ; Non-zero: use specified slot
A5CF LDA #3 ; A=3: start searching from slot 3
A5D1 .loop_find_rx_slot←1← A5E3 BNE
JSR byte_to_2bit_index ; Convert slot to 2-bit workspace index
A5D4 BCS store_rx_result ; C set: slot invalid, store result
A5D6 LSR ; Shift index right (divide by 4)
A5D7 LSR ; Continue shift
A5D8 TAX ; Transfer to X as workspace offset
A5D9 LDA (osword_flag),y ; Load workspace byte at offset
A5DB BEQ store_rx_result ; Zero: slot empty, store result
A5DD CMP #&3f ; Compare with &3F ('?' marker)
A5DF BEQ store_rx_slot_found ; Match: slot found for receive
A5E1 INX ; Try next slot index
A5E2 TXA ; Transfer back to A
A5E3 BNE loop_find_rx_slot ; Loop back (A != 0)
A5E5 .store_rx_slot_found←1← A5DF BEQ
TXA ; Transfer found slot to A
A5E6 LDX #0 ; X=0: index for indirect store
A5E8 STA (ws_ptr_hi,x) ; Store slot number to PB byte 0
A5EA .use_specified_slot←1← A5CD BNE
JSR byte_to_2bit_index ; Convert specified slot to workspace index
A5ED BCS store_rx_result ; C set: slot invalid, store result
A5EF DEY ; Y=Y-1: adjust workspace offset
A5F0 STY osword_flag ; Update workspace pointer low
A5F2 LDA #&c0 ; A=&C0: slot active marker
A5F4 LDY #1 ; Y=1: workspace byte offset
A5F6 LDX #&0b ; X=&0B: byte count for PB copy
A5F8 CPY work_ae ; Compare Y with OSWORD flag
A5FA ADC (osword_flag),y ; Add workspace byte (check slot state)
A5FC BEQ copy_pb_and_mark ; Zero: slot ready, copy PB and mark
A5FE BMI increment_and_retry ; Negative: slot busy, increment and retry
A600 .loop_copy_slot_data←1← A610 BNE
CLC ; Clear carry for PB copy
A601 .copy_pb_and_mark←1← A5FC BEQ
JSR copy_pb_byte_to_ws ; Copy PB byte to workspace slot
A604 BCS osword_11_done ; C set: copy done, finish
A606 LDA #&3f ; A=&3F: mark slot as pending ('?')
A608 LDY #1 ; Y=1: workspace flag offset
A60A STA (osword_flag),y ; Store pending marker to workspace
A60C BNE osword_11_done ; ALWAYS branch
A60E .increment_and_retry←1← A5FE BMI
ADC #1 ; Increment retry counter
A610 BNE loop_copy_slot_data ; Non-zero: retry copy loop
A612 DEY ; Decrement Y (adjust offset)
A613 .store_rx_result←3← A5D4 BCS← A5DB BEQ← A5ED BCS
STA (ws_ptr_hi),y ; Store result A to PB via Y
A615 .osword_11_done←2← A604 BCS← A60C BNE
ROL econet_flags ; Rotate Econet flags back (restore state)
A618 RTS ; Return from OSWORD 11 handler
A619 .osword_12_handler
LDA net_rx_ptr_hi ; Set workspace from RX ptr high
A61B STA ws_ptr_lo ; Store to ws_ptr_lo
A61D LDY #&7f ; Y=&7F: last byte of RX buffer
A61F LDA (net_rx_ptr),y ; Load port/count from RX buffer
A621 INY ; Y=&80: set workspace pointer Y=&80
A622 STY osword_flag ; Store as osword_flag
A624 TAX ; X = port/count value
A625 DEX ; X-1: adjust count
A626 LDY #0 ; Y=0 for copy
A628 JSR copy_pb_byte_to_ws ; Copy workspace data
A62B JMP commit_state_byte ; Update state and return
A62E .osword_13_dispatch
TAX ; X = sub-code
A62F CMP #&13 ; Sub-code < &13?
A631 BCS return_from_osword_13 ; Out of range: return
A633 LDA osword_13_hi_table,x ; Load handler address high byte
A636 PHA ; Push high byte
A637 LDA osword_13_lo_table,x ; Load handler address low byte
A63A PHA ; Push low byte
A63B .return_from_osword_13←1← A631 BCS
RTS ; RTS dispatches to handler
A63C .osword_13_lo_table←1← A637 LDA
EQUB <(osword_13_read_station-1)
A63D EQUB <(osword_13_set_station-1)
A63E EQUB <(osword_13_read_ws_pair-1)
A63F EQUB <(osword_13_write_ws_pair-1)
A640 EQUB <(osword_13_read_prot-1)
A641 EQUB <(osword_13_write_prot-1)
A642 EQUB <(osword_13_read_handles-1)
A643 EQUB <(osword_13_set_handles-1)
A644 EQUB <(osword_13_read_rx_flag-1)
A645 EQUB <(osword_13_read_rx_port-1)
A646 EQUB <(osword_13_read_error-1)
A647 EQUB <(osword_13_read_context-1)
A648 EQUB <(osword_13_read_csd-1)
A649 EQUB <(osword_13_write_csd-1)
A64A EQUB <(osword_13_read_free_bufs-1)
A64B EQUB <(osword_13_read_ctx_3-1)
A64C EQUB <(osword_13_write_ctx_3-1)
A64D EQUB <(osword_13_bridge_query-1)
A64E .osword_13_hi_table←1← A633 LDA
EQUB >(osword_13_read_station-1) ; hi-sub 0: read FS station
A64F EQUB >(osword_13_set_station-1) ; hi-sub 1: set FS station
A650 EQUB >(osword_13_read_ws_pair-1) ; hi-sub 2: read workspace pair
A651 EQUB >(osword_13_write_ws_pair-1) ; hi-sub 3: write workspace pair
A652 EQUB >(osword_13_read_prot-1) ; hi-sub 4: read protection mask
A653 EQUB >(osword_13_write_prot-1) ; hi-sub 5: write protection mask
A654 EQUB >(osword_13_read_handles-1) ; hi-sub 6: read FCB handles
A655 EQUB >(osword_13_set_handles-1) ; hi-sub 7: set FCB handles
A656 EQUB >(osword_13_read_rx_flag-1) ; hi-sub 8: read RX flag
A657 EQUB >(osword_13_read_rx_port-1) ; hi-sub 9: read RX port
A658 EQUB >(osword_13_read_error-1) ; hi-sub 10: read error flag
A659 EQUB >(osword_13_read_context-1) ; hi-sub 11: read context byte
A65A EQUB >(osword_13_read_csd-1) ; hi-sub 12: read CSD path
A65B EQUB >(osword_13_write_csd-1) ; hi-sub 13: write CSD path
A65C EQUB >(osword_13_read_free_bufs-1) ; hi-sub 14: read free buffers
A65D EQUB >(osword_13_read_ctx_3-1) ; hi-sub 15: read 3 context bytes
A65E EQUB >(osword_13_write_ctx_3-1) ; hi-sub 16: write 3 context bytes
A65F EQUB >(osword_13_bridge_query-1) ; hi-sub 17: query bridge status

OSWORD &13 sub 0: read file server station

Returns the current file server station and network numbers in PB[1..2]. If the NFS is not active (l0d6c bit 7 clear), returns zero in PB[0] instead.

A660 .osword_13_read_station
BIT fs_flags ; NFS active?
A663 BMI read_station_bytes ; Yes: read station data
A665 .nfs_inactive_exit←1← A676 BPL
JMP return_zero_in_pb ; No: return zero
A668 .read_station_bytes←1← A663 BMI
LDY #2 ; Y=2: copy 2 bytes
A66A .loop_copy_station←1← A670 BNE
LDA fs_server_base,y ; Load station byte
A66D STA (osword_pb_ptr),y ; Store to PB[Y]
A66F DEY ; Previous byte
A670 BNE loop_copy_station ; Loop for bytes 2..1
A672 RTS ; Return

OSWORD &13 sub 1: set file server station

Sets the file server station and network numbers from PB[1..2]. Processes all FCBs, then scans the 16-entry FCB table to reassign handles matching the new station. If the NFS is not active, returns zero.

A673 .osword_13_set_station
BIT fs_flags ; NFS active?
A676 BPL nfs_inactive_exit ; No: return zero
A678 LDY #0 ; Y=0 for process_all_fcbs
A67A JSR process_all_fcbs ; Close all open FCBs
A67D LDY #2 ; Y=2: copy 2 bytes
A67F .loop_store_station←1← A685 BNE
LDA (osword_pb_ptr),y ; Load new station byte from PB
A681 STA fs_server_base,y ; Store to l0dff
A684 DEY ; Previous byte
A685 BNE loop_store_station ; Loop for bytes 2..1
A687 JSR clear_if_station_match ; Clear handles if station matches
A68A LDX #&0f ; X=&0F: scan 16 FCB entries
A68C .scan_fcb_entry←1← A6E5 BPL
LDA chan_status,x ; Load FCB flags
A68F TAY ; Save flags in Y
A690 AND #2 ; Test bit 1 (FCB allocated?)
A692 BEQ next_fcb_entry ; No: skip to next entry
A694 TYA ; Restore flags
A695 AND #&df ; Clear bit 5 (pending update)
A697 STA chan_status,x ; Store updated flags
A69A TAY ; Save in Y
A69B JSR match_station_net ; Does FCB match new station?
A69E BNE next_fcb_entry ; No match: skip to next
A6A0 CLC ; Clear carry for ADC
A6A1 TYA ; Restore flags
A6A2 AND #4 ; Test bit 2 (handle 1 active?)
A6A4 BEQ check_handle_2 ; No: check handle 2
A6A6 TYA ; Restore flags
A6A7 ORA #&20 ; Set bit 5 (handle reassigned)
A6A9 TAY ; Save updated flags
A6AA LDA fcb_net_or_port,x ; Get FCB high byte
A6AD STA fs_urd_handle ; Store as handle 1 station
A6B0 TXA ; FCB index
A6B1 ADC #&20 ; Add &20 for FCB table offset
A6B3 STA handle_1_fcb ; Store as handle 1 FCB index
A6B6 .check_handle_2←1← A6A4 BEQ
TYA ; Restore flags
A6B7 AND #8 ; Test bit 3 (handle 2 active?)
A6B9 BEQ check_handle_3 ; No: check handle 3
A6BB TYA ; Restore flags
A6BC ORA #&20 ; Set bit 5
A6BE TAY ; Save updated flags
A6BF LDA fcb_net_or_port,x ; Get FCB high byte
A6C2 STA fs_csd_handle ; Store as handle 2 station
A6C5 TXA ; FCB index
A6C6 ADC #&20 ; Add &20 for FCB table offset
A6C8 STA handle_2_fcb ; Store as handle 2 FCB index
A6CB .check_handle_3←1← A6B9 BEQ
TYA ; Restore flags
A6CC AND #&10 ; Test bit 4 (handle 3 active?)
A6CE BEQ store_updated_status ; No: store final flags
A6D0 TYA ; Restore flags
A6D1 ORA #&20 ; Set bit 5
A6D3 TAY ; Save updated flags
A6D4 LDA fcb_net_or_port,x ; Get FCB high byte
A6D7 STA fs_lib_handle ; Store as handle 3 station
A6DA TXA ; FCB index
A6DB ADC #&20 ; Add &20 for FCB table offset
A6DD STA handle_3_fcb ; Store as handle 3 FCB index
A6E0 .store_updated_status←1← A6CE BEQ
TYA ; Store final flags for this FCB
A6E1 STA chan_status,x ; Update l1060[X]
A6E4 .next_fcb_entry←2← A692 BEQ← A69E BNE
DEX ; Next FCB entry
A6E5 BPL scan_fcb_entry ; Loop for all 16 entries
A6E7 RTS ; Return

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.

A6E8 .osword_13_read_csd
CLC ; C=0: workspace-to-PB direction
A6E9 BCC setup_csd_copy ; Skip SEC ALWAYS branch

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.

A6EB .osword_13_write_csd
SEC ; C=1: PB-to-workspace direction
A6EC .setup_csd_copy←1← A6E9 BCC
LDA #&17 ; Workspace offset &17
A6EE STA osword_flag ; Set ws_ptr_lo
A6F0 LDA net_rx_ptr_hi ; Page from RX pointer high byte
A6F2 STA ws_ptr_lo ; Set ws_ptr_hi
A6F4 LDY #1 ; Y=1: first PB data byte
A6F6 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
A6F8 .copy_pb_byte_to_ws←5← A5BB JSR← A601 JSR← A628 JSR← A704 BPL← A711 BCC
BCC copy_ws_byte_to_pb ; C=0: skip PB-to-WS copy
A6FA LDA (ws_ptr_hi),y ; C=1: load from parameter block
A6FC STA (osword_flag),y ; Store to workspace
A6FE .copy_ws_byte_to_pb←1← A6F8 BCC
LDA (osword_flag),y ; Load from workspace
A700 STA (ws_ptr_hi),y ; Store to parameter block
A702 INY ; Next byte
A703 DEX ; Count down
A704 BPL copy_pb_byte_to_ws ; Loop for all bytes
A706 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.

A707 .osword_13_read_ws_pair
LDA nfs_workspace_hi ; Load workspace page high byte
A709 STA ws_ptr_lo ; Set ws_ptr_hi
A70B INY ; Y=1
A70C TYA ; A=1
A70D STA osword_flag ; Set ws_ptr_lo = 1
A70F TAX ; X=1: copy 2 bytes
A710 CLC ; C=0: workspace-to-PB direction
A711 BCC copy_pb_byte_to_ws ; Copy via copy_pb_byte_to_ws ALWAYS branch

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.

A713 .osword_13_write_ws_pair
INY ; Y=1: first PB data byte
A714 LDA (ws_ptr_hi),y ; Load PB[1]
A716 INY ; Y=2
A717 STA (nfs_workspace),y ; Store to (nfs_workspace)+2
A719 LDA (ws_ptr_hi),y ; Load PB[2]
A71B INY ; Y=3
A71C STA (nfs_workspace),y ; Store to (nfs_workspace)+3
A71E JSR init_bridge_poll ; Reinitialise bridge routing
A721 EOR (nfs_workspace),y ; Compare result with workspace
A723 BNE return_from_write_ws_pair ; Different: leave unchanged
A725 STA (nfs_workspace),y ; Same: clear workspace byte
A727 .return_from_write_ws_pair←1← A723 BNE
RTS ; Return

OSWORD &13 sub 4: read protection mask

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

A728 .osword_13_read_prot
LDA ws_0d68 ; Load protection mask
A72B JMP store_a_to_pb_1 ; Store to PB[1] and return

OSWORD &13 sub 5: write protection mask

Sets the protection mask from PB[1] via store_prot_mask.

A72E .osword_13_write_prot
INY ; Y=1: PB data offset
A72F LDA (ws_ptr_hi),y ; Load new mask from PB[1]
A731 JMP store_prot_mask ; Store via store_prot_mask

OSWORD &13 sub 6: read FCB handle info

Returns the 3-byte FCB handle/port data from l1071[1..3] in PB[1..3]. If the NFS is not active, returns zero in PB[0].

A734 .osword_13_read_handles
BIT fs_flags ; NFS active?
A737 BPL return_zero_in_pb ; No: return zero
A739 LDY #3 ; Y=3: copy 3 bytes
A73B .loop_copy_handles←1← A741 BNE
LDA fs_lib_flags,y ; Load handle byte
A73E STA (ws_ptr_hi),y ; Store to PB[Y]
A740 DEY ; Previous byte
A741 BNE loop_copy_handles ; Loop for bytes 3..1
A743 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 l1010/l1040 tables. For valid handles with bit 2 set in l1040, stores the station to l0e01+Y and FCB index to l1071+Y, then updates flag bits across all FCB entries via update_fcb_flag_bits.

A744 .osword_13_set_handles
BIT fs_flags ; NFS active?
A747 BMI start_set_handles ; Yes: process handles
A749 .return_zero_in_pb←2← A665 JMP← A737 BPL
LDA #0 ; A=0
A74B TAY ; Y=&00
A74C STA (ws_ptr_hi),y ; Store 0 to PB[0]
A74E RTS ; Return
A74F .start_set_handles←1← A747 BMI
LDY #1 ; Y=1: first handle in PB
A751 .validate_handle←1← A791 BNE
LDA (ws_ptr_hi),y ; Load handle value from PB[Y]
A753 CMP #&20 ; Must be >= &20
A755 BCC handle_invalid ; Below range: invalid
A757 CMP #&30 ; Must be < &30
A759 BCS handle_invalid ; Above range: invalid
A75B TAX ; X = handle value
A75C LDA fcb_attr_or_count_mid,x ; Load l1010[handle]
A75F BNE check_handle_alloc ; Non-zero: FCB exists
A761 .handle_invalid←3← A755 BCC← A759 BCS← A76D BEQ
LDA #0 ; Invalid: store 0 to PB[0]
A763 TAX ; X=&00
A764 STA (ws_ptr_hi,x) ; Clear PB[0] status
A766 BEQ next_handle_slot ; Skip to next handle ALWAYS branch
A768 .check_handle_alloc←1← A75F BNE
LDA fcb_flags,x ; Load l1040[handle] flags
A76B AND #2 ; Test bit 1 (allocated?)
A76D BEQ handle_invalid ; Not allocated: invalid
A76F TXA ; X = handle value
A770 STA fs_lib_flags,y ; Store handle to l1071+Y
A773 LDA fcb_attr_or_count_mid,x ; Load station from l1010
A776 STA fs_server_net,y ; Store station to l0e01+Y
A779 CPY #1 ; Is this handle 1 (Y=1)?
A77B BNE assign_handle_2 ; No: check handle 2
A77D TYA ; Save Y
A77E PHA ; Push Y
A77F LDY #4 ; Bit mask &04 for handle 1
A781 JSR update_fcb_flag_bits ; Update flags across all FCBs
A784 PLA ; Restore Y
A785 TAY ; Back to Y
A786 LDA fcb_flags,x ; Reload l1040 flags
A789 ORA #&24 ; Set bits 2+5 (active+updated)
A78B STA fcb_flags,x ; Store updated flags
A78E .next_handle_slot←3← A766 BEQ← A7AA BNE← A7BD BNE
INY ; Next handle slot
A78F CPY #4 ; Done all 3 handles?
A791 BNE validate_handle ; No: process next handle
A793 DEY ; Y=3 for return
A794 RTS ; Return
A795 .assign_handle_2←1← A77B BNE
CPY #2 ; Is this handle 2 (Y=2)?
A797 BNE assign_handle_3 ; No: must be handle 3
A799 TYA ; Save Y
A79A PHA ; Push Y
A79B LDY #8 ; Bit mask &08 for handle 2
A79D JSR update_fcb_flag_bits ; Update flags across all FCBs
A7A0 PLA ; Restore Y
A7A1 TAY ; Back to Y
A7A2 LDA fcb_flags,x ; Reload l1040 flags
A7A5 ORA #&28 ; Set bits 3+5 (active+updated)
A7A7 STA fcb_flags,x ; Store updated flags
A7AA BNE next_handle_slot ; Next handle slot ALWAYS branch
A7AC .assign_handle_3←1← A797 BNE
TYA ; Handle 3: save Y
A7AD PHA ; Push Y
A7AE LDY #&10 ; Bit mask &10 for handle 3
A7B0 JSR update_fcb_flag_bits ; Update flags across all FCBs
A7B3 PLA ; Restore Y
A7B4 TAY ; Back to Y
A7B5 LDA fcb_flags,x ; Reload l1040 flags
A7B8 ORA #&30 ; Set bits 4+5 (active+updated)
A7BA STA fcb_flags,x ; Store updated flags
A7BD BNE next_handle_slot ; Next handle slot ALWAYS branch

Update FCB flag bits across all entries

Scans all 16 FCB entries in l1060. 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)
A7BF .update_fcb_flag_bits←3← A781 JSR← A79D JSR← A7B0 JSR
TXA ; Save X (current FCB index)
A7C0 PHA ; Push X
A7C1 LDX #&0f ; X=&0F: scan 16 FCB entries
A7C3 .loop_scan_fcb_flags←1← A7DF BPL
LDA chan_status,x ; Load FCB flags
A7C6 ROL ; Shift bits 6-7 into bits 7-0
A7C7 ROL ; Bit 6 now in bit 7 (N flag)
A7C8 BPL next_flag_entry ; Bit 6 clear: skip entry
A7CA TYA ; Restore Y (bit mask)
A7CB AND chan_status,x ; Test mask bits against flags
A7CE BEQ no_flag_match ; Zero: no matching bits
A7D0 TYA ; Matching: restore Y
A7D1 ORA #&20 ; Set bit 5 (updated)
A7D3 BNE clear_flag_bits ; Skip clear path ALWAYS branch
A7D5 .no_flag_match←1← A7CE BEQ
TYA ; No match: restore Y
A7D6 .clear_flag_bits←1← A7D3 BNE
EOR #&ff ; Invert all bits
A7D8 AND chan_status,x ; Clear tested bits in flags
A7DB STA chan_status,x ; Store updated flags
A7DE .next_flag_entry←1← A7C8 BPL
DEX ; Next FCB entry
A7DF BPL loop_scan_fcb_flags ; Loop for all 16 entries
A7E1 PLA ; Restore original X
A7E2 TAX ; Back to X
A7E3 RTS ; Return

OSWORD &13 sub 8: read RX control block flag

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

A7E4 .osword_13_read_rx_flag
LDY #1 ; Y=1: RX control block offset
A7E6 LDA (net_rx_ptr),y ; Load (net_rx_ptr)+1
A7E8 LDY #0 ; Y=0
A7EA 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].

A7ED .osword_13_read_rx_port
LDY #&7f ; Y=&7F: port byte offset
A7EF LDA (net_rx_ptr),y ; Load (net_rx_ptr)+&7F
A7F1 LDY #1 ; Y=1
A7F3 STA (ws_ptr_hi),y ; Store to PB[1]
A7F5 INY ; Y=&02
A7F6 LDA #&80 ; A=&80
A7F8 STA (ws_ptr_hi),y ; Store &80 to PB[2]
A7FA RTS ; Return

OSWORD &13 sub 10: read error flag

Returns the error flag (l0e09) in PB[1].

A7FB .osword_13_read_error
LDA 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
A7FE .store_a_to_pb_1←4← A72B JMP← A7EA JMP← A805 BPL← A80D BCS
INY ; Y=1: parameter block offset 1
A7FF STA (ws_ptr_hi),y ; Store result to PB[1]
A801 RTS ; Return

OSWORD &13 sub 11: read context byte

Returns the context byte (l0d6d) in PB[1].

A802 .osword_13_read_context
LDA l0e08 ; Load context byte
A805 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.

A807 .osword_13_read_free_bufs
LDA #&6f ; Total buffers = &6F
A809 SEC ; Subtract used count
A80A SBC spool_buf_idx ; Free = &6F - l0d6b
A80D 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.

A80F .osword_13_read_ctx_3←1← A817 BNE
INY ; Next byte offset
A810 LDA fs_flags,y ; Return Load l0d6d[Y]
A813 STA (ws_ptr_hi),y ; Store to PB[Y]
A815 CPY #3 ; Done 3 bytes?
A817 BNE osword_13_read_ctx_3 ; No: loop
A819 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.

A81A .osword_13_write_ctx_3←1← A822 BNE
INY ; Next byte offset
A81B LDA (ws_ptr_hi),y ; Load PB[Y]
A81D STA fs_flags,y ; Store to l0d6d[Y]
A820 CPY #3 ; Done 3 bytes?
A822 BNE osword_13_write_ctx_3 ; No: loop
A824 RTS ; Return

OSWORD &13 sub 17: query bridge status

Calls init_bridge_poll, then returns the bridge status. If l0d72 is &FF (no bridge), stores 0 in PB[0]. Otherwise stores l0d72 in PB[1] and conditionally updates PB[3] based on station comparison.

A825 .osword_13_bridge_query
JSR init_bridge_poll ; Poll for bridge
A828 LDY #0 ; Y=0
A82A LDA l0d71 ; Load bridge status
A82D CMP #&ff ; Is it &FF (no bridge)?
A82F BNE bridge_found ; No: bridge found
A831 TYA ; A=&00
A832 STA (ws_ptr_hi),y ; PB[0] = 0 (no bridge)
A834 INY ; Y=&01
A835 BNE store_bridge_station ; ALWAYS branch
A837 .bridge_found←1← A82F BNE
INY ; Y=1
A838 STA (ws_ptr_hi),y ; PB[1] = bridge status
A83A INY ; Y=2
A83B INY ; Y=3
A83C LDA (ws_ptr_hi),y ; Load PB[3] (caller value)
A83E BEQ use_default_station ; Zero: use default station
A840 .compare_bridge_status
EOR l0d71 ; Compare with bridge status
A843 BNE return_from_bridge_query ; Different: return unchanged
A845 BEQ store_bridge_station ; Same: confirm station ALWAYS branch
A847 .use_default_station←1← A83E BEQ
LDA fs_server_net ; Load default from l0e01
A84A .store_bridge_station←2← A835 BNE← A845 BEQ
STA (ws_ptr_hi),y ; Store to PB[3]
A84C .return_from_bridge_query←1← A843 BNE
RTS ; Return
; Bridge discovery init data (24 bytes)
; Two 12-byte templates copied simultaneously by
; loop_copy_bridge_init. X counts down &0B to 0,
; copying the TXCB template into &C0. Y counts up
; &18 to &23, copying the RXCB data into workspace
; via bridge_ws_init_data (compare_bridge_status+1)
; + Y to reach the RXCB data area.
; The TX broadcasts "BRIDGE" as immediate data on
; port &9C to all stations (FF.FF). The RX listens
; on the same port for a reply into the bridge
; status bytes at &0D72.
A84D .bridge_txcb_init_table←1← A87A LDA
EQUB &82 ; TX 0: ctrl = &82 (immediate mode)
A84E EQUB &9C ; TX 1: port = &9C (bridge discovery)
A84F EQUB &FF ; TX 2: dest station = &FF (broadcast)
A850 EQUB &FF ; TX 3: dest network = &FF (all nets)
A851 EQUS "BRIDGE" ; TX 4-9: immediate data payload
A857 EQUB &9C ; TX 10: &9C (port echo)
A858 EQUB &00 ; TX 11: &00 (terminator)
A859 .bridge_rxcb_init_data
EQUB &7F ; RX 0: ctrl = &7F (receive)
A85A EQUB &9C ; RX 1: port = &9C (bridge discovery)
A85B EQUB &00 ; RX 2: station = &00 (any)
A85C EQUB &00 ; RX 3: network = &00 (any)
A85D EQUB &71
A85E EQUB &0D ; RX 5: buf start hi (&0D) -> &0D72
A85F EQUB &FF ; RX 6: extended addr fill (&FF)
A860 EQUB &FF ; RX 7: extended addr fill (&FF)
A861 EQUB &73
A862 EQUB &0D ; RX 9: buf end hi (&0D) -> &0D74
A863 EQUB &FF ; RX 10: extended addr fill (&FF)
A864 EQUB &FF ; RX 11: extended addr fill (&FF)

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.

A865 .init_bridge_poll←4← 8E09 JSR← A0B4 JSR← A71E JSR← A825 JSR
LDA l0d71 ; Check bridge status
A868 CMP #&ff ; Is it &FF (uninitialised)?
A86A BNE return_from_bridge_poll ; No: bridge already active, return
A86C TYA ; Save Y
A86D PHA ; Preserve Y on stack
A86E LDY #&18 ; Y=&18: workspace offset for init
A870 LDX #&0b ; X=&0B: 12 bytes to copy
A872 ROR econet_flags ; Rotate l0d61 right (save flag)
A875 .loop_copy_bridge_init←1← A881 BPL
LDA bridge_ws_init_data,y ; Load init data byte
A878 STA (nfs_workspace),y ; Store to workspace
A87A LDA bridge_txcb_init_table,x ; Load TXCB template byte
A87D STA txcb_ctrl,x ; Store to TX control block
A87F INY ; Next workspace byte
A880 DEX ; Next template byte
A881 BPL loop_copy_bridge_init ; Loop for all 12 bytes
A883 STX l0d71 ; Store X (-1) as bridge counter
A886 ROL econet_flags ; Restore l0d61 flag
A889 .loop_wait_ws_status←2← A88C BCC← A8B3 BPL
ASL ws_0d60 ; Shift ws_0d60 left (check status)
A88C BCC loop_wait_ws_status ; C=0: status clear, retry
A88E LDA #&82 ; Control byte &82 for TX
A890 STA txcb_ctrl ; Set in TX control block
A892 LDA #&c0 ; Data block at &00C0
A894 STA nmi_tx_block ; Set NMI TX block low
A896 LDA #0 ; High byte = 0 (page 0)
A898 STA nmi_tx_block_hi ; Set NMI TX block high
A89A JSR tx_begin ; Begin Econet transmission
A89D .loop_wait_tx_done←1← A89F BMI
BIT txcb_ctrl ; Test TX control block bit 7
A89F BMI loop_wait_tx_done ; Negative: TX still in progress
A8A1 TXA ; Transfer TX completion status to A
A8A2 PHA ; Save TX status
A8A3 LDA #osbyte_vsync ; OSBYTE &13: wait for VSYNC
A8A5 JSR osbyte ; Wait for vertical sync
A8A8 PLA ; Restore TX status
A8A9 TAX ; Back to X
A8AA LDY #&18 ; Y=&18: check workspace response
A8AC LDA (nfs_workspace),y ; Load bridge response
A8AE BMI bridge_responded ; Negative: bridge responded
A8B0 JSR advance_x_by_8 ; Advance retry counter by 8
A8B3 BPL loop_wait_ws_status ; Positive: retry poll loop
A8B5 .bridge_responded←1← A8AE BMI
LDA #&3f ; Set response to &3F (OK)
A8B7 STA (nfs_workspace),y ; Store to workspace
A8B9 PLA ; Restore saved Y
A8BA TAY ; Back to Y
A8BB LDA l0d71 ; Load bridge status
A8BE TAX ; X = bridge status
A8BF EOR #&ff ; Complement status
A8C1 BEQ return_from_bridge_poll ; Status was &FF: return (no bridge)
A8C3 TXA ; Return bridge station in A
A8C4 .return_from_bridge_poll←3← A86A BNE← A8C1 BEQ← A8CC BPL
RTS ; Return
A8C5 .osword_14_handler
CMP #1 ; Compare sub-code with 1
A8C7 BCS handle_tx_request ; Sub-code >= 1: handle TX request
A8C9 BIT fs_flags ; Test station active flag
A8CC BPL return_from_bridge_poll ; Not active: return
A8CE LDY #&23 ; Y=&23: workspace offset for params
A8D0 JSR mask_owner_access ; Set owner access mask
A8D3 .loop_copy_txcb_init←1← A8E0 BNE
LDA init_txcb,y ; Load TXCB init byte
A8D6 BNE store_txcb_init_byte ; Non-zero: use template value
A8D8 LDA txcb_default_base,y ; Zero: use workspace default value
A8DB .store_txcb_init_byte←1← A8D6 BNE
STA (nfs_workspace),y ; Store to workspace
A8DD DEY ; Next byte down
A8DE CPY #&17 ; Until Y reaches &17
A8E0 BNE loop_copy_txcb_init ; Loop for all bytes
A8E2 INY ; Y=&18 (INY from &17)
A8E3 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.

A8E5 .store_osword_pb_ptr
LDY #&1c ; Y=&1C: workspace offset for PB pointer
A8E7 LDA ws_ptr_hi ; Load PB page number
A8E9 ADC #1 ; PB starts at next page boundary (+1)
A8EB JSR store_ptr_at_ws_y ; Store PB start pointer at ws[&1C]
A8EE LDY #1 ; Y=1: PB byte 1 (transfer length)
A8F0 LDA (ws_ptr_hi),y ; Load transfer length from PB
A8F2 LDY #&20 ; Y=&20: workspace offset for buffer end
A8F4 ADC ws_ptr_hi ; Add PB base for buffer end address
A8F6 JSR store_ptr_at_ws_y ; Store PB pointer to workspace
A8F9 LDY #2 ; Y=2: parameter offset
A8FB LDA #&90 ; Control byte &90
A8FD STA need_release_tube ; Set escapable flag
A8FF STA (ws_ptr_hi),y ; Store control byte to PB
A901 INY ; Y=&03
A902 INY ; Y=&04
A903 .loop_copy_ws_to_pb←1← A90B BNE
LDA osword_ws_base,y ; Load workspace data
A906 STA (ws_ptr_hi),y ; Store to parameter block
A908 INY ; Next byte
A909 CPY #7 ; Until Y reaches 7
A90B BNE loop_copy_ws_to_pb ; Loop for 3 bytes (Y=4,5,6)
A90D LDA nfs_workspace_hi ; Set TX pointer high byte
A90F STA net_tx_ptr_hi ; Store to net_tx_ptr_hi
A911 JSR enable_irq_and_poll ; Enable interrupts Send the network packet
A914 LDY #&20 ; Y=&20: workspace offset
A916 LDA #&ff ; Set to &FF (pending)
A918 STA (nfs_workspace),y ; Mark send pending in workspace
A91A INY ; Y=&21
A91B STA (nfs_workspace),y ; Also mark offset &21
A91D LDY #&19 ; Y=&19: control offset
A91F LDA #&90 ; Control byte &90
A921 STA (nfs_workspace),y ; Store to workspace
A923 DEY ; Y=&18: RX control offset Y=&18
A924 LDA #&7f ; Control byte &7F
A926 STA (nfs_workspace),y ; Store RX control
A928 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
A92B .store_ptr_at_ws_y←2← A8EB JSR← A8F6 JSR
STA (nfs_workspace),y ; Store address low byte at ws[Y]
A92D INY ; Advance to high byte offset
A92E LDA table_idx ; Load high byte base (table_idx)
A930 ADC #0 ; Add carry for page crossing
A932 STA (nfs_workspace),y ; Store address high byte at ws[Y+1]
A934 RTS ; Return
A935 .handle_tx_request←1← A8C7 BCS
PHP ; Save processor flags
A936 LDY #1 ; Y=1: PB offset for station
A938 LDA (ws_ptr_hi),y ; Load station number from PB
A93A TAX ; X = station number
A93B INY ; Y=&02
A93C LDA (ws_ptr_hi),y ; Load network number from PB
A93E INY ; Y=3: workspace start offset Y=&03
A93F STY osword_flag ; Store Y as ws_ptr_lo
A941 LDY #&72 ; Y=&72: workspace offset for dest
A943 STA (net_rx_ptr),y ; Store network to workspace
A945 DEY ; Y=&71
A946 TXA ; A = station (from X)
A947 STA (net_rx_ptr),y ; Store station to workspace
A949 PLP ; Restore flags from PHP
A94A BNE handle_burst_xfer ; Non-zero sub-code: handle burst
A94C .loop_send_pb_chars←1← A968 BNE
LDY osword_flag ; Load current offset
A94E INC osword_flag ; Advance offset for next byte
A950 LDA (ws_ptr_hi),y ; Load next char from PB
A952 BEQ return_1 ; Zero: end of data, return
A954 LDY #&7d ; Y=&7D: workspace char offset
A956 STA (net_rx_ptr),y ; Store char to RX buffer
A958 PHA ; Save char for later test
A959 JSR init_ws_copy_wide ; Init workspace copy for wide xfer
A95C SEC ; Set carry for flag set
A95D ROR need_release_tube ; Set bit 7: Tube needs release
A95F JSR enable_irq_and_poll ; Enable IRQ and send packet
A962 .loop_bridge_tx_delay←1← A963 BNE
DEX ; Delay countdown
A963 BNE loop_bridge_tx_delay ; Loop for short delay
A965 PLA ; Restore char
A966 EOR #&0d ; Test if char was CR (&0D)
A968 BNE loop_send_pb_chars ; Not CR: send next char
A96A .return_1←1← A952 BEQ
RTS ; CR sent: return
A96B .handle_burst_xfer←1← A94A BNE
JSR init_ws_copy_wide ; Init workspace for wide copy
A96E LDY #&7b ; Y=&7B: workspace offset
A970 LDA (net_rx_ptr),y ; Load buffer size
A972 ADC #3 ; Add 3 for header
A974 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.

A976 .enable_irq_and_poll←2← A911 JSR← A95F JSR
CLI ; Enable interrupts
A977 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). Address stored at netv_handler_addr (&8E8A) in the extended vector data area.

A97A .netv_handler
PHP ; Save processor flags
A97B PHA ; Save A
A97C TXA ; Save X
A97D PHA ; Push X
A97E TYA ; Save Y
A97F PHA ; Push Y
A980 TSX ; Get stack pointer
A981 LDA stack_page_3,x ; Read OSWORD number from stack
A984 CMP #9 ; OSWORD >= 9?
A986 BCS restore_regs_return ; Yes: out of range, restore + return
A988 TAX ; X = OSWORD number
A989 JSR push_osword_handler_addr ; Push handler address for dispatch
A98C .restore_regs_return←1← A986 BCS
PLA ; Restore Y
A98D TAY ; Back to Y
A98E PLA ; Restore X
A98F TAX ; Back to X
A990 PLA ; Restore A
A991 PLP ; Restore processor flags
A992 RTS ; RTS dispatches via pushed address

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.

A993 .push_osword_handler_addr←1← A989 JSR
LDA osword_handler_hi_table,x ; Load handler high byte from table
A996 PHA ; Push for RTS dispatch
A997 LDA osword_handler_lo_table,x ; Load handler low byte from table
A99A PHA ; Push for RTS dispatch
A99B LDA osbyte_a_copy ; Reload OSWORD number for handler
A99D RTS ; RTS will dispatch to handler
; OSWORD handler dispatch table
; 9-entry PHA/PHA/RTS table for OSWORD numbers
; 0-8. push_osword_handler_addr indexes by the
; OSWORD number, pushes the handler address-1,
; then RTS dispatches to the handler with the
; OSWORD number reloaded in A.
A99E .osword_handler_lo_table←1← A997 LDA
EQUB <(dispatch_rts-1)
A99F EQUB <(netv_print_data-1)
A9A0 EQUB <(netv_print_data-1)
A9A1 EQUB <(netv_print_data-1)
A9A2 EQUB <(osword_4_handler-1)
A9A3 EQUB <(netv_spool_check-1)
A9A4 EQUB <(dispatch_rts-1)
A9A5 EQUB <(netv_claim_release-1)
A9A6 EQUB <(osword_8_handler-1)
A9A7 .osword_handler_hi_table←1← A993 LDA
EQUB >(dispatch_rts-1) ; hi OSWORD 0: no-op (RTS)
A9A8 EQUB >(netv_print_data-1) ; hi OSWORD 1: printer spool data
A9A9 EQUB >(netv_print_data-1) ; hi OSWORD 2: printer spool data
A9AA EQUB >(netv_print_data-1) ; hi OSWORD 3: printer spool data
A9AB EQUB >(osword_4_handler-1) ; hi OSWORD 4: clear carry + abort
A9AC EQUB >(netv_spool_check-1) ; hi OSWORD 5: spool buffer check
A9AD EQUB >(dispatch_rts-1) ; hi OSWORD 6: no-op (RTS)
A9AE EQUB >(netv_claim_release-1) ; hi OSWORD 7: claim/release handler
A9AF EQUB >(osword_8_handler-1) ; hi OSWORD 8: copy PB + abort

OSWORD 4 handler: clear carry and send abort

Clears the carry flag in the stacked processor status, stores the original Y to workspace at offset &DA, and falls through to tx_econet_abort with A=0. Called via OSWORD handler dispatch table for OSWORD 4 (write interval timer).

A9B0 .osword_4_handler
TSX ; Get stack pointer
A9B1 ROR stack_page_6,x ; Clear bit 0 of stacked P (carry)
A9B4 ASL stack_page_6,x ; Shift back (clears carry flag)
A9B7 TYA ; A = original Y
A9B8 LDY #&da ; Y=&DA: workspace offset
A9BA STA (nfs_workspace),y ; Store Y to workspace
A9BC LDA #0 ; Abort code = 0
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.

A9BE .tx_econet_abort←3← 8AE7 JSR← AA11 JSR← AA77 JSR
LDY #&d9 ; Y=&D9: workspace abort offset
A9C0 STA (nfs_workspace),y ; Store abort code to workspace
A9C2 LDA #&80 ; Control byte &80 (abort)
A9C4 LDY #&0c ; Y=&0C: control offset
A9C6 STA (nfs_workspace),y ; Store control byte
A9C8 LDA net_tx_ptr ; Save current TX ptr low
A9CA PHA ; Push on stack
A9CB LDA net_tx_ptr_hi ; Save current TX ptr high
A9CD PHA ; Push on stack
A9CE STY net_tx_ptr ; Set TX ptr to workspace offset
A9D0 LDX nfs_workspace_hi ; Load workspace high byte
A9D2 STX net_tx_ptr_hi ; Set TX ptr high
A9D4 JSR send_net_packet ; Send the abort packet
A9D7 LDA #&3f ; Set status to &3F (complete)
A9D9 STA (net_tx_ptr,x) ; Store at TX ptr offset 0
A9DB PLA ; Restore TX ptr high
A9DC STA net_tx_ptr_hi ; Back to net_tx_ptr_hi
A9DE PLA ; Restore TX ptr low
A9DF STA net_tx_ptr ; Back to net_tx_ptr
A9E1 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.

A9E2 .netv_claim_release
LDY osword_pb_ptr_hi ; Load PB pointer high
A9E4 CMP #&81 ; Compare with &81 (special case)
A9E6 BEQ process_match_result ; Match: skip to processing
A9E8 LDY #1 ; Y=1: first claim code position
A9EA LDX #&0a ; X=&0A: 11 codes to check
A9EC JSR match_rx_code ; Search claim code table
A9EF BEQ process_match_result ; Found: skip to processing
A9F1 DEY ; Try second table range
A9F2 DEY ; Y=-1: flag second range
A9F3 LDX #&11 ; X=&11: 18 codes to check
A9F5 JSR match_rx_code ; Search claim code table
A9F8 BEQ process_match_result ; Found: skip to processing
A9FA INY ; Not found: increment Y
A9FB .process_match_result←3← A9E6 BEQ← A9EF BEQ← A9F8 BEQ
LDX #2 ; X=2: default state
A9FD TYA ; A = Y (search result)
A9FE BEQ return_from_claim_release ; Zero: not found, return
AA00 PHP ; Save result flags
AA01 BPL save_tube_state ; Positive: use state X=2
AA03 INX ; X=&03
AA04 .save_tube_state←1← AA01 BPL
LDY #&dc ; Y=&DC: workspace offset for save
AA06 .loop_save_tube_bytes←1← AA0E BPL
LDA tube_claimed_id,y ; Load tube claim ID byte
AA09 STA (nfs_workspace),y ; Store to workspace
AA0B DEY ; Next byte down
AA0C CPY #&da ; Until Y reaches &DA
AA0E BPL loop_save_tube_bytes ; Loop for 3 bytes
AA10 TXA ; A = state (2 or 3)
AA11 JSR tx_econet_abort ; Send abort with state code
AA14 PLP ; Restore flags
AA15 BPL return_from_claim_release ; Positive: return without poll
AA17 LDA #&7f ; Set control to &7F
AA19 LDY #&0c ; Y=&0C: control offset
AA1B STA (nfs_workspace),y ; Store control byte
AA1D .loop_poll_ws_status←1← AA1F BPL
LDA (nfs_workspace),y ; Load status from workspace
AA1F BPL loop_poll_ws_status ; Positive: keep waiting
AA21 TSX ; Get stack pointer
AA22 LDY #&dd ; Y=&DD: workspace result offset
AA24 LDA (nfs_workspace),y ; Load result byte
AA26 ORA #&44 ; Set bit 6 and bit 2
AA28 BNE store_stack_byte ; Always branch (NZ from ORA) ALWAYS branch
AA2A .loop_restore_stack←1← AA33 BNE
DEY ; Previous workspace byte
AA2B DEX ; Previous stack position
AA2C LDA (nfs_workspace),y ; Load workspace byte
AA2E .store_stack_byte←1← AA28 BNE
STA stack_page_6,x ; Store to caller's stack frame
AA31 CPY #&da ; Reached start of save area?
AA33 BNE loop_restore_stack ; No: copy next byte
AA35 .return_from_claim_release←2← A9FE BEQ← AA15 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
AA36 .match_rx_code←3← A9EC JSR← A9F5 JSR← AA3C BPL
CMP osword_claim_codes,x ; Compare A with code at index X
AA39 BEQ return_from_match_rx_code ; Match: return with Z set
AA3B DEX ; Try next code
AA3C BPL match_rx_code ; More codes: continue search
AA3E .return_from_match_rx_code←2← AA39 BEQ← AA59 BNE
RTS ; Return (Z clear = not found)
; OSWORD claim code table
; Table of OSWORD numbers that trigger NMI
; claim processing. Searched in two passes by
; the OSWORD 7 handler: first the 11-entry
; range (indices 0-&0A), then the full 18-entry
; range (indices 0-&11). A match in the first
; range sets state 2 (standard claim); a match
; only in the extended range sets state 3.
AA3F .osword_claim_codes←1← AA36 CMP
EQUB &04 ; Range 1+2: OSWORD &04
AA40 EQUB &09 ; Range 1+2: OSWORD &09
AA41 EQUB &0A ; Range 1+2: OSWORD &0A
AA42 EQUB &14 ; Range 1+2: OSWORD &14
AA43 EQUB &15 ; Range 1+2: OSWORD &15
AA44 EQUB &9A ; Range 1+2: OSWORD &9A
AA45 EQUB &9B ; Range 1+2: OSWORD &9B
AA46 EQUB &E1 ; Range 1+2: OSWORD &E1
AA47 EQUB &E2 ; Range 1+2: OSWORD &E2
AA48 EQUB &E3 ; Range 1+2: OSWORD &E3
AA49 EQUB &E4 ; Range 1+2: OSWORD &E4
AA4A EQUB &0B ; Range 2 only: OSWORD &0B
AA4B EQUB &0C ; Range 2 only: OSWORD &0C
AA4C EQUB &0F ; Range 2 only: OSWORD &0F
AA4D EQUB &79 ; Range 2 only: OSWORD &79
AA4E EQUB &7A ; Range 2 only: OSWORD &7A
AA4F EQUB &86 ; Range 2 only: OSWORD &86
AA50 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.

AA51 .osword_8_handler
LDY #&0e ; Y=&0E: copy 15 bytes (0-14)
AA53 CMP #7 ; OSWORD 7?
AA55 BEQ copy_pb_to_ws ; Yes: handle
AA57 CMP #8 ; OSWORD 8?
AA59 BNE return_from_match_rx_code ; No: return
AA5B .copy_pb_to_ws←1← AA55 BEQ
LDX #&db ; Workspace low = &DB
AA5D STX nfs_workspace ; Set nfs_workspace low byte
AA5F .loop_copy_pb_to_ws←1← AA64 BPL
LDA (osword_pb_ptr),y ; Load PB[Y]
AA61 STA (nfs_workspace),y ; Store to workspace[Y]
AA63 DEY ; Next byte down
AA64 BPL loop_copy_pb_to_ws ; Loop for 15 bytes
AA66 INY ; Y=0
AA67 DEC nfs_workspace ; Workspace low = &DA
AA69 LDA osbyte_a_copy ; Load OSWORD number
AA6B STA (nfs_workspace),y ; Store at workspace+0 (= &DA)
AA6D STY nfs_workspace ; Workspace low = 0 (restore)
AA6F LDY #&14 ; Y=&14: control offset
AA71 LDA #&e9 ; Control value &E9
AA73 STA (nfs_workspace),y ; Store to workspace+&14
AA75 LDA #1 ; Abort code = 1
AA77 JSR tx_econet_abort ; Send abort packet
AA7A STX nfs_workspace ; 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.

AA7C .init_ws_copy_wide←2← A959 JSR← A96B JSR
LDX #&0d ; X=&0D: 14 bytes to copy
AA7E LDY #&7c ; Y=&7C: workspace destination offset
AA80 BIT bit_test_ff ; Test bit 6 via BIT (V flag check)
AA83 BVS loop_copy_ws_template ; 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.

AA85 .init_ws_copy_narrow←1← 95A2 JSR
LDY #&17 ; Y=&17: narrow mode dest offset
AA87 LDX #&1a ; 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.

AA89 .ws_copy_vclr_entry←1← AB4A JSR
CLV ; Clear V flag for narrow mode
AA8A .loop_copy_ws_template←2← AA83 BVS← AAAB BPL
LDA ws_txcb_template_data,x ; Load template byte
AA8D CMP #&fe ; Is it &FE? (end marker)
AA8F BEQ done_ws_template_copy ; Yes: finished, set TX ptr
AA91 CMP #&fd ; Is it &FD? (skip marker)
AA93 BEQ advance_template_idx ; Yes: skip store, just advance
AA95 CMP #&fc ; Is it &FC? (page ptr marker)
AA97 BNE select_store_target ; No: use literal value
AA99 LDA net_rx_ptr_hi ; &FC: load RX buffer page
AA9B BVS store_tx_ptr_hi ; V=1: use net_rx_ptr_hi
AA9D LDA nfs_workspace_hi ; V=0: use nfs_workspace_hi
AA9F .store_tx_ptr_hi←1← AA9B BVS
STA net_tx_ptr_hi ; Store as TX ptr high
AAA1 .select_store_target←1← AA97 BNE
BVS store_via_rx_ptr ; V=1: store to net_rx_ptr target
AAA3 STA (nfs_workspace),y ; V=0: store to nfs_workspace
AAA5 BVC advance_template_idx ; Continue to next byte ALWAYS branch
AAA7 .store_via_rx_ptr←1← AAA1 BVS
STA (net_rx_ptr),y ; V=1: store to net_rx_ptr
AAA9 .advance_template_idx←2← AA93 BEQ← AAA5 BVC
DEY ; Advance workspace offset down
AAAA DEX ; Advance template index
AAAB BPL loop_copy_ws_template ; More bytes: continue copy
AAAD .done_ws_template_copy←1← AA8F BEQ
INY ; Adjust Y for start of TX data
AAAE STY net_tx_ptr ; Set net_tx_ptr from Y
AAB0 RTS ; Return
; Workspace TXCB init template
; 39-byte template with three overlapping
; regions, each a TXCB/RXCB structure:
; Wide [0..13]: ws+&6F..&7C via net_rx_ptr
; Narrow [14..26]: ws+&0C..&17 via workspace
; Spool [27..38]: ws+&01..&0B via workspace
; Markers: &FE=end, &FD=skip, &FC=page ptr.
AAB1 .ws_txcb_template_data←1← AA8A LDA
EQUB &85 ; Wide &6F: ctrl=&85
AAB2 EQUB &00 ; Wide &70: port=&00
AAB3 EQUB &FD ; Wide &71: skip (dest station)
AAB4 EQUB &FD ; Wide &72: skip (dest network)
AAB5 EQUB &7D ; Wide &73: buf start lo=&7D
AAB6 EQUB &FC ; Wide &74: buf start hi=page ptr
AAB7 EQUB &FF ; Wide &75: buf start ext lo
AAB8 EQUB &FF ; Wide &76: buf start ext hi
AAB9 EQUB &7E ; Wide &77: buf end lo=&7E
AABA EQUB &FC ; Wide &78: buf end hi=page ptr
AABB EQUB &FF ; Wide &79: buf end ext lo
AABC EQUB &FF ; Wide &7A: buf end ext hi
AABD EQUB &00 ; Wide &7B: zero
AABE EQUB &00 ; Wide &7C: zero
AABF EQUB &FE ; Narrow stop (&FE terminator)
AAC0 EQUB &80 ; Narrow &0C: ctrl=&80 (standard)
AAC1 EQUB &93 ; Narrow &0D: port=&93
AAC2 EQUB &FD ; Narrow &0E: skip (dest station)
AAC3 EQUB &FD ; Narrow &0F: skip (dest network)
AAC4 EQUB &D9 ; Narrow &10: buf start lo=&D9
AAC5 EQUB &FC ; Narrow &11: buf start hi=page ptr
AAC6 EQUB &FF ; Narrow &12: buf start ext lo
AAC7 EQUB &FF ; Narrow &13: buf start ext hi
AAC8 EQUB &DE ; Narrow &14: buf end lo=&DE
AAC9 EQUB &FC ; Narrow &15: buf end hi=page ptr
AACA EQUB &FF ; Narrow &16: buf end ext lo
AACB EQUB &FF ; Narrow &17: buf end ext hi
AACC EQUB &FE ; Spool stop (&FE terminator)
AACD EQUB &D1 ; Spool &01: port=&D1
AACE EQUB &FD
AACF EQUB &FD ; Spool &03: skip (dest network)
AAD0 EQUB &21
AAD1 EQUB &FD ; Spool &05: skip (buf start hi)
AAD2 EQUB &FF ; Spool &06: buf start ext lo
AAD3 EQUB &FF ; Spool &07: buf start ext hi
AAD4 EQUB &FD ; Spool &08: skip (buf end lo)
AAD5 EQUB &FD ; Spool &09: skip (buf end hi)
AAD6 EQUB &FF ; Spool &0A: buf end ext lo
AAD7 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.

AAD8 .netv_spool_check
DEX ; X = X - 1
AAD9 CPX osword_pb_ptr ; Match osword_pb_ptr?
AADB BNE return_from_spool_reset ; No: return (not our PB)
AADD LDA vdu_status ; Load spool state byte
AADF ROR ; Rotate bit 0 into carry
AAE0 BCS return_from_spool_reset ; C=1: already active, return
fall through ↓

Reset spool buffer to initial state

Sets the spool buffer pointer to &25 (first available data position) and the control state byte to &41 (ready for new data). Called after processing a complete spool data block.

AAE2 .reset_spool_buf_state←2← 8F27 JSR← AB33 JMP
LDA #&21 ; Buffer start at &25
AAE4 STA spool_buf_idx ; Store as buffer pointer
AAE7 LDA #&41 ; Control state &41
AAE9 STA ws_0d6a ; Store as spool control state
AAEC .return_from_spool_reset←4← AADB BNE← AAE0 BCS← AAEF BNE← AB03 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.

AAED .netv_print_data
CPY #4 ; Check Y == 4
AAEF BNE return_from_spool_reset ; No: return
AAF1 TXA ; A = X (control byte)
AAF2 DEX ; Decrement X
AAF3 BNE handle_spool_ctrl_byte ; Non-zero: handle spool ctrl byte
AAF5 TSX ; Get stack pointer
AAF6 ORA stack_page_6,x ; OR with stack value
AAF9 STA stack_page_6,x ; Store back to stack
AAFC .loop_drain_printer_buf←2← AB0B BCC← AB10 BCC
LDA #osbyte_read_buffer ; OSBYTE &91: read buffer
AAFE LDX #buffer_printer ; X=3: printer buffer
AB00 JSR osbyte ; Read character from buffer Get character from input buffer (C is set if the buffer is empty, otherwise Y=extracted character)
AB03 BCS return_from_spool_reset ; C=1: buffer empty, return
AB05 TYA ; A = extracted character Y is the character extracted from the buffer
AB06 JSR append_byte_to_rxbuf ; Add byte to RX buffer
AB09 CPY #&6e ; Buffer past &6E limit?
AB0B BCC loop_drain_printer_buf ; No: read more from buffer
AB0D JSR process_spool_data ; Buffer full: send packet
AB10 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
AB12 .append_byte_to_rxbuf←3← AB06 JSR← AB2D JSR← ABE4 JSR
LDY spool_buf_idx ; Load current buffer index
AB15 STA (net_rx_ptr),y ; Store byte at buffer position
AB17 INC spool_buf_idx ; Advance buffer index
AB1A 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.

AB1B .handle_spool_ctrl_byte←2← 8F5A JSR← AAF3 BNE
ROR ; Rotate bit 0 into carry
AB1C BCC check_spool_state ; Bit 0 clear: not active path
AB1E LDA ws_0d6a ; Load spool control state
AB21 PHA ; Save for bit test
AB22 ROR ; Rotate bit 0 into carry
AB23 PLA ; Restore state
AB24 BCS done_spool_ctrl ; C=1: already started, reset
AB26 ORA #3 ; Set bits 0-1 (active + pending)
AB28 STA ws_0d6a ; Store updated state
AB2B LDA #3 ; Control byte 3 for header
AB2D JSR append_byte_to_rxbuf ; Add to RX buffer
AB30 JSR process_spool_data ; Send current buffer
AB33 .done_spool_ctrl←1← AB24 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.

AB36 .process_spool_data←4← AB0D JSR← AB30 JSR← AB79 BCC← ABE7 JSR
LDY #8 ; Y=8: workspace offset for length
AB38 LDA spool_buf_idx ; Load buffer index (=length)
AB3B STA (nfs_workspace),y ; Store length to workspace
AB3D LDA net_rx_ptr_hi ; Set data page high byte
AB3F INY ; Y=&09
AB40 STA (nfs_workspace),y ; Store to workspace+9
AB42 LDY #5 ; Y=5: workspace offset
AB44 STA (nfs_workspace),y ; Store page to workspace+5
AB46 LDY #&0b ; Y=&0B: template start offset
AB48 LDX #&26 ; X=&26: template index
AB4A JSR ws_copy_vclr_entry ; Copy template to workspace
AB4D DEY ; Adjust Y down
AB4E LDA ws_0d6a ; Load spool control state
AB51 PHA ; Save state
AB52 ROL ; Rotate to get carry (bit 7)
AB53 PLA ; Restore state
AB54 EOR #&80 ; Toggle bit 7
AB56 STA ws_0d6a ; Store updated state
AB59 ROL ; Shift to get both flag bits
AB5A STA (nfs_workspace),y ; Store flags to workspace
AB5C LDA vdu_status ; Save l00d0 (exec flag)
AB5E PHA ; Push for later restore
AB5F AND #&fe ; Clear bit 0 of exec flag
AB61 STA vdu_status ; Store modified exec flag
AB63 LDY #&21 ; Reset buffer start to &25
AB65 STY spool_buf_idx ; Store reset buffer index
AB68 LDA #0 ; A=0 for disconnect reply
AB6A TAX ; X=0 X=&00
AB6B LDY nfs_workspace_hi ; Y = workspace page
AB6D CLI ; Enable interrupts
AB6E JSR send_disconnect_reply ; Send disconnect reply packet
AB71 PLA ; Restore exec flag
AB72 STA vdu_status ; Store original exec flag
AB74 RTS ; Return
AB75 .check_spool_state←1← AB1C BCC
LDA ws_0d6a ; Load spool control state
AB78 ROR ; Rotate bit 0 to carry
AB79 BCC process_spool_data ; C=0: send current buffer
AB7B LDA vdu_status ; Save exec flag
AB7D PHA ; Push for restore
AB7E AND #&fe ; Clear bit 0
AB80 STA vdu_status ; Store modified flag
AB82 LDA #&14 ; Control byte &14 (repeat count)
AB84 .start_spool_retry←1← ABF8 BNE
PHA ; Save retry count
AB85 LDX #&0b ; X=&0B: 12 bytes of template
AB87 LDY #&2c ; Y=&2C: workspace offset for TXCB
AB89 .loop_copy_spool_tx←1← AB90 BPL
LDA tx_econet_txcb_template,x ; Load template byte
AB8C STA (net_rx_ptr),y ; Store to workspace
AB8E DEY ; Next byte down
AB8F DEX ; Next template byte
AB90 BPL loop_copy_spool_tx ; Loop for 12 bytes
AB92 STX need_release_tube ; X=-1: disable escape checking
AB94 LDY #2 ; Y=2: workspace offset for station
AB96 LDA (nfs_workspace),y ; Load station number
AB98 PHA ; Save station
AB99 INY ; Y=&03
AB9A LDA (nfs_workspace),y ; Load network number
AB9C LDY #&24 ; Y=&24: TXCB dest network offset
AB9E STA (net_rx_ptr),y ; Store network to TXCB
ABA0 DEY ; Y=&23
ABA1 PLA ; Restore station
ABA2 STA (net_rx_ptr),y ; Store station to TXCB
ABA4 LDX #&0b ; X=&0B: 12 bytes of RX template
ABA6 LDY #&0b ; Y=&0B: workspace RX offset
ABA8 .loop_copy_spool_rx←1← ABB9 BPL
LDA rx_palette_txcb_template,x ; Load RX template byte
ABAB CMP #&fd ; Is it &FD? (skip marker)
ABAD BEQ advance_spool_rx_idx ; Yes: skip store
ABAF CMP #&fc ; Is it &FC? (page ptr marker)
ABB1 BNE store_spool_rx_byte ; No: use literal value
ABB3 LDA net_rx_ptr_hi ; &FC: substitute RX buffer page
ABB5 .store_spool_rx_byte←1← ABB1 BNE
STA (nfs_workspace),y ; Store to workspace
ABB7 .advance_spool_rx_idx←1← ABAD BEQ
DEY ; Next byte down
ABB8 DEX ; Next template byte
ABB9 BPL loop_copy_spool_rx ; Loop for 12 bytes
ABBB LDA #&21 ; TX data start at &25
ABBD STA net_tx_ptr ; Set net_tx_ptr low
ABBF LDA net_rx_ptr_hi ; Set data page high byte
ABC1 STA net_tx_ptr_hi ; Set net_tx_ptr high
ABC3 JSR setup_pass_txbuf ; Set up password in TX buffer
ABC6 JSR send_net_packet ; Send the packet
ABC9 LDA #0 ; Clear net_tx_ptr low (page base)
ABCB STA net_tx_ptr ; Store zero
ABCD LDA nfs_workspace_hi ; Set TX high to workspace page
ABCF STA net_tx_ptr_hi ; Store workspace high byte
ABD1 JSR wait_net_tx_ack ; Wait for TX acknowledgement
ABD4 LDY #&2d ; Y=&2D: reply status offset
ABD6 LDA (net_rx_ptr),y ; Load reply byte
ABD8 BEQ spool_tx_succeeded ; Zero: success
ABDA CMP #3 ; Status = 3? (busy, can retry)
ABDC BNE spool_tx_retry ; Other error: handle failure
ABDE .spool_tx_succeeded←1← ABD8 BEQ
PLA ; Discard retry count
ABDF PLA ; Discard saved exec flag
ABE0 STA vdu_status ; Restore l00d0
ABE2 LDA #0 ; A=0: null terminator
ABE4 JSR append_byte_to_rxbuf ; Add zero to RX buffer (end marker)
ABE7 JSR process_spool_data ; Send final buffer
ABEA LDA ws_0d6a ; Load spool state
ABED AND #&f0 ; Clear low nibble
ABEF STA ws_0d6a ; Store cleaned state
ABF2 RTS ; Return
ABF3 .spool_tx_retry←1← ABDC BNE
TAX ; X = error code
ABF4 PLA ; Restore retry count
ABF5 SEC ; Set carry for subtract
ABF6 SBC #1 ; Decrement retry count
ABF8 BNE start_spool_retry ; Non-zero: retry send
ABFA CPX #1 ; Error code = 1? (busy)
ABFC BNE error_printer_jammed ; No: printer jammed error
ABFE .err_printer_busy←1← AFF5 JMP
LDA #&a6 ; A=&A6: printer busy error number
AC00 JSR error_inline_log ; Generate 'Printer busy' error
AC03 EQUS "Printer busy."
AC10 .error_printer_jammed←1← ABFC BNE
LDA #&a7 ; A=&A7: printer jammed error number
AC12 JSR error_inline_log ; Generate 'Printer jammed' error
AC15 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.

AC24 .send_disconnect_reply←3← 9509 JSR← AB6E JSR← B951 JSR
STX net_tx_ptr ; Set TX ptr low byte
AC26 STY net_tx_ptr_hi ; Set TX ptr high byte
AC28 PHA ; Save disconnect code
AC29 ORA #0 ; Test if zero
AC2B BEQ send_disconnect_status ; Zero: skip station search
AC2D LDX #&ff ; X=&FF: start search from -1
AC2F TAY ; Y = disconnect code
AC30 .loop_scan_disconnect←2← AC39 BNE← AC43 BNE
TYA ; A = disconnect code
AC31 INX ; Next station index
AC32 CMP fcb_net_or_port,x ; Compare with station table entry
AC35 BEQ verify_stn_match ; Match: verify station/network
AC37 CPX #&0f ; Past last station?
AC39 BNE loop_scan_disconnect ; No: try next
AC3B LDA #0 ; Not found: A=0
AC3D BEQ send_disconnect_status ; Skip to status update ALWAYS branch
AC3F .verify_stn_match←1← AC35 BEQ
TAY ; Y = disconnect code for compare
AC40 JSR match_station_net ; Check station and network match
AC43 BNE loop_scan_disconnect ; No match: try next station
AC45 LDA chan_status,x ; Load station status flags
AC48 AND #1 ; Isolate bit 0 (active flag)
AC4A .send_disconnect_status←2← AC2B BEQ← AC3D BEQ
LDY #0 ; Y=0: TX buffer status offset
AC4C ORA (net_tx_ptr),y ; OR with existing status byte
AC4E PHA ; Save combined status
AC4F STA (net_tx_ptr),y ; Store to TX buffer
AC51 JSR send_net_packet ; Send the packet
AC54 LDA #&ff ; Set end markers to &FF
AC56 LDY #8 ; Y=8: first end marker offset
AC58 STA (net_tx_ptr),y ; Store &FF
AC5A INY ; Y=&09
AC5B STA (net_tx_ptr),y ; Store &FF at offset 9 too
AC5D PLA ; Restore disconnect code
AC5E TAX ; X = status for control byte
AC5F LDY #&d1 ; Y=&D1: default control
AC61 PLA ; Check original disconnect code
AC62 PHA ; Peek but keep on stack
AC63 BEQ store_tx_ctrl_byte ; Zero: use &D1 control
AC65 LDY #&90 ; Non-zero: use &90 control
AC67 .store_tx_ctrl_byte←1← AC63 BEQ
TYA ; A = control byte (Y)
AC68 LDY #1 ; Y=1: control byte offset
AC6A STA (net_tx_ptr),y ; Store control byte
AC6C TXA ; A = X (status)
AC6D DEY ; Y=0 Y=&00
AC6E PHA ; Save status on stack
AC6F .loop_wait_disc_tx_ack←1← AC7B BCS
LDA #&7f ; Set status to &7F (waiting)
AC71 STA (net_tx_ptr),y ; Store at TX buffer offset 0
AC73 JSR wait_net_tx_ack ; Wait for TX acknowledgement
AC76 PLA ; Restore status
AC77 PHA ; Keep on stack for next check
AC78 EOR (net_tx_ptr),y ; Compare with current TX buffer
AC7A ROR ; Rotate result bit 0 to carry
AC7B BCS loop_wait_disc_tx_ack ; C=1: status changed, retry
AC7D PLA ; Done: discard status
AC7E PLA ; Discard disconnect code
AC7F RTS ; Return
; Spool TX control block template
; 12-byte TXCB template copied directly (no
; marker processing) to workspace at offset
; &21..&2C. Destination station and network
; (&23/&24) are filled in from (nfs_workspace)
; after the copy.
AC80 .tx_econet_txcb_template←1← AB89 LDA
EQUB &80 ; ctrl=&80 (standard TX)
AC81 EQUB &9F ; port=&9F
AC82 EQUB &00 ; dest station=&00 (filled later)
AC83 EQUB &00 ; dest network=&00 (filled later)
AC84 EQUB &59
AC85 EQUB &8E ; buf start hi=&8E
AC86 EQUB &FF ; buf start ext lo=&FF
AC87 EQUB &FF ; buf start ext hi=&FF
AC88 EQUB &61
AC89 EQUB &8E ; buf end hi=&8E
AC8A EQUB &FF ; buf end ext lo=&FF
AC8B EQUB &FF ; buf end ext hi=&FF
; Spool RX control block template
; 12-byte RXCB template with marker processing:
; &FD skips the offset (preserves existing value)
; and &FC substitutes net_rx_ptr_hi. Copied to
; workspace at offset &00..&0B. Sets up a 3-byte
; receive buffer at &xx31..&xx34.
AC8C .rx_palette_txcb_template←1← ABA8 LDA
EQUB &7F ; ctrl=&7F (RX listen)
AC8D EQUB &9E ; port=&9E
AC8E EQUB &FD ; skip: preserve dest station
AC8F EQUB &FD
AC90 EQUB &2D
AC91 EQUB &FC ; buf start hi=page ptr (&FC)
AC92 EQUB &FF ; buf start ext lo=&FF
AC93 EQUB &FF ; buf start ext hi=&FF
AC94 EQUB &30
AC95 EQUB &FC ; buf end hi=page ptr (&FC)
AC96 EQUB &FF ; buf end ext lo=&FF
AC97 EQUB &FF ; buf end ext hi=&FF
AC98 .lang_2_save_palette_vdu
LDA osword_flag ; Save l00ad counter
AC9A PHA ; Push for later restore
AC9B LDA #&e9 ; Set workspace low to &E9
AC9D STA nfs_workspace ; Store to nfs_workspace low
AC9F LDY #0 ; Y=0: initial palette index
ACA1 STY osword_flag ; Clear palette counter
ACA3 LDA vdu_screen_mode ; Load current screen mode
ACA6 STA (nfs_workspace),y ; Store mode to workspace
ACA8 INC nfs_workspace ; Advance workspace ptr
ACAA LDA vdu_display_start_hi ; Load video ULA copy
ACAD PHA ; Save for later restore
ACAE TYA ; A=0 for first palette entry A=&00
ACAF .loop_read_palette←1← ACCE BNE
STA (nfs_workspace),y ; Store logical colour to workspace
ACB1 LDX nfs_workspace ; X = workspace ptr low
ACB3 LDY nfs_workspace_hi ; Y = workspace ptr high
ACB5 LDA #osword_read_palette ; OSWORD &0B: read palette
ACB7 JSR osword ; Read palette entry Read palette
ACBA PLA ; Restore previous ULA value
ACBB LDY #0 ; Y=0: reset index
ACBD STA (nfs_workspace),y ; Store ULA value to workspace
ACBF INY ; Y=1: physical colour offset Y=&01
ACC0 LDA (nfs_workspace),y ; Load physical colour
ACC2 PHA ; Save for next iteration
ACC3 LDX nfs_workspace ; X = workspace ptr
ACC5 INC nfs_workspace ; Advance workspace ptr
ACC7 INC osword_flag ; Advance palette counter
ACC9 DEY ; Y=0 Y=&00
ACCA LDA osword_flag ; Load counter
ACCC CPX #&f9 ; Reached &F9 workspace limit?
ACCE BNE loop_read_palette ; No: read next palette entry
ACD0 PLA ; Discard last ULA value
ACD1 STY osword_flag ; Clear counter
ACD3 INC nfs_workspace ; Advance workspace ptr
ACD5 JSR serialise_palette_entry ; Store extra palette info
ACD8 INC nfs_workspace ; Advance workspace ptr again
ACDA PLA ; Restore original l00ad
ACDB STA osword_flag ; Store restored counter
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.

ACDD .commit_state_byte←4← 9586 JMP← 95AE JSR← 95D5 JSR← A62B JMP
LDA ws_0d69 ; Load current state
ACE0 STA ws_0d68 ; Store as committed state
ACE3 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.

ACE4 .serialise_palette_entry←1← ACD5 JSR
LDA vdu_mode ; Load palette register value
ACE7 STA (nfs_workspace),y ; Store to workspace
ACE9 LDX vdu_mode ; X = palette register
ACEC JSR read_osbyte_to_ws ; Read OSBYTE for this mode
ACEF INC nfs_workspace ; Advance workspace ptr
ACF1 TYA ; A=0
ACF2 STA (nfs_workspace,x) ; Store zero to workspace
ACF4 JSR read_osbyte_to_ws_x0 ; Read OSBYTE with X=0
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.

ACF7 .read_osbyte_to_ws_x0←1← ACF4 JSR
LDX #0 ; X=0: read mode info
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.

ACF9 .read_osbyte_to_ws←1← ACEC JSR
LDY osword_flag ; Load OSBYTE code index
ACFB INC osword_flag ; Advance index counter
ACFD INC nfs_workspace ; Advance workspace ptr
ACFF LDA osbyte_mode_read_codes,y ; Load OSBYTE number from table
AD02 LDY #&ff ; Y=&FF: read current value
AD04 JSR osbyte ; Call OSBYTE to read value
AD07 TXA ; A = result (from X)
AD08 LDX #0 ; X=0 for indexed store
AD0A STA (nfs_workspace,x) ; Store result to workspace
AD0C RTS ; Return
; OSBYTE mode read codes
; Three OSBYTE numbers used by read_osbyte_to_ws
; to save display mode state to workspace before
; a language 2 file transfer.
AD0D .osbyte_mode_read_codes←1← ACFF LDA
EQUB &84
AD0E EQUB &C2 ; OSBYTE &C2: read video ULA ctrl
AD0F EQUB &C3 ; OSBYTE &C3: read video ULA palette

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

On EntryYcommand line offset in text pointer
AD10 .cmd_cdir
TYA ; Save command line offset
AD11 PHA ; Push onto stack
AD12 JSR mask_owner_access ; Set owner-only access mask
AD15 JSR skip_to_next_arg ; Skip to optional size argument
AD18 CMP #&0d ; End of line?
AD1A BNE parse_cdir_size ; No: parse size argument
AD1C LDX #2 ; Default allocation size index = 2
AD1E BNE done_cdir_size ; ALWAYS branch
AD20 .parse_cdir_size←1← AD1A BNE
LDA #&ff ; A=&FF: mark as decimal parse
AD22 STA fs_work_4 ; Store decimal parse flag
AD24 JSR parse_addr_arg ; Parse numeric size argument
AD27 LDX #&1b ; X=&1B: top of 26-entry size table
AD29 .loop_find_alloc_size←1← AD2D BCC
DEX ; Try next lower index
AD2A CMP cdir_alloc_size_table,x ; Compare size with threshold
AD2D BCC loop_find_alloc_size ; A < threshold: keep searching
AD2F .done_cdir_size←1← AD1E BNE
STX fs_cmd_data ; Store allocation size index
AD32 PLA ; Restore command line offset
AD33 TAY ; Transfer to Y
AD34 JSR save_ptr_to_os_text ; Save text pointer for filename parse
AD37 JSR parse_filename_arg ; Parse directory name argument
AD3A LDX #1 ; X=1: one argument to copy
AD3C JSR copy_arg_to_buf ; Copy directory name to TX buffer
AD3F LDY #&1b ; Y=&1B: *CDir FS command code
AD41 .cdir_dispatch_col
JMP save_net_tx_cb ; Send command to file server
; *CDir allocation size threshold table
; 26 thresholds dividing 0-255 into size classes.
; Table base (cdir_dispatch_col+2) overlaps with
; the JMP high byte (entry 0, never reached). Searched
; from index 26 down to 0; the result index (1-26)
; is stored as the directory allocation size class.
; Default when no size argument given: index 2.
AD44 EQUB &00 ; Index 1: threshold 0 (catch-all)
AD45 EQUB &0A ; Index 2: threshold 10 (default)
AD46 EQUB &14 ; Index 3: threshold 20
AD47 EQUB &1D ; Index 4: threshold 29
AD48 EQUB &27 ; Index 5: threshold 39
AD49 EQUB &31 ; Index 6: threshold 49
AD4A EQUB &3B ; Index 7: threshold 59
AD4B EQUB &45 ; Index 8: threshold 69
AD4C EQUB &4F ; Index 9: threshold 79
AD4D EQUB &58 ; Index 10: threshold 88
AD4E EQUB &62 ; Index 11: threshold 98
AD4F EQUB &6C ; Index 12: threshold 108
AD50 EQUB &76 ; Index 13: threshold 118
AD51 EQUB &80 ; Index 14: threshold 128
AD52 EQUB &8A ; Index 15: threshold 138
AD53 EQUB &94 ; Index 16: threshold 148
AD54 EQUB &9D ; Index 17: threshold 157
AD55 EQUB &A7 ; Index 18: threshold 167
AD56 EQUB &B1 ; Index 19: threshold 177
AD57 EQUB &BB ; Index 20: threshold 187
AD58 EQUB &C5 ; Index 21: threshold 197
AD59 EQUB &CF ; Index 22: threshold 207
AD5A EQUB &D8 ; Index 23: threshold 216
AD5B EQUB &E2 ; Index 24: threshold 226
AD5C EQUB &EC ; Index 25: threshold 236
AD5D EQUB &F6 ; Index 26: threshold 246
AD5E EQUB &FF ; Unused (index 27, never accessed)

*LCat command handler

Sets the library flag by rotating SEC into bit 7 of l1071, then branches 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
AD5F .cmd_lcat
ROR fs_lib_flags ; Rotate carry into lib flag bit 7
AD62 SEC ; Set carry (= library directory)
AD63 BCS cat_set_lib_flag ; ALWAYS branch

*LEx command handler

Sets the library flag by rotating SEC into bit 7 of l1071, then branches 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
AD65 .cmd_lex
ROR fs_lib_flags ; Rotate carry into lib flag bit 7
AD68 SEC ; Set carry (= library directory)
AD69 BCS ex_set_lib_flag ; ALWAYS branch

*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
AD6B .cmd_ex
ROR fs_lib_flags ; Rotate carry into lib flag bit 7
AD6E CLC ; Clear carry (= current directory)
AD6F .ex_set_lib_flag←1← AD69 BCS
ROL fs_lib_flags ; Rotate carry back, clearing bit 7
AD72 LDA #&ff ; A=&FF: initial column counter
AD74 STA fs_spool_handle ; Store column counter
AD76 LDA #1 ; One entry per line (Ex format)
AD78 STA fs_work_7 ; Store entries per page
AD7A LDA #3 ; FS command code 3: Examine
AD7C STA fs_work_5 ; Store command code
AD7E BNE setup_ex_request ; ALWAYS branch
AD80 .fscv_5_cat
JSR set_xfer_params ; Set transfer parameters
AD83 LDY #0 ; Y=0: start from entry 0
AD85 ROR fs_lib_flags ; Rotate carry into lib flag
AD88 CLC ; Clear carry (= current directory)
AD89 .cat_set_lib_flag←1← AD63 BCS
ROL fs_lib_flags ; Rotate carry back, clearing bit 7
AD8C LDA #3 ; Three entries per column (Cat)
AD8E STA fs_spool_handle ; Store column counter
AD90 STA fs_work_7 ; Store entries per page
AD92 LDA #&0b ; FS command code &0B: Catalogue
AD94 STA fs_work_5 ; Store command code
AD96 .setup_ex_request←1← AD7E BNE
JSR save_ptr_to_os_text ; Save text pointer
AD99 LDA #&ff ; A=&FF: enable escape checking
AD9B STA need_release_tube ; Set escapable flag
AD9D LDA #6 ; Command code 6
AD9F STA fs_cmd_data ; Store in TX buffer
ADA2 JSR parse_filename_arg ; Parse directory argument
ADA5 LDX #1 ; X=1: offset in buffer
ADA7 JSR copy_arg_to_buf ; Copy argument to TX buffer
ADAA LDA fs_lib_flags ; Get library/FS flags
ADAD LSR ; Shift bit 0 to carry
ADAE BCC store_owner_flags ; Bit 0 clear: skip
ADB0 ORA #&40 ; Set bit 6 (owner access flag)
ADB2 .store_owner_flags←1← ADAE BCC
ROL ; Rotate back Get cycle number
ADB3 STA fs_lib_flags ; Store modified flags
ADB6 LDY #&12 ; Y=&12: FS command for examine
ADB8 JSR save_net_tx_cb ; Send request to file server Print ') '
ADBB LDX #3 ; X=3: offset to directory title
ADBD JSR print_10_chars ; Print directory title (10 chars)
ADC0 JSR print_inline ; Print '('
ADC3 EQUS "("
ADC4 LDA fs_reply_stn
ADC7 JSR print_decimal_3dig
ADCA JSR print_inline
ADCD EQUS ") "
ADD3 LDY fs_access_level ; Get owner/public flag
ADD6 BNE print_public_label ; Non-zero: public access
ADD8 JSR print_inline ; Print 'Owner' + CR
ADDB EQUS "Owner."
ADE1 BNE send_dir_info_req ; Skip public; ALWAYS branch
ADE3 .print_public_label←1← ADD6 BNE
JSR print_inline ; Print 'Public' + CR
ADE6 EQUS "Public."
ADED .send_dir_info_req←1← ADE1 BNE
LDA fs_lib_flags ; Get flags
ADF0 PHA ; Save flags
ADF1 JSR mask_owner_access ; Mask owner access bits
ADF4 LDY #&15 ; Y=&15: FS command for dir info
ADF6 JSR save_net_tx_cb ; Send request to file server
ADF9 INX ; Advance X past header
ADFA LDY #&10 ; Y=&10: print 16 chars
ADFC JSR print_chars_from_buf ; Print file entry
ADFF JSR print_inline ; Print ' Option '
AE02 EQUS " Option "
AE0D LDA fs_boot_option ; Get option byte
AE10 TAX ; Transfer to X for table lookup
AE11 JSR print_hex_byte ; Print option as hex
AE14 JSR print_inline ; Print ' ('
AE17 EQUS " ("
AE19 LDY option_str_offset_data,x ; Y=string offset for boot option X
AE1C .loop_print_option_str←1← AE25 BNE
LDA roff_off_string,y ; Load option description character
AE1F BMI print_dir_header ; Bit 7 set: end of option string
AE21 JSR osasci ; Write character
AE24 INY
AE25 BNE loop_print_option_str
AE27 .print_dir_header←1← AE1F BMI
JSR print_inline
AE2A EQUS ").Dir. "
AE31 LDX #&11 ; Offset &11: directory name
AE33 JSR print_10_chars ; Print directory name (10 chars)
AE36 JSR print_inline ; Print ' Lib. '
AE39 EQUS " Lib. " ; Print ' Lib. '
AE43 LDX #&1b ; Offset &1B: library name
AE45 JSR print_10_chars ; Print library name (10 chars)
AE48 JSR osnewl ; Write newline (characters 10 and 13)
AE4B PLA ; Restore flags
AE4C STA fs_lib_flags ; Store restored flags
AE4F .setup_ex_pagination←1← AE80 BNE
STY fs_func_code ; Store entry count
AE52 STY fs_work_4 ; Also store in work_4
AE54 LDX fs_work_5 ; Get command code
AE56 STX fs_data_count ; Store in buffer
AE59 LDX fs_work_7 ; Get entries per page
AE5B STX fs_cmd_data ; Store in buffer
AE5E LDX #3 ; X=3: buffer offset
AE60 JSR copy_arg_to_buf ; Copy argument to buffer
AE63 LDY #3 ; Y=3: FS command for examine/cat
AE65 JSR save_net_tx_cb ; Send request to file server
AE68 INX ; Advance past header
AE69 LDA fs_cmd_data ; Get number of entries returned
AE6C BEQ jmp_osnewl ; Zero: no more entries
AE6E PHA ; Save entry count
AE6F .loop_scan_entry_data←1← AE73 BPL
INY ; Advance Y
AE70 LDA fs_cmd_data,y ; Get entry data byte
AE73 BPL loop_scan_entry_data ; Bit 7 clear: more data
AE75 STA fs_cmd_lib,y ; Store terminator byte
AE78 JSR ex_print_col_sep ; Print entry with column separator
AE7B PLA ; Restore entry count
AE7C CLC ; Clear carry for addition
AE7D ADC fs_work_4 ; Add entries processed
AE7F TAY ; Transfer to Y
AE80 BNE setup_ex_pagination ; More entries: loop
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
AE82 .print_10_chars←3← ADBD JSR← AE33 JSR← AE45 JSR
LDY #&0a ; Y=10: characters to print
fall through ↓

Print Y characters from buffer via OSASCI

Loops Y times, loading each byte from l0f05+X and printing it via OSASCI. Advances X after each character, leaving X pointing past the last printed byte.

On EntryXbuffer offset
Ycharacter count
AE84 .print_chars_from_buf←2← ADFC JSR← AE8C BNE
LDA fs_cmd_data,x ; Get character from buffer
AE87 JSR osasci ; Write character
AE8A INX ; Next buffer position
AE8B DEY ; Decrement count
AE8C BNE print_chars_from_buf ; Loop until 10 printed
AE8E RTS ; Return
AE8F .jmp_osnewl←1← AE6C BEQ
JMP osnewl ; Write newline (characters 10 and 13)

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.

AE92 .parse_cmd_arg_y0←2← 9D05 JSR← A1C7 JSR
LDY #0 ; Y=0: start of command line
fall through ↓

Parse filename via GSREAD with prefix handling

Calls gsread_to_buf to read the command line string into the &0E30 buffer, then falls through to parse_access_prefix to process '&', ':', '.', and '#' prefix characters.

AE94 .parse_filename_arg←4← AD37 JSR← ADA2 JSR← AF77 JSR← B363 JSR
JSR gsread_to_buf ; Read string to buffer via GSREAD
fall through ↓

Parse access and FS selection prefix characters

Examines the first character(s) of the parsed buffer at &0E30 for prefix characters: '&' sets the FS selection flag (bit 6 of l1071) 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.

AE97 .parse_access_prefix←4← 92EE JSR← 9396 JSR← 93CF JSR← 993E JSR
LDA fs_filename_buf ; Get first parsed character
AE9A EOR #&26 ; Is it '&'?
AE9C BNE check_colon_prefix ; No: check for ':' prefix
AE9E LDA fs_lib_flags ; Get flags
AEA1 ORA #&40 ; Set FS selection flag (bit 6)
AEA3 STA fs_lib_flags ; Store updated flags
AEA6 JSR strip_token_prefix ; Remove '&' prefix character
AEA9 LDA fs_filename_buf ; Get next character
AEAC EOR #&2e ; Is it '.'?
AEAE BNE check_hash_prefix ; No: check for '#'
AEB0 LDA fs_filename_buf_1 ; Get char after '.'
AEB3 EOR #&0d ; Is it CR (end of line)?
AEB5 BEQ error_bad_prefix ; Yes: '&.' + CR only = bad filename
fall through ↓

Strip first character from parsed token buffer

Shifts all bytes in the &0E30 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 ':'.

AEB7 .strip_token_prefix←5← 931C JSR← 93B6 JSR← 93BC JSR← AEA6 JSR← AEF9 BNE
TXA ; Save X
AEB8 PHA ; Push X
AEB9 LDX #&ff ; X=&FF, will increment to 0
AEBB .loop_shift_str_left←1← AEC4 BNE
INX ; Increment X
AEBC LDA fs_filename_buf_1,x ; Get character at offset+1
AEBF STA fs_filename_buf,x ; Store at offset (shift left)
AEC2 EOR #&0d ; Is it CR (end of line)?
AEC4 BNE loop_shift_str_left ; No: continue shifting
AEC6 TXA ; Get shifted string length
AEC7 BEQ done_strip_prefix ; Zero length: skip trailing trim
AEC9 .loop_trim_trailing←1← AED6 BNE
LDA fs_filename_buf_m1,x ; Get character at end of string
AECC EOR #&20 ; Is it a space?
AECE BNE done_strip_prefix ; No: done trimming
AED0 LDA #&0d ; Replace trailing space with CR
AED2 STA fs_filename_buf_m1,x ; Store CR
AED5 DEX ; Move back
AED6 BNE loop_trim_trailing ; Loop while more trailing spaces
AED8 .done_strip_prefix←2← AEC7 BEQ← AECE BNE
PLA ; Restore X
AED9 TAX ; Transfer back to X
AEDA .return_from_strip_prefix←3← AEDD BEQ← AEE4 BNE← AEEF BNE
RTS ; Return
AEDB .check_hash_prefix←1← AEAE BNE
EOR #&23 ; Is it '#'?
AEDD BEQ return_from_strip_prefix ; Yes: '#' prefix accepted
AEDF .error_bad_prefix←2← AEB5 BEQ← AF12 BEQ
JMP error_bad_filename ; Bad filename error
AEE2 .check_colon_prefix←1← AE9C BNE
EOR #&1c ; Check for ':' prefix
AEE4 BNE return_from_strip_prefix ; Neither '&' nor ':': no prefix
AEE6 LDA fs_filename_buf_2 ; Get character after ':'
AEE9 EOR #&2e ; Is it '.'?
AEEB BEQ set_fs_select_flag ; Yes: ':.' qualified prefix
AEED EOR #&23 ; Is it CR (end of line)?
AEEF BNE return_from_strip_prefix ; No: no FS prefix, return
AEF1 .set_fs_select_flag←1← AEEB BEQ
LDA fs_lib_flags ; Get flags
AEF4 ORA #&40 ; Set FS selection flag (bit 6)
AEF6 STA fs_lib_flags ; Store updated flags
AEF9 BNE strip_token_prefix ; ALWAYS branch
AEFB .option_str_offset_data←1← AE19 LDY
BRK ; Data: option string offset table
AEFC EQUS "/<c"
AEFF .roff_off_string←1← AE1C 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.

AF02 .copy_arg_to_buf_x0←6← 8DBC JSR← 8E20 JSR← 994C JSR← 9B3C JSR← A299 JSR← AF7A JSR
LDX #0 ; 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.

AF04 .copy_arg_to_buf←10← 99FE JSR← 9B35 JSR← 9B58 JSR← 9D0A JSR← A1CC JSR← A1F9 JSR← AD3C JSR← ADA7 JSR← AE60 JSR← B378 JSR
LDY #0 ; Y=0: start of argument
fall through ↓

Copy command line characters to TX buffer

Copies characters from (fs_crc_lo)+Y to l0f05+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
AF06 .copy_arg_validated←3← 8DB7 JSR← 941E JSR← 9454 JSR
SEC ; Set carry: enable '&' validation
AF07 .loop_copy_char←1← AF1A BNE
LDA (fs_crc_lo),y ; Get character from command line
AF09 STA fs_cmd_data,x ; Store in TX buffer
AF0C BCC advance_positions ; Carry clear: skip validation
AF0E CMP #&21 ; Is it '!' or above?
AF10 EOR #&26 ; Is it '&'?
AF12 BEQ error_bad_prefix ; Yes: '&' not allowed in filenames
AF14 .restore_after_check
EOR #&26 ; '&' in filename: bad filename Restore A (undo '&' EOR)
AF16 .advance_positions←1← AF0C BCC
INX ; Advance buffer position
AF17 INY ; Advance source position
AF18 EOR #&0d ; Is it CR (end of line)?
AF1A BNE loop_copy_char ; No: continue copying
AF1C .loop_caf1c←1← AF29 BNE
LDA fs_cmd_csd,x ; Load character from end of buffer
AF1F EOR #&20 ; Test for space (&20)
AF21 BNE done_trim_spaces ; Not a space: done trimming
AF23 DEX ; Back up one position
AF24 LDA #&0d ; CR terminator
AF26 STA fs_cmd_lib,x ; Replace trailing space with CR
AF29 BNE loop_caf1c ; ALWAYS: trim next character back ALWAYS branch
AF2B .done_trim_spaces←1← AF21 BNE
LDA #0 ; A=0: success return code
AF2D .return_from_copy_arg←1← AF43 BMI
RTS ; Return
AF2E EQUS "Load"

Clear FS selection flags from options word

ANDs the l1071 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.

AF32 .mask_owner_access←13← 8E3B JSR← 9390 JSR← 93CA JSR← 993B JSR← 9D55 JSR← 9E36 JSR← A1C4 JSR← A250 JSR← A25A JSR← A8D0 JSR← AD12 JSR← ADF1 JSR← B359 JSR
LDA fs_lib_flags ; Get flags
AF35 AND #&1f ; Mask to low 5 bits only
AF37 STA fs_lib_flags ; Store masked flags
AF3A RTS ; Return
AF3B EQUS "Run"
AF3E .fsreply_0_print_dir
LDX #0 ; X=0: start from first entry
AF40 .loop_scan_entries←1← AF60 BNE
LDA fs_cmd_data,x ; Get entry byte from buffer
AF43 BMI return_from_copy_arg ; High bit set: end of entries
AF45 BNE print_entry_char ; Non-zero: printable character
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.

AF47 .ex_print_col_sep←1← AE78 JSR
LDY fs_spool_handle ; Get column counter
AF49 BMI print_col_newline ; Negative: newline mode (Ex)
AF4B INY ; Increment column counter
AF4C TYA ; Transfer to A
AF4D AND #3 ; Modulo 4 (Cat: 3 per row)
AF4F STA fs_spool_handle ; Store updated counter
AF51 BEQ print_col_newline ; Zero: row full, print newline
AF53 JSR print_inline ; Print ' ' column separator
AF56 EQUS " "
AF58 BNE next_col_entry ; Skip newline; ALWAYS branch
AF5A .print_col_newline←2← AF49 BMI← AF51 BEQ
LDA #&0d ; CR character for newline
AF5C .print_entry_char←1← AF45 BNE
JSR osasci ; Write character 13
AF5F .next_col_entry←1← AF58 BNE
INX ; Advance to next entry
AF60 BNE loop_scan_entries ; Loop for more entries
AF62 EOR zp_0078 ; Embedded string data 'Exec'
AF64 ADC zp_0063 ; Embedded string data (contd)
fall through ↓

*Remove command handler

Like *Delete but suppresses the 'Not found' error, making it suitable for use in programs where a missing file should not cause an unexpected error. Validates that exactly one argument is present — raises 'Syntax' if extra arguments follow. Parses the filename via parse_filename_arg, copies it to the TX buffer, and sends FS command code &14 with the V flag set via BIT for save_net_tx_cb_vset dispatch.

On EntryYcommand line offset in text pointer
AF66 .cmd_remove
TYA ; Save command line offset
AF67 PHA ; Push onto stack
AF68 JSR skip_to_next_arg ; Skip to check for extra arguments
AF6B CMP #&0d ; End of line?
AF6D BEQ done_extra_arg_check ; Yes: single arg, proceed
AF6F JMP error_syntax ; No: extra args, syntax error
AF72 .done_extra_arg_check←1← AF6D BEQ
PLA ; Restore command line offset
AF73 TAY ; Transfer to Y
AF74 JSR save_ptr_to_os_text ; Save text pointer for parsing
AF77 JSR parse_filename_arg ; Parse filename argument
AF7A JSR copy_arg_to_buf_x0 ; Copy filename to TX buffer
AF7D LDY #&14 ; Y=&14: *Delete FS command code
AF7F BIT bit_test_ff ; Set V flag (via BIT #&FF)
AF82 JMP save_net_tx_cb_vset ; Send to FS with V-flag dispatch

Print decimal number with leading zero suppression

Sets V via BIT bit_test_ff to enable leading zero suppression, then falls through to print_decimal_3dig. Used by print_station_id for compact station number display.

On EntryAnumber to print (0-255)
AF85 .print_num_no_leading←1← 9007 JSR
BIT bit_test_ff ; 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
AF88 .print_decimal_3dig←3← ADC7 JSR← B19D JSR← B1B4 JMP
TAY ; Transfer value to Y (remainder)
AF89 LDA #&64 ; A=100: hundreds divisor
AF8B JSR print_decimal_digit ; Print hundreds digit
AF8E LDA #&0a ; A=10: tens divisor
AF90 JSR print_decimal_digit ; Print tens digit
AF93 CLV ; Clear V (always print units)
AF94 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
AF96 .print_decimal_digit←2← AF8B JSR← AF90 JSR
STA fs_error_ptr ; Store divisor
AF98 TYA ; Get remaining value
AF99 LDX #&2f ; X='0'-1: digit counter
AF9B SEC ; Set carry for subtraction
AF9C PHP ; Save V flag for leading zero check
AF9D .loop_divide_digit←1← AFA0 BCS
INX ; Count quotient digit
AF9E SBC fs_error_ptr ; Subtract divisor
AFA0 BCS loop_divide_digit ; No underflow: continue dividing
AFA2 ADC fs_error_ptr ; Add back divisor (get remainder)
AFA4 TAY ; Remainder to Y for next digit
AFA5 TXA ; Digit character to A
AFA6 PLP ; Restore V flag
AFA7 BVC print_nonzero_digit ; V clear: always print digit
AFA9 CMP #&30 ; V set: is digit '0'?
AFAB BEQ return_from_print_digit ; Yes: suppress leading zero
AFAD .print_nonzero_digit←1← AFA7 BVC
LDX fs_error_ptr ; Save divisor across OSASCI call
AFAF JSR osasci ; Write character
AFB2 STX fs_error_ptr ; Restore divisor
AFB4 .return_from_print_digit←1← AFAB BEQ
RTS ; Return

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.

AFB5 .save_ptr_to_os_text←8← 9D5B JSR← A1C1 JSR← AD34 JSR← AD96 JSR← AF74 JSR← B038 JSR← B216 JSR← B360 JSR
PHA ; Save A
AFB6 LDA fs_crc_lo ; Copy text pointer low byte
AFB8 STA os_text_ptr ; To OS text pointer low
AFBA LDA fs_crc_hi ; Copy text pointer high byte
AFBC STA os_text_ptr_hi ; To OS text pointer high
AFBE PLA ; Restore A
AFBF RTS ; Return
AFC0 .loop_advance_char←1← AFCB 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 ExitAfirst non-space character or CR
Yoffset of that character
AFC1 .skip_to_next_arg←2← AD15 JSR← AF68 JSR
LDA (fs_crc_lo),y ; Load char from command line
AFC3 CMP #&20 ; Space?
AFC5 BEQ loop_skip_space_chars ; Yes: skip trailing spaces
AFC7 CMP #&0d ; CR (end of line)?
AFC9 BEQ return_from_skip_arg ; Yes: return (at end)
AFCB BNE loop_advance_char ; ALWAYS branch
AFCD .loop_skip_space_chars←2← AFC5 BEQ← AFD2 BEQ
INY ; Advance past space
AFCE LDA (fs_crc_lo),y ; Load next character
AFD0 CMP #&20 ; Still a space?
AFD2 BEQ loop_skip_space_chars ; Yes: skip multiple spaces
AFD4 .return_from_skip_arg←1← AFC9 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.

AFD5 .save_ptr_to_spool_buf←2← AFFB JSR← B1D2 JSR
PHA ; Save A
AFD6 LDA fs_crc_lo ; Copy text pointer low byte
AFD8 STA fs_options ; To spool buffer pointer low
AFDA LDA fs_crc_hi ; Copy text pointer high byte
AFDC STA fs_block_offset ; To spool buffer pointer high
AFDE PLA ; Restore A
AFDF 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 (l00af), and clears the low byte (l00ae) to zero. Preserves Y on the stack.

AFE0 .init_spool_drive←2← AFF8 JSR← B1C5 JSR
TYA ; Save Y
AFE1 PHA ; Push it
AFE2 JSR get_ws_page ; Get workspace page number
AFE5 STA addr_work ; Store as spool drive page high
AFE7 PLA ; Restore Y
AFE8 TAY ; Transfer to Y
AFE9 LDA #0 ; A=0
AFEB STA work_ae ; Clear spool drive page low
AFED 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
AFEE .cmd_ps
LDA #1 ; A=1: check printer ready
AFF0 BIT ws_0d6a ; Test printer server workspace flag
AFF3 BNE done_ps_available ; Non-zero: printer available
AFF5 JMP err_printer_busy ; Printer not available: error
AFF8 .done_ps_available←1← AFF3 BNE
JSR init_spool_drive ; Initialise spool drive
AFFB JSR save_ptr_to_spool_buf ; Save pointer to spool buffer
AFFE LDA (fs_options),y ; Get first argument character
B000 CMP #&0d ; End of command line?
B002 BEQ no_ps_name_given ; Yes: no argument given
B004 CLV ; Clear V (= explicit PS name given)
B005 JSR is_decimal_digit ; Is first char a decimal digit?
B008 BCC save_ps_cmd_ptr ; Yes: station number, skip PS name
B00A TYA ; PS name follows
B00B PHA ; Save Y
B00C JSR load_ps_server_addr ; Load PS server address
B00F PLA ; Restore Y
B010 TAY ; Back to Y register
B011 JSR parse_fs_ps_args ; Parse FS/PS arguments
B014 JMP store_ps_station_addr ; 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.

B017 .copy_ps_data_y1c←1← 8EF9 JSR
LDY #&18 ; Start at offset &1C
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 (&8E59). This 6502 trick reaches data 248 bytes past the base label using a single instruction.

B019 .copy_ps_data←1← B1F8 JSR
LDX #&f8 ; X=&F8: offset into template
B01B .loop_copy_ps_tmpl←1← B022 BNE
LDA ps_template_base,x ; Get template byte
B01E STA (net_rx_ptr),y ; Store in RX buffer
B020 INY ; Next destination offset
B021 INX ; Next source offset
B022 BNE loop_copy_ps_tmpl ; Loop until X wraps to 0
B024 RTS ; Return
B025 .no_ps_name_given←1← B002 BEQ
BIT bit_test_ff ; Set V (= no explicit PS name)
B028 .save_ps_cmd_ptr←1← B008 BCC
STY ws_ptr_hi ; Save command line pointer
B02A BVS done_ps_name_parse ; V set: skip PS name parsing
B02C LDX #6 ; Max 6 characters for PS name
B02E LDY #&18 ; Buffer offset &1C for PS name
B030 LDA #&20 ; Space character
B032 .loop_pad_ps_name←1← B036 BNE
STA (net_rx_ptr),y ; Fill buffer with space
B034 INY ; Next position
B035 DEX ; Count down
B036 BNE loop_pad_ps_name ; Loop until 6 spaces filled
B038 JSR save_ptr_to_os_text ; Save text pointer
B03B LDY ws_ptr_hi ; Restore command line pointer
B03D JSR gsinit ; Initialise string reading
B040 BEQ done_ps_name_parse ; Empty string: skip to send
B042 LDX #6 ; Max 6 characters
B044 STY ws_ptr_hi ; Save updated string pointer
B046 LDY #&18 ; Buffer offset for PS name
B048 STY table_idx ; Save buffer position
B04A .loop_read_ps_char←1← B056 BNE
LDY ws_ptr_hi ; Restore string pointer
B04C JSR gsread ; Read next character
B04F STY ws_ptr_hi ; Save updated pointer
B051 BCS done_ps_name_parse ; End of string: go to send
B053 JSR store_char_uppercase ; Store char uppercased in buffer
B056 BNE loop_read_ps_char ; Loop for more characters
B058 .done_ps_name_parse←3← B02A BVS← B040 BEQ← B051 BCS
JSR reverse_ps_name_to_tx ; Copy reversed PS name to TX
B05B JSR send_net_packet ; Send PS status request
B05E JSR pop_requeue_ps_scan ; Pop and requeue PS scan
B061 JSR load_ps_server_addr ; Load PS server address
B064 LDA #0 ; A=0
B066 TAX ; X=&00
B067 LDY #&20 ; Offset &24 in buffer
B069 STA (net_rx_ptr),y ; Clear PS status byte
B06B .loop_pop_ps_slot←1← B097 BNE
PLA ; Get slot offset from stack
B06C BEQ done_ps_scan ; Zero: all slots done
B06E PHA ; Save slot offset
B06F TAY ; Transfer to Y
B070 LDA (nfs_workspace),y ; Read slot status
B072 BPL done_ps_slot_mark ; Bit 7 clear: slot inactive
B074 JSR advance_y_by_4 ; Advance Y by 4 (to status page)
B077 LDA (nfs_workspace),y ; Read status page pointer
B079 STA work_ae ; Store pointer low
B07B LDA (work_ae,x) ; Read printer status byte
B07D BEQ read_ps_station_addr ; Zero (idle): show station info
B07F CMP #3 ; Status 3 (paused)?
B081 BNE done_ps_slot_mark ; Non-zero (busy): skip
B083 .read_ps_station_addr←1← B07D BEQ
DEY ; Back to network number
B084 LDA (nfs_workspace),y ; Read network number
B086 STA fs_work_6 ; Store network number
B088 DEY ; Back to station number
B089 LDA (nfs_workspace),y ; Read station number
B08B STA fs_work_5 ; Store station low
B08D LDY #&20 ; Offset &24 in buffer
B08F STA (net_rx_ptr),y ; Store ready station in buffer
B091 .done_ps_slot_mark←2← B072 BPL← B081 BNE
PLA ; Retrieve slot offset
B092 TAY ; Transfer to Y
B093 LDA #&3f ; Mark slot as processed (&3F)
B095 STA (nfs_workspace),y ; Write marker to workspace
B097 BNE loop_pop_ps_slot ; ALWAYS branch
B099 .done_ps_scan←1← B06C BEQ
JSR print_printer_server_is ; Print 'Printer server is '
B09C LDY #&20 ; Offset &24: PS station number
B09E LDA (net_rx_ptr),y ; Get stored station number
B0A0 BNE print_ps_now ; Non-zero: server changed
B0A2 JSR print_inline ; Print 'still '
B0A5 EQUS "still "
B0AB CLV ; Clear V
B0AC BVC done_ps_status_msg ; ALWAYS branch
B0AE .print_ps_now←1← B0A0 BNE
JSR print_inline ; Print 'now '
B0B1 EQUS "now "
B0B5 NOP ; Padding
B0B6 .done_ps_status_msg←1← B0AC BVC
JSR print_fs_info_newline ; Workspace offset 2
B0B9 .store_ps_station_addr←1← B014 JMP
LDY #2 ; Y=2: workspace offset for station
B0BB LDA fs_work_5 ; Get station low
B0BD STA (nfs_workspace),y ; Store in workspace
B0BF INY ; Y=&03
B0C0 LDA fs_work_6 ; Get network number
B0C2 STA (nfs_workspace),y ; Store in workspace
B0C4 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.

B0C5 .print_file_server_is←1← A09B JSR
JSR print_inline ; Print 'File'
B0C8 EQUS "File"
B0CC CLV ; Clear V
B0CD BVC print_server_is_suffix ; ALWAYS branch

Print 'Printer server is ' prefix

Uses print_inline to output the full label 'Printer server is ' with trailing space.

B0CF .print_printer_server_is←2← B099 JSR← B240 JSR
JSR print_inline ; Print 'Printer'
B0D2 EQUS "Printer"
B0D9 NOP ; Padding
B0DA .print_server_is_suffix←1← B0CD BVC
JSR print_inline ; Print ' server is '
B0DD EQUS " server is "
B0E8 NOP ; Padding
B0E9 RTS ; Return

Load printer server address from workspace

Reads the station and network bytes from workspace offsets 2 and 3 into the station/network variables.

B0EA .load_ps_server_addr←4← B00C JSR← B061 JSR← B1E3 JSR← B243 JSR
LDY #2 ; Workspace offset 2
B0EC LDA (nfs_workspace),y ; Read station low
B0EE STA fs_work_5 ; Store station low
B0F0 INY ; Y=&03
B0F1 LDA (nfs_workspace),y ; Read network number
B0F3 STA fs_work_6 ; Store network number
B0F5 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.

B0F6 .pop_requeue_ps_scan←2← B05E JSR← B23D JSR
PLA ; Pop return address low
B0F7 STA osword_flag ; Save return address low
B0F9 PLA ; Pop return address high
B0FA STA ws_ptr_lo ; Save return address high
B0FC LDA #0 ; Push 0 as end-of-list marker
B0FE PHA ; Push it
B0FF LDA #&84 ; Start scanning from offset &84
B101 STA ws_ptr_hi ; Store scan position
B103 LSR econet_flags ; Shift PS slot flags right
B106 LDA #3 ; Counter: 3 PS slots
B108 .loop_scan_ps_slots←1← B11A BNE
JSR byte_to_2bit_index ; Convert to 2-bit workspace index
B10B BCS done_ps_slot_scan ; Carry set: no more slots
B10D LSR ; Shift right twice
B10E LSR ; To get slot offset
B10F TAX ; Transfer to X
B110 LDA (nfs_workspace),y ; Read slot status byte
B112 BEQ done_ps_slot_scan ; Zero: empty slot, done
B114 CMP #&3f ; Is it processed marker (&3F)?
B116 BEQ reinit_ps_slot ; Yes: re-initialise this slot
B118 .skip_next_ps_slot←1← B141 JMP
INX ; Try next slot
B119 TXA ; Transfer slot index to A
B11A BNE loop_scan_ps_slots ; Loop for more slots
B11C .reinit_ps_slot←1← B116 BEQ
TYA ; Y = workspace offset of slot
B11D PHA ; Push slot offset for scan list
B11E LDA #&7f ; Set active status (&7F)
B120 STA (nfs_workspace),y ; Write status byte
B122 INY ; Next byte
B123 LDA #&9e ; Low byte: workspace page
B125 STA (nfs_workspace),y ; Write workspace pointer low
B127 LDA #0 ; A=0
B129 JSR write_two_bytes_inc_y ; Write two zero bytes + advance Y
B12C LDA ws_ptr_hi ; Get current scan page
B12E STA (nfs_workspace),y ; Write RX buffer page low
B130 CLC ; Clear carry for addition
B131 PHP ; Save processor status
B132 ADC #3 ; Advance by 3 pages
B134 PLP ; Restore processor status
B135 STA ws_ptr_hi ; Update scan position
B137 JSR write_ps_slot_byte_ff ; Write buffer page + &FF bytes
B13A LDA ws_ptr_hi ; Get updated scan position
B13C STA (nfs_workspace),y ; Write RX buffer page high
B13E .write_ps_slot_hi_link
JSR write_ps_slot_byte_ff ; Write another page + &FF bytes
B141 JMP skip_next_ps_slot ; Continue scanning slots
B144 .done_ps_slot_scan←2← B10B BCS← B112 BEQ
ASL econet_flags ; Shift PS slot flags back
B147 LDA ws_ptr_lo ; Restore return address high
B149 PHA ; Push onto stack
B14A LDA osword_flag ; Restore return address low
B14C PHA ; Push onto stack
B14D LDA #&0a ; Delay counter: 10
B14F TAY ; Y=&0a
B150 TAX ; X=&0a
B151 STA fs_work_4 ; Outer loop counter = 10
B153 .loop_ps_delay←3← B154 BNE← B157 BNE← B15B BNE
DEY ; Decrement Y (inner loop)
B154 BNE loop_ps_delay ; Inner loop: 10 iterations
B156 DEX ; Decrement X (middle loop)
B157 BNE loop_ps_delay ; Middle loop: 10 iterations
B159 DEC fs_work_4 ; Decrement outer counter
B15B BNE loop_ps_delay ; Outer loop: ~1000 delay cycles
B15D 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.

B15E .write_ps_slot_byte_ff←2← B137 JSR← B13E JSR
INY ; Advance Y
B15F LDA addr_work ; Get buffer page
B161 STA (nfs_workspace),y ; Store in workspace
B163 LDA #&ff ; 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
B165 .write_two_bytes_inc_y←1← B129 JSR
INY ; Advance Y
B166 STA (nfs_workspace),y ; Write byte to workspace
B168 INY ; Advance Y
B169 STA (nfs_workspace),y ; Write byte to workspace
B16B INY ; Advance Y
B16C RTS ; Return

Reverse-copy printer server name to TX buffer

Copies 8 bytes from the RX buffer (offsets &1C-&23) to the TX buffer (offsets &13-&1B) in reversed byte order, pushing onto the stack then popping back.

B16D .reverse_ps_name_to_tx←2← B058 JSR← B1CA JSR
LDY #&18 ; Start of PS name at offset &1C
B16F .loop_push_ps_name←1← B175 BNE
LDA (net_rx_ptr),y ; Load byte from RX buffer
B171 PHA ; Push to stack (for reversal)
B172 INY ; Next source byte
B173 CPY #&20 ; End of PS name field (&20)?
B175 BNE loop_push_ps_name ; No: continue pushing
B177 LDY #&17 ; End of TX name field at &1B
B179 .loop_pop_ps_name←1← B17F BNE
PLA ; Pop byte (reversed order)
B17A STA (net_rx_ptr),y ; Store in RX buffer
B17C DEY ; Previous position
B17D CPY #&0f ; Start of TX field (&0F)?
B17F BNE loop_pop_ps_name ; No: continue popping
B181 LDA net_rx_ptr_hi ; Copy RX page to TX
B183 STA net_tx_ptr_hi ; Set TX pointer high
B185 LDA #&0c ; TX offset &10
B187 STA net_tx_ptr ; Set TX pointer low
B189 LDY #3 ; Copy 4 header bytes
B18B .loop_copy_tx_hdr←1← B191 BPL
LDA ps_tx_header_template,y ; Get header template byte
B18E STA (net_tx_ptr),y ; Store in TX buffer
B190 DEY ; Previous byte
B191 BPL loop_copy_tx_hdr ; Loop until all 4 copied
B193 RTS ; Return
; Printer server TX header template
; 4-byte header copied to the TX control block by
; reverse_ps_name_to_tx. Sets up an immediate
; transmit on port &9F (PS port) to any station.
B194 .ps_tx_header_template←1← B18B LDA
EQUB &80 ; Control byte &80 (immediate TX)
B195 EQUB &9F ; Port &9F (printer server)
B196 EQUB &FF ; Station &FF (any)
B197 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.

B198 .print_station_addr←4← A0A1 JSR← B249 JSR← B281 JSR← B2D1 JSR
PHP ; Save V flag (controls padding)
B199 LDA fs_work_6 ; Get network number
B19B BEQ skip_if_local_net ; Zero: no network prefix
B19D JSR print_decimal_3dig ; Print network as 3 digits
B1A0 LDA #&2e ; '.' separator
B1A2 JSR osasci ; Write character 46
B1A5 BIT bit_test_ff ; Set V (suppress station padding)
B1A8 .skip_if_local_net←1← B19B BEQ
BVS print_station_only ; V set: skip padding spaces
B1AA JSR print_inline ; Print 4 spaces (padding)
B1AD EQUS " "
B1B1 .print_station_only←1← B1A8 BVS
LDA fs_work_5 ; Get station number
B1B3 PLP ; Restore flags
B1B4 JMP print_decimal_3dig ; Print station as 3 digits
; PS slot transmit control block template
; 12-byte Econet TXCB initialisation template for
; printer server slot buffers. Not referenced by
; label; accessed indirectly by init_ps_slot_from_rx
; via LDA write_ps_slot_link_addr,Y where the base
; address write_ps_slot_hi_link+1 plus Y offset &78 computes to ; &B1B7.
; Structure: 4-byte header (control, port, station,
; network) followed by two 4-byte buffer descriptors
; (lo address, hi page, end lo, end hi). The hi page
; bytes at positions 5 and 9 are overwritten with
; net_rx_ptr_hi during the copy to point into the
; actual RX buffer page. End bytes &FF are
; placeholders filled in later by the caller.
B1B7 .ps_slot_txcb_template
EQUB &80 ; Control byte &80 (immediate TX)
B1B8 EQUB &9F ; Port &9F (printer server)
B1B9 EQUB &00 ; Station 0 (filled in later)
B1BA EQUB &00 ; Network 0 (filled in later)
B1BB EQUB &10
B1BC EQUB &00 ; Data buffer start hi (= rx page)
B1BD EQUB &FF ; Data buffer end lo (placeholder)
B1BE EQUB &FF ; Data buffer end hi (placeholder)
B1BF EQUB &18
B1C0 EQUB &00 ; Reply buffer start hi (= rx page)
B1C1 EQUB &FF ; Reply buffer end lo (placeholder)
B1C2 EQUB &FF ; Reply buffer end hi (placeholder)

*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
B1C3 .cmd_pollps
STY ws_ptr_hi ; Save command line pointer high
B1C5 JSR init_spool_drive ; Initialise spool/print drive
B1C8 STA fs_work_4 ; Save spool drive number
B1CA JSR reverse_ps_name_to_tx ; Copy PS name to TX buffer
B1CD JSR init_ps_slot_from_rx ; Init PS slot from RX data
B1D0 LDY ws_ptr_hi ; Restore command line pointer
B1D2 JSR save_ptr_to_spool_buf ; Save pointer to spool buffer
B1D5 LDA (fs_options),y ; Get first argument character
B1D7 CMP #&0d ; End of command line?
B1D9 BEQ no_poll_name_given ; Yes: no argument given
B1DB CLV ; Clear V (= explicit PS name given)
B1DC JSR is_decimal_digit ; Is first char a decimal digit?
B1DF BCC skip_if_no_poll_arg ; Yes: station number, skip PS name
B1E1 TYA ; PS name follows
B1E2 PHA ; Save Y
B1E3 JSR load_ps_server_addr ; Load PS server address
B1E6 PLA ; Restore Y
B1E7 TAY ; Back to Y register
B1E8 JSR parse_fs_ps_args ; Parse FS/PS arguments
B1EB LDY #&7a ; Offset &7A in slot buffer
B1ED LDA fs_work_5 ; Get parsed station low
B1EF STA (work_ae),y ; Store station number low
B1F1 INY ; Y=&7b
B1F2 LDA fs_work_6 ; Get parsed network number
B1F4 STA (work_ae),y ; Store station number high
B1F6 LDY #&10 ; Offset &14 in TX buffer
B1F8 JSR copy_ps_data ; Copy PS data to TX buffer
B1FB LDA addr_work ; Get buffer page high
B1FD STA net_tx_ptr_hi ; Set TX pointer high byte
B1FF LDA #&78 ; Offset &78 in buffer
B201 STA net_tx_ptr ; Set TX pointer low byte
B203 BNE done_poll_name_parse ; ALWAYS branch
B205 .no_poll_name_given←1← B1D9 BEQ
BIT bit_test_ff ; Set V (= no explicit PS name)
B208 .skip_if_no_poll_arg←1← B1DF BCC
BVS done_poll_name_parse ; V set (no arg): skip to send
B20A LDX #6 ; Max 6 characters for PS name
B20C LDY #&10 ; Buffer offset for PS name
B20E LDA #&20 ; Space character
B210 .loop_pad_poll_name←1← B214 BNE
STA (net_rx_ptr),y ; Fill buffer position with space
B212 INY ; Next position
B213 DEX ; Count down
B214 BNE loop_pad_poll_name ; Loop until 6 spaces filled
B216 JSR save_ptr_to_os_text ; Save pointer to OS text
B219 LDY ws_ptr_hi ; Restore command line pointer
B21B JSR gsinit ; Initialise string reading
B21E BEQ done_poll_name_parse ; Empty string: skip to send
B220 LDX #6 ; Max 6 characters
B222 STY ws_ptr_hi ; Save updated string pointer
B224 LDY #&10 ; Buffer offset for PS name
B226 STY table_idx ; Save buffer position
B228 .loop_read_poll_char←1← B234 BNE
LDY ws_ptr_hi ; Restore string pointer
B22A JSR gsread ; Read next char from string
B22D STY ws_ptr_hi ; Save updated string pointer
B22F BCS done_poll_name_parse ; End of string: go to send
B231 JSR store_char_uppercase ; Store char uppercased in buffer
B234 BNE loop_read_poll_char ; Loop if more chars to copy
B236 .done_poll_name_parse←4← B203 BNE← B208 BVS← B21E BEQ← B22F BCS
LDA #&80 ; Enable escape checking
B238 STA need_release_tube ; Set escapable flag
B23A JSR send_net_packet ; Send the poll request packet
B23D JSR pop_requeue_ps_scan ; Pop and requeue PS scan
B240 JSR print_printer_server_is ; Print 'Printer server '
B243 JSR load_ps_server_addr ; Load PS server address
B246 BIT bit_test_ff ; Set V and N flags
B249 JSR print_station_addr ; Print station address
B24C JSR print_inline ; Print ' "'
B24F EQUS " ""
B251 LDY #&18 ; Y=&18: name field offset in RX buffer
B253 .loop_print_poll_name←1← B25F BNE
LDA (net_rx_ptr),y ; Get character from name field
B255 CMP #&20 ; Is it a space?
B257 BEQ done_poll_name_print ; Yes: end of name
B259 JSR osasci ; Write character
B25C INY ; Next character
B25D CPY #&1e ; Past end of name field?
B25F BNE loop_print_poll_name ; No: continue printing name
B261 .done_poll_name_print←1← B257 BEQ
JSR print_inline ; Print '"' + CR
B264 EQUS ""."
B266 NOP ; Padding byte
B267 .loop_pop_poll_slot←1← B2DD BNE
PLA ; Get slot offset from stack
B268 BEQ return_from_poll_slots ; Zero: all slots done, return
B26A PHA ; Save slot offset
B26B TAY ; Transfer to Y
B26C LDA (nfs_workspace),y ; Read slot status byte
B26E BPL done_poll_slot_mark ; Bit 7 clear: slot inactive
B270 INY ; Advance to station number
B271 INY ; Offset+2 in slot
B272 LDA (nfs_workspace),y ; Read station number low
B274 STA fs_work_5 ; Store station low
B276 INY ; Next byte (offset+3)
B277 LDA (nfs_workspace),y ; Read network number
B279 STA fs_work_6 ; Store network number
B27B INY ; Next byte (offset+4)
B27C LDA (nfs_workspace),y ; Read status page pointer
B27E STA work_ae ; Store pointer low
B280 CLV ; Clear V flag
B281 JSR print_station_addr ; Print station address (V=0)
B284 JSR print_inline ; Print ' is '
B287 EQUS " is "
B28B LDX #0 ; X=0 for indirect indexed access
B28D LDA (work_ae,x) ; Read printer status byte
B28F BNE check_poll_jammed ; Non-zero: not ready
B291 JSR print_inline ; Print 'ready'
B294 EQUS "ready"
B299 CLV ; Clear V
B29A BVC done_poll_status_line ; ALWAYS branch
B29C .check_poll_jammed←1← B28F BNE
CMP #2 ; Status = 2?
B29E BNE check_poll_busy ; No: check for busy
B2A0 .print_poll_jammed←1← B2AE BNE
JSR print_inline ; Print 'jammed'
B2A3 EQUS "jammed"
B2A9 CLV ; Clear V
B2AA BVC done_poll_status_line ; ALWAYS branch
B2AC .check_poll_busy←1← B29E BNE
CMP #1 ; Status = 1?
B2AE BNE print_poll_jammed ; Not 1 or 2: default to jammed
B2B0 JSR print_inline ; Print 'busy'
B2B3 EQUS "busy"
B2B7 INC work_ae ; Advance past status byte
B2B9 LDA (work_ae,x) ; Read client station number
B2BB STA fs_work_5 ; Store station low
B2BD BEQ done_poll_status_line ; Zero: no client info, skip
B2BF JSR print_inline ; Print ' with station '
B2C2 EQUS " with "
B2C8 INC work_ae ; Advance pointer to network byte
B2CA LDA (work_ae,x) ; Load client network number
B2CC STA fs_work_6 ; Store network number
B2CE BIT bit_test_ff ; Set V flag
B2D1 JSR print_station_addr ; Print client station address
B2D4 .done_poll_status_line←3← B29A BVC← B2AA BVC← B2BD BEQ
JSR osnewl ; Write newline (characters 10 and 13)
B2D7 .done_poll_slot_mark←1← B26E BPL
PLA ; Retrieve slot offset
B2D8 TAY ; Transfer to Y
B2D9 LDA #&3f ; Mark slot as processed (&3F)
B2DB STA (nfs_workspace),y ; Write marker to workspace
B2DD BNE loop_pop_poll_slot ; ALWAYS branch
B2DF .return_from_poll_slots←1← B268 BEQ
RTS ; Return

Initialise PS slot buffer from template data

Copies the 12-byte ps_slot_txcb_template (&B1B7) 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.

B2E0 .init_ps_slot_from_rx←1← B1CD JSR
LDY #&78 ; Start at offset &78
B2E2 .loop_copy_slot_tmpl←1← B2F4 BNE
LDA write_ps_slot_link_addr,y ; Load template byte
B2E5 CPY #&7d ; At offset &7D?
B2E7 BEQ subst_rx_page_byte ; Yes: substitute RX page
B2E9 CPY #&81 ; At offset &81?
B2EB BNE store_slot_tmpl_byte ; No: use template byte
B2ED .subst_rx_page_byte←1← B2E7 BEQ
LDA net_rx_ptr_hi ; Use RX buffer page instead
B2EF .store_slot_tmpl_byte←1← B2EB BNE
STA (work_ae),y ; Store byte in slot buffer
B2F1 INY ; Next offset
B2F2 CPY #&84 ; Past end of slot (&84)?
B2F4 BNE loop_copy_slot_tmpl ; No: continue copying
B2F6 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
B2F7 .store_char_uppercase←2← B053 JSR← B231 JSR
LDY table_idx ; Y = current buffer position
B2F9 AND #&7f ; Strip high bit
B2FB CMP #&61 ; Is it lowercase 'a' or above?
B2FD BCC done_uppercase_store ; Below 'a': not lowercase
B2FF CMP #&7b ; Above 'z'?
B301 BCS done_uppercase_store ; Yes: not lowercase
B303 AND #&5f ; Convert to uppercase
B305 .done_uppercase_store←2← B2FD BCC← B301 BCS
STA (net_rx_ptr),y ; Store in RX buffer
B307 INY ; Next buffer position
B308 STY table_idx ; Update buffer position
B30A DEX ; Decrement character count
B30B RTS ; Return (Z set if count=0)

*Prot command handler

With no arguments, sets all protection bits (&FF). Otherwise parses attribute keywords via match_fs_cmd with table offset &D3, accumulating bits via ORA. Stores the final protection mask in ws_0d68 and ws_0d69.

On EntryYcommand line offset in text pointer
B30C .cmd_prot
LDA (fs_crc_lo),y ; Get next char from command line
B30E EOR #&0d ; Compare with CR (end of line)
B310 BNE parse_prot_keywords ; Not CR: attribute keywords follow
B312 LDA #&ff ; A=&FF: protect all attributes
B314 BNE store_prot_mask ; ALWAYS branch
B316 .parse_prot_keywords←1← B310 BNE
LDA ws_0d68 ; Load current protection mask
B319 PHA ; Save as starting value
B31A .loop_match_prot_attr←1← B32A BNE
LDX #&d3 ; X=&D3: attribute keyword table offset
B31C LDA (fs_crc_lo),y ; Get next char from command line
B31E STA ws_page ; Save for end-of-args check
B320 JSR match_fs_cmd ; Match attribute keyword in table
B323 BCS prot_check_arg_end ; No match: check if end of arguments
B325 PLA ; Retrieve accumulated mask
B326 ORA cmd_table_fs_lo,x ; OR in attribute bit for keyword
B329 PHA ; Save updated mask
B32A BNE loop_match_prot_attr ; Always non-zero after ORA: loop
B32C .prot_check_arg_end←2← B323 BCS← B350 BCS
LDA ws_page ; Get the unmatched character
B32E EOR #&0d ; Is it CR?
B330 BEQ done_prot_args ; Yes: arguments ended correctly
B332 JMP error_bad_command ; No: invalid attribute keyword
B335 .done_prot_args←1← B330 BEQ
PLA ; Retrieve final protection mask
B336 .store_prot_mask←3← A731 JMP← B314 BNE← B341 BEQ
STA ws_0d68 ; Store protection mask
B339 STA ws_0d69 ; Store protection mask copy
B33C RTS ; Return

*Unprot command handler

With no arguments, clears all protection bits (EOR yields 0). Otherwise parses attribute keywords, clearing bits via AND with the complement. Shares the protection mask storage path with cmd_prot. Falls through to cmd_wipe.

On EntryYcommand line offset in text pointer
B33D .cmd_unprot
LDA (fs_crc_lo),y ; Get next char from command line
B33F EOR #&0d ; Compare with CR (end of line)
B341 BEQ store_prot_mask ; No args: A=0 clears all protection
B343 LDA ws_0d68 ; Load current protection mask
B346 PHA ; Save as starting value
B347 .loop_match_unprot_attr←1← B357 BCC
LDX #&d3 ; X=&D3: attribute keyword table offset
B349 LDA (fs_crc_lo),y ; Get next char from command line
B34B STA ws_page ; Save for end-of-args check
B34D JSR match_fs_cmd ; Match attribute keyword in table
B350 BCS prot_check_arg_end ; No match: check if end of arguments
B352 PLA ; Retrieve accumulated mask
B353 AND cmd_table_fs_hi,x ; AND to clear matched attribute bit
B356 PHA ; Save updated mask
B357 BCC loop_match_unprot_attr ; ALWAYS branch

*Wipe command handler

Masks owner access, parses a wildcard filename, and loops sending examine requests to the file server. Skips locked files and non-empty directories. Shows each filename with a '(Y/N/?) ' prompt — '?' shows full file info with a '(Y/N) ' reprompt, 'Y' builds the delete command in the TX buffer. Falls through to flush_and_read_char on completion.

On EntryYcommand line offset in text pointer
B359 .cmd_wipe
JSR mask_owner_access ; Mask owner access flags to 5 bits
B35C LDA #0 ; Initialise file index to 0
B35E STA fs_work_5 ; Store file counter
B360 JSR save_ptr_to_os_text ; Save pointer to command text
B363 JSR parse_filename_arg ; Parse wildcard filename argument
B366 INX ; Advance past CR terminator
B367 STX fs_work_6 ; Save end-of-argument buffer position
B369 .request_next_wipe←1← B3A5 JMP
LDA #1 ; Command code 1 = examine directory
B36B STA fs_cmd_data ; Store command in TX buffer byte 0
B36E STA fs_data_count ; Store flag in TX buffer byte 2
B371 LDX fs_work_5 ; Load current file index
B373 STX fs_func_code ; Store file index in TX buffer byte 1
B376 LDX #3 ; X=3: copy from TX buffer offset 3
B378 JSR copy_arg_to_buf ; Copy filename argument to TX buffer
B37B LDY #3 ; Function code 3 = examine
B37D LDA #&80 ; Flag &80 = escapable
B37F STA need_release_tube ; Mark operation as escapable
B381 JSR save_net_tx_cb ; Send examine request to file server
B384 LDA fs_cmd_data ; Get server response status
B387 BNE check_wipe_attr ; Non-zero: file found, process it
B389 LDA #osbyte_flush_buffer_class ; OSBYTE &0F: flush buffer class
B38B LDX #1 ; X=1: flush input buffers
B38D JSR osbyte ; Flush keyboard buffer Flush input buffers (X non-zero)
B390 LDA #osbyte_scan_keyboard_from_16 ; OSBYTE &7A: keyboard scan from 16
B392 JSR osbyte ; Scan keyboard to clear state Keyboard scan starting from key 16
B395 LDY #0 ; Y=0: no key pressed Y=key
B397 LDA #osbyte_write_keys_pressed ; OSBYTE &78: write keys pressed
B399 JMP osbyte ; Clear keyboard state and return Write current keys pressed (X and Y)
B39C .check_wipe_attr←1← B387 BNE
LDA fs_exam_attr_char ; Load first attribute char of response
B39F .loop_check_if_locked←1← B3AF BNE
CMP #&4c ; Is file locked?
B3A1 BNE check_wipe_dir ; No: check if directory
B3A3 .skip_wipe_locked←1← B420 JMP
INC fs_work_5 ; Skip locked file, advance index
B3A5 JMP request_next_wipe ; Request next file from server
B3A8 .check_wipe_dir←1← B3A1 BNE
CMP #&44 ; Is it a directory entry?
B3AA BNE show_wipe_prompt ; No: regular file, show prompt
B3AC LDA fs_exam_dir_flag ; Check directory contents flag
B3AF BNE loop_check_if_locked ; Non-empty dir: treat as locked, skip
B3B1 .show_wipe_prompt←1← B3AA BNE
LDX #1 ; X=1: start from response byte 1
B3B3 LDY fs_work_6 ; Y = destination index in delete buffer
B3B5 .loop_copy_wipe_name←1← B3C2 BNE
LDA fs_func_code,x ; Load filename char from response
B3B8 JSR osasci ; Print filename character to screen Write character
B3BB STA fs_filename_buf,y ; Store in delete command buffer too
B3BE INY ; Advance destination index
B3BF INX ; Advance source index
B3C0 CPX #&0c ; Copied all 11 filename characters?
B3C2 BNE loop_copy_wipe_name ; No: continue copying
B3C4 JSR print_inline ; Print '(Y/N/?) ' prompt
B3C7 EQUS "(?/"
B3CA NOP ; Inline string terminator (NOP)
B3CB JSR prompt_yn ; Read user response character
B3CE CMP #&3f ; User pressed '?'?
B3D0 BNE check_wipe_response ; No: check for Y/N response
B3D2 LDA #&0d ; Carriage return before full info
B3D4 JSR oswrch ; Print CR Write character 13
B3D7 LDX #2 ; X=2: start from response byte 2
B3D9 .loop_print_wipe_info←1← B3E2 BNE
LDA fs_cmd_data,x ; Load file info character
B3DC JSR osasci ; Print file info character Write character
B3DF INX ; Advance to next character
B3E0 CPX #&3e ; Printed all &3C info bytes?
B3E2 BNE loop_print_wipe_info ; No: continue printing
B3E4 JSR print_inline ; Print ' (Y/N) ' prompt (no '?')
B3E7 EQUS " ("
B3E9 NOP ; Padding (string terminator)
B3EA JSR prompt_yn ; Read user response (Y/N only)
B3ED .check_wipe_response←1← B3D0 BNE
AND #&df ; Force uppercase
B3EF CMP #&59 ; User said 'Y' (yes)?
B3F1 BNE skip_wipe_to_next ; No: print newline, skip to next file
B3F3 JSR osasci ; Echo 'Y' to screen Write character
B3F6 LDX #0 ; X=0: start of stored filename
B3F8 LDA fs_filename_buf,x ; Check first byte of stored name
B3FB CMP #&0d ; Is first byte CR (empty first field)?
B3FD BEQ use_wipe_leaf_name ; Yes: use second filename field
B3FF .loop_build_wipe_cmd←1← B414 BNE
LDA fs_filename_buf,x ; Load byte from stored filename
B402 CMP #&0d ; Is it CR (field separator)?
B404 BNE skip_if_not_space ; No: check for space
B406 LDA #&2e ; Replace CR with '.' directory sep
B408 .skip_if_not_space←1← B404 BNE
CMP #&20 ; Is it a space (name terminator)?
B40A BNE store_wipe_tx_char ; No: keep character as-is
B40C .set_wipe_cr_end←1← B42F BEQ
LDA #&0d ; Replace space with CR (end of name)
B40E .store_wipe_tx_char←1← B40A BNE
STA fs_cmd_data,x ; Store in delete command TX buffer
B411 INX ; Advance to next character
B412 CMP #&0d ; Was it the CR terminator?
B414 BNE loop_build_wipe_cmd ; No: continue building delete command
B416 LDY #&14 ; Function code &14 = delete file
B418 JSR save_net_tx_cb ; Send delete request to file server
B41B DEC fs_work_5 ; Adjust file index after deletion
B41D .skip_wipe_to_next←1← B3F1 BNE
JSR osnewl ; Print newline after user response Write newline (characters 10 and 13)
B420 JMP skip_wipe_locked ; Advance index, process next file
B423 .use_wipe_leaf_name←1← B3FD BEQ
DEX ; DEX to offset following INX
B424 .loop_copy_wipe_leaf←1← B42D BNE
INX ; Advance to next byte
B425 LDA fs_filename_buf_1,x ; Load byte from second field
B428 STA fs_cmd_data,x ; Store in delete command TX buffer
B42B CMP #&20 ; Is it a space (field terminator)?
B42D BNE loop_copy_wipe_leaf ; No: continue copying second field
B42F BEQ set_wipe_cr_end ; Space found: terminate with CR 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
B431 .prompt_yn←2← B3CB JSR← B3EA JSR
JSR print_inline ; Print 'Y/N) ' prompt
B434 EQUS "Y/N) "
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).

B439 .flush_and_read_char
LDA #osbyte_flush_buffer_class ; OSBYTE &0F: flush buffer class
B43B LDX #1 ; X=1: flush input buffers
B43D JSR osbyte ; Flush keyboard buffer before read Flush input buffers (X non-zero)
B440 JSR osrdch ; Read character from input stream Read a character from the current input stream
B443 BCC return_2 ; C clear: character read OK
B445 JMP raise_escape_error ; Escape pressed: raise error
B448 .return_2←1← B443 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).

B449 .init_channel_table←1← 8B75 JSR
LDA #0 ; A=0: clear value
B44B TAY ; Y=0: start index Y=&00
B44C .loop_clear_chan_table←1← B450 BNE
STA fcb_count_lo,y ; Clear channel table entry
B44F INY ; Next entry
B450 BNE loop_clear_chan_table ; Loop until all 256 bytes cleared
B452 LDY #&0b ; Offset &0F in receive buffer
B454 LDA (net_rx_ptr),y ; Get number of available channels
B456 SEC ; Prepare subtraction
B457 SBC #&5a ; Subtract 'Z' to get negative count
B459 TAY ; Y = negative channel count (index)
B45A LDA #&40 ; Channel marker &40 (available)
B45C .loop_mark_chan_avail←1← B462 BPL
STA fcb_count_lo,y ; Mark channel slot as available
B45F DEY ; Previous channel slot
B460 CPY #&b8 ; Reached start of channel range?
B462 BPL loop_mark_chan_avail ; No: continue marking channels
B464 INY ; Point to first channel slot
B465 LDA #&c0 ; Active channel marker &C0
B467 STA fcb_count_lo,y ; Mark first channel as active
B46A 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
B46B .attr_to_chan_index←5← 92BD JSR← 92D4 JSR← 9C6E JSR← 9CAC JSR← B73D JSR
PHP ; Save flags
B46C SEC ; Prepare subtraction
B46D SBC #&20 ; Subtract &20 to get table index
B46F BMI error_chan_out_of_range ; Negative: out of valid range
B471 CMP #&10 ; Above maximum channel index &0F?
B473 BCC return_chan_index ; In range: valid index
B475 .error_chan_out_of_range←1← B46F BMI
LDA #&ff ; Out of range: return &FF (invalid)
B477 .return_chan_index←1← B473 BCC
PLP ; Restore flags
B478 TAX ; X = channel index (or &FF)
B479 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
B47A .check_chan_char←2← 9DA3 JSR← B4F2 JSR
CMP #&20 ; Below space?
B47C BCC err_net_chan_invalid ; Yes: invalid channel character
B47E CMP #&30 ; Below '0'?
B480 BCC lookup_chan_by_char ; In range &20-&2F: look up channel
B482 .err_net_chan_invalid←2← 9BD5 JMP← B47C BCC
PHA ; Save channel character
B483 .error_chan_not_found←1← B4B5 BEQ
LDA #&de ; Error code &DE
B485 .err_net_chan_not_found
JSR error_inline_log ; Generate 'Net channel' error
B488 EQUS "Net channel."
B494 JSR false_ref_6f6e ; Error string continuation (unreachable)
B497 EQUS "t on this file server"
B4AC EQUB &00
fall through ↓

Look up channel by character code

Converts the character to a table index via attr_to_chan_index, checks the station/network match via match_station_net, and returns the channel flags in A.

On EntryAchannel character
On ExitAchannel flags
B4AD .lookup_chan_by_char←2← 9ED6 JSR← B480 BCC
PHA ; Save channel character
B4AE SEC ; Prepare subtraction
B4AF SBC #&20 ; Convert char to table index
B4B1 TAX ; X = channel table index
B4B2 LDA fcb_net_or_port,x ; Look up network number for channel
B4B5 BEQ error_chan_not_found ; Zero: channel not found, raise error
B4B7 JSR match_station_net ; Check station/network matches current
B4BA BNE error_chan_not_here ; No match: build detailed error msg
B4BC PLA ; Discard saved channel character
B4BD LDA chan_status,x ; Load channel status flags
B4C0 RTS ; Return; A = channel flags
B4C1 .error_chan_not_here←1← B4BA BNE
LDA #&de ; Error code &DE
B4C3 STA error_text ; Store error code in error block
B4C6 LDA #0 ; BRK opcode
B4C8 STA error_block ; Store BRK at start of error block
B4CB TAX ; X=0: copy index X=&00
B4CC .loop_copy_chan_err_str←1← B4D3 BNE
INX ; Advance copy position
B4CD LDA net_channel_err_string,x ; Load 'Net channel' string byte
B4D0 STA error_text,x ; Copy to error text
B4D3 BNE loop_copy_chan_err_str ; Continue until NUL terminator
B4D5 STX fs_load_addr_2 ; Save end-of-string position
B4D7 STX fs_work_4 ; Save for suffix append
B4D9 PLA ; Retrieve channel character
B4DA JSR append_space_and_num ; Append ' N' (channel number)
B4DD LDY fs_work_4 ; Load 'Net channel' end position
B4DF .loop_append_err_suffix←1← B4E7 BNE
INY ; Skip past NUL to suffix string
B4E0 INX ; Advance destination position
B4E1 LDA net_channel_err_string,y ; Load ' not on this...' suffix byte
B4E4 STA error_text,x ; Append to error message
B4E7 BNE loop_append_err_suffix ; Continue until NUL
B4E9 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.

B4EC .store_result_check_dir←2← B7D3 JSR← B859 JSR
LDA cur_chan_attr ; Load current channel attribute
B4EF 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.

B4F2 .check_not_dir←2← 9C08 JSR← 9E53 JSR
JSR check_chan_char ; Validate and look up channel
B4F5 AND #2 ; Test directory flag (bit 1)
B4F7 BEQ return_from_dir_check ; Not a directory: return OK
B4F9 LDA #&a8 ; Error code &A8
B4FB JSR error_inline_log ; Generate 'Is a dir.' error
B4FE EQUS "Is a dir.."
B508 .return_from_dir_check←1← B4F7 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
B509 .alloc_fcb_slot←7← 9D49 JSR← 9D7E JSR← A280 JSR← A31F JSR← A34A JSR← A381 JSR← B540 JSR
PHA ; Save channel attribute
B50A LDX #&20 ; Start scanning from FCB slot &20
B50C .loop_scan_fcb_slots←1← B514 BNE
LDA fcb_attr_or_count_mid,x ; Load FCB station byte
B50F BEQ done_found_free_slot ; Zero: slot is free, use it
B511 INX ; Try next slot
B512 CPX #&30 ; Past last FCB slot &2F?
B514 BNE loop_scan_fcb_slots ; No: check next slot
B516 PLA ; No free slot: discard saved attribute
B517 LDA #0 ; A=0: return failure (Z set)
B519 RTS ; Return
B51A .done_found_free_slot←1← B50F BEQ
PLA ; Restore channel attribute
B51B STA fcb_attr_or_count_mid,x ; Store attribute in FCB slot
B51E LDA #0 ; A=0: clear value
B520 STA fcb_xfer_count_lo,x ; Clear FCB transfer count low
B523 STA fcb_xfer_count_mid,x ; Clear FCB transfer count mid
B526 STA fcb_count_lo,x ; Clear FCB transfer count high
B529 LDA fs_server_stn ; Load current station number
B52C STA fcb_station_or_count_hi,x ; Store station in FCB
B52F LDA fs_server_net ; Load current network number
B532 STA fcb_net_or_port,x ; Store network in FCB
B535 TXA ; Get FCB slot index
B536 PHA ; Save slot index
B537 SEC ; Prepare subtraction
B538 SBC #&20 ; Convert slot to channel index (0-&0F)
B53A TAX ; X = channel index
B53B PLA ; Restore A = FCB slot index
B53C 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.

B53D .alloc_fcb_or_error←2← 9CF8 JSR← A1ED JSR
PHA ; Save argument
B53E LDA #0 ; A=0: allocate any available slot
B540 JSR alloc_fcb_slot ; Try to allocate an FCB slot
B543 BNE return_alloc_success ; Success: slot allocated
B545 LDA #&c0 ; Error code &C0
B547 JSR error_inline_log ; Generate 'No more FCBs' error
B54A EQUS "No more FCBs."
B557 .return_alloc_success←1← B543 BNE
PLA ; Restore argument
B558 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
B559 .close_all_net_chans←3← 94A8 JSR← 9533 JSR← A391 JSR
CLC ; C=0: close all matching channels
B55A .skip_set_carry
BIT bit_test_ff ; Branch always to scan entry Set V flag via BIT (alternate mode)
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.

B55D .scan_fcb_flags←1← 9DB8 JSR
LDX #&10 ; Start from FCB slot &10
B55F .loop_scan_fcb_down←4← B56B BVC← B575 BVS← B57A BNE← B584 BEQ
DEX ; Previous FCB slot
B560 BPL skip_if_slots_done ; More slots to check
B562 RTS ; All FCB slots processed, return
B563 .skip_if_slots_done←1← B560 BPL
LDA chan_status,x ; Load channel flags for this slot
B566 TAY ; Save flags in Y
B567 AND #2 ; Test active flag (bit 1)
B569 BEQ done_check_station ; Not active: check station match
B56B BVC loop_scan_fcb_down ; V clear (close all): next slot
B56D BCC done_check_station ; C clear: check station match
B56F TYA ; Restore original flags
B570 AND #&df ; Clear write-pending flag (bit 5)
B572 STA chan_status,x ; Update channel flags
B575 BVS loop_scan_fcb_down ; Next slot (V always set here) ALWAYS branch
B577 .done_check_station←2← B569 BEQ← B56D BCC
JSR match_station_net ; Check if channel belongs to station
B57A BNE loop_scan_fcb_down ; No match: skip to next slot
B57C LDA #0 ; A=0: clear channel
B57E STA chan_status,x ; Clear channel flags (close it)
B581 STA fcb_net_or_port,x ; Clear network number
B584 BEQ loop_scan_fcb_down ; Continue to next slot ALWAYS branch

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
B586 .match_station_net←7← A306 JSR← A331 JSR← A368 JSR← A69B JSR← AC40 JSR← B4B7 JSR← B577 JSR
LDA fcb_flags,x ; Load FCB station number
B589 EOR fs_server_stn ; Compare with current station (EOR)
B58C BNE return_from_match_stn ; Different: Z=0, no match
B58E LDA fcb_net_num,x ; Load FCB network number
B591 EOR fs_server_net ; Compare with current network (EOR)
B594 .return_from_match_stn←1← B58C 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.

B595 .find_open_fcb←2← B694 JSR← B782 JSR
LDX cur_fcb_index ; Load current FCB index
B598 BIT bit_test_ff ; Set V flag (first pass marker)
B59B .loop_find_fcb←4← B5AA BVC← B5B0 BPL← B5CD BVS← B5D4 BNE
INX ; Next FCB slot
B59C CPX #&10 ; Past end of table (&10)?
B59E BNE skip_if_no_wrap ; No: continue checking
B5A0 LDX #0 ; Wrap around to slot 0
B5A2 .skip_if_no_wrap←1← B59E BNE
CPX cur_fcb_index ; Back to starting slot?
B5A5 BNE done_check_fcb_status ; No: check this slot
B5A7 BVC loop_scan_empty_fcb ; V clear (second pass): scan empties
B5A9 CLV ; Clear V for second pass
B5AA BVC loop_find_fcb ; Continue scanning ALWAYS branch
B5AC .done_check_fcb_status←1← B5A5 BNE
LDA fcb_status,x ; Load FCB status flags
B5AF ROL ; Shift bit 7 (in-use) into carry
B5B0 BPL loop_find_fcb ; Not in use: skip
B5B2 AND #4 ; Test bit 2 (modified flag)
B5B4 BNE skip_if_modified_fcb ; Modified: check further conditions
B5B6 .done_select_fcb←1← B5D6 BEQ
DEX ; Adjust for following INX
B5B7 .loop_scan_empty_fcb←2← B5A7 BVC← B5C2 BPL
INX ; Next FCB slot
B5B8 CPX #&10 ; Past end of table?
B5BA BNE done_test_empty_slot ; No: continue
B5BC LDX #0 ; Wrap around to slot 0
B5BE .done_test_empty_slot←1← B5BA BNE
LDA fcb_status,x ; Load FCB status flags
B5C1 ROL ; Shift bit 7 into carry
B5C2 BPL loop_scan_empty_fcb ; Not in use: continue scanning
B5C4 SEC ; Set carry for ROR restore
B5C5 ROR ; Restore original flags
B5C6 STA fcb_status,x ; Save flags back (mark as found)
B5C9 LDX cur_fcb_index ; Restore original FCB index
B5CC RTS ; Return with found slot in X
B5CD .skip_if_modified_fcb←1← B5B4 BNE
BVS loop_find_fcb ; V set (first pass): skip modified
B5CF LDA fcb_status,x ; Load FCB status flags
B5D2 AND #&20 ; Test bit 5 (offset pending)
B5D4 BNE loop_find_fcb ; Bit 5 set: skip this slot
B5D6 BEQ done_select_fcb ; Use this slot ALWAYS branch

Initialise byte counters for wipe/transfer

Clears the pass counter, byte counter, offset counter, and transfer flag. Stores &FF sentinels in l10cd/l10ce. Returns with X/Y pointing at workspace offset &10CA.

On ExitX&CA (workspace offset low)
Y&10 (workspace page)
B5D8 .init_wipe_counters←2← B61B JSR← B6C6 JSR
LDY #1 ; Initial pass count = 1
B5DA STY xfer_pass_count ; Store pass counter
B5DD DEY ; Y=0 Y=&00
B5DE STY xfer_count_lo ; Clear byte counter low
B5E1 STY xfer_offset ; Clear offset counter
B5E4 STY xfer_flag ; Clear transfer flag
B5E7 TYA ; A=0 A=&00
B5E8 LDX #2 ; Clear 3 counter bytes
B5EA .loop_clear_counters←1← B5EE BPL
STA xfer_counter,x ; Clear counter byte
B5ED DEX ; Next byte
B5EE BPL loop_clear_counters ; Loop for indices 2, 1, 0
B5F0 STX xfer_sentinel_1 ; Store &FF as sentinel in l10cd
B5F3 STX xfer_sentinel_2 ; Store &FF as sentinel in l10ce
B5F6 LDX #&ca ; X=&CA: workspace offset
B5F8 LDY #&10 ; Y=&10: page &10
B5FA 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
B5FB .start_wipe_pass←2← B68B JSR← B7B7 JSR
JSR verify_ws_checksum ; Verify workspace checksum integrity
B5FE STX cur_fcb_index ; Save current FCB index
B601 LDA fcb_status,x ; Load FCB status flags
B604 ROR ; Shift bit 0 (active) into carry
B605 BCC done_clear_fcb_active ; Not active: clear status and return
B607 LDA work_stn_lo ; Save current station low to stack
B60A PHA ; Push station low
B60B LDA work_stn_hi ; Save current station high
B60E PHA ; Push station high
B60F LDA fcb_stn_lo,x ; Load FCB station low
B612 STA work_stn_lo ; Set as working station low
B615 LDA fcb_stn_hi,x ; Load FCB station high
B618 STA work_stn_hi ; Set as working station high
B61B JSR init_wipe_counters ; Reset transfer counters
B61E DEC xfer_offset ; Set offset to &FF (no data yet)
B621 DEC xfer_pass_count ; Set pass counter to 0 (flush mode)
B624 LDX cur_fcb_index ; Reload FCB index
B627 TXA ; Transfer to A
B628 CLC ; Prepare addition
B629 ADC #&11 ; Add &11 for buffer page offset
B62B STA fcb_buf_page ; Store buffer address high byte
B62E LDA fcb_status,x ; Load FCB status flags
B631 AND #&20 ; Test bit 5 (has saved offset)
B633 BEQ done_restore_offset ; No offset: skip restore
B635 LDA fcb_buf_offset,x ; Load saved byte offset
B638 STA xfer_offset ; Restore offset counter
B63B .done_restore_offset←1← B633 BEQ
LDA fcb_attr_ref,x ; Load FCB attribute reference
B63E STA cur_attr_ref ; Store as current reference
B641 TAX ; Transfer to X
B642 JSR read_rx_attribute ; Read saved receive attribute
B645 PHA ; Push to stack
B646 TXA ; Restore attribute to A
B647 STA (net_rx_ptr),y ; Set attribute in receive buffer
B649 LDX #&ca ; X=&CA: workspace offset
B64B LDY #&10 ; Y=&10: page &10
B64D LDA #0 ; A=0: standard transfer mode
B64F JSR send_and_receive ; Send data and receive response
B652 LDX cur_fcb_index ; Reload FCB index
B655 PLA ; Restore saved receive attribute
B656 JSR store_rx_attribute ; Restore receive attribute
B659 PLA ; Restore station high
B65A STA work_stn_hi ; Store station high
B65D PLA ; Restore station low
B65E STA work_stn_lo ; Store station low
B661 .done_clear_fcb_active←1← B605 BCC
LDA #&dc ; Mask &DC: clear bits 0, 1, 5
B663 AND fcb_status,x ; Clear active and offset flags
B666 STA fcb_status,x ; Update FCB status
B669 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)
B66A .save_fcb_context←2← B72D JSR← B8EF JSR
LDX #&0c ; Copy 13 bytes (indices 0 to &0C)
B66C .loop_save_tx_context←1← B676 BPL
LDA txcb_reply_port,x ; Load TX buffer byte
B66F STA fcb_ctx_save,x ; Save to context buffer at &10D9
B672 LDA fs_load_addr,x ; Load workspace byte from fs_load_addr
B674 PHA ; Save to stack
B675 DEX ; Next byte down
B676 BPL loop_save_tx_context ; Loop for all 13 bytes
B678 CPY #0 ; Y=0? (no FCB to process)
B67A BNE done_save_context ; Non-zero: scan and process FCBs
B67C JMP loop_restore_workspace ; Y=0: skip to restore workspace
B67F .done_save_context←1← B67A BNE
PHP ; Save flags
B680 LDX #&ff ; X=&FF: start scanning from -1
B682 .loop_find_pending_fcb←2← B686 BPL← B689 BPL
INX ; Next FCB slot
B683 LDA fcb_status,x ; Load FCB status flags
B686 BPL loop_find_pending_fcb ; Bit 7 clear: not pending, skip
B688 ASL ; Shift bit 6 to bit 7
B689 BPL loop_find_pending_fcb ; Bit 6 clear: skip
B68B JSR start_wipe_pass ; Flush this FCB's pending data
B68E LDA #&40 ; Pending marker &40
B690 STA fcb_status,x ; Mark FCB as pending-only
B693 PHP ; Save flags
B694 JSR find_open_fcb ; Find next available FCB slot
B697 PLP ; Restore flags
B698 LDA cur_chan_attr ; Load current channel attribute
B69B STA cur_attr_ref ; Store as current reference
B69E PHA ; Save attribute
B69F SEC ; Prepare attribute-to-channel conversion
B6A0 SBC #&20 ; Convert attribute (&20+) to channel index
B6A2 TAY ; Y = attribute index
B6A3 LDA fcb_net_or_port,y ; Load station for this attribute
B6A6 STA fs_cmd_data ; Store station in TX buffer
B6A9 PLA ; Restore attribute
B6AA STA fcb_attr_ref,x ; Store attribute in FCB slot
B6AD LDA work_stn_lo ; Load working station low
B6B0 STA fcb_stn_lo,x ; Store in TX buffer Store station low in FCB
B6B3 LDA work_stn_hi ; Load working station high
B6B6 STA fcb_stn_hi,x ; Store in TX buffer Store station high in FCB
B6B9 TXA ; Get FCB slot index
B6BA CLC ; Prepare addition
B6BB ADC #&11 ; Add &11 for buffer page offset
B6BD STA fcb_buf_page ; Store buffer address high byte
B6C0 PLP ; Restore flags
B6C1 BVC done_init_wipe ; V clear: skip directory request
B6C3 JSR flush_fcb_if_station_known ; Command byte = 0 Send directory request to server
B6C6 .done_init_wipe←1← B6C1 BVC
JSR init_wipe_counters ; Reset transfer counters
B6C9 JSR read_rx_attribute ; Read saved receive attribute
B6CC PHA ; Function code &0D Push to stack
B6CD LDA cur_attr_ref ; Load current reference
B6D0 STA (net_rx_ptr),y ; Set in receive buffer
B6D2 LDY #&10 ; Y=&10: page &10
B6D4 LDA #2 ; A=2: transfer mode 2
B6D6 JSR send_and_receive ; Send and receive data
B6D9 PLA ; Restore receive attribute
B6DA JSR store_rx_attribute ; Restore receive attribute
B6DD LDX cur_fcb_index ; Reload FCB index
B6E0 LDA xfer_pass_count ; Load pass counter
B6E3 BNE done_calc_offset ; Non-zero: data received, calc offset
B6E5 LDA xfer_offset ; Load offset counter
B6E8 BEQ done_set_fcb_active ; Zero: no data received at all
B6EA .done_calc_offset←1← B6E3 BNE
LDA xfer_offset ; Load offset counter
B6ED EOR #&ff ; Negate (ones complement)
B6EF CLC ; Clear carry for add
B6F0 ADC #1 ; Complete twos complement negation
B6F2 STA fcb_buf_offset,x ; Store negated offset in FCB
B6F5 LDA #&20 ; Set bit 5 (has saved offset)
B6F7 ORA fcb_status,x ; Add to FCB flags
B6FA STA fcb_status,x ; Update FCB status
B6FD LDA fcb_buf_page ; Load buffer address high byte
B700 STA fs_load_addr_3 ; Set pointer high byte
B702 LDA #0 ; A=0: pointer low byte and clear val
B704 STA fs_load_addr_2 ; Set pointer low byte
B706 LDY fcb_buf_offset,x ; Load negated offset (start of clear)
B709 .loop_clear_buffer←1← B70C BNE
STA (fs_load_addr_2),y ; Clear buffer byte
B70B INY ; Next byte
B70C BNE loop_clear_buffer ; Loop until page boundary
B70E .done_set_fcb_active←1← B6E8 BEQ
LDA #2 ; Set bit 1 (active flag)
B710 ORA fcb_status,x ; Add active flag to status
B713 STA fcb_status,x ; Update FCB status
B716 LDY #0 ; Y=0: start restoring workspace
B718 .loop_restore_workspace←2← B67C JMP← B71F BNE
PLA ; Restore workspace byte from stack
B719 STA fs_load_addr,y ; Store to fs_load_addr workspace
B71C INY ; Next byte
B71D CPY #&0d ; Restored all 13 bytes?
B71F BNE loop_restore_workspace ; No: continue restoring
fall through ↓

Restore saved catalog entry to TX buffer

Copies 13 bytes from the context buffer at &10D9 back to the TX buffer at &0F00. Falls through to find_matching_fcb.

B721 .restore_catalog_entry←1← B922 JSR
LDY #&0c ; Copy 13 bytes (indices 0 to &0C)
B723 .loop_restore_tx_buf←1← B72A BPL
LDA fcb_ctx_save,y ; Load saved catalog byte from &10D9
B726 STA txcb_reply_port,y ; Restore to TX buffer
B729 DEY ; Next byte down
B72A BPL loop_restore_tx_buf ; Loop for all bytes
B72C RTS ; Return
B72D .loop_save_before_match←1← B74C 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 l10c9. 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
B730 .find_matching_fcb←3← 9DF8 JSR← B7EE JSR← B88B JSR
LDX #&ff ; X=&FF: start scanning from -1
B732 .loop_reload_attr←2← B76E BNE← B776 BNE
LDY cur_chan_attr ; Load channel attribute to match
B735 .loop_next_fcb_slot←2← B754 BEQ← B75A BNE
INX ; Next FCB slot
B736 CPX #&10 ; Past end of table (&10)?
B738 BNE done_test_fcb_active ; No: check this slot
B73A LDA cur_chan_attr ; Load channel attribute
B73D JSR attr_to_chan_index ; Convert to channel index
B740 LDA fcb_station_or_count_hi,x ; Load station for this channel
B743 STA work_stn_hi ; Store as match target station high
B746 LDA fcb_attr_or_count_mid,x ; Load port for this channel
B749 STA work_stn_lo ; Store as match target station low
B74C JMP loop_save_before_match ; Save context and rescan from start
B74F .done_test_fcb_active←1← B738 BNE
LDA fcb_status,x ; Load FCB status flags
B752 AND #2 ; Test active flag (bit 1)
B754 BEQ loop_next_fcb_slot ; Not active: skip to next
B756 TYA ; Get attribute to match
B757 CMP fcb_attr_ref,x ; Compare with FCB attribute ref
B75A BNE loop_next_fcb_slot ; No attribute match: skip
B75C STX cur_fcb_index ; Save matching FCB index
B75F PHP ; Save flags from attribute compare
B760 SEC ; Prepare subtraction
B761 SBC #&20 ; Convert attribute to channel index
B763 PLP ; Restore flags from attribute compare
B764 TAY ; Y = channel index
B765 LDX cur_fcb_index ; Reload FCB index
B768 LDA fcb_attr_or_count_mid,y ; Load channel station byte
B76B CMP fcb_stn_lo,x ; Compare with FCB station
B76E BNE loop_reload_attr ; Station mismatch: try next
B770 LDA fcb_station_or_count_hi,y ; Load channel network byte
B773 CMP fcb_stn_hi,x ; Compare with FCB network
B776 BNE loop_reload_attr ; Network mismatch: try next
B778 LDA fcb_status,x ; Load FCB flags
B77B BPL return_test_offset ; Bit 7 clear: no pending flush
B77D AND #&7f ; Clear pending flag (bit 7)
B77F STA fcb_status,x ; Update FCB status
B782 JSR find_open_fcb ; Find new open FCB slot
B785 LDA fcb_status,x ; Reload FCB flags
B788 .return_test_offset←1← B77B BPL
AND #&20 ; Test bit 5 (has offset data)
B78A RTS ; Return; Z=1 no offset, Z=0 has data

Increment 3-byte FCB transfer count

Increments l1000+X (low), cascading overflow to l1010+X (mid) and l1020+X (high).

On EntryXFCB slot index
B78B .inc_fcb_byte_count←2← B833 JSR← B8CB JSR
INC fcb_count_lo,x ; Increment byte count low
B78E BNE return_from_inc_fcb_count ; No overflow: done
B790 INC fcb_attr_or_count_mid,x ; Increment byte count mid
B793 BNE return_from_inc_fcb_count ; No overflow: done
B795 INC fcb_station_or_count_hi,x ; Increment byte count high
B798 .return_from_inc_fcb_count←2← B78E BNE← B793 BNE
RTS ; Return

Process all active FCB slots

Saves fs_options, fs_block_offset, and X/Y on the stack, 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)
B799 .process_all_fcbs←9← 8D87 JSR← 8FA0 JSR← 94A0 JSR← 9BDC JSR← 9C21 JSR← 9CC4 JSR← 9D8A JSR← 9E58 JSR← A67A JSR
TXA ; Save X
B79A PHA ; Push X to stack
B79B TYA ; Save Y
B79C PHA ; Push Y to stack
B79D LDX #&f7 ; X=&F7: save 9 workspace bytes (&F7..&FF)
B79F .loop_cb79f←1← B7A4 BMI
LDA lffbd,x ; Load workspace byte
B7A2 PHA ; Push fs_options
B7A3 INX ; Next byte
B7A4 BMI loop_cb79f ; X<0: more bytes to save
B7A6 LDX #&0f ; Start from FCB slot &0F
B7A8 STX cur_fcb_index ; Store as current FCB index
B7AB .loop_process_fcb←1← B7BF BPL
LDX cur_fcb_index ; Load current FCB index
B7AE TYA ; Get filter attribute
B7AF BEQ done_flush_fcb ; Zero: process all FCBs
B7B1 CMP fcb_attr_ref,x ; Compare with FCB attribute ref
B7B4 BNE done_advance_fcb ; No match: skip this FCB
B7B6 .done_flush_fcb←1← B7AF BEQ
PHA ; Save filter attribute
B7B7 JSR start_wipe_pass ; Flush pending data for this FCB
B7BA PLA ; Restore filter
B7BB TAY ; Y = filter attribute
B7BC .done_advance_fcb←1← B7B4 BNE
DEC cur_fcb_index ; Previous FCB index
B7BF BPL loop_process_fcb ; More slots: continue loop
B7C1 LDX #8 ; X=8: restore 9 workspace bytes
B7C3 .loop_cb7c3←1← B7C7 BPL
PLA ; Restore fs_block_offset
B7C4 STA fs_work_4,x ; Restore workspace byte
B7C6 DEX ; Next byte down
B7C7 BPL loop_cb7c3 ; More bytes: continue restoring
B7C9 PLA ; Restore Y
B7CA TAY ; Y restored
B7CB PLA ; Restore X
B7CC TAX ; X restored
B7CD RTS ; Return
B7CE STY cur_chan_attr ; Save channel attribute
B7D1 TXA ; Save caller's X
B7D2 PHA ; Push X
B7D3 JSR store_result_check_dir ; Store result and check not directory
B7D6 LDA chan_status,x ; Load channel flags
B7D9 AND #&20 ; Test write-only flag (bit 5)
B7DB BEQ done_read_fcb_byte ; Not write-only: proceed with read
B7DD LDA #&d4 ; Error code &D4
B7DF JSR error_inline_log ; Generate 'Write only' error
B7E2 EQUS "Write only."
B7ED .done_read_fcb_byte←1← B7DB BEQ
CLV ; Clear V (first-pass matching)
B7EE JSR find_matching_fcb ; Find FCB matching this channel
B7F1 BEQ done_load_from_buf ; No offset: read byte from buffer
B7F3 LDA fcb_count_lo,y ; Load byte count for matching FCB
B7F6 CMP fcb_buf_offset,x ; Compare with buffer offset limit
B7F9 BCC done_load_from_buf ; Below offset: data available
B7FB LDA chan_status,y ; Load channel flags for FCB
B7FE TAX ; Transfer to X for testing
B7FF AND #&40 ; Test bit 6 (EOF already signalled)
B801 BNE error_end_of_file ; EOF already set: raise error
B803 TXA ; Restore flags
B804 ORA #&40 ; Set EOF flag (bit 6)
B806 STA chan_status,y ; Update channel flags with EOF
B809 LDA #0 ; A=0: clear receive attribute
B80B JSR store_rx_attribute ; Clear receive attribute (A=0)
B80E PLA ; Restore caller's X
B80F TAX ; X restored
B810 LDA #&fe ; A=&FE: EOF marker byte
B812 LDY cur_chan_attr ; Restore channel attribute
B815 SEC ; C=1: end of file
B816 RTS ; Return
B817 .error_end_of_file←1← B801 BNE
LDA #&df ; Error code &DF
B819 JSR error_inline_log ; Generate 'End of file' error
B81C EQUS "End of file."
B828 .done_load_from_buf←2← B7F1 BEQ← B7F9 BCC
LDA fcb_count_lo,y ; Load current byte count (= offset)
B82B PHA ; Save byte count
B82C TYA ; Get FCB slot index
B82D TAX ; X = FCB slot for byte count inc
B82E LDA #0 ; A=0: clear receive attribute
B830 JSR store_rx_attribute ; Clear receive attribute (A=0)
B833 JSR inc_fcb_byte_count ; Increment byte count for this FCB
B836 PLA ; Restore byte count (= buffer offset)
B837 TAY ; Y = offset into data buffer
B838 LDA cur_fcb_index ; Load current FCB index
B83B CLC ; Prepare addition
B83C ADC #&11 ; Add &11 for buffer page offset
B83E STA fs_load_addr_3 ; Set pointer high byte
B840 LDA #0 ; A=0: pointer low byte
B842 STA fs_load_addr_2 ; Set pointer low byte
B844 PLA ; Restore caller's X
B845 TAX ; X restored
B846 LDA (fs_load_addr_2),y ; Read data byte from buffer
B848 LDY cur_chan_attr ; Restore channel attribute
B84B CLC ; C=0: byte read successfully
B84C RTS ; Return; A = data byte
B84D STY cur_chan_attr ; Save channel attribute
B850 PHA ; Save data byte
B851 TAY ; Y = data byte
B852 TXA ; Save caller's X
B853 PHA ; Push X
B854 TYA ; Restore data byte to A
B855 PHA ; Push data byte for later
B856 STA osbput_saved_byte ; Save data byte in workspace
B859 JSR store_result_check_dir ; Store result and check not directory
B85C LDA chan_status,x ; Load channel flags
B85F BMI done_test_write_flag ; Bit 7 set: channel open, proceed
B861 LDA #&c1 ; Error &C1: Not open for update
B863 JSR error_inline_log ; Raise error with inline string
B866 EQUS "Not open for update."
B87A .done_test_write_flag←1← B85F BMI
AND #&20 ; Test write flag (bit 5)
B87C BEQ done_find_write_fcb ; Not write-capable: use buffer path
B87E LDY fcb_net_or_port,x ; Load reply port for this channel
B881 PLA ; Restore data byte
B882 JSR send_wipe_request ; Send byte directly to server
B885 JMP done_inc_byte_count ; Update byte count and return
B888 .done_find_write_fcb←1← B87C BEQ
BIT bit_test_ff ; Set V flag (alternate match mode)
B88B JSR find_matching_fcb ; Find matching FCB for channel
B88E LDA fcb_count_lo,y ; Load byte count for FCB
B891 CMP #&ff ; Buffer full (&FF bytes)?
B893 BNE done_check_buf_offset ; No: store byte in buffer
B895 JSR flush_fcb_with_init ; Save X Save context and flush FCB data
B898 .done_check_buf_offset←1← B893 BNE
CMP fcb_buf_offset,x ; Push Y Compare count with buffer offset
B89B BCC done_set_dirty_flag ; Below offset: skip offset update
B89D ADC #0 ; Carry set from BCS/BCC above Add carry (count + 1)
B89F STA fcb_buf_offset,x ; Update buffer offset in FCB
B8A2 BNE done_set_dirty_flag ; Non-zero: keep offset flag
B8A4 LDA #&df ; Mask &DF: clear bit 5
B8A6 AND fcb_status,x ; Clear offset flag
B8A9 STA fcb_status,x ; Update FCB status
B8AC .done_set_dirty_flag←2← B89B BCC← B8A2 BNE
LDA #1 ; Set bit 0 (dirty/active)
B8AE ORA fcb_status,x ; Add to FCB flags
B8B1 STA fcb_status,x ; Update FCB status
B8B4 LDA fcb_count_lo,y ; Load byte count (= write position)
B8B7 PHA ; Save count
B8B8 TYA ; Get FCB slot index
B8B9 TAX ; X = FCB slot
B8BA PLA ; Restore byte count
B8BB TAY ; Y = buffer write offset
B8BC LDA cur_fcb_index ; Load current FCB index
B8BF CLC ; Prepare addition
B8C0 ADC #&11 ; Add &11 for buffer page offset
B8C2 STA fs_load_addr_3 ; Set pointer high byte
B8C4 LDA #0 ; A=0: pointer low byte
B8C6 STA fs_load_addr_2 ; Set pointer low byte
B8C8 PLA ; Restore data byte
B8C9 STA (fs_load_addr_2),y ; Write data byte to buffer
B8CB .done_inc_byte_count←1← B885 JMP
JSR inc_fcb_byte_count ; Increment byte count for this FCB
B8CE LDA #0 ; A=0: clear receive attribute
B8D0 JSR store_rx_attribute ; Clear receive attribute (A=0)
B8D3 PLA ; Restore caller's X
B8D4 TAX ; X restored
B8D5 PLA ; Discard saved data byte
B8D6 LDY cur_chan_attr ; Restore channel attribute
B8D9 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
B8DA .flush_fcb_if_station_known←1← B6C3 JSR
PHA ; Save A
B8DB TXA ; Transfer X to A
B8DC PHA ; Save X
B8DD TYA ; Transfer Y to A
B8DE PHA ; Save Y (channel index)
B8DF LDA fcb_net_or_port,y ; Load station for this channel
B8E2 BNE store_station_and_flush ; Non-zero: station known, skip init
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
B8E4 .flush_fcb_with_init←1← B895 JSR
PHA ; Save A
B8E5 TXA ; Transfer X to A
B8E6 PHA ; Save X
B8E7 TYA ; Transfer Y to A
B8E8 PHA ; Save Y (channel index)
B8E9 LDA fcb_net_or_port,y ; Load station for this channel
B8EC PHA ; Save station on stack
B8ED LDY #0 ; Y=0: reset index
B8EF JSR save_fcb_context ; Save current FCB context
B8F2 PLA ; Restore station from stack
B8F3 .store_station_and_flush←1← B8E2 BNE
STA fs_cmd_data ; Store station in command buffer
B8F6 TAX ; X=station number
B8F7 PLA ; Restore Y from stack
B8F8 TAY ; Y=channel index
B8F9 PHA ; Re-save Y on stack
B8FA TXA ; A=station number
B8FB PHA ; Save station for later restore
B8FC LDX #0 ; X=0
B8FE STX fs_func_code ; Clear function code
B901 LDA fcb_count_lo,y ; Load byte count lo from FCB
B904 STA fs_data_count ; Store as data byte count
B907 LDA fcb_attr_or_count_mid,y ; Load byte count mid from FCB
B90A STA fs_reply_cmd ; Store as reply command byte
B90D LDA fcb_station_or_count_hi,y ; Load byte count hi from FCB
B910 STA fs_load_vector ; Store as load vector field
B913 LDY #&0d ; Y=&0D: TX command byte offset
B915 LDX #5 ; X=5: send 5 bytes
B917 JSR save_net_tx_cb ; Send flush request to server
B91A PLA ; Restore station from stack
B91B TAY ; Y=station for wipe request
B91C LDA osbput_saved_byte ; Load saved data byte
B91F JSR send_wipe_request ; Send close/wipe request to server
B922 JSR restore_catalog_entry ; Restore catalog state after flush
B925 PLA ; Restore Y
B926 TAY ; Y restored
B927 PLA ; Restore X
B928 TAX ; X restored
B929 PLA ; Restore A
B92A 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
B92B .send_wipe_request←2← B882 JSR← B91F JSR
STY fs_handle_mask ; Store reply port
B92E STA fs_error_flags ; Store data byte
B931 TYA ; Save Y
B932 PHA ; Push Y to stack
B933 TXA ; Save X
B934 PHA ; Push X to stack
B935 LDA #&90 ; Function code &90
B937 STA fs_putb_buf ; Store in send buffer
B93A JSR init_txcb ; Initialise TX control block
B93D LDA #&dc ; TX start address low = &DC
B93F STA txcb_start ; Set TX start in control block
B941 LDA #&e0 ; TX end address low = &E0
B943 STA txcb_end ; Set TX end in control block
B945 LDA #9 ; Expected reply port = 9
B947 STA fs_getb_buf ; Store reply port in buffer
B94A LDX #&c0 ; TX control = &C0
B94C LDY #0 ; Y=0: no timeout
B94E LDA fs_handle_mask ; Load reply port for addressing
B951 JSR send_disconnect_reply ; Send packet to server
B954 LDA fs_getb_buf ; Load reply status
B957 BEQ done_toggle_station ; Zero: success
B959 STA fs_last_error ; Store error code
B95C LDX #0 ; X=0: copy index
B95E .loop_copy_wipe_err_msg←1← B969 BNE
LDA fs_putb_buf,x ; Load error message byte
B961 STA error_block,x ; Copy to error block
B964 CMP #&0d ; Is it CR (end of message)?
B966 BEQ done_terminate_wipe_err ; Yes: terminate string
B968 INX ; Next byte
B969 BNE loop_copy_wipe_err_msg ; Continue copying error message
B96B .done_terminate_wipe_err←1← B966 BEQ
LDA #0 ; NUL terminator
B96D STA error_block,x ; Terminate error string in block
B970 DEX ; Back up position for error check
B971 JMP check_net_error_code ; Process and raise network error
B974 .done_toggle_station←1← B957 BEQ
LDX cur_chan_attr ; Load channel attribute index
B977 LDA fcb_flags,x ; Load station number for channel
B97A EOR #1 ; Toggle bit 0 (alternate station)
B97C STA fcb_flags,x ; Update station number
B97F PLA ; Restore X
B980 TAX ; X restored
B981 PLA ; Restore Y
B982 TAY ; Y restored
B983 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
B984 .send_and_receive←2← B64F JSR← B6D6 JSR
JSR set_options_ptr ; Set up FS options pointer
B987 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
B98A .read_rx_attribute←4← 96F0 JSR← 9703 JSR← B642 JSR← B6C9 JSR
LDY #&0a ; Y=&0A: receive attribute offset
B98C LDA (net_rx_ptr),y ; Read byte from receive buffer
B98E 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
B98F .store_rx_attribute←10← 8B6C JSR← 9C0C JSR← 9CCC JSR← 9D1C JSR← B4EF JSR← B656 JSR← B6DA JSR← B80B JSR← B830 JSR← B8D0 JSR
LDY #&0a ; Y=&0A: receive attribute offset
B991 STA (net_rx_ptr),y ; Store byte to receive buffer
B993 RTS ; Return

*Close command handler

Loads A=0 and Y=0, then jumps to OSFIND to close all open files on the current file server (equivalent to CLOSE#0). Files open on other file servers are not affected.

B994 .cmd_close
LDA #osfind_close ; A=0: close all open files
B996 TAY ; Y=&00
B997 JMP osfind ; Close all files (Y=0)

*Type command handler

Clears V and branches to the shared open_and_read_file entry in cmd_print. The V-clear state selects line- ending normalisation mode: CR, LF, CR+LF, and LF+CR are all treated as a single newline. Designed for displaying text files.

On EntryYcommand line offset in text pointer
B99A .cmd_type
CLV ; Clear V for unconditional BVC
B99B BVC open_and_read_file ; ALWAYS branch

*Print command handler

Sets V flag (distinguishing from *Type which clears V), then opens the file for reading. Loops reading bytes via OSBGET, checking for escape between each. In type mode (V clear), normalises CR/LF pairs to single newlines by tracking the previous character. In print mode (V set), outputs all bytes raw via OSWRCH. Closes the file and prints a final newline on EOF.

On EntryYcommand line offset in text pointer
B99D .cmd_print
BIT bit_test_ff ; Set V flag (= print mode)
B9A0 .open_and_read_file←1← B99B BVC
JSR open_file_for_read ; Open file for reading
B9A3 LDY ws_page ; Y=file handle
B9A5 LDA #0 ; A = 0
B9A7 STA table_idx ; Clear previous-character tracker
B9A9 PHP ; Save V flag (print/type mode)
B9AA .loop_read_print_byte←3← B9CA JMP← B9EA JMP← B9FD BEQ
JSR osbget ; Read a single byte from an open file Y
B9AD BCC done_print_escape ; Branch if not end of file
B9AF PLP ; EOF: restore processor status
B9B0 JSR close_ws_file ; Close the file
B9B3 JMP osnewl ; Write newline (characters 10 and 13)
B9B6 .done_print_escape←1← B9AD BCC
JSR abort_if_escape ; Check for escape key pressed
B9B9 PLP ; Restore V (print/type mode)
B9BA PHP ; Re-save for next iteration
B9BB BVS done_store_prev_char ; Print mode: skip CR/LF handling
B9BD CMP #&0d ; Is it a carriage return?
B9BF BEQ done_handle_line_end ; Yes: handle line ending
B9C1 CMP #&0a ; Is it a line feed?
B9C3 BEQ done_handle_line_end ; Yes: handle line ending
B9C5 .done_store_prev_char←1← B9BB BVS
STA table_idx ; Save as previous character
B9C7 .loop_write_char←1← B9D8 BNE
JSR oswrch ; Write character
B9CA JMP loop_read_print_byte ; Loop for next byte
B9CD .done_handle_line_end←2← B9BF BEQ← B9C3 BEQ
PHA ; Save the CR or LF character
B9CE LDX vdu_queue_count ; Check output destination flag
B9D1 BEQ done_normalise_crlf ; Zero: normalise line endings
B9D3 LDA #0 ; Non-zero: output raw
B9D5 STA table_idx ; Clear previous-character tracker
B9D7 PLA ; Retrieve CR/LF
B9D8 BNE loop_write_char ; Output it directly; ALWAYS branch
B9DA .done_normalise_crlf←1← B9D1 BEQ
LDA table_idx ; Get previous character
B9DC CMP #&0d ; Was previous a CR?
B9DE BEQ done_check_cr_lf ; Yes: check for CR+LF pair
B9E0 CMP #&0a ; Was previous a LF?
B9E2 BEQ done_check_lf_cr ; Yes: check for LF+CR pair
B9E4 PLA ; Retrieve CR/LF from stack
B9E5 STA table_idx ; Save as previous character
B9E7 .done_write_newline←2← B9F2 BNE← B9F7 BNE
JSR osnewl ; Write newline (characters 10 and 13)
B9EA JMP loop_read_print_byte ; Loop for next byte
B9ED .done_check_cr_lf←1← B9DE BEQ
PLA ; Retrieve current character
B9EE CMP #&0a ; Is it LF? (CR+LF pair)
B9F0 BEQ done_consume_pair ; Yes: consume LF, no extra newline
B9F2 BNE done_write_newline ; No: output extra newline ALWAYS branch
B9F4 .done_check_lf_cr←1← B9E2 BEQ
PLA ; Retrieve current character
B9F5 CMP #&0d ; Is it CR? (LF+CR pair)
B9F7 BNE done_write_newline ; No: output extra newline
B9F9 .done_consume_pair←1← B9F0 BEQ
LDA #0 ; Pair consumed: A = 0
B9FB STA table_idx ; Clear previous-character tracker
B9FD BEQ loop_read_print_byte ; Loop for next byte; ALWAYS branch ALWAYS branch

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.

B9FF .abort_if_escape←2← B9B6 JSR← BA33 JSR
BIT escape_flag ; Test bit 7 of escape flag
BA01 BMI error_escape_pressed ; Escape pressed: handle abort
BA03 RTS ; No escape: return
BA04 .error_escape_pressed←1← BA01 BMI
JSR close_ws_file ; Close the open file
BA07 JSR osnewl ; Write newline (characters 10 and 13)
BA0A LDA #osbyte_acknowledge_escape ; Acknowledge escape condition
BA0C JSR osbyte ; Clear escape condition and perform escape effects
BA0F LDA #&11 ; Error number &11
BA11 JSR error_inline ; Generate 'Escape' BRK error
BA14 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
BA1B .cmd_dump
JSR open_file_for_read ; Open file for reading, set ws_page
BA1E LDX #&14 ; 21 bytes to push (0-&14)
BA20 LDA #0 ; Zero fill value
BA22 .loop_push_zero_buf←1← BA24 BPL
PHA ; Push zero onto stack
BA23 DEX ; Count down
BA24 BPL loop_push_zero_buf ; Loop until all 21 bytes pushed
BA26 TSX ; X = stack pointer (buffer base - 1)
BA27 JSR init_dump_buffer ; Set up buffer pointer and parse args
BA2A LDA (work_ae),y ; Load display address low byte
BA2C AND #&f0 ; Test high nibble
BA2E BEQ loop_dump_line ; Skip header if 16-byte aligned
BA30 JSR print_dump_header ; Print column header for offset start
BA33 .loop_dump_line←2← BA2E BEQ← BAD5 JMP
JSR abort_if_escape ; Check for Escape key
BA36 LDA #&ff ; Start byte counter at -1
BA38 STA osword_flag ; Reset counter
BA3A .loop_read_dump_byte←1← BA49 BNE
LDY ws_page ; Y=file handle
BA3C JSR osbget ; Read a single byte from an open file Y
BA3F BCS done_check_dump_eof ; C=1 from OSBGET: end of file
BA41 INC osword_flag ; Increment byte counter (0-15)
BA43 LDY osword_flag ; Use counter as buffer index
BA45 STA (work_ae),y ; Store byte in data buffer
BA47 CPY #&0f ; Read 16 bytes? (index 0-15)
BA49 BNE loop_read_dump_byte ; No: read next byte
BA4B CLC ; C=0: not EOF, full line read
BA4C .done_check_dump_eof←1← BA3F BCS
PHP ; Save C: EOF status
BA4D LDA osword_flag ; Check byte counter
BA4F BPL done_check_boundary ; Counter >= 0: have data to display
BA51 LDX #&15 ; 22 bytes to pop (21 buffer + PHP)
BA53 .loop_pop_stack_buf←2← BA55 BPL← BADA JMP
PLA ; Pop one byte from stack
BA54 DEX ; Count down
BA55 BPL loop_pop_stack_buf ; Loop until stack cleaned up
BA57 JMP close_ws_file ; Close file and return
BA5A .done_check_boundary←1← BA4F BPL
LDY #&10 ; Point to display address low byte
BA5C LDA (work_ae),y ; Load display address low byte
BA5E AND #&f0 ; Test high nibble
BA60 BNE done_start_dump_addr ; Non-zero: header already current
BA62 JSR print_dump_header ; Crossed 256-byte boundary: new header
BA65 .done_start_dump_addr←1← BA60 BNE
LDY #&13 ; Start from highest address byte
BA67 .loop_print_addr_byte←1← BA71 BNE
LDA (work_ae),y ; Load address byte
BA69 PHA ; Save for address increment later
BA6A JSR print_hex_byte ; Print as two hex digits
BA6D PLA ; Restore address byte
BA6E DEY ; Next byte down
BA6F CPY #&0f ; Printed all 4 address bytes?
BA71 BNE loop_print_addr_byte ; No: print next address byte
BA73 INY ; Y=&10: point to address byte 0
BA74 CLC ; Prepare for 16-byte add
BA75 ADC #&10 ; Add 16 to lowest address byte
BA77 PHP ; Save carry for propagation
BA78 .loop_inc_dump_addr←1← BA83 BNE
PLP ; Restore carry from previous byte
BA79 STA (work_ae),y ; Store updated address byte
BA7B INY ; Next address byte up
BA7C LDA (work_ae),y ; Load next address byte
BA7E ADC #0 ; Add carry
BA80 PHP ; Save carry for next byte
BA81 CPY #&14 ; Past all 4 address bytes?
BA83 BNE loop_inc_dump_addr ; No: continue propagation
BA85 PLP ; Discard final carry
BA86 JSR print_inline ; Print address/data separator
BA89 EQUS " : "
BA8C LDY #0 ; Start from first data byte
BA8E LDX osword_flag ; X = bytes read (counter for display)
BA90 .loop_print_dump_hex←1← BA9B BPL
LDA (work_ae),y ; Load data byte from buffer
BA92 JSR print_hex_and_space ; Print as two hex digits
BA95 .loop_next_dump_col←1← BAA8 JMP
INY ; Space separator Next column
BA96 CPY #&10 ; All 16 columns done?
BA98 BEQ done_print_separator ; Yes: go to ASCII separator
BA9A DEX ; Decrement remaining data bytes
BA9B BPL loop_print_dump_hex ; More data: print next hex byte
BA9D TYA ; Save column position
BA9E PHA ; Preserve Y across print
BA9F JSR print_inline ; Print 3-space padding
BAA2 EQUS " "
BAA5 NOP ; Inline string terminator (NOP)
BAA6 PLA ; Restore column position
BAA7 TAY ; Back to Y
BAA8 JMP loop_next_dump_col ; Check next column
BAAB .done_print_separator←1← BA98 BEQ
DEX ; Adjust X for advance_x_by_8
BAAC JSR print_inline ; Print hex/ASCII separator
BAAF EQUS ": "
BAB1 LDY #0 ; Y=0: start ASCII section from byte 0
BAB3 JSR advance_x_by_8 ; Advance X to ASCII display column
BAB6 .loop_print_dump_ascii←1← BACD BPL
LDA (work_ae),y ; Load data byte
BAB8 AND #&7f ; Strip high bit
BABA CMP #&20 ; Printable? (>= space)
BABC BCS done_test_del ; Yes: check for DEL
BABE .skip_non_printable←1← BAC2 BEQ
LDA #&2e ; Non-printable: substitute '.'
BAC0 .done_test_del←1← BABC BCS
CMP #&7f ; Is it DEL (&7F)?
BAC2 BEQ skip_non_printable ; Yes: substitute '.'
BAC4 JSR osasci ; Print ASCII character Write character
BAC7 INY ; Next column
BAC8 CPY #&10 ; All 16 columns done?
BACA BEQ done_end_dump_line ; Yes: end of line
BACC DEX ; Decrement remaining data bytes
BACD BPL loop_print_dump_ascii ; More data: print next ASCII char
BACF .done_end_dump_line←1← BACA BEQ
JSR osnewl ; Print newline Write newline (characters 10 and 13)
BAD2 PLP ; Restore EOF status from &BA4C
BAD3 BCS done_dump_eof ; C=1: EOF reached, clean up
BAD5 JMP loop_dump_line ; Not EOF: continue with next line
BAD8 .done_dump_eof←1← BAD3 BCS
LDX #&14 ; 21 bytes to pop (buffer only, PHP done)
BADA JMP loop_pop_stack_buf ; Reuse stack cleanup loop

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.

BADD .print_dump_header←2← BA30 JSR← BA62 JSR
LDA (work_ae),y ; Load display address low byte
BADF PHA ; Save as starting column number
BAE0 JSR print_inline ; Print header label with leading CR
BAE3 EQUS ".Address : "
BAEF LDX #&0f ; X=&0F: 16 column numbers to print
BAF1 PLA ; Restore starting column number
BAF2 .loop_cbaf2←1← BAFB BPL
JSR print_hex_and_space ; Print as two hex digits
BAF5 SEC ; Space separator SEC for +1 via ADC
BAF6 ADC #0 ; Increment column number (SEC+ADC 0=+1)
BAF8 AND #&0f ; Wrap to low nibble (0-F)
BAFA DEX ; Restore column number Count down
BAFB BPL loop_cbaf2 ; Loop for all 16 columns
BAFD JSR osnewl ; Print trailer with ASCII label Write newline (characters 10 and 13)
BB00 JMP osnewl ; Write newline (characters 10 and 13)

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
BB03 .print_hex_and_space←2← BA92 JSR← BAF2 JSR
PHA ; Save byte value on stack
BB04 JSR print_hex_byte
BB07 LDA #&20 ; A=&20: space character
BB09 .sub_cbb09
JSR osasci ; Print space character Write character 32
BB0C .done_print_hex_space
PLA ; Restore byte value from stack
BB0D RTS ; Return; Y = offset to next argument

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.

BB0E .parse_dump_range←2← BB7E JSR← BC0A JSR
TYA ; Save command line offset to X
BB0F TAX ; X tracks current position
BB10 LDA #0 ; Zero for clearing accumulator
BB12 TAY ; Y=0 for buffer indexing Y=&00
BB13 .loop_clear_hex_accum←1← BB18 BNE
STA (work_ae),y ; Clear accumulator byte
BB15 INY ; Next byte
BB16 CPY #4 ; All 4 bytes cleared?
BB18 BNE loop_clear_hex_accum ; No: clear next
BB1A .loop_parse_hex_digit←1← BB61 JMP
TXA ; Restore pre-increment offset to A
BB1B INX ; Advance X to next char position
BB1C TAY ; Y = pre-increment offset for indexing
BB1D LDA (os_text_ptr),y ; Load character from command line
BB1F CMP #&0d ; CR: end of input
BB21 BEQ done_test_hex_space ; Done: skip trailing spaces
BB23 CMP #&20 ; Space: end of this parameter
BB25 BEQ done_test_hex_space ; Done: skip trailing spaces
BB27 CMP #&30 ; Below '0'?
BB29 BCC error_bad_hex_value ; Yes: not a hex digit, error
BB2B CMP #&3a ; Below ':'? (i.e. '0'-'9')
BB2D BCC done_mask_hex_digit ; Yes: is a decimal digit
BB2F AND #&5f ; Force uppercase for A-F
BB31 ADC #&b8 ; Map 'A'-'F' → &FA-&FF (C=0 here)
BB33 BCS error_bad_hex_value ; Carry set: char > 'F', error
BB35 CMP #&fa ; Below &FA? (i.e. was < 'A')
BB37 BCC error_bad_hex_value ; Yes: gap between '9' and 'A', error
BB39 .done_mask_hex_digit←1← BB2D BCC
AND #&0f ; Mask to low nibble (0-15)
BB3B PHA ; Save hex digit value
BB3C TXA ; Save current offset
BB3D PHA ; Preserve on stack
BB3E LDX #4 ; 4 bits to shift in
BB40 .loop_shift_nibble←1← BB56 BNE
LDY #0 ; Start from byte 0 (LSB)
BB42 TYA ; Clear A; C from PHA/PLP below A=&00
BB43 .loop_rotate_hex_accum←1← BB4F BNE
PHA ; Transfer carry bit to flags via stack
BB44 PLP ; PLP: C = bit shifted out of prev iter
BB45 LDA (work_ae),y ; Load accumulator byte
BB47 ROL ; Rotate left through carry
BB48 STA (work_ae),y ; Store shifted byte
BB4A PHP ; Save carry for next byte
BB4B PLA ; Transfer to A for PHA/PLP trick
BB4C INY ; Next accumulator byte
BB4D CPY #4 ; All 4 bytes rotated?
BB4F BNE loop_rotate_hex_accum ; No: rotate next byte
BB51 PHA ; Transfer carry to flags
BB52 PLP ; C = overflow bit
BB53 BCS error_hex_overflow ; Overflow: address too large
BB55 DEX ; Count bits shifted
BB56 BNE loop_shift_nibble ; 4 bits shifted? No: shift again
BB58 PLA ; Restore command line offset
BB59 TAX ; Back to X
BB5A PLA ; Restore hex digit value
BB5B LDY #0 ; Point to LSB of accumulator
BB5D ORA (work_ae),y ; OR digit into low nibble
BB5F STA (work_ae),y ; Store updated LSB
BB61 JMP loop_parse_hex_digit ; Parse next character
BB64 .error_hex_overflow←1← BB53 BCS
PLA ; Discard saved offset
BB65 PLA ; Discard saved digit
BB66 SEC ; C=1: overflow
BB67 RTS ; Return with C=1
BB68 .error_bad_hex_value←3← BB29 BCC← BB33 BCS← BB37 BCC
JSR close_ws_file ; Close open file before error
BB6B JMP err_bad_hex ; Generate 'Bad hex' error
BB6E .loop_skip_hex_spaces←1← BB73 BEQ
INY ; Advance past space
BB6F .done_test_hex_space←2← BB21 BEQ← BB25 BEQ
LDA (os_text_ptr),y ; Load next char
BB71 CMP #&20 ; Space?
BB73 BEQ loop_skip_hex_spaces ; Yes: skip it
BB75 CLC ; C=0: valid parse (no overflow)
BB76 RTS ; Return; Y past trailing spaces

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.

BB77 .init_dump_buffer←1← BA27 JSR
INX ; X+1: first byte of buffer
BB78 STX work_ae ; Set buffer pointer low byte
BB7A LDX #1 ; Buffer is on stack in page 1
BB7C STX addr_work ; Set buffer pointer high byte
BB7E JSR parse_dump_range ; Parse start offset from command line
BB81 BCS error_outside_file ; Overflow: 'Outside file' error
BB83 TYA ; A = command line offset after parse
BB84 PHA ; Save for later (past start addr)
BB85 LDY ws_page ; Y=file handle
BB87 LDX #&aa ; X=zero page address for result
BB89 LDA #2 ; A=2: read file extent (length)
BB8B JSR osargs ; Get length of file into zero page address X (A=2)
BB8E LDY #3 ; Check from MSB down
BB90 .loop_cmp_file_length←1← BB98 BPL
LDA osword_flag,y ; Load file length byte
BB93 CMP (work_ae),y ; Compare with start offset byte
BB95 BNE done_check_outside ; Mismatch: check which is larger
BB97 DEY ; Next byte down
BB98 BPL loop_cmp_file_length ; More bytes to compare
BB9A BMI done_advance_start ; All equal: start = length, within file ALWAYS branch
BB9C .done_check_outside←1← BB95 BNE
BCC error_outside_file ; Length < start: outside file
BB9E LDY #&ff ; Y=&FF: length > start, flag for later
BBA0 BNE done_advance_start ; Continue to copy start address ALWAYS branch
BBA2 .error_outside_file←2← BB81 BCS← BB9C BCC
JSR close_ws_file ; Close file before error
BBA5 LDA #&b7 ; Error number &B7
BBA7 JSR error_inline ; Generate 'Outside file' error
BBAA EQUS "Outside file."
BBB7 .loop_copy_osword_data←1← BBBF BNE
.loop_copy_start_addr←1← BBBF BNE
LDA (work_ae),y ; Load start address byte from buffer
BBB9 STA osword_flag,y ; Store to osword_flag (&AA-&AD)
BBBC .done_advance_start←2← BB9A BMI← BBA0 BNE
INY ; Next byte
BBBD CPY #4 ; All 4 bytes copied?
BBBF BNE loop_copy_osword_data ; No: copy next byte
BBC1 LDX #&aa ; X=zero page address to write from
BBC3 LDY ws_page ; Y=file handle
BBC5 LDA #1 ; A=1: write file pointer
BBC7 JSR osargs ; OSARGS: set file pointer Write sequential file pointer from zero page address X (A=1)
BBCA PLA ; Restore saved command line offset
BBCB TAY ; Back to Y for command line indexing
BBCC LDA (os_text_ptr),y ; Load next char from command line
BBCE CMP #&0d ; End of command? (CR)
BBD0 BNE done_parse_disp_base ; No: parse display base address
BBD2 LDY #1 ; Copy 2 bytes: os_text_ptr to buffer
BBD4 .loop_copy_osfile_ptr←1← BBDA BPL
LDA os_text_ptr,y ; Load os_text_ptr byte
BBD7 STA (work_ae),y ; Store as filename pointer in OSFILE CB
BBD9 DEY ; Next byte
BBDA BPL loop_copy_osfile_ptr ; Copy both low and high bytes
BBDC LDA #osfile_read_catalogue_info ; Read catalogue information
BBDE LDX work_ae ; X = control block low
BBE0 LDY addr_work ; Y = control block high
BBE2 JSR osfile ; OSFILE: read file info Read catalogue information (A=5)
BBE5 LDY #2 ; Start at OSFILE +2 (load addr byte 0)
BBE7 .loop_shift_osfile_data←1← BBF2 BNE
LDA (work_ae),y ; Load from OSFILE result offset
BBE9 DEY ; Y-2: destination is 2 bytes earlier
BBEA DEY ; Continue decrement
BBEB STA (work_ae),y ; Store to buf[Y-2]
BBED INY ; Y += 3: advance source index
BBEE INY ; (continued)
BBEF INY ; (continued)
BBF0 CPY #6 ; Copied all 4 load address bytes?
BBF2 BNE loop_shift_osfile_data ; No: copy next byte
BBF4 DEY ; Y=6 after loop exit
BBF5 DEY ; Y=4: check from buf[4] downward
BBF6 .loop_check_ff_addr←1← BBFD BNE
LDA (work_ae),y ; Load address byte
BBF8 CMP #&ff ; Is it &FF?
BBFA BNE done_add_disp_base ; No: valid load address, use it
BBFC DEY ; Check next byte down
BBFD BNE loop_check_ff_addr ; More bytes to check for &FF
BBFF LDY #3 ; Clear all 4 bytes
BC01 LDA #0 ; Zero value
BC03 .loop_zero_load_addr←1← BC06 BPL
STA (work_ae),y ; Clear byte
BC05 DEY ; Next byte down
BC06 BPL loop_zero_load_addr ; Loop for all 4 bytes
BC08 BMI done_add_disp_base ; Continue to compute display address ALWAYS branch
BC0A .done_parse_disp_base←1← BBD0 BNE
JSR parse_dump_range ; Parse second hex parameter
BC0D BCC done_add_disp_base ; Valid: use as display base
BC0F JSR close_ws_file ; Invalid: close file before error
BC12 LDA #&fc ; Error number &FC
BC14 JSR error_bad_inline ; Generate 'Bad address' error
BC17 EQUS "address."
BC1F .done_add_disp_base←3← BBFA BNE← BC08 BMI← BC0D BCC
LDY #0 ; Start from LSB
BC21 LDX #4 ; 4 bytes to add
BC23 CLC ; Clear carry for addition
BC24 .loop_add_disp_bytes←1← BC2E BNE
LDA (work_ae),y ; Load display base byte
BC26 ADC osword_flag,y ; Add start offset byte
BC29 STA osword_flag,y ; Store result in osword_flag
BC2C INY ; Next byte
BC2D DEX ; Count down
BC2E BNE loop_add_disp_bytes ; Loop for all 4 bytes
BC30 LDY #&14 ; Point past end of address area
BC32 LDX #3 ; Start from MSB (byte 3)
BC34 .loop_store_disp_addr←1← BC3A BPL
DEY ; Pre-decrement Y
BC35 LDA osword_flag,x ; Load computed display address byte
BC37 STA (work_ae),y ; Store to buf[&10-&13]
BC39 DEX ; Next byte down
BC3A BPL loop_store_disp_addr ; Loop for all 4 bytes
BC3C RTS ; Return; Y=&10 (address low byte)

Close file handle stored in workspace

Loads the file handle from ws_page and closes it via OSFIND with A=0.

BC3D .close_ws_file←6← B9B0 JSR← BA04 JSR← BA57 JMP← BB68 JSR← BBA2 JSR← BC0F JSR
LDY ws_page ; Load file handle from workspace
BC3F LDA #osfind_close ; A=0: close file
BC41 JMP osfind ; Close one or all files

Open file for reading via OSFIND

Computes the filename address from the command text pointer plus the Y offset, calls OSFIND with A=&40 (open for input). Stores the handle in ws_page. Raises 'Not found' if the returned handle is zero.

BC44 .open_file_for_read←2← B9A0 JSR← BA1B JSR
PHP ; Save caller flags
BC45 TYA ; A=filename offset from Y
BC46 CLC ; Clear carry for 16-bit addition
BC47 ADC os_text_ptr ; Add text pointer low byte
BC49 PHA ; Save filename address low
BC4A TAX ; X=filename address low (for OSFIND)
BC4B LDA #0 ; A=0: carry propagation only
BC4D ADC os_text_ptr_hi ; Add text pointer high byte + carry
BC4F PHA ; Save filename address high
BC50 TAY ; Y=filename address high (for OSFIND)
BC51 LDA #osfind_open_input ; A=&40: open for reading
BC53 JSR osfind ; Open file for input (A=64)
BC56 TAY ; A=file handle (or zero on failure)
BC57 STA ws_page ; Store file handle in workspace
BC59 BNE restore_text_ptr ; Non-zero: file opened successfully
BC5B LDA #&d6 ; Error &D6
BC5D JSR error_inline ; Raise 'Not found' error
BC60 EQUS "Not found."
BC6A .restore_text_ptr←1← BC59 BNE
PLA ; Restore text pointer high from stack
BC6B STA os_text_ptr_hi ; Set OS text pointer high
BC6D PLA ; Restore text pointer low from stack
BC6E STA os_text_ptr ; Set OS text pointer low
BC70 LDY #0 ; Y=0: start from beginning
BC72 .loop_skip_filename←1← BC7B BNE
INY ; Advance to next character
BC73 LDA (os_text_ptr),y ; Load character from command text
BC75 CMP #&0d ; CR (end of line)?
BC77 BEQ done_skip_filename ; Yes: finished parsing filename
BC79 CMP #&20 ; Space (word separator)?
BC7B BNE loop_skip_filename ; No: still within filename
BC7D .loop_skip_fn_spaces←1← BC82 BEQ
INY ; Advance past space
BC7E LDA (os_text_ptr),y ; Load next character
BC80 CMP #&20 ; Another space?
BC82 BEQ loop_skip_fn_spaces ; Yes: skip consecutive spaces
BC84 .done_skip_filename←1← BC77 BEQ
PLP ; Restore caller flags
BC85 RTS ; Return; Y=offset past filename

Advance X by 8 via nested JSR chain

Calls advance_x_by_4 (which itself JSRs inx4 then falls through to inx4), then falls through to inx4 for a total of 4+4=8 INX operations.

BC86 .advance_x_by_8←3← 9BED JSR← A8B0 JSR← BAB3 JSR
JSR advance_x_by_4 ; JSR+fall-through: 8+8=16 INXs total
fall through ↓

Advance X by 4 via JSR and fall-through

JSRs to inx4 for 4 INX operations, then falls through to inx4 for another 4 — but when called directly (not from advance_x_by_8), the caller returns after the first inx4, yielding X+4.

BC89 .advance_x_by_4←1← BC86 JSR
JSR inx4 ; 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.

ROMExec
BC8C .inx4←1← BC89 JSR
INX ; X += 4
BC8D INX ; (continued)
BC8E INX ; (continued)
BC8F INX ; (continued)
BC90 RTS ; Return
BC91 EQUB &FF, &FF
BC93 EQUB &FF ; Padding; next byte is reloc_p5_src
BC94 0500 .tube_r2_dispatch_table←2← 0050 JMP← BEA7 STA
EQUW tube_osrdch ; R2 cmd 0: OSRDCH
BC96 0502 EQUW tube_oscli ; R2 cmd 1: OSCLI
BC98 0504 EQUW tube_osbyte_2param ; R2 cmd 2: OSBYTE (2-param)
BC9A 0506 EQUW tube_osbyte_long ; R2 cmd 3: OSBYTE (3-param)
BC9C 0508 EQUW tube_osword ; R2 cmd 4: OSWORD
BC9E 050A EQUW tube_osword_rdln ; R2 cmd 5: OSWORD 0 (read line)
BCA0 050C EQUW tube_osargs ; R2 cmd 6: OSARGS
BCA2 050E EQUW tube_osbget ; R2 cmd 7: OSBGET
BCA4 0510 EQUW tube_osbput ; R2 cmd 8: OSBPUT
BCA6 0512 EQUW tube_osfind ; R2 cmd 9: OSFIND
BCA8 0514 EQUW tube_osfile ; R2 cmd 10: OSFILE
BCAA 0516 EQUW tube_osgbpb ; R2 cmd 11: OSGBPB
; Tube ULA control register values, indexed by transfer
; type (0-7). Written to &FEE0 after clearing V+M with
; &18. Bit layout: S=set/clear, T=reset regs, P=PRST,
; V=2-byte R3, M=PNMI(R3), J=PIRQ(R4), I=PIRQ(R1),
; Q=HIRQ(R4). Bits 1-7 select flags; bit 0 (S) is the
; value to set or clear.
BCAC 0518 .tube_ctrl_values←1← 0453 LDA
EQUB &86 ; Type 0: set I+J (1-byte R3, parasite to host)
BCAD 0519 EQUB &88 ; Type 1: set M (1-byte R3, host to parasite)
BCAE 051A EQUB &96 ; Type 2: set V+I+J (2-byte R3, parasite to host)
BCAF 051B EQUB &98 ; Type 3: set V+M (2-byte R3, host to parasite)
BCB0 051C EQUB &18 ; Type 4: clear V+M (execute code at address)
BCB1 051D EQUB &18 ; Type 5: clear V+M (release address claim)
BCB2 051E EQUB &82 ; Type 6: set I (define event handler)
BCB3 051F EQUB &18 ; Type 7: clear V+M (transfer and release)
; Tube OSBPUT handler (R2 cmd 8)
; Reads file handle and data byte from R2, then
; calls OSBPUT (&FFD4) to write the byte. Falls through
; to tube_reply_ack to send &7F acknowledgement.
BCB4 0520 .tube_osbput
JSR tube_read_r2 ; Read channel handle from R2 for BPUT
BCB7 0523 TAY ; Y=channel handle for OSBPUT
BCB8 0524 JSR tube_read_r2 ; Read data byte from R2 for OSBPUT
BCBB 0527 .tube_poll_r1_wrch
JSR osbput ; Write a single byte A to an open file Y
BCBE 052A JMP tube_reply_ack ; Reply with &7F ack after OSBPUT
; Tube OSBGET handler (R2 cmd 7)
; Reads file handle from R2, calls OSBGET (&FFD7)
; to read a byte, then falls through to tube_rdch_reply
; which encodes the carry flag (error) into bit 7 and
; sends the result byte via R2.
BCC1 052D .tube_osbget
JSR tube_read_r2 ; Read channel handle from R2 for BGET
BCC4 0530 TAY ; Y=file handle
BCC5 0531 JSR osbget ; Read a single byte from an open file Y
BCC8 0534 JMP tube_rdch_reply ; Reply with carry+byte via RDCH protocol
; Tube OSRDCH handler (R2 cmd 0)
; Calls OSRDCH (&FFE0) to read a character from
; the current input stream, then falls through to
; tube_rdch_reply which encodes the carry flag (error)
; into bit 7 and sends the result byte via R2.
BCCB 0537 .tube_osrdch
JSR osrdch ; Read a character from the current input stream
BCCE 053A .tube_rdch_reply←2← 0534 JMP← 05EF JMP
ROR ; ROR A: encode carry (error flag) into bit 7
BCCF 053B JSR tube_send_r2 ; Send carry+data byte to Tube R2
BCD2 053E ROL ; ROL A: restore carry flag
BCD3 053F JMP tube_reply_byte ; Return via tube_reply_byte
; Tube OSFIND handler (R2 cmd 9)
; Reads open mode from R2. If zero, reads a file
; handle and closes that file. Otherwise saves the mode,
; reads a filename string into &0700 via tube_read_string,
; then calls OSFIND (&FFCE) to open the file. Sends the
; resulting file handle (or &00) via tube_reply_byte.
BCD6 0542 .tube_osfind
JSR tube_read_r2 ; Read open mode from R2 for OSFIND
BCD9 0545 BEQ tube_osfind_close ; Mode=0: close file(s)
BCDB 0547 PHA ; Save open mode on stack
BCDC 0548 JSR tube_read_string ; Read filename string from R2
BCDF 054B PLA ; Restore open mode
BCE0 054C JSR osfind ; Open or close file(s)
BCE3 054F JMP tube_reply_byte ; Reply with file handle via R2
BCE6 0552 .tube_osfind_close←1← 0545 BEQ
JSR tube_read_r2 ; OSFIND close: read handle from R2
BCE9 0555 TAY ; Transfer handle to Y
BCEA 0556 LDA #osfind_close ; A=0: close file
BCEC 0558 JSR osfind ; Close one or all files
BCEF 055B JMP tube_reply_ack ; Reply with acknowledgement via R2
; Tube OSARGS handler (R2 cmd 6)
; Reads file handle from R2 into Y, then reads
; a 4-byte argument and reason code into zero page.
; Calls OSARGS (&FFDA), sends the result A and 4-byte
; return value via R2, then returns to the main loop.
BCF2 055E .tube_osargs
JSR tube_read_r2 ; Read file handle from R2 for OSARGS
BCF5 0561 TAY ; Y=file handle for OSARGS
BCF6 0562 .tube_read_params
LDX #4 ; Read 4-byte arg + reason from R2 into ZP
BCF8 0564 .read_osargs_params←1← 056A BNE
JSR tube_read_r2 ; Read next param byte from R2
BCFB 0567 STA escape_flag,x ; Store param at ZP+X (escape_flag downward)
BCFD 0569 DEX ; Decrement index
BCFE 056A BNE read_osargs_params ; More params: continue reading
BD00 056C JSR tube_read_r2 ; Read OSARGS reason code from R2
BD03 056F JSR osargs ; Read or write a file's attributes
BD06 0572 JSR tube_send_r2 ; Send result A via R2
BD09 0575 LDX #3 ; X=3: send 4 result bytes
BD0B 0577 .send_osargs_result←1← 057D BPL
LDA zp_ptr_lo,x ; Load result byte from zero page
BD0D 0579 JSR tube_send_r2 ; Send result byte via R2
BD10 057C DEX ; Decrement byte counter
BD11 057D BPL send_osargs_result ; More bytes: continue sending
BD13 057F JMP tube_main_loop ; Return to Tube main loop
; Read string from Tube R2 into buffer
; Loops reading bytes from tube_read_r2 into the
; string buffer at &0700, storing at string_buf+Y.
; Terminates on CR (&0D) or when Y wraps to zero
; (256-byte overflow). Returns with X=0, Y=7 so that
; XY = &0700, ready for OSCLI or OSFIND dispatch.
; Called by the Tube OSCLI and OSFIND handlers.
; On Exit:
; X: 0 (low byte of &0700)
; Y: 7 (high byte of &0700)
BD16 0582 .tube_read_string←3← 0548 JSR← 0596 JSR← 05B3 JSR
LDX #0 ; X=0: initialise string buffer index
BD18 0584 LDY #0 ; Y=0: initialise string offset
BD1A 0586 .strnh←1← 0591 BNE
JSR tube_read_r2 ; Read next string byte from R2
BD1D 0589 STA string_buf,y ; Store in string buffer at &0700+Y
BD20 058C INY ; Advance string index
BD21 058D BEQ string_buf_done ; Buffer full (256 bytes): done
BD23 058F CMP #&0d ; Check for CR terminator
BD25 0591 BNE strnh ; Not CR: continue reading
BD27 0593 .string_buf_done←1← 058D BEQ
LDY #7 ; Y=7: set XY=&0700 for OSCLI/OSFIND
BD29 0595 RTS ; Return with XY pointing to string buffer
; Tube OSCLI handler (R2 cmd 1)
; Reads a command string from R2 into &0700 via
; tube_read_string, then calls OSCLI (&FFF7) to execute
; it. Falls through to tube_reply_ack to send &7F
; acknowledgement.
BD2A 0596 .tube_oscli
JSR tube_read_string ; Read command string from R2 into &0700
BD2D 0599 JSR oscli ; Execute command string via OSCLI
BD30 059C .tube_reply_ack←3← 0489 JMP← 052A JMP← 055B JMP
LDA #&7f ; &7F = success acknowledgement
BD32 059E .tube_reply_byte←4← 053F JMP← 054F JMP← 05A1 BVC← 067D JMP
BIT tube_status_register_2 ; Poll R2 status until ready
BD35 05A1 BVC tube_reply_byte ; Bit 6 clear: not ready, loop
BD37 05A3 STA tube_data_register_2 ; Write byte to R2 data register
BD3A 05A6 .mj←1← 05CF BEQ
JMP tube_main_loop ; Return to Tube main loop
; Tube OSFILE handler (R2 cmd 10)
; Reads a 16-byte control block into zero page,
; a filename string into &0700 via tube_read_string,
; and a reason code from R2. Calls OSFILE (&FFDD),
; then sends the result A and updated 16-byte control
; block back via R2. Returns to the main loop via mj.
BD3D 05A9 .tube_osfile
LDX #&10 ; Read 16-byte OSFILE control block from R2
BD3F 05AB .argsw←1← 05B1 BNE
JSR tube_read_r2 ; Read next control block byte from R2
BD42 05AE STA zp_ptr_hi,x ; Store at ZP+X (control block)
BD44 05B0 DEX ; Decrement index
BD45 05B1 BNE argsw ; More bytes: continue reading
BD47 05B3 JSR tube_read_string ; Read filename string from R2
BD4A 05B6 STX zp_ptr_lo ; Set filename ptr low = 0
BD4C 05B8 STY zp_ptr_hi ; Set filename ptr high = &07
BD4E 05BA LDY #0 ; Y=0: OSFILE reason code index
BD50 05BC JSR tube_read_r2 ; Read OSFILE reason code from R2
BD53 05BF JSR osfile ; Execute OSFILE
BD56 05C2 JSR tube_send_r2 ; Send result A via R2
BD59 05C5 LDX #&10 ; X=&10: send 16 result bytes
BD5B 05C7 .send_osfile_ctrl_blk←1← 05CD BNE
LDA zp_ptr_hi,x ; Load control block byte
BD5D 05C9 JSR tube_send_r2 ; Send control block byte via R2
BD60 05CC DEX ; Decrement byte counter
BD61 05CD BNE send_osfile_ctrl_blk ; More bytes: continue sending
BD63 05CF BEQ mj ; ALWAYS branch
; Tube OSGBPB handler (R2 cmd 11)
; Reads a 13-byte control block and reason code
; from R2 into zero page. Calls OSGBPB (&FFD1), then
; sends 12 result bytes and the carry+result byte
; (via tube_rdch_reply) back via R2.
BD65 05D1 .tube_osgbpb
LDX #&0d ; X=&0D: read 13-byte OSGBPB ctrl block
BD67 05D3 .read_osgbpb_ctrl_blk←1← 05D9 BNE
JSR tube_read_r2 ; Read next control block byte from R2
BD6A 05D6 STA escape_flag,x ; Store at ZP+X (escape_flag downward)
BD6C 05D8 DEX ; Decrement index
BD6D 05D9 BNE read_osgbpb_ctrl_blk ; More bytes: continue reading
BD6F 05DB JSR tube_read_r2 ; Read OSGBPB reason code from R2
BD72 05DE LDY #0 ; Y=0: OSGBPB direction/count
BD74 05E0 JSR osgbpb ; Read or write multiple bytes to an open file
BD77 05E3 PHA ; Save result A on stack
BD78 05E4 LDX #&0c ; X=&0C: send 12 result bytes
BD7A 05E6 .send_osgbpb_result←1← 05EC BPL
LDA zp_ptr_lo,x ; Load result byte from zero page
BD7C 05E8 JSR tube_send_r2 ; Send result byte via R2
BD7F 05EB DEX ; Decrement byte counter
BD80 05EC BPL send_osgbpb_result ; More bytes: continue sending
BD82 05EE PLA ; Recover completion status from stack
BD83 05EF JMP tube_rdch_reply ; Reply with RDCH-style result
; Tube OSBYTE 2-param handler (R2 cmd 2)
; Reads X and A from R2, calls OSBYTE (&FFF4)
; with Y=0, then sends the result X via
; tube_reply_byte. Used for OSBYTE calls that take
; only A and X parameters.
BD86 05F2 .tube_osbyte_2param
JSR tube_read_r2 ; Read X parameter from R2
BD89 05F5 TAX ; Transfer to X register
BD8A 05F6 JSR tube_read_r2 ; Read A (OSBYTE function code) from R2
BD8D 05F9 JSR osbyte ; Execute OSBYTE A,X
BD90 05FC .tube_poll_r2_result←2← 05FF ref← 0625 BVS
BIT tube_status_register_2 ; Poll R2 status for result send
BD93 05FF EQUB &50 ; BVC: page 5/6 boundary straddle
BD94 0600 .tube_osbyte_reply_block←1← BEAD STA
EQUB &FB ; Send carry+status to co-processor via R2
BD95 0601 STX tube_data_register_2 ; Send X result for 2-param OSBYTE
BD98 0604 .bytex←1← 0617 BEQ
JMP tube_main_loop ; Return to main event loop
; Tube OSBYTE 3-param handler (R2 cmd 3)
; Reads X, Y, and A from R2, calls OSBYTE
; (&FFF4), then sends carry+Y and X as result bytes
; via R2. Used for OSBYTE calls needing all three
; parameters and returning both X and Y results.
BD9B 0607 .tube_osbyte_long
JSR tube_read_r2 ; Read X, Y, A from R2 for 3-param OSBYTE
BD9E 060A TAX ; Save in X
BD9F 060B JSR tube_read_r2 ; Read Y parameter from co-processor
BDA2 060E TAY ; Save in Y
BDA3 060F JSR tube_read_r2 ; Read A (OSBYTE function code)
BDA6 0612 JSR osbyte ; Execute OSBYTE A,X,Y
BDA9 0615 EOR #&9d ; Test for OSBYTE &9D (fast Tube BPUT)
BDAB 0617 BEQ bytex ; OSBYTE &9D (fast Tube BPUT): no result needed
BDAD 0619 ROR ; Encode carry (error flag) into bit 7
BDAE 061A JSR tube_send_r2 ; Send carry+status byte via R2
BDB1 061D .tube_osbyte_send_y←1← 0620 BVC
BIT tube_status_register_2 ; Poll R2 status for ready
BDB4 0620 BVC tube_osbyte_send_y ; Not ready: keep polling
BDB6 0622 STY tube_data_register_2 ; Send Y result, then fall through to send X
BDB9 0625 .tube_osbyte_short
BVS tube_poll_r2_result ; BVS always: jump to send X via R2
; Tube OSWORD handler (R2 cmd 4)
; Reads OSWORD number A and in-length from R2,
; then reads the parameter block into &0128. Calls
; OSWORD (&FFF1), then sends the out-length result
; bytes from the parameter block back via R2.
; Returns to the main loop via tube_return_main.
BDBB 0627 .tube_osword
JSR tube_read_r2 ; Overlapping entry: &20 = JSR c06c5 (OSWORD)
BDBE 062A TAY ; Save OSWORD number in Y
BDBF 062B .tube_osword_read←1← 062E BPL
BIT tube_status_register_2 ; Poll R2 status for data ready
BDC2 062E BPL tube_osword_read ; Not ready: keep polling
BDC4 0630 .tube_osbyte_send_x
LDX tube_data_register_2 ; Read param block length from R2
BDC7 0633 DEX ; DEX: length 0 means no params to read
BDC8 0634 BMI skip_param_read ; No params (length=0): skip read loop
BDCA 0636 .tube_osword_read_lp←2← 0639 BPL← 0642 BPL
BIT tube_status_register_2 ; Poll R2 status for data ready
BDCD 0639 BPL tube_osword_read_lp ; Not ready: keep polling
BDCF 063B LDA tube_data_register_2 ; Read param byte from R2
BDD2 063E STA tube_osword_pb,x ; Store param bytes into block at &0128
BDD5 0641 DEX ; Next param byte (descending)
BDD6 0642 BPL tube_osword_read_lp ; Loop until all params read
BDD8 0644 TYA ; Restore OSWORD number from Y
BDD9 0645 .skip_param_read←1← 0634 BMI
LDX #<(tube_osword_pb) ; XY=&0128: param block address for OSWORD
BDDB 0647 LDY #>(tube_osword_pb) ; Y=&01: param block at &0128
BDDD 0649 JSR osword ; Execute OSWORD with XY=&0128
BDE0 064C .poll_r2_osword_result←1← 064F BPL
BIT tube_status_register_2 ; Poll R2 status for ready
BDE3 064F BPL poll_r2_osword_result ; Not ready: keep polling
BDE5 0651 LDX tube_data_register_2 ; Read result block length from R2
BDE8 0654 DEX ; Decrement result byte counter
BDE9 0655 BMI tube_return_main ; No results to send: return to main loop
BDEB 0657 .tube_osword_write←1← 0663 BPL
LDY tube_osword_pb,x ; Send result block bytes from &0128 via R2
BDEE 065A .tube_osword_write_lp←1← 065D BVC
BIT tube_status_register_2 ; Poll R2 status for ready
BDF1 065D BVC tube_osword_write_lp ; Not ready: keep polling
BDF3 065F STY tube_data_register_2 ; Send result byte via R2
BDF6 0662 DEX ; Next result byte (descending)
BDF7 0663 BPL tube_osword_write ; Loop until all results sent
BDF9 0665 .tube_return_main←1← 0655 BMI
JMP tube_main_loop ; Return to main event loop
; Tube OSWORD 0 handler (R2 cmd 5)
; Handles OSWORD 0 (read line) specially. Reads
; 4 parameter bytes from R2 into &0128 (max length,
; min char, max char, flags). Calls OSWORD 0 (&FFF1)
; to read a line, then sends &7F+CR or the input line
; byte-by-byte via R2, followed by &80 (error/escape)
; or &7F (success).
BDFC 0668 .tube_osword_rdln
LDX #4 ; Read 5-byte OSWORD 0 control block from R2
BDFE 066A .read_rdln_ctrl_block←1← 0670 BPL
JSR tube_read_r2 ; Read control block byte from R2
BE01 066D STA zp_ptr_lo,x ; Store in zero page params
BE03 066F DEX ; Next byte (descending)
BE04 0670 BPL read_rdln_ctrl_block ; Loop until all 5 bytes read
BE06 0672 INX ; X=0 after loop, A=0 for OSWORD 0 (read line)
BE07 0673 LDY #0 ; Y=0 for OSWORD 0
BE09 0675 TXA ; A=0: OSWORD 0 (read line)
BE0A 0676 JSR osword ; Read input line from keyboard
BE0D 0679 BCC tube_rdln_send_line ; C=0: line read OK; C=1: escape pressed
BE0F 067B LDA #&ff ; &FF = escape/error signal to co-processor
BE11 067D JMP tube_reply_byte ; Escape: send &FF error to co-processor
BE14 0680 .tube_rdln_send_line←1← 0679 BCC
LDX #0 ; X=0: start of input buffer at &0700
BE16 0682 LDA #&7f ; &7F = line read successfully
BE18 0684 JSR tube_send_r2 ; Send &7F (success) to co-processor
BE1B 0687 .tube_rdln_send_loop←1← 0690 BNE
LDA string_buf,x ; Load char from input buffer
BE1E 068A .tube_rdln_send_byte
JSR tube_send_r2 ; Send char to co-processor
BE21 068D INX ; Next character
BE22 068E CMP #&0d ; Check for CR terminator
BE24 0690 BNE tube_rdln_send_loop ; Loop until CR terminator sent
BE26 0692 JMP tube_main_loop ; Return to main event loop
; Send byte to Tube data register R2
; Polls Tube status register 2 until bit 6 (TDRA)
; is set, then writes A to the data register. Uses a
; tight BIT/BVC polling loop. Called by 12 sites
; across the Tube host code for all R2 data
; transmission: command responses, file data, OSBYTE
; results, and control block bytes.
; On Entry:
; A: byte to send
; On Exit:
; A: preserved (value written)
BE29 0695 .tube_send_r2←14← 0020 JSR← 0026 JSR← 002C JSR← 0474 JSR← 053B JSR← 0572 JSR← 0579 JSR← 05C2 JSR← 05C9 JSR← 05E8 JSR← 061A JSR← 0684 JSR← 068A JSR← 0698 BVC
BIT tube_status_register_2 ; Poll R2 status (bit 6 = ready)
BE2C 0698 BVC tube_send_r2 ; Not ready: keep polling
BE2E 069A STA tube_data_register_2 ; Write A to Tube R2 data register
BE31 069D RTS ; Return to caller
; Send byte to Tube data register R4
; Polls Tube status register 4 until bit 6 is set,
; then writes A to the data register. Uses a tight
; BIT/BVC polling loop. R4 is the command/control
; channel used for address claims (ADRR), data transfer
; setup (SENDW), and release commands. Called by 7
; sites, primarily during tube_release_claim and
; tube_transfer_setup sequences.
; On Entry:
; A: byte to send
; On Exit:
; A: preserved (value written)
BE32 069E .tube_send_r4←8← 0018 JSR← 0418 JSR← 041D JSR← 043B JSR← 0443 JSR← 0448 JSR← 0463 JSR← 06A1 BVC
BIT tube_status_register_4_and_cpu_control ; Poll R4 status (bit 6 = ready)
BE35 06A1 BVC tube_send_r4 ; Not ready: keep polling
BE37 06A3 STA tube_data_register_4 ; Write A to Tube R4 data register
BE3A 06A6 RTS ; Return to caller
BE3B 06A7 .tube_escape_check←1← 0403 JMP
LDA escape_flag ; Check OS escape flag at &FF
BE3D 06A9 SEC ; SEC+ROR: put bit 7 of &FF into carry+bit 7
BE3E 06AA ROR ; ROR: shift escape bit 7 to carry
BE3F 06AB BMI tube_send_r1 ; Escape set: forward to co-processor via R1
BE41 06AD .tube_event_handler
PHA ; EVNTV: forward event A, Y, X to co-processor
BE42 06AE LDA #0 ; Send &00 prefix (event notification)
BE44 06B0 JSR tube_send_r1 ; Send zero prefix via R1
BE47 06B3 TYA ; Y value for event
BE48 06B4 JSR tube_send_r1 ; Send Y via R1
BE4B 06B7 TXA ; X value for event
BE4C 06B8 JSR tube_send_r1 ; Send X via R1
BE4F 06BB PLA ; Restore A (event type)
; Send byte to Tube data register R1
; Polls Tube status register 1 until bit 6 is set,
; then writes A to the data register. Uses a tight
; BIT/BVC polling loop. R1 is used for asynchronous
; event and escape notification to the co-processor.
; Called by tube_event_handler to forward event type,
; Y, and X parameters, and reached via BMI from
; tube_escape_check when the escape flag is set.
; On Entry:
; A: byte to send
; On Exit:
; A: preserved (value written)
BE50 06BC .tube_send_r1←5← 06AB BMI← 06B0 JSR← 06B4 JSR← 06B8 JSR← 06BF BVC
BIT tube_status_1_and_tube_control ; Poll R1 status (bit 6 = ready)
BE53 06BF BVC tube_send_r1 ; Not ready: keep polling
BE55 06C1 STA tube_data_register_1 ; Write A to Tube R1 data register
BE58 06C4 RTS ; Return to caller
; Read a byte from Tube data register R2
; Polls Tube status register 2 until bit 7 (RDA)
; is set, then loads and returns the byte from Tube
; data register 2. Uses a BIT/BPL polling loop (testing
; the N flag). R2 is the primary data channel from the
; co-processor. Called by 14 sites across the Tube host
; code for command dispatch, OSFILE/OSGBPB control block
; reads, string reads, and OSBYTE parameter reception.
; On Exit:
; A: byte read from R2
BE59 06C5 .tube_read_r2←21← 0520 JSR← 0524 JSR← 052D JSR← 0542 JSR← 0552 JSR← 055E JSR← 0564 JSR← 056C JSR← 0586 JSR← 05AB JSR← 05BC JSR← 05D3 JSR← 05DB JSR← 05F2 JSR← 05F6 JSR← 0607 JSR← 060B JSR← 060F JSR← 0627 JSR← 066A JSR← 06C8 BPL
BIT tube_status_register_2 ; Poll R2 status (bit 7 = ready)
BE5C 06C8 BPL tube_read_r2 ; Not ready: keep polling
BE5E 06CA LDA tube_data_register_2 ; Read data byte from R2
BE61 06CD RTS ; Return with byte in A
BE62 06CE CMP #&fe ; Is byte &FE (VDU stream start)?
BE64 06D0 BCC tube_vdu_normal_byte ; Below &FE: normal byte
BE66 06D2 BNE setup_tube_vectors ; &FF: set up event/break vectors
BE68 06D4 CPY #0 ; &FE: check Y parameter
BE6A 06D6 BEQ tube_vdu_normal_byte ; Y=0: treat as normal byte
BE6C 06D8 LDX #6 ; X=6: six extra pages
BE6E 06DA LDA #osbyte_explode_chars ; OSBYTE &14: explode char defs
BE70 06DC JSR osbyte ; Explode character definition RAM (six extra pages), can redefine all characters 32-255 (X=6)
BE73 06DF .loop_poll_r1_vdu←1← 06E2 BPL
BIT tube_status_1_and_tube_control ; Poll R1 status (bit 6 = ready)
BE76 06E2 BPL loop_poll_r1_vdu ; Not ready: keep polling
BE78 06E4 LDA tube_data_register_1 ; Read byte from Tube R1
BE7B 06E7 BEQ tube_vdu_stream_end ; Zero: end of VDU stream
BE7D 06E9 JSR oswrch ; Write character
BE80 06EC .svc_11_nmi_claim
JMP loop_poll_r1_vdu_rom ; Loop back to read next R1 byte
BE83 06EF .setup_tube_vectors←1← 06D2 BNE
LDA #&ad ; EVNTV low byte (&AD)
BE85 06F1 STA evntv ; Store in EVNTV vector low
BE88 06F4 LDA #6 ; EVNTV high byte (page 6)
BE8A 06F6 STA evntv+1 ; Store in EVNTV vector high
BE8D 06F9 LDA #&16 ; BRKV low byte (&16)
BE8F 06FB STA brkv ; Store in BRKV vector
BE92 06FE LDA #0 ; A=0
; Resume normal ROM address space
; The preceding 512 bytes are the source data for
; two relocated code blocks (see move() calls):
; page 5 source -> &0500-&05FF (Tube host code)
; page 6 source -> &0600-&06FF (Econet handlers)
; py8dis assembles those blocks at their runtime
; addresses (&0500/&0600) via org directives. This
; org restores the origin to the actual ROM address
; for the remaining non-relocated code.
BE94 STA brkv+1 ; Store BRK vector high byte
BE97 LDA #&8e ; A=&8E: Tube control register value
BE99 STA tube_status_1_and_tube_control ; Write Tube control register
BE9C LDY #0 ; Y=0: copy 256 bytes per page
BE9E .loop_copy_reloc_pages←1← BEB1 BNE
LDA reloc_p4_src,y ; Load page 4 source byte
BEA1 STA tube_page4_vectors,y ; Store to page 4 destination
BEA4 LDA reloc_p5_src,y ; Load page 5 source byte
BEA7 STA tube_r2_dispatch_table,y ; Store to page 5 destination
BEAA LDA reloc_p6_src,y ; Load page 6 source byte
BEAD STA tube_osbyte_reply_block,y ; Store to page 6 destination
BEB0 DEY ; Decrement byte counter
BEB1 BNE loop_copy_reloc_pages ; Non-zero: continue copying
BEB3 JSR clear_tube_claim ; Clear tube claim state
BEB6 LDX #&41 ; X=&41: copy 66 bytes of ZP workspace
BEB8 .loop_copy_zp_workspace←1← BEBE BPL
LDA reloc_zp_src,x ; Load ZP source byte from ROM
BEBB STA nmi_workspace_start,x ; Store to NMI workspace at &16+X
BEBD DEX ; Decrement byte counter
BEBE BPL loop_copy_zp_workspace ; More bytes: continue copying
BEC0 LDA #0 ; A=0: return success
BEC2 RTS ; Return to caller
BEC3 0016 .nmi_workspace_start←1← BEBB STA
LDA #&ff ; A=&FF: signal error to co-processor via R4
BEC5 0018 JSR tube_send_r4 ; Send &FF error signal to Tube R4
BEC8 001B LDA tube_data_register_2 ; Flush any pending R2 byte
BECB 001E LDA #0 ; A=0: send zero prefix to R2
BECD 0020 .tube_send_zero_r2
JSR tube_send_r2 ; Send zero prefix byte via R2
BED0 0023 TAY ; Y=0: start of error block at (&FD)
BED1 0024 LDA (brk_ptr),y ; Load error number from (&FD),0
BED3 0026 .tube_send_error_num
JSR tube_send_r2 ; Send error number via R2
BED6 0029 .tube_brk_send_loop←1← 0030 BNE
INY ; Advance to next error string byte
BED7 002A .tube_send_error_byte
LDA (brk_ptr),y ; Load next error string byte
BED9 002C JSR tube_send_r2 ; Send error string byte via R2
BEDC 002F TAX ; Zero byte = end of error string
BEDD 0030 BNE tube_brk_send_loop ; Loop until zero terminator sent
BEDF 0032 .tube_reset_stack←1← 0477 JMP
LDX #&ff ; Reset stack pointer to top
BEE1 0034 TXS ; TXS: set stack pointer from X
BEE2 0035 CLI ; Enable interrupts for main loop
BEE3 0036 .tube_main_loop←6← 0044 BPL← 057F JMP← 05A6 JMP← 0604 JMP← 0665 JMP← 0692 JMP
BIT tube_status_1_and_tube_control ; BIT R1 status: check WRCH request
BEE6 0039 BPL tube_poll_r2 ; R1 not ready: check R2 instead
BEE8 003B .tube_handle_wrch←1← 0049 BMI
LDA tube_data_register_1 ; Read character from Tube R1 data
BEEB 003E JSR oswrch ; Write character
BEEE 0041 .tube_poll_r2←1← 0039 BPL
BIT tube_status_register_2 ; BIT R2 status: check command byte
BEF1 0044 BPL tube_main_loop ; R2 not ready: loop back to R1 check
BEF3 0046 BIT tube_status_1_and_tube_control ; Re-check R1: WRCH has priority over R2
BEF6 0049 BMI tube_handle_wrch ; R1 ready: handle WRCH first
BEF8 004B LDX tube_data_register_2 ; Read command byte from Tube R2 data
BEFB 004E STX tube_cmd_lo ; Self-modify JMP low byte for dispatch
BEFD 0050 .tube_dispatch_cmd
JMP (tube_r2_dispatch_table) ; Dispatch to handler via indirect JMP
BF00 0053 .tube_transfer_addr←2← 04DA STY← 04EA STA
EQUB &00 ; Tube transfer address low byte
BF01 0054 .tube_xfer_page←3← 04B2 INC← 04D0 STA← 04EF STA
EQUB &80 ; Tube transfer page (default &80)
BF02 0055 .tube_xfer_addr_2←2← 04B6 INC← 04F9 STY
EQUB &00 ; Tube transfer address byte 2
BF03 0056 .tube_xfer_addr_3←2← 04BA INC← 04F7 STA
EQUB &00 ; Tube transfer address byte 3
BF04 0400 .tube_page4_vectors←1← BEA1 STA
JMP tube_begin ; JMP to BEGIN startup entry
BF07 0403 JMP tube_escape_check ; JMP to tube_escape_check (&06A7)
; Tube address/data dispatch
; Called by 10 sites across the Tube host and Econet
; code. Routes requests based on the value of A:
; A < &80: data transfer setup (SENDW) at &0435
; &80 <= A < &C0: release -- maps A via ORA #&40
; and compares with tube_claimed_id; if we own
; this address, falls through to tube_release_claim
; A >= &C0: external address claim from another host
; Falls through to tube_release_claim when releasing our
; current claim.
; On Entry:
; A: request type (<&80 data, &80-&BF release, &C0+ claim)
; X: transfer address low (data transfer only)
; Y: transfer address high (data transfer only)
BF0A 0406 .tube_addr_data_dispatch←10← 0493 JSR← 04CB JMP← 837C JSR← 844F JSR← 8933 JSR← 893B JSR← 9FF2 JSR← A009 JSR← A075 JSR← A2EC JMP
CMP #&80 ; A>=&80: address claim; A<&80: data transfer
BF0C 0408 BCC tube_transfer_setup ; A<&80: data transfer setup (SENDW)
BF0E 040A CMP #&c0 ; A>=&C0: new address claim from another host
BF10 040C BCS addr_claim_external ; C=1: external claim, check ownership
BF12 040E ORA #&40 ; Map &80-&BF range to &C0-&FF for comparison
BF14 0410 CMP tube_claimed_id ; Is this for our currently-claimed address?
BF16 0412 BNE return_tube_init ; Not our address: return
; Release Tube address claim via R4 command 5
; Saves interrupt state (PHP/SEI) to protect the R4
; protocol sequence, sends R4 command 5 (release) followed
; by the currently-claimed address from tube_claimed_id
; (&15), then restores interrupts (PLP). Falls through to
; clear_tube_claim to reset the claimed-address state to
; the &80 sentinel.
BF18 0414 .tube_release_claim←1← 0471 JSR
PHP ; PHP: save interrupt state for release
BF19 0415 SEI ; SEI: disable interrupts during R4 protocol
BF1A 0416 LDA #5 ; R4 cmd 5: release our address claim
BF1C 0418 JSR tube_send_r4 ; Send release command to co-processor
BF1F 041B LDA tube_claimed_id ; Load our currently-claimed address
BF21 041D JSR tube_send_r4 ; Send our address as release parameter
BF24 0420 PLP ; Restore interrupt state
; Reset Tube address claim state
; Stores &80 into both tube_claimed_id (&15) and
; tube_claim_flag (&14). The &80 sentinel indicates no
; address is currently claimed and no claim is in
; progress. Called after tube_release_claim (via
; fall-through) and during initial workspace setup.
BF25 0421 .clear_tube_claim←1← BEB3 JSR
LDA #&80 ; &80 sentinel: clear address claim
BF27 0423 STA tube_claimed_id ; &80 sentinel = no address currently claimed
BF29 0425 STA tube_claim_flag ; Store to claim-in-progress flag
BF2B 0427 RTS ; Return from tube_post_init
BF2C 0428 .addr_claim_external←1← 040C BCS
ASL tube_claim_flag ; Another host claiming; check if we're owner
BF2E 042A BCS accept_new_claim ; C=1: we have an active claim
BF30 042C CMP tube_claimed_id ; Compare with our claimed address
BF32 042E BEQ return_tube_init ; Match: return (we already have it)
BF34 0430 CLC ; Not ours: CLC = we don't own this address
BF35 0431 RTS ; Return with C=0 (claim denied)
BF36 0432 .accept_new_claim←1← 042A BCS
STA tube_claimed_id ; Accept new claim: update our address
BF38 0434 .return_tube_init←2← 0412 BNE← 042E BEQ
RTS ; Return with address updated
BF39 0435 .tube_transfer_setup←1← 0408 BCC
PHP ; PHP: save interrupt state
BF3A 0436 SEI ; SEI: disable interrupts for R4 protocol
BF3B 0437 .setup_data_transfer
STY tube_data_ptr_hi ; Save 16-bit transfer address from (X,Y)
BF3D 0439 STX tube_data_ptr ; Store address pointer low byte
BF3F 043B JSR tube_send_r4 ; Send transfer type byte to co-processor
BF42 043E TAX ; X = transfer type for table lookup
BF43 043F LDY #3 ; Y=3: send 4 bytes (address + claimed addr)
BF45 0441 LDA tube_claimed_id ; Send our claimed address + 4-byte xfer addr
BF47 0443 JSR tube_send_r4 ; Send transfer address byte
BF4A 0446 .send_xfer_addr_bytes←1← 044C BPL
LDA (tube_data_ptr),y ; Load transfer address byte from (X,Y)
BF4C 0448 JSR tube_send_r4 ; Send address byte to co-processor via R4
BF4F 044B DEY ; Previous byte (big-endian: 3,2,1,0)
BF50 044C BPL send_xfer_addr_bytes ; Loop for all 4 address bytes
BF52 044E LDY #&18 ; Y=&18: enable Tube control register
BF54 0450 STY tube_status_1_and_tube_control ; Enable Tube interrupt generation
BF57 0453 LDA tube_ctrl_values,x ; Look up Tube control bits for this xfer type
BF5A 0456 STA tube_status_1_and_tube_control ; Apply transfer- specific control bits
BF5D 0459 LSR ; LSR: check bit 2 (2-byte flush needed?)
BF5E 045A LSR ; LSR: shift bit 2 to carry
BF5F 045B BCC skip_r3_flush ; C=0: no flush needed, skip R3 reads
BF61 045D BIT tube_data_register_3 ; Dummy R3 reads: flush for 2-byte transfers
BF64 0460 BIT tube_data_register_3 ; Second dummy read to flush R3 FIFO
BF67 0463 .skip_r3_flush←1← 045B BCC
JSR tube_send_r4 ; Trigger co-processor ack via R4
BF6A 0466 .poll_r4_copro_ack←1← 0469 BVC
BIT tube_status_register_4_and_cpu_control ; Poll R4 status for co- processo r response
BF6D 0469 BVC poll_r4_copro_ack ; Bit 6 clear: not ready, keep polling
BF6F 046B BCS copro_ack_nmi_check ; R4 bit 7: co-processor acknowledged transfer
BF71 046D CPX #4 ; Type 4 = SENDW (host-to-parasite word xfer)
BF73 046F BNE skip_nmi_release ; Not SENDW type: skip release path
BF75 0471 .tube_sendw_complete←1← 048F BEQ
JSR tube_release_claim ; SENDW complete: release, sync, restart
BF78 0474 JSR tube_send_r2 ; Sync via R2 send
BF7B 0477 JMP tube_reset_stack ; Restart Tube main loop
BF7E 047A .copro_ack_nmi_check←1← 046B BCS
LSR ; LSR: check bit 0 (NMI used?)
BF7F 047B BCC skip_nmi_release ; C=0: NMI not used, skip NMI release
BF81 047D LDY #&88 ; Release Tube NMI (transfer used interrupts)
BF83 047F STY tube_status_1_and_tube_control ; Write &88 to Tube control to release NMI
BF86 0482 .skip_nmi_release←2← 046F BNE← 047B BCC
PLP ; Restore interrupt state
BF87 0483 .return_tube_xfer
RTS ; Return from transfer setup
; Tube host startup entry (BEGIN)
; Entry point via JMP from &0400. Enables interrupts, ; checks
; break type via OSBYTE &FD: soft break re-initialises ; Tube and
; restarts, hard break claims address &FF. Sends ROM ; contents
; to co-processor page by page via SENDW, then claims the ; final
; transfer address.
BF88 0484 .tube_begin←1← 0400 JMP
CLI ; BEGIN: enable interrupts for Tube host code
BF89 0485 BCS claim_addr_ff ; C=1: hard break, claim addr &FF
BF8B 0487 BNE check_break_type ; C=0, A!=0: re-init path
BF8D 0489 JMP tube_reply_ack ; Z=1 from C=0 path: just acknowledge
BF90 048C .check_break_type←1← 0487 BNE
LDX last_break_type ; Read last break type from OS workspace
BF93 048F BEQ tube_sendw_complete ; Soft break (X=0): re-init Tube and restart
BF95 0491 .claim_addr_ff←2← 0485 BCS← 0496 BCC
LDA #&ff ; Claim address &FF (startup = highest prio)
BF97 0493 JSR tube_addr_data_dispatch ; Request address claim from Tube system
BF9A 0496 BCC claim_addr_ff ; C=0: claim failed, retry
BF9C 0498 JSR tube_init_reloc ; Init reloc pointers from ROM header
BF9F 049B .send_next_rom_page←1← 04C0 BVC
PHP ; Save interrupt state
BFA0 049C SEI ; Disable interrupts during ROM transfer
BFA1 049D .next_rom_page
LDA #7 ; R4 cmd 7: SENDW to send ROM to parasite
BFA3 049F JSR tube_claim_default ; Set up Tube for SENDW transfer
BFA6 04A2 LDY #0 ; Y=0: start at beginning of page
BFA8 04A4 STY zp_ptr_lo ; Store to zero page pointer low byte
BFAA 04A6 .send_rom_page_bytes←1← 04AF BNE
LDA (zp_ptr_lo),y ; Send 256-byte page via R3, byte at a time
BFAC 04A8 STA tube_data_register_3 ; Write byte to Tube R3 data register
BFAF 04AB NOP ; Timing delay: Tube data register needs NOPs
BFB0 04AC NOP ; NOP delay (2)
BFB1 04AD NOP ; NOP delay (3)
BFB2 04AE INY ; Next byte in page
BFB3 04AF BNE send_rom_page_bytes ; Loop for all 256 bytes
BFB5 04B1 PLP ; Restore interrupt state after page sent
BFB6 04B2 INC tube_xfer_page ; Increment 24-bit destination addr
BFB8 04B4 BNE skip_addr_carry ; No carry: skip higher bytes
BFBA 04B6 INC tube_xfer_addr_2 ; Carry into second byte
BFBC 04B8 BNE skip_addr_carry ; No carry: skip third byte
BFBE 04BA INC tube_xfer_addr_3 ; Carry into third byte
BFC0 04BC .skip_addr_carry←2← 04B4 BNE← 04B8 BNE
INC zp_ptr_hi ; Increment page counter
BFC2 04BE BIT zp_ptr_hi ; Bit 6 set = all pages transferred
BFC4 04C0 BVC send_next_rom_page ; More pages: loop back to SENDW
BFC6 04C2 JSR tube_init_reloc ; Re-init reloc pointers for final claim
BFC9 04C5 LDA #4 ; A=4: transfer type for final address claim
; Claim default Tube transfer address
; Sets Y=0, X=&53 (address &0053), then JMP ; tube_addr_claim
; to initiate a Tube address claim for the default ; transfer
; address. Called from the BEGIN startup path and after ; the
; page transfer loop completes.
BFCB 04C7 .tube_claim_default←1← 049F JSR
LDY #0 ; Y=0: transfer address low byte
BFCD 04C9 LDX #&53 ; X=&53: transfer address high byte (&0053)
BFCF 04CB JMP tube_addr_data_dispatch ; Claim Tube address for transfer
; Initialise relocation address for ROM transfer
; Sets the Tube transfer source page to &8000
; (tube_xfer_page = &80) and the page counter to &80.
; Checks ROM type bit 5 for a relocation address in the
; ROM header. If set, scans past the null-terminated
; copyright string and extracts the 4-byte relocation
; address into tube_transfer_addr (&53), tube_xfer_page
; (&54), tube_xfer_addr_2 (&55), and tube_xfer_addr_3
; (&56). If clear, uses the default &8000 start address.
; Called twice during tube_begin: once for initial setup
; and once after each page transfer completes.
BFD2 04CE .tube_init_reloc←2← 0498 JSR← 04C2 JSR
LDA #&80 ; Init: start sending from &8000
BFD4 04D0 STA tube_xfer_page ; Store &80 as source page high byte
BFD6 04D2 STA zp_ptr_hi ; Store &80 as page counter initial value
BFD8 04D4 LDA #&20 ; A=&20: bit 5 mask for ROM type check
BFDA 04D6 AND rom_type ; ROM type bit 5: reloc address in header?
BFDD 04D9 TAY ; Y = 0 or &20 (reloc flag)
BFDE 04DA STY tube_transfer_addr ; Store as transfer address selector
BFE0 04DC BEQ store_xfer_end_addr ; No reloc addr: use defaults
BFE2 04DE LDX copyright_offset ; Skip past copyright string to find reloc addr
BFE5 04E1 .scan_copyright_end←1← 04E5 BNE
INX ; Skip past null-terminated copyright string
BFE6 04E2 LDA rom_header,x ; Load next byte from ROM header
BFE9 04E5 BNE scan_copyright_end ; Loop until null terminator found
BFEB 04E7 LDA rom_header_byte1,x ; Read 4-byte reloc address from ROM header
BFEE 04EA STA tube_transfer_addr ; Store reloc addr byte 1 as transfer addr
BFF0 04EC LDA rom_header_byte2,x ; Load reloc addr byte 2
BFF3 04EF STA tube_xfer_page ; Store as source page start
BFF5 04F1 LDY service_entry,x ; Load reloc addr byte 3
BFF8 04F4 LDA service_handler_lo,x ; Load reloc addr byte 4 (highest)
BFFB 04F7 .store_xfer_end_addr←1← 04DC BEQ
STA tube_xfer_addr_3 ; Store high byte of end address
BFFD 04F9 STY tube_xfer_addr_2 ; Store byte 3 of end address
BFFF 04FB RTS ; Return with pointers initialised