Acorn ANFS 4.08.53

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← 04E6 LDA
.language_entry←1← 04E6 LDA
.pydis_start←1← 04E6 LDA
EQUB &00, &42, &43
8003 .service_entry←1← 04F5 LDY
JMP service_handler ; JMP service_handler
8006 .rom_type←1← 04DA AND
EQUB &82 ; ROM type: service + language
8007 .copyright_offset←1← 04E2 LDX
EQUB copyright - rom_header
8008 .binary_version
EQUB &04
8009 .title
EQUS "Acorn ANFS"
8013 .version
EQUB &00
8014 .copyright
EQUB &00 ; Null terminator before copyright
8015 .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
8023 .svc5_irq_check
LDA #4 ; A=4: SR bit mask for IFR test
8025 BIT system_via_ifr ; Test IFR bit 2: SR complete
8028 BNE save_registers ; SR set: shift register complete
802A LDA #5 ; A=5: not our interrupt, pass on
802C RTS ; Return service code 5 to MOS
802D .save_registers←1← 8028 BNE
TXA ; Save X on stack
802E PHA ; Push saved X
802F TYA ; Save Y on stack
8030 PHA ; Push saved Y
8031 LDA system_via_acr ; Read ACR for shift register restore
8034 AND #&e3 ; Clear SR mode bits (2-4)
8036 ORA ws_0d64 ; Restore saved SR mode from ws_0d64
8039 STA system_via_acr ; Write restored ACR to system VIA
803C LDA system_via_sr ; Read SR to clear shift register IRQ
803F LDA #4 ; A=4: SR bit mask
8041 STA system_via_ifr ; Clear SR interrupt flag in IFR
8044 STA system_via_ier ; Disable SR interrupt in IER
8047 LDY tx_op_type ; Load TX operation type for dispatch
804A TYA ; Copy to A for sign test
804B BMI set_jsr_protection ; Bit 7 set: dispatch via table
804D LDY #&fe ; Y=&FE: Econet receive event
804F JMP tx_done_fire_event ; Fire event (enable: *FX52,150)

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)
8052 .set_jsr_protection←1← 804B BMI
CPY #&86 ; Y >= &86: above dispatch range
8054 BCS dispatch_svc5 ; Out of range: skip protection
8056 LDA ws_0d68 ; Save current JSR protection mask
8059 STA ws_0d69 ; Backup to saved_jsr_mask
805C ORA #&1c ; Set protection bits 2-4
805E STA ws_0d68 ; Apply protection during dispatch
8061 .dispatch_svc5←1← 8054 BCS
LDA #&85 ; Push return addr high (&85)
8063 PHA ; High byte on stack for RTS
8064 LDA set_rx_buf_len_hi,y ; Load dispatch target low byte
8067 PHA ; Low byte on stack for RTS
8068 .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.

8069 .adlc_init←1← 8F40 JSR
BIT station_id_disable_net_nmis ; INTOFF: read station ID, disable NMIs
806C JSR adlc_full_reset ; Full ADLC hardware reset
806F LDA #&ea ; OSBYTE &EA: check Tube co-processor
8071 LDX #0 ; X=0 for OSBYTE
8073 STX ws_0d62 ; Clear Econet init flag before setup
8076 JSR osbyte_x0 ; Check Tube presence via OSBYTE &EA
8079 STX tube_present ; Store Tube presence flag from OSBYTE &EA
807C LDA #&8f ; OSBYTE &8F: issue service request
807E LDX #&0c ; X=&0C: NMI claim service
8080 JSR osbyte_yff ; Issue NMI claim service request
8083 LDY #5 ; Y=5: NMI claim service number
8085 .econet_restore
CPY #5 ; Check if NMI service was claimed (Y changed)
8087 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).

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

80B3 .nmi_rx_scout←1← 89A8 JMP
LDA #1 ; A=&01: mask for SR2 bit0 (AP = Address Present)
80B5 BIT econet_control23_or_status2 ; BIT SR2: Z = A AND SR2 -- tests if AP is set
80B8 BEQ scout_error ; AP not set, no incoming data -- check for errors
80BA LDA econet_data_continue_frame ; Read first RX byte (destination station address)
80BD CMP station_id_disable_net_nmis ; Compare to our station ID (&FE18 read = INTOFF, disables NMIs)
80C0 BEQ accept_frame ; Match -- accept frame
80C2 CMP #&ff ; Check for broadcast address (&FF)
80C4 BNE scout_reject ; Neither our address nor broadcast -- reject frame
80C6 LDA #&40 ; Flag &40 = broadcast frame
80C8 STA rx_src_net ; Store broadcast flag in rx_src_net
80CB .accept_frame←1← 80C0 BEQ
LDA #&d0 ; Install nmi_rx_scout_net NMI handler
80CD 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.

80D0 .nmi_rx_scout_net
BIT econet_control23_or_status2 ; BIT SR2: test for RDA (bit7 = data available)
80D3 BPL scout_error ; No RDA -- check errors
80D5 LDA econet_data_continue_frame ; Read destination network byte
80D8 BEQ accept_local_net ; Network = 0 -- local network, accept
80DA EOR #&ff ; EOR &FF: test if network = &FF (broadcast)
80DC BEQ accept_scout_net ; Broadcast network -- accept
80DE .scout_reject←1← 80C4 BNE
LDA #&a2 ; Reject: wrong network. CR1=&A2: RIE|RX_DISCONTINUE
80E0 STA econet_control1_or_status1 ; Write CR1 to discontinue RX
80E3 JMP set_nmi_rx_scout ; Return to idle scout listening
80E6 .accept_local_net←1← 80D8 BEQ
STA rx_src_net ; Network = 0 (local): clear tx_flags
80E9 .accept_scout_net←1← 80DC BEQ
STA port_buf_len ; Store Y offset for scout data buffer
80EB LDA #2 ; Install scout data handler (&8102)
80ED LDY #&81 ; High byte of scout data handler
80EF 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.

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

812C .scout_complete←2← 8115 BNE← 8120 BEQ
LDA #0 ; Save Y for next iteration
812E STA econet_control1_or_status1 ; Write CR1
8131 LDA #&84 ; CR2=&84: disable PSE, enable RDA_SUPPRESS_FV
8133 STA econet_control23_or_status2 ; Write CR2
8136 LDA #2 ; A=&02: FV mask for SR2 bit1
8138 BIT econet_control23_or_status2 ; BIT SR2: test FV (Z) and RDA (N)
813B BEQ scout_error ; No FV -- not a valid frame end, error
813D BPL scout_error ; FV set but no RDA -- missing last byte, error
813F LDA econet_data_continue_frame ; Read last byte from RX FIFO
8142 STA scout_buf,y ; Store last byte at &0D3D+Y
8145 LDA #&44 ; CR1=&44: RX_RESET | TIE (switch to TX for ACK)
8147 STA econet_control1_or_status1 ; Write CR1: switch to TX mode
814A SEC ; Set bit7 of need_release_tube flag
814B ROR need_release_tube ; Rotate C=1 into bit7: mark Tube release needed
814D LDA scout_port ; Check port byte: 0 = immediate op, non-zero = data transfer
8150 BNE scout_match_port ; Port non-zero -- look for matching receive block
8152 .scout_no_match
JMP immediate_op ; Port = 0 -- immediate operation handler
8155 .scout_match_port←1← 8150 BNE
BIT rx_src_net ; Check if broadcast (bit6 of tx_flags)
8158 BVC scan_port_list ; Not broadcast -- skip CR2 setup
815A LDA #7 ; CR2=&07: broadcast prep
815C STA econet_control23_or_status2 ; Write CR2: broadcast frame prep
815F .scan_port_list←1← 8158 BVC
BIT econet_flags ; Check if RX port list active (bit7)
8162 BPL try_nfs_port_list ; No active ports -- try NFS workspace
8164 LDA #&c0 ; Start scanning port list at page &C0
8166 LDY #0 ; Y=0: start offset within each port slot
8168 .scan_nfs_port_list←1← 81AD BNE
STA port_ws_offset ; Store page to workspace pointer low
816A STY rx_buf_offset ; Store page high byte for slot scanning
816C .check_port_slot←1← 819F BCC
LDY #0 ; Y=0: read control byte from start of slot
816E .scout_ctrl_check
LDA (port_ws_offset),y ; Read port control byte from slot
8170 BEQ discard_no_match ; Zero = end of port list, no match
8172 CMP #&7f ; &7F = any-port wildcard
8174 BNE next_port_slot ; Not wildcard -- check specific port match
8176 INY ; Y=1: advance to port byte in slot
8177 LDA (port_ws_offset),y ; Read port number from slot (offset 1)
8179 BEQ check_station_filter ; Zero port in slot = match any port
817B CMP scout_port ; Check if port matches this slot
817E BNE next_port_slot ; Port mismatch -- try next slot
8180 .check_station_filter←1← 8179 BEQ
INY ; Y=2: advance to station byte
8181 LDA (port_ws_offset),y ; Read station filter from slot (offset 2)
8183 BEQ port_match_found ; Zero station = match any station, accept
8185 CMP scout_buf ; Check if source station matches
8188 BNE next_port_slot ; Station mismatch -- try next slot
818A .scout_port_match
INY ; Y=3: advance to network byte
818B LDA (port_ws_offset),y ; Read network filter from slot (offset 3)
818D BEQ port_match_found ; Zero = accept any network
818F CMP scout_src_net ; Check if source network matches
8192 BEQ port_match_found ; Network matches or zero = accept
8194 .next_port_slot←3← 8174 BNE← 817E BNE← 8188 BNE
LDA rx_buf_offset ; Check if NFS workspace search pending
8196 BEQ try_nfs_port_list ; No NFS workspace -- try fallback path
8198 LDA port_ws_offset ; Load current slot base address
819A CLC ; CLC for 12-byte slot advance
819B ADC #&0c ; Advance to next 12-byte port slot
819D STA port_ws_offset ; Update workspace pointer to next slot
819F BCC check_port_slot ; Always branches (page &C0 won't overflow)
81A1 .discard_no_match←2← 8170 BEQ← 81A7 BVC
JMP nmi_error_dispatch ; No match found -- discard frame
81A4 .try_nfs_port_list←2← 8162 BPL← 8196 BEQ
BIT econet_flags ; Try NFS workspace if paged list exhausted
81A7 BVC discard_no_match ; No NFS workspace RX (bit6 clear) -- discard
81A9 LDA #0 ; NFS workspace starts at offset 0 in page
81AB LDY nfs_workspace_hi ; NFS workspace high byte for port list
81AD BNE scan_nfs_port_list ; Scan NFS workspace port list
81AF .port_match_found←4← 8183 BEQ← 818D BEQ← 8192 BEQ← 84AC JMP
LDA #3 ; Match found: set scout_status = 3
81B1 STA rx_port ; Record match for completion handler
81B4 JSR tx_calc_transfer ; Calculate transfer parameters
81B7 BCC nmi_error_dispatch ; C=0: no Tube claimed -- discard
81B9 BIT rx_src_net ; Check broadcast flag for ACK path
81BC BVC send_data_rx_ack ; Not broadcast -- normal ACK path
81BE JMP copy_scout_to_buffer ; Broadcast: different completion path
81C1 .send_data_rx_ack←2← 81BC BVC← 84A1 JMP
LDA #&44 ; CR1=&44: RX_RESET | TIE
81C3 STA econet_control1_or_status1 ; Write CR1: TX mode for ACK
81C6 LDA #&a7 ; CR2=&A7: RTS | CLR_TX_ST | FC_TDRA | PSE
81C8 STA econet_control23_or_status2 ; Write CR2: enable TX with PSE
81CB LDA #&d2 ; Install data_rx_setup at &81D2
81CD LDY #&81 ; High byte of data_rx_setup handler
81CF JMP ack_tx_write_dest ; Send ACK with data_rx_setup as next NMI
81D2 .data_rx_setup
LDA #&82 ; CR1=&82: TX_RESET | RIE (switch to RX for data frame)
81D4 STA econet_control1_or_status1 ; Write CR1: switch to RX for data frame
81D7 LDA #&dc ; Install nmi_data_rx at &81DC
81D9 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: &81DC (AP+addr check) -> &81F0 (net=0 check) -> &8206 (skip ctrl+port) -> &8239 (bulk data read) -> &826D (completion)

81DC .nmi_data_rx
LDA #1 ; A=1: AP mask for SR2 bit test
81DE BIT econet_control23_or_status2 ; BIT SR2: test AP bit
81E1 BEQ nmi_error_dispatch ; No AP: wrong frame or error
81E3 LDA econet_data_continue_frame ; Read first byte (dest station)
81E6 CMP station_id_disable_net_nmis ; Compare to our station ID (INTOFF)
81E9 BNE nmi_error_dispatch ; Not for us: error path
81EB LDA #&f0 ; Install net check handler at &81F0
81ED JMP install_nmi_handler ; Set NMI vector via RAM shim
81F0 .nmi_data_rx_net
BIT econet_control23_or_status2 ; Validate source network = 0
81F3 BPL nmi_error_dispatch ; SR2 bit7 clear: no data ready -- error
81F5 LDA econet_data_continue_frame ; Read dest network byte
81F8 BNE nmi_error_dispatch ; Network != 0: wrong network -- error
81FA LDA #6 ; Install skip handler at &8206
81FC LDY #&82 ; High byte of &8206 handler
81FE BIT econet_control1_or_status1 ; SR1 bit7: IRQ, data already waiting
8201 BMI nmi_data_rx_skip ; Data ready: skip directly, no RTI
8203 JMP set_nmi_vector ; Install handler and return via RTI
8206 .nmi_data_rx_skip←1← 8201 BMI
BIT econet_control23_or_status2 ; Skip control and port bytes (already known from scout)
8209 BPL nmi_error_dispatch ; SR2 bit7 clear: error
820B LDA econet_data_continue_frame ; Discard control byte
820E 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.

8211 .install_data_rx_handler←1← 88BC JMP
LDA #2 ; A=2: Tube transfer flag mask
8213 BIT rx_src_net ; Check if Tube transfer active
8216 BNE install_tube_rx ; Tube active: use Tube RX path
8218 LDA #&39 ; Install bulk read at &8239
821A LDY #&82 ; High byte of &8239 handler
821C BIT econet_control1_or_status1 ; SR1 bit7: more data already waiting?
821F BMI nmi_data_rx_bulk ; Yes: enter bulk read directly
8221 JMP set_nmi_vector ; No: install handler and RTI
8224 .install_tube_rx←1← 8216 BNE
LDA #&96 ; Tube: install Tube RX at &8296
8226 LDY #&82 ; High byte of &8296 handler
8228 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).

822B .nmi_error_dispatch←12← 81A1 JMP← 81B7 BCC← 81E1 BEQ← 81E9 BNE← 81F3 BPL← 81F8 BNE← 8209 BPL← 824C BEQ← 827E BEQ← 8284 BEQ← 8341 JMP← 847B JMP
LDA rx_src_net ; Check tx_flags for error path
822E BPL rx_error_reset ; Bit7 clear: RX error path
8230 JMP tx_result_fail ; Bit7 set: TX result = not listening
8233 .rx_error_reset←1← 822E BPL
JSR adlc_full_reset ; Full ADLC reset on RX error
8236 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 &826D. SR2 = 0 -> RTI, wait for next NMI to continue.

8239 .nmi_data_rx_bulk←1← 821F BMI
LDY port_buf_len ; Y = buffer offset, resume from last position
823B LDA econet_control23_or_status2 ; Read SR2 for next pair
823E .data_rx_loop←1← 8268 BNE
BPL data_rx_complete ; SR2 bit7 clear: frame complete (FV)
8240 LDA econet_data_continue_frame ; Read first byte of pair from RX FIFO
8243 STA (open_port_buf),y ; Store byte to buffer
8245 INY ; Advance buffer offset
8246 BNE read_sr2_between_pairs ; Y != 0: no page boundary crossing
8248 INC open_port_buf_hi ; Crossed page: increment buffer high byte
824A DEC port_buf_len_hi ; Decrement remaining page count
824C BEQ nmi_error_dispatch ; No pages left: handle as complete
824E .read_sr2_between_pairs←1← 8246 BNE
LDA econet_control23_or_status2 ; Read SR2 between byte pairs
8251 BMI read_second_rx_byte ; SR2 bit7 set: more data available
8253 BNE data_rx_complete ; SR2 non-zero, bit7 clear: frame done
8255 .read_second_rx_byte←1← 8251 BMI
LDA econet_data_continue_frame ; Read second byte of pair from RX FIFO
8258 STA (open_port_buf),y ; Store byte to buffer
825A INY ; Advance buffer offset
825B STY port_buf_len ; Save updated buffer position
825D BNE check_sr2_loop_again ; Y != 0: no page boundary crossing
825F INC open_port_buf_hi ; Crossed page: increment buffer high byte
8261 DEC port_buf_len_hi ; Decrement remaining page count
8263 BEQ data_rx_complete ; No pages left: frame complete
8265 .check_sr2_loop_again←1← 825D BNE
LDA econet_control23_or_status2 ; Read SR2 for next iteration
8268 BNE data_rx_loop ; SR2 non-zero: more data, loop back
826A 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 (&812C): 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.

826D .data_rx_complete←3← 823E BPL← 8253 BNE← 8263 BEQ
LDA #&84 ; CR1=&00: disable all interrupts
826F STA econet_control23_or_status2 ; Write CR2: disable PSE for bit testing
8272 LDA #0 ; CR2=&84: disable PSE for individual bit testing
8274 STA econet_control1_or_status1 ; Write CR1: disable all interrupts
8277 STY port_buf_len ; Save Y (byte count from data RX loop)
8279 LDA #2 ; A=&02: FV mask
827B BIT econet_control23_or_status2 ; BIT SR2: test FV (Z) and RDA (N)
827E BEQ nmi_error_dispatch ; No FV -- error
8280 BPL send_ack ; FV set, no RDA -- proceed to ACK
8282 LDA port_buf_len_hi ; Check if buffer space remains
8284 .read_last_rx_byte←3← 82A1 BEQ← 82C8 BEQ← 82D4 BEQ
BEQ nmi_error_dispatch ; No buffer space: error/discard frame
8286 LDA econet_data_continue_frame ; FV+RDA: read and store last data byte
8289 LDY port_buf_len ; Y = current buffer write offset
828B STA (open_port_buf),y ; Store last byte in port receive buffer
828D INC port_buf_len ; Advance buffer write offset
828F BNE send_ack ; No page wrap: proceed to send ACK
8291 INC open_port_buf_hi ; Page boundary: advance buffer page
8293 .send_ack←2← 8280 BPL← 828F BNE
JMP ack_tx ; Send ACK frame to complete handshake
8296 .nmi_data_rx_tube
LDA econet_control23_or_status2 ; Read SR2 for Tube data receive path
8299 .rx_tube_data←1← 82B4 BNE
BPL data_rx_tube_complete ; RDA clear: no more data, frame complete
829B LDA econet_data_continue_frame ; Read data byte from ADLC RX FIFO
829E JSR advance_buffer_ptr ; Check buffer limits and transfer size
82A1 BEQ read_last_rx_byte ; Zero: buffer full, handle as error
82A3 STA tube_data_register_3 ; Send byte to Tube data register 3
82A6 LDA econet_data_continue_frame ; Read second data byte (paired transfer)
82A9 STA tube_data_register_3 ; Send second byte to Tube
82AC JSR advance_buffer_ptr ; Check limits after byte pair
82AF BEQ data_rx_tube_complete ; Zero: Tube transfer complete
82B1 LDA econet_control23_or_status2 ; Re-read SR2 for next byte pair
82B4 BNE rx_tube_data ; More data available: continue loop
82B6 .data_rx_tube_error
JMP nmi_rti ; Unexpected end: return from NMI
82B9 .data_rx_tube_complete←2← 8299 BPL← 82AF BEQ
LDA #0 ; CR1=&00: disable all interrupts
82BB STA econet_control1_or_status1 ; Write CR1 for individual bit testing
82BE LDA #&84 ; CR2=&84: disable PSE
82C0 STA econet_control23_or_status2 ; Write CR2: same pattern as main path
82C3 LDA #2 ; A=&02: FV mask for Tube completion
82C5 BIT econet_control23_or_status2 ; BIT SR2: test FV (Z) and RDA (N)
82C8 BEQ read_last_rx_byte ; No FV: incomplete frame, error
82CA BPL ack_tx ; FV set, no RDA: proceed to ACK
82CC LDA port_buf_len ; Check if any buffer was allocated
82CE ORA port_buf_len_hi ; OR all 4 buffer pointer bytes together
82D0 ORA open_port_buf ; Check buffer low byte
82D2 ORA open_port_buf_hi ; Check buffer high byte
82D4 BEQ read_last_rx_byte ; All zero (null buffer): error
82D6 LDA econet_data_continue_frame ; Read extra trailing byte from FIFO
82D9 STA rx_extra_byte ; Save extra byte at &0D5D for later use
82DC LDA #&20 ; Bit5 = extra data byte available flag
82DE ORA rx_src_net ; Set extra byte flag in tx_flags
82E1 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.

82E4 .ack_tx←2← 8293 JMP← 82CA BPL
LDA rx_src_net ; Load TX flags to check ACK type
82E7 BPL ack_tx_configure ; Bit7 clear: normal scout ACK
82E9 JSR advance_rx_buffer_ptr ; Final ACK: call completion handler
82EC JMP tx_result_ok ; Jump to TX success result
82EF .ack_tx_configure←1← 82E7 BPL
LDA #&44 ; CR1=&44: RX_RESET | TIE (switch to TX mode)
82F1 STA econet_control1_or_status1 ; Write CR1: switch to TX mode
82F4 LDA #&a7 ; CR2=&A7: RTS|CLR_TX_ST|FC_TDRA|2_1_BYTE|PSE
82F6 STA econet_control23_or_status2 ; Write CR2: enable TX with status clear
82F9 LDA #&8b ; Install saved next handler (&838B for scout ACK)
82FB LDY #&83 ; High byte of post-ACK handler
82FD .ack_tx_write_dest←2← 81CF JMP← 84E9 JMP
STA saved_nmi_lo ; Store next handler low byte
8300 STY saved_nmi_hi ; Store next handler high byte
8303 LDA scout_buf ; Load dest station from RX scout buffer
8306 BIT econet_control1_or_status1 ; BIT SR1: test TDRA (V=bit6)
8309 BVC dispatch_nmi_error ; TDRA not ready -- error
830B STA econet_data_continue_frame ; Write dest station to TX FIFO
830E LDA scout_src_net ; Write dest network to TX FIFO
8311 STA econet_data_continue_frame ; Write dest net byte to FIFO
8314 LDA #&1b ; Install handler at &831B (write src addr)
8316 LDY #&83 ; High byte of nmi_ack_tx_src
8318 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.

831B .nmi_ack_tx_src
LDA station_id_disable_net_nmis ; Load our station ID (also INTOFF)
831E BIT econet_control1_or_status1 ; BIT SR1: test TDRA
8321 BVC dispatch_nmi_error ; TDRA not ready -- error
8323 STA econet_data_continue_frame ; Write our station to TX FIFO
8326 LDA #0 ; Write network=0 to TX FIFO
8328 STA econet_data_continue_frame ; Write network=0 (local) to TX FIFO
832B LDA rx_src_net ; Check tx_flags for data phase
832E BMI start_data_tx ; bit7 set: start data TX phase
8330 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.

8332 .post_ack_scout
STA econet_control23_or_status2 ; Write CR2 to clear status after ACK TX
8335 LDA saved_nmi_lo ; Install saved handler from &0D4B/&0D4C
8338 LDY saved_nmi_hi ; Load saved next handler high byte
833B JMP set_nmi_vector ; Install next NMI handler
833E .start_data_tx←1← 832E BMI
JMP data_tx_begin ; Jump to start data TX phase
8341 .dispatch_nmi_error←2← 8309 BVC← 8321 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.

8344 .advance_rx_buffer_ptr←2← 82E9 JSR← 839A JSR
LDA #2 ; A=2: test bit1 of tx_flags
8346 BIT rx_src_net ; BIT tx_flags: check data transfer bit
8349 BEQ return_rx_complete ; Bit1 clear: no transfer -- return
834B CLC ; CLC: init carry for 4-byte add
834C PHP ; Save carry on stack for loop
834D LDY #8 ; Y=8: RXCB high pointer offset
834F .add_rxcb_ptr←1← 835B BCC
LDA (port_ws_offset),y ; Load RXCB[Y] (buffer pointer byte)
8351 PLP ; Restore carry from stack
8352 ADC net_tx_ptr,y ; Add transfer count byte
8355 STA (port_ws_offset),y ; Store updated pointer back to RXCB
8357 INY ; Next byte
8358 PHP ; Save carry for next iteration
8359 CPY #&0c ; Done 4 bytes? (Y reaches &0C)
835B BCC add_rxcb_ptr ; No: continue adding
835D PLP ; Discard final carry
835E LDA #&20 ; A=&20: test bit5 of tx_flags
8360 BIT rx_src_net ; BIT tx_flags: check Tube bit
8363 BEQ skip_tube_update ; No Tube: skip Tube update
8365 TXA ; Save X on stack
8366 PHA ; Push X
8367 LDA #8 ; A=8: offset for Tube address
8369 CLC ; CLC for address calculation
836A ADC port_ws_offset ; Add workspace base offset
836C TAX ; X = address low for Tube claim
836D LDY rx_buf_offset ; Y = address high for Tube claim
836F LDA #1 ; A=1: Tube claim type (read)
8371 JSR tube_addr_data_dispatch ; Claim Tube address for transfer
8374 LDA rx_extra_byte ; Load extra RX data byte
8377 STA tube_data_register_3 ; Send to Tube via R3
837A SEC ; SEC: init carry for increment
837B LDY #8 ; Y=8: start at high pointer
837D .inc_rxcb_ptr←1← 8384 BCS
LDA #0 ; A=0: add carry only (increment)
837F ADC (port_ws_offset),y ; Add carry to pointer byte
8381 STA (port_ws_offset),y ; Store back to RXCB
8383 INY ; Next byte
8384 BCS inc_rxcb_ptr ; Keep going while carry propagates
8386 PLA ; Restore X from stack
8387 TAX ; Transfer to X register
8388 .skip_tube_update←1← 8363 BEQ
LDA #&ff ; A=&FF: return value (transfer done)
838A .return_rx_complete←1← 8349 BEQ
RTS ; Return
838B LDA scout_port ; Load received port byte
838E BNE rx_complete_update_rxcb ; Port != 0: data transfer frame
8390 LDY scout_ctrl ; Port=0: load control byte
8393 CPY #&82 ; Ctrl = &82 (POKE)?
8395 BEQ rx_complete_update_rxcb ; Yes: POKE also needs data transfer
8397 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.

839A .rx_complete_update_rxcb←3← 838E BNE← 8395 BEQ← 842A JMP
JSR advance_rx_buffer_ptr ; Update buffer pointer and check for Tube
839D BNE skip_buf_ptr_update ; Transfer not done: skip buffer update
839F .add_buf_to_base
LDA port_buf_len ; Load buffer bytes remaining
83A1 CLC ; CLC for address add
83A2 ADC open_port_buf ; Add to buffer base address
83A4 BCC store_buf_ptr_lo ; No carry: skip high byte increment
83A6 .inc_rxcb_buf_hi
INC open_port_buf_hi ; Carry: increment buffer high byte
83A8 .store_buf_ptr_lo←1← 83A4 BCC
LDY #8 ; Y=8: store updated buffer position
83AA .store_rxcb_buf_ptr
STA (port_ws_offset),y ; Store updated low byte to RXCB
83AC INY ; Y=9: buffer high byte offset
83AD LDA open_port_buf_hi ; Load updated buffer high byte
83AF .store_rxcb_buf_hi
STA (port_ws_offset),y ; Store high byte to RXCB
83B1 .skip_buf_ptr_update←1← 839D BNE
LDA scout_port ; Check port byte again
83B4 BEQ discard_reset_rx ; Port=0: immediate op, discard+listen
83B6 LDA scout_src_net ; Load source network from scout buffer
83B9 LDY #3 ; Y=3: RXCB source network offset
83BB STA (port_ws_offset),y ; Store source network to RXCB
83BD DEY ; Y=2: source station offset Y=&02
83BE LDA scout_buf ; Load source station from scout buffer
83C1 STA (port_ws_offset),y ; Store source station to RXCB
83C3 DEY ; Y=1: port byte offset Y=&01
83C4 LDA scout_port ; Load port byte
83C7 STA (port_ws_offset),y ; Store port to RXCB
83C9 DEY ; Y=0: control/flag byte offset Y=&00
83CA LDA scout_ctrl ; Load control byte from scout
83CD ORA #&80 ; Set bit7 = reception complete flag
83CF STA (port_ws_offset),y ; Store to RXCB (marks CB as complete)
83D1 LDA fs_flags ; Load callback event flags
83D4 ROR ; Shift bit 0 into carry
83D5 BCC discard_reset_rx ; Bit 0 clear: no callback, skip to reset
83D7 SEC ; Set carry for subtraction
83D8 LDA port_ws_offset ; Load RXCB workspace pointer low byte
83DA .loop_count_rxcb_slot←1← 83DD BCS
INY ; Count slots
83DB SBC #&0c ; Subtract 12 bytes per RXCB slot
83DD BCS loop_count_rxcb_slot ; Loop until pointer exhausted
83DF DEY ; Adjust for off-by-one
83E0 CPY #3 ; Check slot index >= 3
83E2 BCC discard_reset_rx ; Slot < 3: no callback, skip to reset
83E4 JSR discard_reset_listen ; Discard scout and reset listen state
83E7 TYA ; Pass slot index as callback parameter
83E8 JMP setup_sr_tx ; Jump to TX completion with slot index
83EB .discard_reset_rx←6← 8236 JMP← 83B4 BEQ← 83D5 BCC← 83E2 BCC← 886B JMP← 88D5 JMP
JSR discard_reset_listen ; Discard scout and reset RX listen
83EE .reset_adlc_rx_listen←3← 80FF JMP← 8466 BCS← 8522 JMP
JSR adlc_rx_listen ; Reset ADLC and return to RX listen
83F1 .set_nmi_rx_scout←2← 80E3 JMP← 80FC JMP
LDA #&b3 ; A=&B3: low byte of nmi_rx_scout
83F3 LDY #&80 ; Y=&80: high byte of nmi_rx_scout
83F5 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.

83F8 .discard_reset_listen←2← 83E4 JSR← 83EB JSR
LDA #2 ; Tube flag bit 1 AND tx_flags bit 1
83FA AND tube_present ; Check if Tube transfer active
83FD BIT rx_src_net ; Test tx_flags for Tube transfer
8400 BEQ return_from_discard_reset ; No Tube transfer active -- skip release
8402 JSR release_tube ; Release Tube claim before discarding
8405 .return_from_discard_reset←1← 8400 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.

8406 .copy_scout_to_buffer←1← 81BE JMP
TXA ; Save X on stack
8407 PHA ; Push X
8408 LDX #4 ; X=4: start at scout byte offset 4
840A LDA #2 ; A=2: Tube transfer check mask
840C .copy_scout_select
BIT rx_src_net ; BIT tx_flags: check Tube bit
840F BNE copy_scout_via_tube ; Tube active: use R3 write path
8411 LDY port_buf_len ; Y = current buffer position
8413 .copy_scout_bytes←1← 8426 BNE
LDA scout_buf,x ; Load scout data byte
8416 STA (open_port_buf),y ; Store to port buffer
8418 INY ; Advance buffer pointer
8419 BNE next_scout_byte ; No page crossing
841B INC open_port_buf_hi ; Page crossing: inc buffer high byte
841D DEC port_buf_len_hi ; Decrement remaining page count
841F BEQ scout_page_overflow ; No pages left: overflow
8421 .next_scout_byte←1← 8419 BNE
INX ; Next scout data byte
8422 STY port_buf_len ; Save updated buffer position
8424 CPX #&0c ; Done all scout data? (X reaches &0C)
8426 BNE copy_scout_bytes ; No: continue copying
8428 .scout_copy_done←2← 843D BEQ← 8477 BEQ
PLA ; Restore X from stack
8429 TAX ; Transfer to X register
842A JMP rx_complete_update_rxcb ; Jump to completion handler
842D .copy_scout_via_tube←2← 840F BNE← 843B BNE
LDA scout_buf,x ; Tube path: load scout data byte
8430 STA tube_data_register_3 ; Send byte to Tube via R3
8433 JSR advance_buffer_ptr ; Increment buffer position counters
8436 BEQ check_scout_done ; Counter overflow: handle end of buffer
8438 INX ; Next scout data byte
8439 CPX #&0c ; Done all scout data?
843B BNE copy_scout_via_tube ; No: continue Tube writes
843D 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.

843F .release_tube←2← 8402 JSR← 8934 JSR
BIT need_release_tube ; Check if Tube needs releasing
8441 BMI clear_release_flag ; Bit7 set: already released
8443 LDA #&82 ; A=&82: Tube release claim type
8445 JSR tube_addr_data_dispatch ; Release Tube address claim
8448 .clear_release_flag←1← 8441 BMI
LSR need_release_tube ; Clear release flag (LSR clears bit7)
844A 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.

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

RX immediate: JSR/UserProc/OSProc setup

Sets up the port buffer to receive remote procedure data. Copies the 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).

8486 .rx_imm_exec
LDA #0 ; A=0: port buffer lo at page boundary
8488 STA open_port_buf ; Set port buffer lo
848A LDA #&82 ; Buffer length lo = &82
848C STA port_buf_len ; Set buffer length lo
848E LDA #1 ; Buffer length hi = 1
8490 STA port_buf_len_hi ; Set buffer length hi
8492 LDA net_rx_ptr_hi ; Load RX page hi for buffer
8494 STA open_port_buf_hi ; Set port buffer hi
8496 LDY #1 ; Y=1: copy 2 bytes (1 down to 0)
8498 .copy_addr_loop←1← 849F BPL
LDA scout_data,y ; Load remote address byte
849B STA exec_addr_lo,y ; Store to exec address workspace
849E DEY ; Next byte (descending)
849F BPL copy_addr_loop ; Loop until all 4 bytes copied
84A1 .jmp_send_data_rx_ack
JMP send_data_rx_ack ; Enter common data-receive path Svc 5 dispatch table low bytes

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.

84A4 .rx_imm_poke
LDA #&2e ; Port workspace offset = &3D
84A6 STA port_ws_offset ; Store workspace offset lo
84A8 LDA #&0d ; RX buffer page = &0D
84AA STA rx_buf_offset ; Store workspace offset hi
84AC 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.

84AF .rx_imm_machine_type
LDA #1 ; Buffer length hi = 1
84B1 .set_rx_buf_len_hi←1← 8064 LDA
STA port_buf_len_hi ; Set buffer length hi
84B3 LDA #&fc ; Buffer length lo = &FC
84B5 STA port_buf_len ; Set buffer length lo
84B7 LDA #&c1 ; Buffer start lo = &25
84B9 STA open_port_buf ; Set port buffer lo
84BB LDA #&88 ; Buffer hi = &7F (below screen)
84BD STA open_port_buf_hi ; Set port buffer hi
84BF 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.

84C1 .rx_imm_peek
LDA #&2e ; Port workspace offset = &3D
84C3 STA port_ws_offset ; Store workspace offset lo
84C5 LDA #&0d ; RX buffer page = &0D
84C7 STA rx_buf_offset ; Store workspace offset hi
84C9 LDA #2 ; Scout status = 2 (PEEK response)
84CB STA rx_port ; Store scout status
84CE JSR tx_calc_transfer ; Calculate transfer size for response
84D1 BCC imm_op_discard ; C=0: transfer not set up, discard
84D3 .set_tx_reply_flag←1← 84BF BNE
LDA rx_src_net ; Mark TX flags bit 7 (reply pending)
84D6 ORA #&80 ; Set reply pending flag
84D8 STA rx_src_net ; Store updated TX flags
84DB .rx_imm_halt_cont
LDA #&44 ; CR1=&44: TIE | TX_LAST_DATA
84DD STA econet_control1_or_status1 ; Write CR1: enable TX interrupts
84E0 .tx_cr2_setup
LDA #&a7 ; CR2=&A7: RTS|CLR_RX_ST|FC_TDRA|PSE
84E2 STA econet_control23_or_status2 ; Write CR2 for TX setup
84E5 .tx_nmi_setup
LDA #2 ; NMI handler lo byte (self-modifying)
84E7 LDY #&85 ; Y=&85: NMI handler high byte
84E9 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.

84EC .imm_op_build_reply←1← 8397 JMP
LDA port_buf_len ; Get buffer position for reply header
84EE CLC ; Clear carry for offset addition
84EF ADC #&80 ; Data offset = buf_len + &80 (past header)
84F1 LDY #&7f ; Y=&7F: reply data length slot
84F3 STA (net_rx_ptr),y ; Store reply data length in RX buffer
84F5 LDY #&80 ; Y=&80: source station slot
84F7 LDA scout_buf ; Load requesting station number
84FA STA (net_rx_ptr),y ; Store source station in reply header
84FC INY ; Y=&81
84FD LDA scout_src_net ; Load requesting network number
8500 STA (net_rx_ptr),y ; Store source network in reply header
8502 LDA scout_ctrl ; Load control byte from received frame
8505 .setup_sr_tx←1← 83E8 JMP
STA tx_op_type ; Save TX operation type for SR dispatch
8508 LDA #&84 ; IER bit 2: disable SR interrupt
850A STA system_via_ier ; Write IER to disable SR
850D LDA system_via_acr ; Read ACR for shift register config
8510 AND #&1c ; Isolate shift register mode bits (2-4)
8512 STA ws_0d64 ; Save original SR mode for later restore
8515 LDA system_via_acr ; Reload ACR for modification
8518 AND #&e3 ; Clear SR mode bits (keep other bits)
851A ORA #8 ; SR mode 2: shift in under φ2
851C STA system_via_acr ; Apply new shift register mode
851F BIT system_via_sr ; Read SR to clear pending interrupt
8522 .imm_op_discard←1← 84D1 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.

8525 .advance_buffer_ptr←3← 829E JSR← 82AC JSR← 8433 JSR
INC port_buf_len ; Increment buffer length low byte
8527 BNE return_from_advance_buf ; No overflow: done
8529 INC port_buf_len_hi ; Increment buffer length high byte
852B BNE return_from_advance_buf ; No overflow: done
852D INC open_port_buf ; Increment buffer pointer low byte
852F BNE return_from_advance_buf ; No overflow: done
8531 INC open_port_buf_hi ; Increment buffer pointer high byte
8533 .return_from_advance_buf←3← 8527 BNE← 852B BNE← 852F 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 &84B1
; + 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.
8534 .tx_done_dispatch_lo
EQUB <(tx_done_jsr-1) ; Y=&83: lo &38 -> tx_done_jsr (&8539)
8535 EQUB <(tx_done_econet_event-1) ; Y=&84: lo &41 -> tx_done_econet_event
8536 EQUB <(tx_done_os_proc-1) ; Y=&85: lo &4F -> tx_done_os_proc
8537 EQUB <(tx_done_halt-1) ; Y=&86: lo &5B -> tx_done_halt
8538 EQUB <(tx_done_continue-1) ; Y=&87: lo &72 -> tx_done_continue

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.

8539 .tx_done_jsr
LDA #&85 ; Hi byte of tx_done_exit-1
853B PHA ; Push hi byte on stack
853C LDA #&7a ; Push lo of (tx_done_exit-1)
853E PHA ; Push lo byte on stack
853F 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.

8542 .tx_done_econet_event
LDX exec_addr_lo ; X = remote address lo from l0d66
8545 LDA exec_addr_hi ; A = remote address hi from l0d67
8548 LDY #event_network_error ; Y = 8: Econet event number
854A .tx_done_fire_event←1← 804F JMP
JSR oseven ; Generate event Y='Network error'
854D 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.

8550 .tx_done_os_proc
LDX exec_addr_lo ; X = remote address lo
8553 LDY exec_addr_hi ; Y = remote address hi
8556 JSR dir_op_dispatch ; Call ROM entry point at &8000
8559 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.

855C .tx_done_halt
LDA #4 ; A=&04: bit 2 mask for rx_flags
855E BIT econet_flags ; Test if already halted
8561 BNE tx_done_exit ; Already halted: skip to exit
8563 ORA econet_flags ; Set bit 2 in rx_flags
8566 STA econet_flags ; Store halt flag
8569 LDA #4 ; A=4: re-load halt bit mask
856B CLI ; Enable interrupts during halt wait
856C .halt_spin_loop←1← 856F BNE
BIT econet_flags ; Test halt flag
856F BNE halt_spin_loop ; Still halted: keep spinning
8571 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.

8573 .tx_done_continue
LDA econet_flags ; Load current RX flags
8576 AND #&fb ; Clear bit 2: release halted station
8578 STA econet_flags ; Store updated flags
857B .tx_done_exit←4← 854D JMP← 8559 JMP← 8561 BNE← 8571 BEQ
PLA ; Restore Y from stack
857C TAY ; Transfer to Y register
857D PLA ; Restore X from stack
857E TAX ; Transfer to X register
857F LDA #0 ; A=0: success status
8581 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.

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

85EA .inactive_poll
STA rx_remote_addr ; Save TX index
85ED PHA ; Push timeout byte 1 on stack
85EE PHA ; Push timeout byte 2 on stack
85EF LDY #&e7 ; Y=&E7: CR2 value for TX prep (RTS|CLR_TX_ST|CLR_RX_ST|FC_TDRA|2_1_ BYTE|PSE)
85F1 .reload_inactive_mask←3← 8617 BNE← 861C BNE← 8621 BNE
LDA #4 ; A=&04: INACTIVE bit mask for SR2 test
85F3 .test_inactive_retry
PHP ; Save interrupt state
85F4 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.

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

8629 .tx_line_jammed←1← 8623 BEQ
LDA #7 ; CR2=&07: FC_TDRA | 2_1_BYTE | PSE (abort TX)
862B STA econet_control23_or_status2 ; Write CR2 to abort TX
862E PLA ; Clean 3 bytes of timeout loop state
862F PLA ; Pop saved register
8630 PLA ; Pop saved register
8631 LDA #&40 ; Error &40 = 'Line Jammed'
8633 BNE store_tx_error ; ALWAYS branch to shared error handler ALWAYS branch
8635 .tx_no_clock_error←1← 85DE BNE
LDA #&43 ; Error &43 = 'No Clock'
8637 .store_tx_error←2← 8627 BNE← 8633 BNE
LDY #0 ; Offset 0 = error byte in TX control block
8639 STA (nmi_tx_block),y ; Store error code in TX CB byte 0
863B LDA #&80 ; &80 = TX complete flag
863D STA ws_0d60 ; Signal TX operation complete
8640 PLA ; Restore X saved by caller
8641 TAX ; Move to X register
8642 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.

8643 .tx_prepare←1← 860D BNE
STY econet_control23_or_status2 ; Write CR2 = Y (&E7: RTS|CLR_TX_ST|CLR_RX_ST| FC_TDRA|2_1_BYTE|PSE)
8646 LDX #&44 ; CR1=&44: RX_RESET | TIE (TX active, TX interrupts enabled)
8648 STX econet_control1_or_status1 ; Write to ADLC CR1
864B LDX #&e0 ; Install NMI handler at &86E0 (TX data handler)
864D LDY #&86 ; High byte of NMI handler address
864F STX nmi_jmp_lo ; Write NMI vector low byte directly
8652 STY nmi_jmp_hi ; Write NMI vector high byte directly
8655 SEC ; Set need_release_tube flag (SEC/ROR = bit7)
8656 ROR need_release_tube ; Rotate carry into bit 7 of flag
8658 BIT video_ula_control ; INTON -- NMIs now fire for TDRA (&FE20 read)
865B LDA tx_port ; Load destination port number
865E BNE setup_data_xfer ; Port != 0: standard data transfer
8660 LDY tx_ctrl_byte ; Port 0: load control byte for table lookup
8663 LDA tube_tx_sr1_operand,y ; Look up tx_flags from table
8666 STA rx_src_net ; Store operation flags
8669 LDA tube_tx_inc_operand,y ; Look up tx_length from table
866C STA rx_ctrl ; Store expected transfer length
866F LDA #&86 ; Push high byte of return address (&9C)
8671 PHA ; Push high byte for PHA/PHA/RTS dispatch
8672 LDA intoff_disable_nmi_op,y ; Look up handler address low from table
8675 PHA ; Push low byte for PHA/PHA/RTS dispatch
8676 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 &8672 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.
8677 .tx_ctrl_dispatch_lo
EQUB <(tx_ctrl_peek-1) ; Ctrl &81 PEEK: tx_ctrl_peek
8678 EQUB <(tx_ctrl_poke-1) ; Ctrl &82 POKE: tx_ctrl_poke
8679 EQUB <(proc_op_status2-1) ; Ctrl &83 JSR: proc_op_status2
867A EQUB <(proc_op_status2-1) ; Ctrl &84 UserProc: proc_op_status2
867B EQUB <(proc_op_status2-1) ; Ctrl &85 OSProc: proc_op_status2
867C EQUB <(tx_ctrl_exit-1) ; Ctrl &86 HALT: tx_ctrl_exit
867D EQUB <(tx_ctrl_exit-1) ; Ctrl &87 CONTINUE: tx_ctrl_exit
867E EQUB <(tx_ctrl_machine_type-1) ; Ctrl &88 MachType: tx_ctrl_machine_type

TX ctrl: machine type query setup

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

867F .tx_ctrl_machine_type
LDA #3 ; scout_status=3 (machine type query)
8681 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.

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

8687 .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)
8689 .tx_ctrl_store_and_add←1← 8685 BNE
STA rx_port ; Store scout status
868C CLC ; Clear carry for 4-byte addition
868D PHP ; Save carry on stack
868E LDY #&0c ; Y=&0C: start at offset 12
8690 .add_bytes_loop←1← 869D BCC
LDA tx_addr_base,y ; Load workspace address byte
8693 PLP ; Restore carry from previous byte
8694 ADC (nmi_tx_block),y ; Add TXCB address byte
8696 STA tx_addr_base,y ; Store updated address byte
8699 INY ; Next byte
869A 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.

869B .tx_ctrl_proc
CPY #&10 ; Compare Y with 16-byte boundary
869D BCC add_bytes_loop ; Below boundary: continue addition
869F PLP ; Restore processor flags
86A0 BNE skip_buf_setup ; Skip buffer setup if transfer size is zero
86A2 .setup_data_xfer←1← 865E BNE
LDA tx_dst_stn ; Load dest station for broadcast check
86A5 AND tx_dst_net ; AND with dest network
86A8 CMP #&ff ; Both &FF = broadcast address?
86AA BNE setup_unicast_xfer ; Not broadcast: unicast path
86AC LDA #&0e ; Broadcast scout: 14 bytes total
86AE STA rx_ctrl ; Store broadcast scout length
86B1 LDA #&40 ; A=&40: broadcast flag
86B3 STA rx_src_net ; Set broadcast flag in tx_flags
86B6 LDY #4 ; Y=4: start of address data in TXCB
86B8 .copy_bcast_addr←1← 86C0 BCC
LDA (nmi_tx_block),y ; Copy TXCB address bytes to scout buffer
86BA STA tx_src_stn,y ; Store to TX source/data area
86BD INY ; Next byte
86BE CPY #&0c ; Done 8 bytes? (Y reaches &0C)
86C0 BCC copy_bcast_addr ; No: continue copying
86C2 BCS tx_ctrl_exit ; ALWAYS branch
86C4 .setup_unicast_xfer←1← 86AA BNE
LDA #0 ; A=0: clear flags for unicast
86C6 STA rx_src_net ; Clear tx_flags
86C9 .proc_op_status2
LDA #2 ; scout_status=2: data transfer pending
86CB .store_status_copy_ptr←1← 8681 BNE
STA rx_port ; Store scout status
86CE .skip_buf_setup←1← 86A0 BNE
LDA nmi_tx_block ; Copy TX block pointer to workspace ptr
86D0 STA port_ws_offset ; Store low byte
86D2 LDA nmi_tx_block_hi ; Copy TX block pointer high byte
86D4 STA rx_buf_offset ; Store high byte
86D6 JSR tx_calc_transfer ; Calculate transfer size from RXCB
86D9 .tx_ctrl_exit←1← 86C2 BCS
PLP ; Restore processor status from stack
86DA PLA ; Restore stacked registers (4 PLAs)
86DB PLA ; Second PLA
86DC PLA ; Third PLA
86DD PLA ; Fourth PLA
86DE TAX ; Restore X from A
86DF 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.

86E0 .nmi_tx_data
LDY rx_remote_addr ; Load TX buffer index
86E3 BIT econet_control1_or_status1 ; BIT SR1: V=bit6(TDRA), N=bit7(IRQ)
86E6 .tx_fifo_write←1← 8701 BMI
BVC tx_fifo_not_ready ; TDRA not set -- TX error
86E8 LDA tx_dst_stn,y ; Load byte from TX buffer
86EB STA econet_data_continue_frame ; Write to TX_DATA (continue frame)
86EE INY ; Next TX buffer byte
86EF LDA tx_dst_stn,y ; Load second byte from TX buffer
86F2 INY ; Advance TX index past second byte
86F3 STY rx_remote_addr ; Save updated TX buffer index
86F6 STA econet_data_continue_frame ; Write second byte to TX_DATA
86F9 CPY rx_ctrl ; Compare index to TX length
86FC BCS tx_last_data ; Frame complete -- go to TX_LAST_DATA
86FE BIT econet_control1_or_status1 ; Check if we can send another pair
8701 BMI tx_fifo_write ; IRQ set -- send 2 more bytes (tight loop)
8703 JMP nmi_rti ; RTI -- wait for next NMI
8706 .tx_error←1← 8749 BEQ
LDA #&42 ; Error &42
8708 BNE tx_store_error ; ALWAYS branch
870A .tx_fifo_not_ready←1← 86E6 BVC
LDA #&67 ; CR2=&67: clear status, return to listen
870C STA econet_control23_or_status2 ; Write CR2: clear status, idle listen
870F LDA #&41 ; Error &41 (TDRA not ready)
8711 .tx_store_error←1← 8708 BNE
LDY station_id_disable_net_nmis ; INTOFF (also loads station ID)
8714 .delay_nmi_disable←1← 8717 BNE
PHA ; PHA/PLA delay loop (256 iterations for NMI disable)
8715 PLA ; PHA/PLA delay (~7 cycles each)
8716 INY ; Increment delay counter
8717 BNE delay_nmi_disable ; Loop 256 times for NMI disable
8719 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)

871C .tx_last_data←1← 86FC BCS
LDA #&3f ; CR2=&3F: TX_LAST_DATA | CLR_RX_ST | FLAG_IDLE | FC_TDRA | 2_1_BYTE | PSE
871E STA econet_control23_or_status2 ; Write to ADLC CR2
8721 LDA #&28 ; Install NMI handler at &8728 (TX completion)
8723 LDY #&87 ; High byte of handler address
8725 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 &886E - Otherwise -> install nmi_reply_scout at &8744

8728 .nmi_tx_complete
LDA #&82 ; Jump to error handler
872A STA econet_control1_or_status1 ; Write CR1 to switch from TX to RX
872D BIT rx_src_net ; Test workspace flags
8730 BVC check_handshake_bit ; bit6 not set -- check bit0
8732 JMP tx_result_ok ; bit6 set -- TX completion
8735 .check_handshake_bit←1← 8730 BVC
LDA #1 ; A=1: mask for bit0 test
8737 BIT rx_src_net ; Test tx_flags bit0 (handshake)
873A BEQ install_reply_scout ; bit0 clear: install reply handler
873C JMP handshake_await_ack ; bit0 set -- four-way handshake data phase
873F .install_reply_scout←1← 873A BEQ
LDA #&44 ; Install RX reply handler at &8744
8741 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).

8744 .nmi_reply_scout
LDA #1 ; A=&01: AP mask for SR2
8746 BIT econet_control23_or_status2 ; BIT SR2: test AP (Address Present)
8749 BEQ tx_error ; No AP -- error
874B LDA econet_data_continue_frame ; Read first RX byte (destination station)
874E CMP station_id_disable_net_nmis ; Compare to our station ID (INTOFF side effect)
8751 BNE reject_reply ; Not our station -- error/reject
8753 LDA #&58 ; Install next handler at &8758 (reply continuation)
8755 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 (&876F) 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 &876F without an RTI, avoiding NMI re-entry overhead for short frames where all bytes arrive in quick succession.

8758 .nmi_reply_cont
BIT econet_control23_or_status2 ; Read RX byte (destination station)
875B BPL reject_reply ; No RDA -- error
875D LDA econet_data_continue_frame ; Read destination network byte
8760 BNE reject_reply ; Non-zero -- network mismatch, error
8762 LDA #&6f ; Install next handler at &876F (reply validation)
8764 BIT econet_control1_or_status1 ; BIT SR1: test IRQ (N=bit7) -- more data ready?
8767 BMI nmi_reply_validate ; IRQ set -- fall through to &876F without RTI
8769 JMP install_nmi_handler ; IRQ not set -- install handler and RTI
876C .reject_reply←7← 8751 BNE← 875B BPL← 8760 BNE← 8772 BPL← 877A BNE← 8782 BNE← 8789 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 &876F -- must see data available 2. Read source station at &8774, 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).

876F .nmi_reply_validate←1← 8767 BMI
BIT econet_control23_or_status2 ; BIT SR2: test RDA (bit7). Must be set for valid reply.
8772 BPL reject_reply ; No RDA -- error (FV masking RDA via PSE would cause this)
8774 LDA econet_data_continue_frame ; Read source station
8777 CMP tx_dst_stn ; Compare to original TX destination station (&0D20)
877A BNE reject_reply ; Mismatch -- not the expected reply, error
877C LDA econet_data_continue_frame ; Read source network
877F CMP tx_dst_net ; Compare to original TX destination network (&0D21)
8782 BNE reject_reply ; Mismatch -- error
8784 LDA #2 ; A=&02: FV mask for SR2 bit1
8786 BIT econet_control23_or_status2 ; BIT SR2: test FV -- frame must be complete
8789 BEQ reject_reply ; No FV -- incomplete frame, error
878B LDA #&a7 ; CR2=&A7: RTS|CLR_TX_ST|FC_TDRA|2_ 1_BYTE|PSE (TX in handshake)
878D STA econet_control23_or_status2 ; Write CR2: enable RTS for TX handshake
8790 LDA #&44 ; CR1=&44: RX_RESET | TIE (TX active for scout ACK)
8792 STA econet_control1_or_status1 ; Write CR1: reset RX, enable TX interrupt
8795 LDA #&6e ; Install next handler at &886E (four-way data phase) into &0D43/&0D44
8797 LDY #&88 ; High byte &88 of next handler address
8799 STA saved_nmi_lo ; Store low byte to nmi_next_lo
879C STY saved_nmi_hi ; Store high byte to nmi_next_hi
879F LDA tx_dst_stn ; Load dest station for scout ACK TX
87A2 BIT econet_control1_or_status1 ; BIT SR1: test TDRA (V=bit6)
87A5 BVC tx_check_tdra_ready ; TDRA not ready -- error
87A7 STA econet_data_continue_frame ; Write dest station to TX FIFO
87AA LDA tx_dst_net ; Write dest network to TX FIFO
87AD STA econet_data_continue_frame ; Write dest network to TX FIFO
87B0 LDA #&b7 ; Install handler at &87B7 (write src addr for scout ACK)
87B2 LDY #&87 ; High byte &87 of handler address
87B4 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 &87E4. Installs the chosen handler via set_nmi_vector. Shares the tx_check_tdra entry at &87BD with ack_tx.

87B7 .nmi_scout_ack_src
LDA station_id_disable_net_nmis ; Load our station ID (also INTOFF)
87BA BIT econet_control1_or_status1 ; BIT SR1: test TDRA
87BD .tx_check_tdra_ready←1← 87A5 BVC
BVC data_tx_check_fifo ; TDRA not ready -- error
87BF STA econet_data_continue_frame ; Write our station to TX FIFO
87C2 LDA #0 ; Write network=0 to TX FIFO
87C4 STA econet_data_continue_frame ; Write network byte to TX FIFO
87C7 .data_tx_begin←1← 833E JMP
LDA #2 ; Test bit 1 of tx_flags
87C9 BIT rx_src_net ; Check if immediate-op or data-transfer
87CC BNE install_imm_data_nmi ; Bit 1 set: immediate op, use alt handler
87CE LDA #&e4 ; Install nmi_data_tx at &87E4
87D0 LDY #&87 ; High byte of handler address
87D2 JMP set_nmi_vector ; Install and return via set_nmi_vector
87D5 .install_imm_data_nmi←1← 87CC BNE
LDA #&2d ; Install nmi_imm_data at &882D
87D7 LDY #&88 ; High byte of handler address
87D9 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.

87DC .nmi_data_tx←1← 87E6 BEQ
LDY port_buf_len_hi ; Y = buffer offset, resume from last position
87DE BEQ data_tx_last ; No pages left: send final partial page
87E0 LDY port_buf_len ; Load remaining byte count
87E2 BEQ check_tdra_status ; Zero bytes left: skip to TDRA check
87E4 LDY port_buf_len ; Load remaining byte count (alt entry)
87E6 BEQ nmi_data_tx ; Zero: loop back to top of handler
87E8 .check_tdra_status←1← 87E2 BEQ
BIT econet_control1_or_status1 ; BIT SR1: test TDRA (V=bit6)
87EB .data_tx_check_fifo←2← 87BD BVC← 880E BMI
BVC tube_tx_fifo_write ; TDRA not ready -- error
87ED LDA (open_port_buf),y ; Write data byte to TX FIFO
87EF STA econet_data_continue_frame ; Write first byte of pair to FIFO
87F2 INY ; Advance buffer offset
87F3 BNE write_second_tx_byte ; No page crossing
87F5 DEC port_buf_len_hi ; Page crossing: decrement page count
87F7 BEQ data_tx_last ; No pages left: send last data
87F9 INC open_port_buf_hi ; Increment buffer high byte
87FB .write_second_tx_byte←1← 87F3 BNE
LDA (open_port_buf),y ; Load second byte of pair
87FD STA econet_data_continue_frame ; Write second byte to FIFO
8800 INY ; Advance buffer offset
8801 STY port_buf_len ; Save updated buffer position
8803 BNE check_irq_loop ; No page crossing
8805 DEC port_buf_len_hi ; Page crossing: decrement page count
8807 BEQ data_tx_last ; No pages left: send last data
8809 INC open_port_buf_hi ; Increment buffer high byte
880B .check_irq_loop←1← 8803 BNE
BIT econet_control1_or_status1 ; BIT SR1: test IRQ (N=bit7) for tight loop
880E BMI data_tx_check_fifo ; IRQ still set: write 2 more bytes
8810 JMP nmi_rti ; No IRQ: return, wait for next NMI
8813 .data_tx_last←5← 87DE BEQ← 87F7 BEQ← 8807 BEQ← 8846 BEQ← 885C BEQ
LDA #&3f ; CR2=&3F: TX_LAST_DATA (close data frame)
8815 STA econet_control23_or_status2 ; Write CR2 to close frame
8818 LDA rx_src_net ; Check tx_flags for next action
881B BPL install_saved_handler ; Bit7 clear: error, install saved handler
881D LDA #&eb ; Install discard_reset_listen at &83EB
881F LDY #&83 ; High byte of &83EB handler
8821 JMP set_nmi_vector ; Set NMI vector and return
8824 .install_saved_handler←1← 881B BPL
LDA saved_nmi_lo ; Load saved next handler low byte
8827 LDY saved_nmi_hi ; Load saved next handler high byte
882A JMP set_nmi_vector ; Install saved handler and return
882D .nmi_data_tx_tube
BIT econet_control1_or_status1 ; Tube TX: BIT SR1 test TDRA
8830 .tube_tx_fifo_write←2← 87EB BVC← 8861 BMI
BVC tx_tdra_error ; TDRA not ready -- error
8832 LDA tube_data_register_3 ; Read byte from Tube R3
8835 STA econet_data_continue_frame ; Write to TX FIFO
8838 INC port_buf_len ; Increment 4-byte buffer counter
883A BNE write_second_tube_byte ; Low byte didn't wrap
883C INC port_buf_len_hi ; Carry into second byte
883E BNE write_second_tube_byte ; No further carry
8840 INC open_port_buf ; Carry into third byte
8842 BNE write_second_tube_byte ; No further carry
8844 INC open_port_buf_hi ; Carry into fourth byte
8846 BEQ data_tx_last ; Counter wrapped to zero: last data
8848 .write_second_tube_byte←3← 883A BNE← 883E BNE← 8842 BNE
LDA tube_data_register_3 ; Read second Tube byte from R3
884B STA econet_data_continue_frame ; Write second byte to TX FIFO
884E INC port_buf_len ; Increment 4-byte counter (second byte)
8850 BNE check_tube_irq_loop ; Low byte didn't wrap
8852 .tube_tx_inc_byte2
INC port_buf_len_hi ; Carry into second byte
8854 BNE check_tube_irq_loop ; No further carry
8856 .tube_tx_inc_byte3
INC open_port_buf ; Carry into third byte
8858 BNE check_tube_irq_loop ; No further carry
885A .tube_tx_inc_byte4
INC open_port_buf_hi ; Carry into fourth byte
885C BEQ data_tx_last ; Counter wrapped to zero: last data
885E .check_tube_irq_loop←3← 8850 BNE← 8854 BNE← 8858 BNE
BIT econet_control1_or_status1 ; BIT SR1: test IRQ for tight loop
8861 BMI tube_tx_fifo_write ; IRQ still set: write 2 more bytes
8863 JMP nmi_rti ; No IRQ: return, wait for next NMI
8866 .tx_tdra_error←1← 8830 BVC
LDA rx_src_net ; TX error: check flags for path
8869 BPL tx_result_fail ; Bit7 clear: TX result = not listening
886B 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.

886E .handshake_await_ack←1← 873C JMP
LDA #&82 ; CR1=&82: TX_RESET | RIE (switch to RX for final ACK)
8870 STA econet_control1_or_status1 ; Write to ADLC CR1
8873 LDA #&7a ; Install nmi_final_ack handler
8875 LDY #&88 ; High byte of handler address
8877 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 (&8744-&876F): &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.

887A .nmi_final_ack
LDA #1 ; A=&01: AP mask
887C BIT econet_control23_or_status2 ; BIT SR2: test AP
887F BEQ tx_result_fail ; No AP -- error
8881 LDA econet_data_continue_frame ; Read dest station
8884 CMP station_id_disable_net_nmis ; Compare to our station (INTOFF side effect)
8887 BNE tx_result_fail ; Not our station -- error
8889 LDA #&8e ; Install nmi_final_ack_net handler
888B JMP install_nmi_handler ; Install continuation handler
888E .nmi_final_ack_net
BIT econet_control23_or_status2 ; BIT SR2: test RDA
8891 BPL tx_result_fail ; No RDA -- error
8893 LDA econet_data_continue_frame ; Read dest network
8896 BNE tx_result_fail ; Non-zero -- network mismatch, error
8898 LDA #&a2 ; Install nmi_final_ack_validate handler
889A BIT econet_control1_or_status1 ; BIT SR1: test IRQ -- more data ready?
889D BMI nmi_final_ack_validate ; IRQ set -- fall through to validate
889F 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.

88A2 .nmi_final_ack_validate←1← 889D BMI
BIT econet_control23_or_status2 ; BIT SR2: test RDA
88A5 BPL tx_result_fail ; No RDA -- error
88A7 LDA econet_data_continue_frame ; Read source station
88AA CMP tx_dst_stn ; Compare to TX dest station (&0D20)
88AD BNE tx_result_fail ; Mismatch -- error
88AF LDA econet_data_continue_frame ; Read source network
88B2 CMP tx_dst_net ; Compare to TX dest network (&0D21)
88B5 BNE tx_result_fail ; Mismatch -- error
88B7 LDA rx_src_net ; Load TX flags for next action
88BA BPL check_fv_final_ack ; bit7 clear: no data phase
88BC JMP install_data_rx_handler ; Install data RX handler
88BF .check_fv_final_ack←1← 88BA BPL
LDA #2 ; A=&02: FV mask for SR2 bit1
88C1 BIT econet_control23_or_status2 ; BIT SR2: test FV -- frame must be complete
88C4 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.

88C6 .tx_result_ok←2← 82EC JMP← 8732 JMP
LDA #0 ; A=0: success result code
88C8 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.

88CA .tx_result_fail←11← 8230 JMP← 876C JMP← 8869 BPL← 887F BEQ← 8887 BNE← 8891 BPL← 8896 BNE← 88A5 BPL← 88AD BNE← 88B5 BNE← 88C4 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)
88CC .tx_store_result←2← 8719 JMP← 88C8 BEQ
LDY #0 ; Y=0: index into TX control block
88CE STA (nmi_tx_block),y ; Store result/error code at (nmi_tx_block),0
88D0 LDA #&80 ; &80: completion flag for &0D3A
88D2 STA ws_0d60 ; Signal TX complete
88D5 JMP discard_reset_rx ; Full ADLC reset and return to idle listen
; Unreferenced dead data (16 bytes)
; 16 bytes between JMP discard_reset_rx (&88D5) and
; tx_calc_transfer (&88E8). Unreachable as code (after
; an unconditional JMP) and unreferenced as data. No
; label, index, or indirect pointer targets any address
; in the &88D8-&88E7 range. Likely unused remnant from
; development.
88D8 EQUB &0E ; Dead data: &0E
88D9 EQUB &0E ; Dead data: &0E
88DA EQUB &0A ; Dead data: &0A
88DB EQUB &0A ; Dead data: &0A
88DC EQUB &0A ; Dead data: &0A
88DD EQUB &06 ; Dead data: &06
88DE EQUB &06 ; Dead data: &06
88DF EQUB &0A ; Dead data: &0A
88E0 EQUB &81 ; Dead data: &81
88E1 EQUB &00 ; Dead data: &00
88E2 EQUB &00 ; Dead data: &00
88E3 EQUB &00 ; Dead data: &00
88E4 EQUB &00 ; Dead data: &00
88E5 EQUB &01 ; Dead data: &01
88E6 EQUB &01 ; Dead data: &01
88E7 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.

88E8 .tx_calc_transfer←3← 81B4 JSR← 84CE JSR← 86D6 JSR
LDY #7 ; Y=7: offset to RXCB buffer addr byte 3
88EA LDA (port_ws_offset),y ; Read RXCB[7] (buffer addr high byte)
88EC CMP #&ff ; Compare to &FF
88EE BNE check_tx_in_progress ; Not &FF: normal buffer, skip Tube check
88F0 DEY ; Y=&06
88F1 LDA (port_ws_offset),y ; Read RXCB[6] (buffer addr byte 2)
88F3 CMP #&fe ; Check if addr byte 2 >= &FE (Tube range)
88F5 BCS fallback_calc_transfer ; Tube/IO address: use fallback path
88F7 .check_tx_in_progress←1← 88EE BNE
LDA tube_present ; Transmit in progress?
88FA BEQ fallback_calc_transfer ; No: fallback path
88FC LDA rx_src_net ; Load TX flags for transfer setup
88FF ORA #2 ; Set bit 1 (transfer complete)
8901 STA rx_src_net ; Store with bit 1 set (Tube xfer)
8904 SEC ; Init borrow for 4-byte subtract
8905 PHP ; Save carry on stack
8906 LDY #4 ; Y=4: start at RXCB offset 4
8908 .calc_transfer_size←1← 891A BCC
LDA (port_ws_offset),y ; Load RXCB[Y] (current ptr byte)
890A INY ; Y += 4: advance to RXCB[Y+4]
890B INY ; (continued)
890C INY ; (continued)
890D INY ; (continued)
890E PLP ; Restore borrow from previous byte
890F SBC (port_ws_offset),y ; Subtract RXCB[Y+4] (start ptr byte)
8911 STA net_tx_ptr,y ; Store result byte
8914 DEY ; Y -= 3: next source byte
8915 DEY ; (continued)
8916 DEY ; (continued)
8917 PHP ; Save borrow for next byte
8918 CPY #8 ; Done all 4 bytes?
891A BCC calc_transfer_size ; No: next byte pair
891C PLP ; Discard final borrow
891D TXA ; Save X
891E PHA ; Save X
891F LDA #4 ; Compute address of RXCB+4
8921 CLC ; CLC for base pointer addition
8922 ADC port_ws_offset ; Add RXCB base to get RXCB+4 addr
8924 TAX ; X = low byte of RXCB+4
8925 LDY rx_buf_offset ; Y = high byte of RXCB ptr
8927 LDA #&c2 ; Tube claim type &C2
8929 JSR tube_addr_data_dispatch ; Claim Tube transfer address
892C BCC restore_x_and_return ; No Tube: skip reclaim
892E LDA rx_port ; Tube: reclaim with scout status
8931 JSR tube_addr_data_dispatch ; Reclaim with scout status type
8934 JSR release_tube ; Release Tube claim after reclaim
8937 SEC ; C=1: Tube address claimed
8938 .restore_x_and_return←1← 892C BCC
PLA ; Restore X
8939 TAX ; Restore X from stack
893A RTS ; Return with C = transfer status
893B .fallback_calc_transfer←2← 88F5 BCS← 88FA BEQ
LDY #4 ; Y=4: RXCB current pointer offset
893D LDA (port_ws_offset),y ; Load RXCB[4] (current ptr lo)
893F LDY #8 ; Y=8: RXCB start address offset
8941 SEC ; Set carry for subtraction
8942 SBC (port_ws_offset),y ; Subtract RXCB[8] (start ptr lo)
8944 STA port_buf_len ; Store transfer size lo
8946 LDY #5 ; Y=5: current ptr hi offset
8948 LDA (port_ws_offset),y ; Load RXCB[5] (current ptr hi)
894A SBC #0 ; Propagate borrow only
894C STA open_port_buf_hi ; Temp store of adjusted hi byte
894E LDY #8 ; Y=8: start address lo offset
8950 LDA (port_ws_offset),y ; Copy RXCB[8] to open port buffer lo
8952 STA open_port_buf ; Store to scratch (side effect)
8954 LDY #9 ; Y=9: start address hi offset
8956 LDA (port_ws_offset),y ; Load RXCB[9]
8958 SEC ; Set carry for subtraction
8959 SBC open_port_buf_hi ; Subtract adjusted hi byte
895B STA port_buf_len_hi ; Store transfer size hi
895D SEC ; Return with C=1
895E .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.

895F .adlc_full_reset←3← 806C JSR← 80F9 JSR← 8233 JSR
LDA #&c1 ; CR1=&C1: TX_RESET | RX_RESET | AC (both sections in reset, address control set)
8961 STA econet_control1_or_status1 ; Write CR1 to ADLC register 0
8964 LDA #&1e ; CR4=&1E (via AC=1): 8-bit RX word length, abort extend enabled, NRZ encoding
8966 STA econet_data_terminate_frame ; Write CR4 to ADLC register 3
8969 LDA #0 ; CR3=&00 (via AC=1): no loop-back, no AEX, NRZ, no DTR
896B 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.

896E .adlc_rx_listen←2← 83EE JSR← 899A JMP
LDA #&82 ; CR1=&82: TX_RESET | RIE (TX in reset, RX interrupts enabled)
8970 STA econet_control1_or_status1 ; Write to ADLC CR1
8973 LDA #&67 ; CR2=&67: CLR_TX_ST | CLR_RX_ST | FC_TDRA | 2_1_BYTE | PSE
8975 STA econet_control23_or_status2 ; Write to ADLC CR2
8978 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 (&80B3). When the NMI handler returns to idle, falls through to save_econet_state to clear the initialised flags and re-enter RX listen mode.

8979 .wait_idle_and_reset
BIT ws_0d62 ; Check if Econet has been initialised
897C BPL reset_enter_listen ; Not initialised: skip to RX listen
897E .poll_nmi_idle←2← 8983 BNE← 898A BNE
LDA nmi_jmp_lo ; Read current NMI handler low byte
8981 CMP #&b3 ; Expected: &B3 (nmi_rx_scout low)
8983 BNE poll_nmi_idle ; Not idle: spin and wait
8985 LDA nmi_jmp_hi ; Read current NMI handler high byte
8988 EOR #&80 ; Test if high byte = &80 (page of nmi_rx_scout)
898A 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.

898C .save_econet_state
BIT station_id_disable_net_nmis ; INTOFF: disable NMIs
898F BIT station_id_disable_net_nmis ; INTOFF again (belt-and-braces)
8992 STA ws_0d60 ; TX not in progress
8995 STA ws_0d62 ; Econet not initialised
8998 LDY #5 ; Y=5: service call workspace page
899A .reset_enter_listen←1← 897C 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 (&80B3). 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 &80B3.

899D .nmi_bootstrap_entry
BIT station_id_disable_net_nmis ; INTOFF: disable NMIs while switching ROM
89A0 PHA ; Save A
89A1 TYA ; Transfer Y to A
89A2 PHA ; Save Y (via A)
89A3 LDA #0 ; ROM bank 0 (patched during init for actual bank)
89A5 STA romsel ; Select Econet ROM bank via ROMSEL
89A8 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.

89AB .rom_set_nmi_vector
STY nmi_jmp_hi ; Store handler high byte at &0D0D
89AE STA nmi_jmp_lo ; Store handler low byte at &0D0C
89B1 LDA romsel_copy ; Restore NFS ROM bank
89B3 STA romsel ; Page in via hardware latch
89B6 PLA ; Restore Y from stack
89B7 TAY ; Transfer ROM bank to Y
89B8 PLA ; Restore A from stack
89B9 BIT video_ula_control ; INTON: re-enable NMIs
89BC RTI ; Return from interrupt
; Unreferenced dead data (3 bytes)
; 3 bytes between the RTI at &89BC (end of the NMI
; shim ROM source) and svc_dispatch_lo at &89C0.
; The init copy loop (Y=1..&20) copies &899D-&89BC
; to &0D00-&0D1F; these bytes are outside that range
; and unreferenced. Likely unused development remnant.
89BD EQUB &01 ; Dead data: &01
89BE EQUB &00 ; Dead data: &00
89BF EQUB &08 ; Dead data: &08
; 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).
89C0 .svc_dispatch_lo←1← 8E3C LDA
EQUB &04 ; lo - dummy entry (outside ROM range)
89C1 EQUB <(dispatch_rts-1) ; lo - Svc 0: already claimed (no-op)
89C2 EQUB <(svc_1_abs_workspace-1) ; lo - Svc 1: absolute workspace
89C3 EQUB <(svc_2_private_workspace-1) ; lo - Svc 2: private workspace
89C4 EQUB <(svc_3_autoboot-1) ; lo - Svc 3: auto-boot
89C5 EQUB <(svc_4_star_command-1) ; lo - Svc 4: unrecognised star command
89C6 EQUB <(svc5_irq_check-1) ; lo - Svc 5: unrecognised interrupt
89C7 EQUB <(dispatch_rts-1) ; lo - Svc 6: BRK (no-op)
89C8 EQUB <(svc_7_osbyte-1) ; lo - Svc 7: unrecognised OSBYTE
89C9 EQUB <(svc_8_osword-1) ; lo - Svc 8: unrecognised OSWORD
89CA EQUB <(svc_9_help-1) ; lo - Svc 9: *HELP
89CB EQUB <(dispatch_rts-1) ; lo - Svc 10: static workspace (no-op)
89CC EQUB <(econet_restore-1) ; lo - Svc 11: NMI release (reclaim NMIs)
89CD EQUB <(wait_idle_and_reset-1) ; lo - Svc 12: NMI claim (save NMI state)
89CE EQUB <(svc_18_fs_select-1) ; lo - Svc 18: filing system selection
89CF EQUB <(lang_0_insert_remote_key-1) ; lo - Lang 0: no language / Tube
89D0 EQUB <(lang_1_remote_boot-1) ; lo - Lang 1: normal startup
89D1 EQUB <(lang_2_save_palette_vdu-1) ; lo - Lang 2: softkey byte (Electron)
89D2 EQUB <(lang_3_execute_at_0100-1) ; lo - Lang 3: softkey length (Electron)
89D3 EQUB <(lang_4_remote_validated-1) ; lo - Lang 4: remote validated
89D4 EQUB <(fscv_0_opt_entry-1) ; lo - FSCV 0: *OPT
89D5 EQUB <(fscv_1_eof-1) ; lo - FSCV 1: EOF check
89D6 EQUB <(fscv_2_star_run-1) ; lo - FSCV 2: */ (run)
89D7 EQUB <(fscv_3_star_cmd-1) ; lo - FSCV 3: unrecognised star command
89D8 EQUB <(fscv_2_star_run-1) ; lo - FSCV 4: *RUN
89D9 EQUB <(fscv_5_cat-1) ; lo - FSCV 5: *CAT
89DA EQUB <(fscv_6_shutdown-1) ; lo - FSCV 6: shutdown
89DB EQUB <(fscv_7_read_handles-1) ; lo - FSCV 7: read handle range
89DC EQUB <(fsreply_0_print_dir-1) ; lo - FS reply: print directory name
89DD EQUB <(fsreply_1_copy_handles_boot-1) ; lo - FS reply: copy handles + boot
89DE EQUB <(fsreply_2_copy_handles-1) ; lo - FS reply: copy handles
89DF EQUB <(fsreply_3_set_csd-1) ; lo - FS reply: set CSD handle
89E0 EQUB <(fscv_2_star_run-1) ; lo - FS reply: notify + execute
89E1 EQUB <(fsreply_5_set_lib-1) ; lo - FS reply: set library handle
89E2 EQUB <(net_1_read_handle-1) ; lo - *NET1: read handle from packet
89E3 EQUB <(net_2_read_handle_entry-1) ; lo - *NET2: read handle from workspace
89E4 EQUB <(net_3_close_handle-1) ; lo - *NET3: close handle
89E5 .svc_dispatch_hi←1← 8E38 LDA
EQUB &CB ; hi - dummy entry (outside ROM range)
89E6 EQUB >(dispatch_rts-1) ; hi - Svc 0: already claimed (no-op)
89E7 EQUB >(svc_1_abs_workspace-1) ; hi - Svc 1: absolute workspace
89E8 EQUB >(svc_2_private_workspace-1) ; hi - Svc 2: private workspace
89E9 EQUB >(svc_3_autoboot-1) ; hi - Svc 3: auto-boot
89EA EQUB >(svc_4_star_command-1) ; hi - Svc 4: unrecognised star command
89EB EQUB >(svc5_irq_check-1) ; hi - Svc 5: unrecognised interrupt
89EC EQUB >(dispatch_rts-1) ; hi - Svc 6: BRK (no-op)
89ED EQUB >(svc_7_osbyte-1) ; hi - Svc 7: unrecognised OSBYTE
89EE EQUB >(svc_8_osword-1) ; hi - Svc 8: unrecognised OSWORD
89EF EQUB >(svc_9_help-1) ; hi - Svc 9: *HELP
89F0 EQUB >(dispatch_rts-1) ; hi - Svc 10: static workspace (no-op)
89F1 EQUB >(econet_restore-1) ; hi - Svc 11: NMI release (reclaim NMIs)
89F2 EQUB >(wait_idle_and_reset-1) ; hi - Svc 12: NMI claim (save NMI state)
89F3 EQUB >(svc_18_fs_select-1) ; hi - Svc 18: filing system selection
89F4 EQUB >(lang_0_insert_remote_key-1) ; hi - Lang 0: no language / Tube
89F5 EQUB >(lang_1_remote_boot-1) ; hi - Lang 1: normal startup
89F6 EQUB >(lang_2_save_palette_vdu-1) ; hi - Lang 2: softkey byte (Electron)
89F7 EQUB >(lang_3_execute_at_0100-1) ; hi - Lang 3: softkey length (Electron)
89F8 EQUB >(lang_4_remote_validated-1) ; hi - Lang 4: remote validated
89F9 EQUB >(fscv_0_opt_entry-1) ; hi - FSCV 0: *OPT
89FA EQUB >(fscv_1_eof-1) ; hi - FSCV 1: EOF check
89FB EQUB >(fscv_2_star_run-1) ; hi - FSCV 2: */ (run)
89FC EQUB >(fscv_3_star_cmd-1) ; hi - FSCV 3: unrecognised star command
89FD EQUB >(fscv_2_star_run-1) ; hi - FSCV 4: *RUN
89FE EQUB >(fscv_5_cat-1) ; hi - FSCV 5: *CAT
89FF EQUB >(fscv_6_shutdown-1) ; hi - FSCV 6: shutdown
8A00 EQUB >(fscv_7_read_handles-1) ; hi - FSCV 7: read handle range
8A01 EQUB >(fsreply_0_print_dir-1) ; hi - FS reply: print directory name
8A02 EQUB >(fsreply_1_copy_handles_boot-1) ; hi - FS reply: copy handles + boot
8A03 EQUB >(fsreply_2_copy_handles-1) ; hi - FS reply: copy handles
8A04 EQUB >(fsreply_3_set_csd-1) ; hi - FS reply: set CSD handle
8A05 EQUB >(fscv_2_star_run-1) ; hi - FS reply: notify + execute
8A06 EQUB >(fsreply_5_set_lib-1) ; hi - FS reply: set library handle
8A07 EQUB >(net_1_read_handle-1) ; hi - *NET1: read handle from packet
8A08 EQUB >(net_2_read_handle_entry-1) ; hi - *NET2: read handle from workspace
8A09 EQUB >(net_3_close_handle-1) ; hi - *NET3: close handle
8A0A 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
8A0B .service_handler←1← 8003 JMP
PHA ; Save service call number
8A0C CMP #&0f ; Is it service 15 (vectors claimed)?
8A0E BNE dispatch_service ; No: skip vectors-claimed handling
8A10 TYA ; Save Y parameter
8A11 PHA ; Save Y on stack
8A12 LDA #osbyte_read_os_version ; OSBYTE 0: read OS version
8A14 LDX #1 ; X=1 to request version number
8A16 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)
8A19 CPX #1 ; OS 1.20?
8A1B BEQ restore_rom_slot ; Yes: skip workspace setup
8A1D CPX #2 ; OS 2.00 (BBC B+)?
8A1F BEQ restore_rom_slot ; Yes: skip workspace setup
8A21 TXA ; Transfer OS version to A
8A22 PHP ; Save flags (Z set if OS 1.00)
8A23 LDX romsel_copy ; Get current ROM slot number
8A25 PLP ; Restore flags
8A26 BEQ clear_workspace_byte ; OS 1.00: skip INX
8A28 INX ; Adjust index for OS 3+ workspace
8A29 .clear_workspace_byte←1← 8A26 BEQ
LDA #0 ; A=0
8A2B STA rom_type_table,x ; Clear workspace byte for this ROM
8A2E .restore_rom_slot←2← 8A1B BEQ← 8A1F BEQ
LDX romsel_copy ; Restore ROM slot to X
8A30 PLA ; Restore Y parameter
8A31 TAY ; Transfer to Y
8A32 .dispatch_service←1← 8A0E BNE
PLA ; Restore service call number
8A33 JSR tube_vdu_dispatch ; Check relocated code service dispatch
8A36 PHA ; Save service call number
8A37 CMP #1 ; Service 1 (workspace claim)?
8A39 BNE check_adlc_flag ; No: skip ADLC check
8A3B LDA econet_control1_or_status1 ; Read ADLC status register 1
8A3E AND #&ed ; Mask relevant status bits
8A40 BNE set_adlc_absent ; Non-zero: ADLC absent, set flag
8A42 LDA econet_control23_or_status2 ; Read ADLC status register 2
8A45 AND #&db ; Mask relevant status bits
8A47 BEQ check_adlc_flag ; Zero: ADLC present, skip
8A49 .set_adlc_absent←1← 8A40 BNE
ROL rom_ws_pages,x ; Shift bit 7 into carry
8A4C SEC ; Set carry to mark ADLC absent
8A4D ROR rom_ws_pages,x ; Rotate carry into bit 7 of slot flag
8A50 .check_adlc_flag←2← 8A39 BNE← 8A47 BEQ
LDA rom_ws_pages,x ; Load ROM slot flag byte
8A53 ASL ; Shift bit 7 (ADLC absent) into carry
8A54 PLA ; Restore service call number
8A55 BCC handle_vectors_claimed ; ADLC present: continue dispatch
8A57 RTS ; ADLC absent: decline service, return
8A58 .handle_vectors_claimed←1← 8A55 BCC
CMP #&0f ; Service 15 (vectors claimed)?
8A5A BNE dispatch_svc_with_state ; No: handle other services
8A5C LDX ws_0d6a ; Already initialised?
8A5F BNE init_rom_scan ; Yes: skip first-time init
8A61 INX ; X=1 (mark as initialised)
8A62 STX last_break_type ; Set ROM present flag
8A65 .init_rom_scan←1← 8A5F BNE
STA error_block ; Store service number as ROM counter
8A68 .loop_scan_net_roms←1← 8A93 BPL
LDA #&80 ; Point to ROM header copyright offset
8A6A STA osrdsc_ptr_hi ; Set high byte of OSRDSC pointer
8A6C LDA #&0c ; Offset &0C: copyright string offset
8A6E STA osrdsc_ptr ; Set low byte of OSRDSC pointer
8A70 JSR read_paged_rom ; Read next ROM title char
8A73 CMP #&4e ; First char 'N'?
8A75 BNE next_rom_slot ; No: not a NET ROM, try next
8A77 JSR read_paged_rom ; Read next ROM title char
8A7A CMP #&45 ; Second char 'E'?
8A7C BNE next_rom_slot ; No: not a NET ROM, try next
8A7E JSR read_paged_rom ; Read next ROM title char
8A81 CMP #&54 ; Third char 'T'?
8A83 BNE next_rom_slot ; No: not a NET ROM, try next
8A85 LDX error_block ; Get ROM slot being checked
8A88 LDA rom_ws_pages,x ; Load its slot flag byte
8A8B ORA #&80 ; Set bit 7 to mark as NET ROM
8A8D STA rom_ws_pages,x ; Store updated flag
8A90 .next_rom_slot←3← 8A75 BNE← 8A7C BNE← 8A83 BNE
DEC error_block ; Decrement ROM counter
8A93 BPL loop_scan_net_roms ; More ROMs to check: loop
8A95 BMI 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
8A97 .read_paged_rom←3← 8A70 JSR← 8A77 JSR← 8A7E JSR
INC osrdsc_ptr ; Advance read pointer to next byte
8A99 LDY error_block ; Y=ROM number
8A9C JMP osrdsc ; Read byte from ROM Y or screen
8A9F .dispatch_svc_with_state←1← 8A5A BNE
TAX ; Transfer service number to X
8AA0 LDA svc_state ; Save current service state
8AA2 PHA ; Push old state
8AA3 TXA ; Restore service number to A
8AA4 STA svc_state ; Store as current service state
8AA6 CMP #&0d ; Service < 13?
8AA8 BCC dispatch_svc_index ; Yes: use as dispatch index directly
8AAA SBC #5 ; Subtract 5 (map 13-17 to 8-12)
8AAC CMP #&0d ; Mapped value = 13? (original was 18)
8AAE BEQ dispatch_svc_index ; Yes: valid service 18 (FS select)
8AB0 LDA #0 ; Unknown service: set index to 0
8AB2 .dispatch_svc_index←2← 8AA8 BCC← 8AAE BEQ
TAX ; Transfer dispatch index to X
8AB3 BEQ restore_svc_state ; Index 0: unhandled service, skip
8AB5 LDA ws_page ; Save current workspace page
8AB7 PHA ; Push old page
8AB8 STY ws_page ; Set workspace page from Y parameter
8ABA TYA ; Transfer Y to A
8ABB LDY #0 ; Y=0 for dispatch offset
8ABD JSR svc_dispatch ; Dispatch to service handler via table
8AC0 PLA ; Restore old workspace page
8AC1 STA ws_page ; Store it back
8AC3 .restore_svc_state←1← 8AB3 BEQ
LDX svc_state ; Get service state (return code)
8AC5 PLA ; Restore old service state
8AC6 STA svc_state ; Store it back
8AC8 TXA ; Transfer return code to A
8AC9 .restore_romsel_rts←1← 8A95 BMI
LDX romsel_copy ; Restore ROM slot to X
8ACB RTS ; Return to MOS

*ROFF command handler

Disables remote operation by clearing the flag at offset 4 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.

8ACC .cmd_roff
LDY #4 ; Offset 4 in receive block
8ACE LDA (net_rx_ptr),y ; Load remote operation flag
8AD0 BEQ clear_svc_and_ws ; Zero: already off, skip to cleanup
8AD2 LDA #0 ; A=0
8AD4 TAX ; X=&00
8AD5 STA (net_rx_ptr),y ; Clear remote operation flag
8AD7 TAY ; Y=&00
8AD8 LDA #osbyte_read_write_econet_keyboard_disable ; OSBYTE &C9: keyboard disable
8ADA JSR osbyte ; Enable keyboard (for Econet)
8ADD LDA #&0a ; A=&0A: workspace init parameter
8ADF 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.

8AE2 .scan_remote_keys←1← 9589 JSR
STX nfs_workspace ; Save X in workspace
8AE4 LDA #&ce ; A=&CE: start of key range
8AE6 .loop_scan_key_range←1← 8AF1 BEQ
LDX nfs_workspace ; Restore X from workspace
8AE8 LDY #&7f ; Y=&7F: OSBYTE scan parameter
8AEA JSR osbyte ; OSBYTE: scan keyboard
8AED ADC #1 ; Advance to next key code
8AEF CMP #&d0 ; Reached &D0?
8AF1 BEQ loop_scan_key_range ; No: loop back (scan &CE and &CF)
8AF3 .clear_svc_and_ws←1← 8AD0 BEQ
LDA #0 ; A=0
8AF5 STA svc_state ; Clear service state
8AF7 STA nfs_workspace ; Clear workspace byte
8AF9 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
8AFA .save_text_ptr←3← 8C47 JSR← 8C72 JSR← A2A7 JSR
PHA ; Save A
8AFB LDA os_text_ptr ; Copy OS text pointer low
8AFD STA fs_crc_lo ; to fs_crc_lo
8AFF LDA os_text_ptr_hi ; Copy OS text pointer high
8B01 STA fs_crc_hi ; to fs_crc_hi
8B03 PLA ; Restore A
8B04 .return_from_save_text_ptr←2← 8B07 BNE← 8B0C 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
8B05 .svc_18_fs_select
CPY #5 ; Y=5 (Econet filing system)?
8B07 BNE return_from_save_text_ptr ; No: not ours, return unclaimed
8B09 BIT fs_flags ; Already selected?
8B0C 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.

8B0E .cmd_net_fs←1← 8CD5 JSR
JSR get_ws_page ; Get workspace page for this ROM slot
8B11 STA fs_load_addr_hi ; Store as high byte of load address
8B13 LDA #0 ; A=0
8B15 STA fs_load_addr ; Clear low byte of load address
8B17 CLC ; Clear carry for addition
8B18 LDY #&76 ; Y=&76: checksum range end
8B1A .loop_sum_rom_bytes←1← 8B1D BPL
ADC (fs_load_addr),y ; Add byte to running checksum
8B1C DEY ; Decrement index
8B1D BPL loop_sum_rom_bytes ; Loop until all bytes summed
8B1F LDY #&77 ; Y=&77: checksum storage offset
8B21 EOR (fs_load_addr),y ; Compare with stored checksum
8B23 BEQ done_rom_checksum ; Match: checksum valid
8B25 JMP error_net_checksum ; Mismatch: raise checksum error
8B28 .done_rom_checksum←1← 8B23 BEQ
JSR notify_new_fs ; Call FSCV with A=6 (new FS)
8B2B LDY #&0d ; Y=&0D: end of FS context block
8B2D .loop_copy_fs_ctx←1← 8B35 BNE
LDA (net_rx_ptr),y ; Load byte from receive block
8B2F STA fs_context_save,y ; Store into FS workspace
8B32 DEY ; Decrement index
8B33 CPY #5 ; Reached offset 5?
8B35 BNE loop_copy_fs_ctx ; No: continue copying
8B37 ROL fs_flags ; Shift bit 7 of FS flags into carry
8B3A CLC ; Clear carry
8B3B ROR fs_flags ; Clear bit 7 of FS flags
8B3E LDY #&0d ; Y=&0D: vector table size - 1
8B40 .loop_set_vectors←1← 8B47 BPL
LDA fs_vector_table,y ; Load FS vector address
8B43 STA filev,y ; Store into FILEV vector table
8B46 DEY ; Decrement index
8B47 BPL loop_set_vectors ; Loop until all vectors installed
8B49 JSR init_adlc_and_vectors ; Initialise ADLC and NMI workspace
8B4C LDY #&1b ; Y=&1B: extended vector offset
8B4E LDX #7 ; X=7: two more vectors to set up
8B50 JSR write_vector_entry ; Set up extended vectors
8B53 LDA #0 ; A=0
8B55 STA fs_eof_flags ; Clear FS state byte
8B58 STA cur_chan_attr ; Clear workspace byte
8B5B STA fs_lib_flags ; Clear workspace byte
8B5E STA svc_state ; Clear service state
8B60 LDY #&0e ; Offset &0E in receive block
8B62 STA (net_rx_ptr),y ; Clear receive block flag
8B64 STA net_context ; Clear workspace byte
8B67 JSR setup_ws_ptr ; Set up workspace pointers
8B6A JSR init_channel_table ; Initialise FS state
8B6D LDY #&77 ; Y=&77: workspace block size - 1
8B6F .loop_copy_ws_page←1← 8B75 BPL
LDA (fs_ws_ptr),y ; Load byte from source workspace
8B71 STA fcb_count_lo,y ; Store to page &10 shadow copy
8B74 DEY ; Decrement index
8B75 BPL loop_copy_ws_page ; Loop until all bytes copied
8B77 LDA #&80 ; A=&80: FS selected flag
8B79 ORA fs_flags ; Set bit 7 of FS flags
8B7C STA fs_flags ; Store updated flags
8B7F JMP issue_svc_15 ; Issue service 15 (FS initialised)
8B82 .help_print_nfs_cmds←1← 8C6F JMP
LDX #&4a ; X=&4A: NFS command table offset
8B84 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.

8B87 .help_utils
LDX #0 ; X=0: utility command table offset
8B89 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.

8B8B .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
8B8D .print_cmd_table←2← 8B84 JSR← 8B89 BEQ
BVC print_table_newline ; V clear: need to print header first
8B8F TXA ; Save X (table offset)
8B90 PHA ; Push it
8B91 TYA ; Save Y
8B92 PHA ; Push it
8B93 JSR print_version_header ; Print version string header
8B96 PLA ; Restore Y
8B97 TAY ; Transfer to Y
8B98 PLA ; Restore X
8B99 TAX ; Transfer to X
8B9A CLV ; Clear overflow flag
8B9B BVC print_cmd_table_loop ; ALWAYS branch
8B9D .print_table_newline←1← 8B8D 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
8BA0 .print_cmd_table_loop←2← 8B9B BVC← 8C62 JSR
TYA ; Save Y (command line offset)
8BA1 PHA ; Push it
8BA2 PHP ; Save processor status
8BA3 .loop_next_entry←1← 8C21 JMP
LDA cmd_table_fs,x ; Load byte from command table
8BA6 BPL print_indent ; Bit 7 clear: valid entry, continue
8BA8 JMP done_print_table ; End of table: finish up
8BAB .print_indent←1← 8BA6 BPL
JSR print_inline ; Print two-space indent
8BAE EQUS " "
8BB0 LDY #9 ; Y=9: max command name length
8BB2 LDA cmd_table_fs,x ; Load first char of command name
8BB5 .loop_print_name←1← 8BBD BPL
JSR osasci ; Write character
8BB8 INX ; Advance table pointer
8BB9 DEY ; Decrement padding counter
8BBA LDA cmd_table_fs,x ; Load next character
8BBD BPL loop_print_name ; Bit 7 clear: more chars, continue
8BBF .loop_pad_spaces←1← 8BC5 BPL
LDA #&20 ; Pad with spaces
8BC1 JSR osasci ; Write character 32
8BC4 DEY ; Decrement remaining pad count
8BC5 BPL loop_pad_spaces ; More padding needed: loop
8BC7 LDA cmd_table_fs,x ; Load syntax descriptor byte
8BCA AND #&1f ; Mask to get syntax string index
8BCC CMP #&0e ; Index &0E: shared commands?
8BCE BEQ print_shared_prefix ; Yes: handle shared commands list
8BD0 TAY ; Use index as Y
8BD1 LDA cmd_syntax_table,y ; Look up syntax string offset
8BD4 TAY ; Transfer offset to Y
8BD5 .loop_print_syntax←2← 8BE2 JMP← 8BE8 JMP
INY ; Advance to next character
8BD6 LDA cmd_syntax_strings,y ; Load syntax string character
8BD9 BEQ done_entry_newline ; Zero terminator: end of syntax
8BDB CMP #&0d ; Carriage return: line continuation
8BDD BNE print_syntax_char ; No: print the character
8BDF JSR help_wrap_if_serial ; Handle line wrap in syntax output
8BE2 JMP loop_print_syntax ; Continue with next character
8BE5 .print_syntax_char←1← 8BDD BNE
JSR osasci ; Write character
8BE8 JMP loop_print_syntax ; Continue with next character
8BEB .print_shared_prefix←1← 8BCE BEQ
TXA ; Save table pointer
8BEC PHA ; Push it
8BED JSR print_inline ; Print opening parenthesis
8BF0 EQUS "("
8BF1 LDY #0 ; Y=0: shared command counter
8BF3 LDX #&d3 ; X=&D3: shared command table start
8BF5 .loop_next_shared←1← 8C17 BNE
LDA cmd_table_fs,x ; Load byte from shared command table
8BF8 BMI done_shared_cmds ; Bit 7 set: end of shared commands
8BFA DEX ; Back up one position
8BFB .loop_print_shared←1← 8C04 JMP
INX ; Advance to next character
8BFC LDA cmd_table_fs,x ; Load command name character
8BFF BMI print_last_char ; Bit 7 set: end of this name
8C01 JSR osasci ; Write character
8C04 JMP loop_print_shared ; Print more characters of name
8C07 .print_last_char←1← 8BFF BMI
AND #&7f ; Strip bit 7 from final character
8C09 JSR osasci ; Write character
8C0C INY ; Count this shared command
8C0D CPY #4 ; Printed 4 commands?
8C0F BNE skip_syntax_bytes ; No: continue on same line
8C11 JSR help_wrap_if_serial ; Handle line wrap after 4 commands
8C14 .skip_syntax_bytes←1← 8C0F BNE
INX ; X += 3: skip syntax descriptor and address
8C15 INX ; (continued)
8C16 INX ; (continued)
8C17 BNE loop_next_shared ; Loop for more shared commands
8C19 .done_shared_cmds←1← 8BF8 BMI
PLA ; Restore original table pointer
8C1A TAX ; Transfer to X
8C1B .done_entry_newline←1← 8BD9 BEQ
JSR osnewl ; Write newline (characters 10 and 13)
8C1E INX ; X += 3: skip syntax descriptor and address
8C1F INX ; (continued)
8C20 INX ; (continued)
8C21 JMP loop_next_entry ; Loop for next command
8C24 .done_print_table←1← 8BA8 JMP
PLP ; Restore processor status
8C25 PLA ; Restore Y
8C26 TAY ; Transfer to Y
8C27 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
8C28 .help_wrap_if_serial←2← 8BDF JSR← 8C11 JSR
LDA vdu_mode ; Read output stream type
8C2B BEQ return_from_help_wrap ; Stream 0 (VDU): no wrapping
8C2D CMP #3 ; Stream 3 (printer)?
8C2F BEQ return_from_help_wrap ; Yes: no wrapping
8C31 TYA ; Save Y
8C32 PHA ; Push it
8C33 JSR osnewl ; Write newline (characters 10 and 13)
8C36 LDY #&0b ; Y=&0B: indent width - 1
8C38 LDA #&20 ; Space character
8C3A .loop_indent_spaces←1← 8C3E BPL
JSR osasci ; Write character 32
8C3D DEY ; Decrement indent counter
8C3E BPL loop_indent_spaces ; More spaces needed: loop
8C40 PLA ; Restore Y
8C41 TAY ; Transfer to Y
8C42 .return_from_help_wrap←2← 8C2B BEQ← 8C2F 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
8C43 .svc_4_star_command
LDX #0 ; X=0: start of utility command table
8C45 LDY ws_page ; Get command line offset
8C47 JSR save_text_ptr ; Save text pointer to fs_crc
8C4A JSR match_fs_cmd ; Try to match command in table
8C4D BCS svc_return_unclaimed ; No match: return to caller
8C4F 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).

8C52 .svc_9_help
JSR check_credits_easter_egg ; Check for credits Easter egg
8C55 LDY ws_page ; Get command line offset
8C57 LDA (os_text_ptr),y ; Load character at offset
8C59 CMP #&0d ; Is it CR (bare *HELP)?
8C5B BNE check_help_topic ; No: check for specific topic
8C5D JSR print_version_header ; Print version string
8C60 LDX #&c4 ; X=&C4: start of help command list
8C62 JSR print_cmd_table_loop ; Print command list from table
8C65 .svc_return_unclaimed←3← 8C4D BCS← 8C92 BEQ← 8CCD BNE
LDY ws_page ; Restore Y (command line offset)
8C67 RTS ; Return unclaimed
8C68 .check_help_topic←1← 8C5B BNE
BIT bit_test_ff ; Test for topic match (sets flags)
8C6B CMP #&2e ; Is first char '.' (abbreviation)?
8C6D BNE match_help_topic ; No: try topic-specific help
8C6F JMP help_print_nfs_cmds ; '.' found: show full command list
8C72 .match_help_topic←1← 8C6D BNE
JSR save_text_ptr ; Save text pointer to fs_crc
8C75 .loop_dispatch_help←1← 8C90 BNE
PHP ; Save flags
8C76 LDX #&c4 ; X=&C4: help command table start
8C78 JSR match_fs_cmd ; Try to match help topic in table
8C7B BCS skip_if_no_match ; No match: try next topic
8C7D PLP ; Restore flags
8C7E LDA #&8c ; Push return address high (&8C)
8C80 PHA ; Push it for RTS dispatch
8C81 LDA #&74 ; Push return address low (&74)
8C83 PHA ; Push it for RTS dispatch
8C84 LDA cmd_table_fs_hi,x ; Load dispatch address high
8C87 PHA ; Push dispatch high for RTS
8C88 LDA cmd_table_fs_lo,x ; Load dispatch address low
8C8B PHA ; Push dispatch low for RTS
8C8C RTS ; Dispatch via RTS (returns to &8C75)
8C8D .skip_if_no_match←1← 8C7B BCS
PLP ; Restore flags from before match
8C8E CMP #&0d ; End of command line?
8C90 BNE loop_dispatch_help ; No: try matching next topic
8C92 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.

8C94 .print_version_header←2← 8B93 JSR← 8C5D JSR
JSR print_inline ; Print version string via inline
8C97 .version_string_cr
EQUS ".Advanced 4.08.53."
8CAA NOP ; NOP (string terminator)
8CAB JMP print_station_id ; Print station number

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)
8CAE .get_ws_page←4← 8B0E JSR← 8CB5 JSR← 8F6E JSR← AFC2 JSR
LDY romsel_copy ; Get current ROM slot number
8CB0 LDA rom_ws_pages,y ; Load workspace page for this slot
8CB3 TAY ; Transfer to Y
8CB4 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
8CB5 .setup_ws_ptr←2← 8B67 JSR← 8ECE JSR
JSR get_ws_page ; Get workspace page for ROM slot
8CB8 STY nfs_temp ; Store page in nfs_temp
8CBA LDA #0 ; A=0
8CBC STA fs_ws_ptr ; Clear low byte of pointer
8CBE .return_from_setup_ws_ptr←1← 8CE0 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.

8CBF .svc_3_autoboot
LDA #osbyte_scan_keyboard_from_16 ; OSBYTE &7A: scan keyboard from key 16
8CC1 JSR osbyte ; Keyboard scan starting from key 16
8CC4 TXA ; X is key number if key is pressed, or &ff otherwise
8CC5 BMI select_net_fs ; No key pressed: select Net FS
8CC7 CMP #&19 ; Key &19 (N)?
8CC9 BEQ write_key_state ; Yes: write key state and boot
8CCB EOR #&55 ; EOR with &55: maps to zero if 'N'
8CCD BNE svc_return_unclaimed ; Not N key: return unclaimed
8CCF .write_key_state←1← 8CC9 BEQ
TAY ; Y=key
8CD0 LDA #osbyte_write_keys_pressed ; OSBYTE &78: write keys pressed
8CD2 JSR osbyte ; Write current keys pressed (X and Y)
8CD5 .select_net_fs←1← 8CC5 BMI
JSR cmd_net_fs ; Select NFS as current filing system
8CD8 JSR print_station_id ; Print station number
8CDB JSR osnewl ; Write newline (characters 10 and 13)
8CDE LDX ws_page ; Get workspace page
8CE0 BNE return_from_setup_ws_ptr ; Non-zero: already initialised, return
8CE2 LDA fs_lib_flags ; Load boot flags
8CE5 ORA #4 ; Set bit 2 (auto-boot in progress)
8CE7 STA fs_lib_flags ; Store updated boot flags
8CEA LDX #4 ; X=4: boot filename parameter
8CEC LDY #&8d ; Y=&8D: boot filename address high
8CEE 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.

8CF1 .notify_new_fs←1← 8B28 JSR
LDA #6 ; A=6: notify new filing system
8CF3 JSR call_fscv ; Call FSCV
8CF6 LDX #&0a ; X=&0A: service 10 parameter
8CF8 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
8CFA .call_fscv←1← 8CF3 JSR
JMP (fscv) ; Dispatch via FSCV
8CFD .issue_svc_15←1← 8B7F JMP
LDX #&0f ; X=&0F: service 15 parameter
8CFF .issue_svc_osbyte←1← 8CF8 BNE
LDA #osbyte_issue_service_request ; OSBYTE &8F: issue service request
8D01 JMP osbyte ; Issue paged ROM service call
8D04 EQUS "i .boot"
8D0B 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.

8D0C .check_credits_easter_egg←1← 8C52 JSR
LDY ws_page ; Get command line offset
8D0E LDX #5 ; X=5: start of credits keyword
8D10 .loop_match_credits←1← 8D19 BNE
LDA (os_text_ptr),y ; Load character from command line
8D12 CMP credits_keyword_start,x ; Compare with credits keyword
8D15 BNE done_credits_check ; Mismatch: check if keyword complete
8D17 INY ; Advance command line pointer
8D18 INX ; Advance keyword pointer
8D19 BNE loop_match_credits ; Continue matching
8D1B .done_credits_check←1← 8D15 BNE
CPX #&0d ; Reached end of keyword (X=&0D)?
8D1D BNE return_from_credits_check ; No: keyword not fully matched, return
8D1F LDX #0 ; X=0: start of credits text
8D21 .loop_emit_credits←1← 8D2A BNE
LDA credits_keyword_start,x ; Load character from credits string
8D24 BEQ return_from_credits_check ; Zero terminator: done printing
8D26 JSR osasci ; Write character
8D29 INX ; Advance string pointer
8D2A BNE loop_emit_credits ; Continue printing
8D2C .return_from_credits_check←2← 8D1D BNE← 8D24 BEQ
RTS ; Return
8D2D .credits_keyword_start←2← 8D12 CMP← 8D21 LDA
EQUB &0D ; CR
8D2E .credits_string
EQUS "The authors of ANFS are;"
8D46 EQUB &0D ; CR
8D47 EQUS "B Co"
8D4B .ps_template_base←1← AFFB LDA
EQUS "ckburn"
8D51 EQUB &0D ; CR
8D52 EQUS "J Dunn"
8D58 EQUB &0D ; CR
8D59 EQUS "B Robertson"
8D64 EQUB &0D ; CR
8D65 EQUS "J Wills"
8D6C EQUB &0D ; CR
8D6D 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.

8D6E .cmd_iam
TYA ; Save Y (command line offset)
8D6F PHA ; Push it
8D70 LDA #osbyte_close_spool_exec ; OSBYTE &77: close SPOOL/EXEC
8D72 STA fs_eof_flags ; Store as pending operation marker
8D75 JSR osbyte ; Close any *SPOOL and *EXEC files
8D78 LDY #0 ; Y=0
8D7A STY fs_work_4 ; Clear password entry flag
8D7C JSR process_all_fcbs ; Reset FS connection state
8D7F LDA #0 ; A=0
8D81 STA fs_eof_flags ; Clear pending operation marker
8D84 PLA ; Restore command line offset
8D85 TAY ; Transfer to Y
8D86 LDA (fs_options),y ; Load first option byte
8D88 JSR is_decimal_digit ; Parse station number if present
8D8B BCC cmd_pass ; Not a digit: skip to password entry
8D8D JSR parse_addr_arg ; Parse user ID string
8D90 BCS skip_no_fs_addr ; No user ID: go to password
8D92 STA fs_server_net ; Store file server station low
8D95 JSR clear_if_station_match ; Check and store FS network
8D98 INY ; Skip separator
8D99 JSR parse_addr_arg ; Parse next argument
8D9C .skip_no_fs_addr←1← 8D90 BCS
BEQ cmd_pass ; No FS address: skip to password
8D9E STA fs_server_stn ; Store file server station high
8DA1 LDX #&ff ; X=&FF: pre-decrement for loop
8DA3 .loop_copy_logon_cmd←1← 8DAA BPL
INX ; Advance index
8DA4 LDA cmd_table_nfs_iam,x ; Load logon command template byte
8DA7 STA fs_cmd_data,x ; Store into transmit buffer
8DAA BPL loop_copy_logon_cmd ; Bit 7 clear: more bytes, loop
8DAC JSR copy_arg_validated ; Send logon with file server lookup
8DAF 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.

8DB1 .cmd_pass←2← 8D8B BCC← 8D9C BEQ
JSR copy_arg_to_buf_x0 ; Build FS command packet
8DB4 .scan_pass_prompt←1← 8DAF BEQ
LDY #&ff ; Y=&FF: pre-increment for loop
8DB6 .loop_scan_colon←1← 8DC0 BNE
INY ; Advance to next byte
8DB7 LDA fs_cmd_data,y ; Load byte from reply buffer
8DBA CMP #&0d ; Is it CR (end of prompt)?
8DBC BEQ send_pass_to_fs ; Yes: no colon found, skip to send
8DBE CMP #&3a ; Is it ':' (password prompt)?
8DC0 BNE loop_scan_colon ; No: keep scanning
8DC2 JSR oswrch ; Write character
8DC5 STY fs_work_4 ; Save position of colon
8DC7 .read_pw_char←4← 8DD7 BNE← 8DDB BEQ← 8DDE BNE← 8DEA BNE
LDA #&ff ; A=&FF: mark as escapable
8DC9 STA escapable ; Set escape flag
8DCB JSR check_escape ; Check for escape condition
8DCE JSR osrdch ; Read a character from the current input stream
8DD1 CMP #&15 ; A=character read
8DD3 BNE check_pw_special ; Not NAK (&15): check other chars
8DD5 LDY fs_work_4 ; Restore colon position
8DD7 BNE read_pw_char ; Non-zero: restart from colon
8DD9 .loop_erase_pw←1← 8DE2 BEQ
CPY fs_work_4 ; At colon position?
8DDB BEQ read_pw_char ; Yes: restart password input
8DDD DEY ; Backspace: move back one character
8DDE BNE read_pw_char ; If not at start: restart input
8DE0 .check_pw_special←1← 8DD3 BNE
CMP #&7f ; Delete key (&7F)?
8DE2 BEQ loop_erase_pw ; Yes: handle backspace
8DE4 STA fs_cmd_data,y ; Store character in password buffer
8DE7 INY ; Advance buffer pointer
8DE8 CMP #&0d ; Is it CR (end of password)?
8DEA BNE read_pw_char ; No: read another character
8DEC JSR osnewl ; Write newline (characters 10 and 13)
8DEF .send_pass_to_fs←1← 8DBC BEQ
TYA ; Transfer string length to A
8DF0 PHA ; Save string length
8DF1 JSR init_txcb ; Set up transmit control block
8DF4 JSR init_tx_ptr_for_pass ; Send to file server and get reply
8DF7 PLA ; Restore string length
8DF8 TAX ; Transfer to X (byte count)
8DF9 INX ; Include terminator
8DFA LDY #0 ; Y=0
8DFC 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
8DFE .clear_if_station_match←2← 8D95 JSR← A68A JSR
JSR init_bridge_poll ; Parse station number from cmd line
8E01 EOR fs_server_net ; Compare with expected station
8E04 BNE return_from_station_match ; Different: return without clearing
8E06 STA fs_server_net ; Same: clear station byte
8E09 .return_from_station_match←1← 8E04 BNE
RTS ; Return
8E0A .pass_send_cmd←1← 944E JMP
JSR copy_arg_to_buf_x0 ; Build FS command packet
8E0D TAY ; Transfer result to Y
8E0E .send_cmd_and_dispatch←2← 8DFC BEQ← 9310 JMP
JSR save_net_tx_cb ; Set up command and send to FS
8E11 LDX fs_cmd_csd ; Load reply function code
8E14 BEQ dispatch_rts ; Zero: no reply, return
8E16 LDA fs_cmd_data ; Load first reply byte
8E19 LDY #&17 ; Y=&17: logon dispatch offset
8E1B BNE svc_dispatch ; ALWAYS branch
8E1D JSR set_xfer_params ; Parse reply as decimal number
8E20 CMP #8 ; Result >= 8?
8E22 BCS dispatch_rts ; Yes: out of range, return
8E24 TAX ; Transfer handle to X
8E25 JSR mask_owner_access ; Look up in open files table
8E28 TYA ; Transfer result to A
8E29 LDY #&13 ; Y=&13: handle dispatch offset
8E2B BNE svc_dispatch ; ALWAYS branch
8E2D .dir_op_dispatch←1← 8556 JSR
CPX #5 ; Handle >= 5?
8E2F BCS dispatch_rts ; Yes: out of range, return
8E31 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
8E33 .svc_dispatch←5← 8ABD JSR← 8E1B BNE← 8E2B BNE← 8E35 BPL← 8E8C JMP
INX ; Advance X to target index
8E34 DEY ; Decrement Y offset counter
8E35 BPL svc_dispatch ; Y still positive: continue counting
8E37 TAY ; Y=&FF: will be ignored by caller
8E38 LDA svc_dispatch_hi,x ; Load dispatch address high byte
8E3B PHA ; Push high byte for RTS dispatch
8E3C .push_dispatch_lo
LDA svc_dispatch_lo,x ; Load dispatch address low byte
8E3F PHA ; Push low byte for RTS dispatch
8E40 LDX fs_options ; Load FS options pointer
8E42 .dispatch_rts←3← 8E14 BEQ← 8E22 BCS← 8E2F 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 =
; &8E43). 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.
8E43 .ps_template_data
EQUS "PRINT " ; PS template: default name "PRINT "
8E49 EQUB &01 ; PS template: status &01
8E4A EQUB &00 ; PS template: terminator &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.

8E4B .fs_vector_table←1← 8B40 LDA
EQUW &FF1B ; FILEV dispatch (&FF1B)
8E4D EQUW &FF1E ; ARGSV dispatch (&FF1E)
8E4F EQUW &FF21 ; BGETV dispatch (&FF21)
8E51 EQUW &FF24 ; BPUTV dispatch (&FF24)
8E53 EQUW &FF27 ; GBPBV dispatch (&FF27)
8E55 EQUW &FF2A ; FINDV dispatch (&FF2A)
8E57 EQUW &FF2D ; FSCV dispatch (&FF2D)
8E59 EQUW &9921 ; FILEV handler (&9921)
8E5B EQUB &4A ; (ROM bank — not read)
8E5C EQUW &9BAF ; ARGSV handler (&9BAF)
8E5E EQUB &44 ; (ROM bank — not read)
8E5F EQUW &B7CF ; BGETV handler (&B7CF)
8E61 EQUB &57 ; (ROM bank — not read)
8E62 EQUW &B850 ; BPUTV handler (&B850)
8E64 EQUB &42 ; (ROM bank — not read)
8E65 EQUW &9E23 ; GBPBV handler (&9E23)
8E67 EQUB &41 ; (ROM bank — not read)
8E68 EQUW &9D42 ; FINDV handler (&9D42)
8E6A EQUB &52 ; (ROM bank — not read)
8E6B EQUW &8E1D ; FSCV handler (&8E1D)

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
8E6D .osbyte_x0←3← 8076 JSR← 8F45 JSR← 96FA 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
8E6F .osbyte_yff←1← 8080 JSR
LDY #&ff ; Y=&FF
8E71 .jmp_osbyte←1← 8E7A 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 (push_dispatch_lo+2).
; Points to netv_handler (&A968) which dispatches
; OSWORDs 0-8 to Econet handlers. Interleaved with
; the OSBYTE wrapper code in the data area.
8E74 .netv_handler_addr
EQUW netv_handler ; NETV handler: netv_handler (&A968)

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 &8E71.

On EntryAOSBYTE number
On ExitX0
Y0
8E76 .osbyte_x0_y0←1← 970D JSR
LDX #0 ; X=0
8E78 LDY #0 ; Y=0
8E7A 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)
8E7C .svc_7_osbyte
LDA osbyte_a_copy ; Get original OSBYTE A parameter
8E7E SBC #&31 ; Subtract &31 (map &32-&35 to 1-4)
8E80 CMP #4 ; In range 0-3?
8E82 BCS return_from_svc_1_workspace ; No: not ours, return unclaimed
8E84 TAX ; Transfer to X as dispatch index
8E85 LDA #0 ; A=0: claim the service call
8E87 STA svc_state ; Set return value to 0 (claimed)
8E89 TYA ; Transfer Y to A (OSBYTE Y param)
8E8A LDY #&21 ; Y=&21: OSBYTE dispatch offset
8E8C 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)
8E8F .svc_1_abs_workspace
CPY #&16 ; Need at least &16 pages?
8E91 BCS return_from_svc_1_workspace ; Already enough: return
8E93 LDY #&16 ; Request &16 pages of workspace
8E95 .return_from_svc_1_workspace←2← 8E82 BCS← 8E91 BCS
RTS ; Return

Record workspace page count (capped at &21)

Stores the workspace allocation from service 1 into offset &0F 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
8E96 .store_ws_page_count←1← 8EC6 JSR
TYA ; Transfer Y to A
8E97 CMP #&21 ; Y >= &21?
8E99 BCC done_cap_ws_count ; No: use Y as-is
8E9B LDA #&21 ; Cap at &21
8E9D .done_cap_ws_count←1← 8E99 BCC
LDY #&0f ; Offset &0F in receive block
8E9F STA (net_rx_ptr),y ; Store workspace page count
8EA1 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
8EA2 .svc_2_private_workspace
STY net_rx_ptr_hi ; Store Y as receive block page
8EA4 INY ; Advance to next page
8EA5 STY nfs_workspace_hi ; Store as NFS workspace page
8EA7 INY ; Advance to next page
8EA8 TYA ; Transfer page to A
8EA9 LDY romsel_copy ; Get current ROM slot number
8EAB STA rom_ws_pages,y ; Store workspace page for this slot
8EAE LDA #0 ; A=0
8EB0 STA net_rx_ptr ; Clear receive block pointer low
8EB2 STA nfs_workspace ; Clear NFS workspace pointer low
8EB4 STA ws_page ; Clear workspace page counter
8EB6 STA ws_0d60 ; Clear workspace byte
8EB9 LDY #4 ; Offset 4 in receive block
8EBB STA (net_rx_ptr),y ; Clear remote operation flag
8EBD LDA #osbyte_issue_service_request ; OSBYTE &8F: issue service request
8EBF LDX #1 ; X=1: workspace claim service
8EC1 LDY #&0e ; Y=&0E: requested pages
8EC3 JSR osbyte ; Issue paged ROM service call, Reason X=1 - Absolute public workspace claim
8EC6 JSR store_ws_page_count ; Record final workspace allocation
8EC9 LDA last_break_type ; Load ROM present flag
8ECC BEQ read_station_id ; Zero: first ROM init, skip FS setup
8ECE JSR setup_ws_ptr ; Set up workspace pointers
8ED1 STA fs_flags ; Clear FS flags
8ED4 TAY ; A=0, transfer to Y
8ED5 .loop_zero_workspace←1← 8EDA BNE
STA (fs_ws_ptr),y ; Clear byte in FS workspace
8ED7 STA (nfs_workspace),y ; Clear byte in NFS workspace
8ED9 INY ; Advance index
8EDA BNE loop_zero_workspace ; Loop until full page zeroed
8EDC LDY #&0c ; Offset &0C in receive block
8EDE STA (net_rx_ptr),y ; Clear protection flags
8EE0 JSR copy_ps_data_y1c ; Initialise station identity block
8EE3 LDY #6 ; Offset 6 in receive block
8EE5 LDA #&fe ; A=&FE: default station ID marker
8EE7 STA fs_server_stn ; Store default station low
8EEA STA (net_rx_ptr),y ; Store into receive block
8EEC LDA #0 ; A=0
8EEE STA fs_server_net ; Clear station high byte
8EF1 INY ; Y=&07
8EF2 STA (net_rx_ptr),y ; Store into receive block
8EF4 LDY #3 ; Offset 3 in NFS workspace
8EF6 STA (nfs_workspace),y ; Clear NFS workspace byte 3
8EF8 DEY ; Y=&02
8EF9 LDA #&eb ; A=&EB: default listen state
8EFB STA (nfs_workspace),y ; Store at NFS workspace offset 2
8EFD LDX #3 ; X=3: init data byte count
8EFF .loop_copy_init_data←1← 8F06 BNE
LDA ws_init_data,x ; Load initialisation data byte
8F02 STA net_context,x ; Store in workspace
8F05 DEX ; Decrement counter
8F06 BNE loop_copy_init_data ; More bytes: loop
8F08 STX ws_0d68 ; Clear workspace flag
8F0B STX fs_boot_option ; Clear workspace byte
8F0E JSR reset_spool_buf_state ; Initialise ADLC protection table
8F11 .loop_alloc_handles←1← 8F1E BNE
LDA ws_page ; Get current workspace page
8F13 JSR byte_to_2bit_index ; Allocate FS handle page
8F16 BCS read_station_id ; Allocation failed: finish init
8F18 LDA #&3f ; A=&3F: default handle permissions
8F1A STA (nfs_workspace),y ; Store handle permissions
8F1C INC ws_page ; Advance to next page
8F1E BNE loop_alloc_handles ; Continue allocating: loop
8F20 JSR restore_fs_context ; Restore FS context from saved state
8F23 .read_station_id←2← 8ECC BEQ← 8F16 BCS
LDY station_id_disable_net_nmis ; Read station ID from hardware
8F26 TYA ; Transfer to A
8F27 BNE store_station_id ; Non-zero: station ID valid
8F29 .error_bad_station←1← 8F30 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 overlaps the high byte
; of JMP err_bad_station_num; byte 0 is the JMP
; operand (&92), never read (BNE exits when X=0).
; Stores to l0d6e, l0d6f, l0d70.
8F2C EQUB &FF ; l0d6e: init=&FF (retry count)
8F2D EQUB &28 ; l0d6f: init=&28 (40, receive poll count)
8F2E EQUB &0A ; l0d70: init=&0A (10, machine peek retries)
8F2F .store_station_id←1← 8F27 BNE
INY ; Increment station ID
8F30 BEQ error_bad_station ; Overflow to 0: report error
8F32 LDY #5 ; Offset 5: station ID in recv block
8F34 STA (net_rx_ptr),y ; Store station ID
8F36 LDX #&40 ; X=&40: Econet flag byte
8F38 STX econet_flags ; Store Econet control flag
8F3B LDA #3 ; A=3: protection level
8F3D 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.

8F40 .init_adlc_and_vectors←1← 8B49 JSR
JSR adlc_init ; Initialise ADLC hardware
8F43 LDA #&a8 ; OSBYTE &A8: read ROM pointer table
8F45 JSR osbyte_x0 ; Read ROM pointer table address
8F48 STX fs_error_ptr ; Store table pointer low
8F4A STY fs_crflag ; Store table pointer high
8F4C LDY #&36 ; Y=&36: NETV vector offset
8F4E STY netv ; Set NETV address
8F51 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
8F53 .write_vector_entry←2← 8B50 JSR← 8F65 BNE
LDA svc_dispatch_lo_offset,y ; Load vector address low byte
8F56 STA (fs_error_ptr),y ; Store into extended vector table
8F58 INY ; Advance to high byte
8F59 LDA svc_dispatch_lo_offset,y ; Load vector address high byte
8F5C STA (fs_error_ptr),y ; Store into extended vector table
8F5E INY ; Advance to ROM ID byte
8F5F LDA romsel_copy ; Load current ROM slot number
8F61 STA (fs_error_ptr),y ; Store ROM ID in extended vector
8F63 INY ; Advance to next vector entry
8F64 DEX ; Decrement vector counter
8F65 BNE write_vector_entry ; More vectors to set: loop
8F67 DEX ; X=&FF
8F68 STX bridge_status ; Store &FF in workspace flag
8F6B JSR fscv_6_shutdown ; Restore FS state if previously active
8F6E JSR get_ws_page ; Get workspace page for ROM slot
8F71 INY ; Advance Y past workspace page
8F72 RTS ; Return

Restore FS context from saved workspace

Copies 8 bytes (offsets 6 to &0D) 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.

8F73 .restore_fs_context←3← 8F20 JSR← 8F8F JSR← A376 JMP
LDY #&0d ; Y=&0D: end of FS context block
8F75 .loop_restore_ctx←1← 8F7D BNE
LDA fs_context_save,y ; Load FS context byte
8F78 STA (net_rx_ptr),y ; Store into receive block
8F7A DEY ; Decrement index
8F7B CPY #5 ; Reached offset 5?
8F7D BNE loop_restore_ctx ; No: continue copying
8F7F 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.

8F80 .fscv_6_shutdown←1← 8F6B JSR
BIT fs_flags ; FS currently selected?
8F83 BPL return_from_fs_shutdown ; No (bit 7 clear): return
8F85 LDY #0 ; Y=0
8F87 JSR process_all_fcbs ; Reset FS connection state
8F8A LDA #osbyte_close_spool_exec ; OSBYTE &77: close SPOOL/EXEC
8F8C JSR osbyte ; Close any *SPOOL and *EXEC files
8F8F JSR restore_fs_context ; Restore FS context to receive block
8F92 LDY #&76 ; Y=&76: checksum range end
8F94 LDA #0 ; A=0: checksum accumulator
8F96 CLC ; Clear carry for addition
8F97 .loop_checksum_byte←1← 8F9B BPL
ADC fcb_count_lo,y ; Add byte from page &10 shadow
8F9A DEY ; Decrement index
8F9B BPL loop_checksum_byte ; Loop until all bytes summed
8F9D LDY #&77 ; Y=&77: checksum storage offset
8F9F BPL store_ws_byte ; ALWAYS branch
8FA1 .loop_copy_to_ws←1← 8FA7 BPL
LDA fcb_count_lo,y ; Load byte from page &10 shadow
8FA4 .store_ws_byte←1← 8F9F BPL
STA (fs_ws_ptr),y ; Copy to FS workspace
8FA6 DEY ; Decrement index
8FA7 BPL loop_copy_to_ws ; Loop until all bytes copied
8FA9 LDA fs_flags ; Load FS flags
8FAC AND #&7f ; Clear bit 7 (FS no longer selected)
8FAE STA fs_flags ; Store updated flags
8FB1 .return_from_fs_shutdown←1← 8F83 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 checksum' 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
8FB2 .verify_ws_checksum←5← 9BAF JSR← 9D42 JSR← 9DE2 JSR← 9E23 JSR← B5EF JSR
PHP ; Save processor status
8FB3 PHA ; Save A
8FB4 TYA ; Transfer Y to A
8FB5 PHA ; Save Y
8FB6 LDY #&76 ; Y=&76: checksum range end
8FB8 LDA #0 ; A=0: checksum accumulator
8FBA CLC ; Clear carry for addition
8FBB .loop_sum_ws←1← 8FBE BPL
ADC (fs_ws_ptr),y ; Add byte from FS workspace
8FBD DEY ; Decrement index
8FBE BPL loop_sum_ws ; Loop until all bytes summed
8FC0 LDY #&77 ; Y=&77: checksum storage offset
8FC2 CMP (fs_ws_ptr),y ; Compare with stored checksum
8FC4 BNE error_net_checksum ; Mismatch: raise checksum error
8FC6 PLA ; Restore Y
8FC7 TAY ; Transfer to Y
8FC8 PLA ; Restore A
8FC9 PLP ; Restore processor status
8FCA RTS ; Return (checksum valid)
8FCB .error_net_checksum←2← 8B25 JMP← 8FC4 BNE
LDA #&aa ; Error number &AA
8FCD JSR error_bad_inline ; Raise 'net checksum' error
8FD0 EQUS "net checksum."
fall through ↓

Print Econet station number and clock status

Uses print_inline to output 'Econet Station ', then reads the station ID from offset 5 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.

8FDD .print_station_id←2← 8CAB JMP← 8CD8 JSR
JSR print_inline ; Print 'Econet Station ' via inline
8FE0 EQUS "Econet Station "
8FEF LDY #5 ; Offset 5: station ID
8FF1 LDA (net_rx_ptr),y ; Load station ID from receive block
8FF3 JSR print_num_no_leading ; Print station number as decimal
8FF6 LDA #&20 ; Space character
8FF8 BIT econet_control23_or_status2 ; Check ADLC status register 2
8FFB BEQ done_print_newline ; Clock present: skip warning
8FFD JSR print_inline ; Print ' No Clock' via inline
9000 EQUS " No Clock"
9009 NOP ; NOP (string terminator)
900A .done_print_newline←1← 8FFB BEQ
JSR osnewl ; Write newline (characters 10 and 13)
900D .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.
900E .cmd_syntax_strings←1← 8BD6 LDA
.syn_opt_dir←1← 8BD6 LDA
EQUS "(<dir>)" ; Syn 1: *Dir, *LCat, *LEx, *Wipe
9015 EQUB &00 ; Null terminator
9016 .syn_iam
EQUS "(<stn. id.>) <user id.> " ; Syn 2: *I Am (login)
902E EQUB &0D ; Line break
902F EQUS "((:<CR>)<password>)" ; Syn 2 continued: password clause
9042 EQUB &00 ; Null terminator
9043 .syn_object
EQUS "<object>" ; Syn 3: *Delete, *FS, *Remove
904B EQUB &00 ; Null terminator
904C .syn_file_offset
EQUS "<filename> (<offset> " ; Syn 4: *Dump
9061 EQUB &0D ; Line break
9062 EQUS "(<address>))" ; Syn 4 continued: address clause
906E EQUB &00 ; Null terminator
906F .syn_dir
EQUS "<dir>" ; Syn 5: *Lib
9074 EQUB &00 ; Null terminator
9075 .syn_dir_num
EQUS "<dir> (<number>)" ; Syn 6: *CDir
9085 EQUB &00 ; Null terminator
9086 .syn_password
EQUS "(:<CR>) <password> " ; Syn 7: *Pass
9099 EQUB &0D ; Line break
909A EQUS "<new password>" ; Syn 7 continued: new password
90A8 EQUB &00 ; Null terminator
90A9 .syn_ps_type
EQUS "(<stn. id.>|<ps type>)" ; Syn 8: *PS, *Pollps
90BF EQUB &00 ; Null terminator
90C0 .syn_access
EQUS "<object> (L)(W)(R)(/(W)(R))" ; Syn 9: *Access
90DB EQUB &00 ; Null terminator
90DC .syn_rename
EQUS "<filename> <new filename>" ; Syn 10: *Rename
90F5 EQUB &00 ; Null terminator
90F6 .syn_opt_stn
EQUS "(<stn. id.>)" ; Syn 11: (station id. argument)
9102 EQUB &00 ; Null terminator
9103 .syn_filename
EQUS "<filename>" ; Syn 12: *Print, *Type
910D EQUB &00 ; Null terminator
; Command syntax string offset table
; 13 offsets into cmd_syntax_strings (&900E).
; 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.
910E .cmd_syntax_table←1← 8BD1 LDA
EQUB syn_iam - cmd_syntax_strings - 2 ; Idx 0: (no synta x)
910F EQUB (syn_opt_dir - cmd_syntax_strings - 1) AND &FF ; Idx 1: "(< dir >)" (Y wraps via &FF)
9110 EQUB syn_iam - cmd_syntax_strings - 1 ; Idx 2: "(< stn. id.>) <user id .>... "
9111 EQUB syn_object - cmd_syntax_strings - 1 ; Idx 3: "< objec t>"
9112 EQUB syn_file_offset - cmd_syntax_strings - 1 ; Idx 4: "< filen ame> (< offse t >...) "
9113 EQUB syn_dir - cmd_syntax_strings - 1 ; Idx 5: "< dir>"
9114 EQUB syn_dir_num - cmd_syntax_strings - 1 ; Idx 6: "< dir> (< numbe r>)"
9115 EQUB syn_password - cmd_syntax_strings - 1 ; Idx 7: "(:< CR>) < passw ord >..."
9116 EQUB syn_ps_type - cmd_syntax_strings - 1 ; Idx 8: "(< stn. id.>| <ps type >)"
9117 EQUB syn_access - cmd_syntax_strings - 1 ; Idx 9: "< objec t> (L)( W)(R )..."
9118 EQUB syn_rename - cmd_syntax_strings - 1 ; Idx 10: "< filen ame> <new filen ame>"
9119 EQUB syn_opt_stn - cmd_syntax_strings - 1 ; Idx 11: "(< stn. id .>)"
911A 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
911B .print_hex_byte←5← 9A54 JSR← ADFF JSR← BA55 JSR← BA7D JSR← BAE5 JSR
PHA ; Save full byte
911C LSR ; Shift high nybble to low
911D LSR ; Continue shifting
911E LSR ; Continue shifting
911F LSR ; High nybble now in bits 0-3
9120 JSR print_hex_nybble ; Print high nybble as hex digit
9123 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)
9124 .print_hex_nybble←1← 9120 JSR
AND #&0f ; Mask to low nybble
9126 CMP #&0a ; Digit >= &0A?
9128 BCC add_ascii_base ; No: skip letter adjustment
912A ADC #6 ; Add 7 to get 'A'-'F' (6 + carry)
912C .add_ascii_base←1← 9128 BCC
ADC #&30 ; Add &30 for ASCII '0'-'9' or 'A'-'F'
912E 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
9131 .print_inline←35← 8BAB JSR← 8BED JSR← 8C94 JSR← 8FDD JSR← 8FFD JSR← 9522 JSR← ADAE JSR← ADB8 JSR← ADC6 JSR← ADD1 JSR← ADED JSR← AE02 JSR← AE15 JSR← AE24 JSR← AF33 JSR← B07E JSR← B08A JSR← B0A1 JSR← B0AB JSR← B0B6 JSR← B186 JSR← B228 JSR← B23D JSR← B260 JSR← B26D JSR← B27C JSR← B28C JSR← B29B JSR← B3A8 JSR← B3CD JSR← BA71 JSR← BA8F JSR← BA9C JSR← BAD1 JSR← BAF6 JSR
PLA ; Pop return address (low) — points to last byte of JSR
9132 STA fs_error_ptr ; Store as string pointer low
9134 PLA ; Pop return address (high)
9135 STA fs_crflag ; Store as string pointer high
9137 LDY #0 ; Y=0: index for indirect loads
9139 .loop_next_char←1← 9154 JMP
INC fs_error_ptr ; Advance pointer to next character
913B BNE load_char ; No page crossing
913D INC fs_crflag ; Carry into high byte
913F .load_char←1← 913B BNE
LDA (fs_error_ptr),y ; Load next byte from inline string
9141 BMI resume_caller ; Bit 7 set? Done — this byte is the next opcode
9143 LDA fs_error_ptr ; Save string pointer on stack
9145 PHA ; (push low byte)
9146 LDA fs_crflag ; Save pointer high byte
9148 PHA ; (push high byte)
9149 LDA (fs_error_ptr),y ; Reload character (pointer may have been clobbered)
914B JSR osasci ; Print character via OSASCI Write character
914E PLA ; Restore string pointer high
914F STA fs_crflag ; Store pointer high
9151 PLA ; Restore string pointer low
9152 STA fs_error_ptr ; Store pointer low
9154 JMP loop_next_char ; Loop for next character
9157 .resume_caller←1← 9141 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.

915A .parse_addr_arg←5← 8D8D JSR← 8D99 JSR← A095 JSR← A0AA JSR← AD12 JSR
LDA #0 ; Clear accumulator
915C STA fs_load_addr_2 ; Initialise result to zero
915E LDA (fs_crc_lo),y ; Get first character of argument
9160 CMP #&26 ; Is it '&' (hex prefix)?
9162 BNE next_dec_char ; No: try decimal path
9164 INY ; Skip '&' prefix
9165 LDA (fs_crc_lo),y ; Get first hex digit
9167 BCS check_digit_range ; C always set from CMP: validate digit
9169 .next_hex_char←1← 9198 BCC
INY ; Advance to next character
916A LDA (fs_crc_lo),y ; Get next character
916C CMP #&2e ; Is it '.' (net.station separator)?
916E BEQ handle_dot_sep ; Yes: handle dot separator
9170 CMP #&21 ; Below '!' (space/control)?
9172 BCC done_parse_num ; Yes: end of number
9174 .check_digit_range←1← 9167 BCS
CMP #&30 ; Below '0'?
9176 BCC skip_if_not_hex ; Not a digit: bad hex
9178 CMP #&3a ; Above '9'?
917A BCC extract_digit_value ; Decimal digit: extract value
917C AND #&5f ; Force uppercase
917E ADC #&b8 ; Map 'A'-'F' to &FA-&FF
9180 BCS err_bad_hex ; Overflow: not A-F
9182 CMP #&fa ; Valid hex letter (A-F)?
9184 .skip_if_not_hex←1← 9176 BCC
BCC err_bad_hex ; Below A: bad hex
9186 .extract_digit_value←1← 917A BCC
AND #&0f ; Extract digit value (0-15)
9188 STA fs_load_addr_3 ; Save current digit
918A LDA fs_load_addr_2 ; Load running result
918C CMP #&10 ; Would shift overflow a byte?
918E BCS error_overflow ; Yes: overflow error
9190 ASL ; Shift result left 4 (x16)
9191 ASL ; (shift 2)
9192 ASL ; (shift 3)
9193 ASL ; (shift 4)
9194 ADC fs_load_addr_3 ; Add new hex digit
9196 STA fs_load_addr_2 ; Store updated result
9198 BCC next_hex_char ; Loop for next hex digit
919A .next_dec_char←2← 9162 BNE← 91C4 BNE
LDA (fs_crc_lo),y ; Get current character
919C CMP #&2e ; Is it '.' (net.station separator)?
919E BEQ handle_dot_sep ; Yes: handle dot separator
91A0 CMP #&21 ; Below '!' (space/control)?
91A2 BCC done_parse_num ; Yes: end of number
91A4 JSR is_dec_digit_only ; Is it a decimal digit?
91A7 BCC error_bad_number ; No: 'Bad number' error
91A9 AND #&0f ; Extract digit value (0-9)
91AB STA fs_load_addr_3 ; Save current digit
91AD ASL fs_load_addr_2 ; result * 2
91AF BCS error_overflow ; Overflow
91B1 LDA fs_load_addr_2 ; Load result * 2
91B3 ASL ; result * 4
91B4 BCS error_overflow ; Overflow
91B6 ASL ; result * 8
91B7 BCS error_overflow ; Overflow
91B9 ADC fs_load_addr_2 ; * 8 + * 2 = result * 10
91BB BCS error_overflow ; Overflow
91BD ADC fs_load_addr_3 ; result * 10 + new digit
91BF BCS error_overflow ; Overflow
91C1 STA fs_load_addr_2 ; Store updated result
91C3 INY ; Advance to next character
91C4 BNE next_dec_char ; Loop (always branches)
91C6 .done_parse_num←2← 9172 BCC← 91A2 BCC
LDA fs_work_4 ; Check parsing mode
91C8 BPL validate_station ; Bit 7 clear: net.station mode
91CA LDA fs_load_addr_2 ; Decimal-only mode: get result
91CC BEQ error_bad_param ; Zero: 'Bad parameter'
91CE RTS ; Return with result in A
91CF .validate_station←1← 91C8 BPL
LDA fs_load_addr_2 ; Get parsed station number
91D1 CMP #&ff ; Station 255 is reserved
91D3 BEQ err_bad_station_num ; 255: 'Bad station number'
91D5 LDA fs_load_addr_2 ; Reload result
91D7 BNE return_parsed ; Non-zero: valid station
91D9 LDA fs_work_4 ; Zero result: check if dot was seen
91DB BEQ err_bad_station_num ; No dot and zero: 'Bad station number'
91DD DEY ; Check character before current pos
91DE LDA (fs_crc_lo),y ; Load previous character
91E0 INY ; Restore Y
91E1 EOR #&2e ; Was previous char '.'?
91E3 BNE err_bad_station_num ; No: 'Bad station number'
91E5 .return_parsed←1← 91D7 BNE
SEC ; C=1: number was parsed
91E6 RTS ; Return (result in fs_load_addr_2)
91E7 .handle_dot_sep←2← 916E BEQ← 919E BEQ
LDA fs_work_4 ; Check if dot already seen
91E9 BNE error_bad_number ; Already seen: 'Bad number'
91EB INC fs_work_4 ; Set dot-seen flag
91ED LDA fs_load_addr_2 ; Get network number (before dot)
91EF CMP #&ff ; Network 255 is reserved
91F1 BEQ error_bad_net_num ; 255: 'Bad network number'
91F3 RTS ; Return to caller with network part
91F4 .err_bad_hex←3← 9180 BCS← 9184 BCC← BBB2 JMP
LDA #&f1 ; Error code &F1
91F6 JSR error_bad_inline ; Generate 'Bad hex' error
91F9 EQUS "hex."
91FD .error_overflow←6← 918E BCS← 91AF BCS← 91B4 BCS← 91B7 BCS← 91BB BCS← 91BF BCS
BIT fs_work_4 ; Test parsing mode
91FF BMI error_bad_param ; Decimal mode: 'Bad parameter'
9201 .err_bad_station_num←4← 8F29 JMP← 91D3 BEQ← 91DB BEQ← 91E3 BNE
LDA #&d0 ; Error code &D0
9203 JSR error_bad_inline ; Generate 'Bad station number' error
9206 EQUS "station number."
9215 .error_bad_number←2← 91A7 BCC← 91E9 BNE
LDA #&f0 ; Error code &F0
9217 JSR error_bad_inline ; Generate 'Bad number' error
921A EQUS "number."
9221 .error_bad_param←2← 91CC BEQ← 91FF BMI
LDA #&94 ; Error code &94
9223 JSR error_bad_inline ; Generate 'Bad parameter' error
9226 EQUS "parameter."
9230 .error_bad_net_num←1← 91F1 BEQ
LDA #&d1 ; Error code &D1
9232 JSR error_bad_inline ; Generate 'Bad network number' error
9235 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
9244 .is_decimal_digit←3← 8D88 JSR← AFE5 JSR← B1B8 JSR
CMP #&26 ; Is it '&' (hex prefix)?
9246 BEQ return_from_digit_test ; Yes: return C set (not decimal)
9248 CMP #&2e ; Is it '.' (separator)?
924A 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
924C .is_dec_digit_only←1← 91A4 JSR
CMP #&3a ; Above '9'?
924E BCS not_a_digit ; Yes: not a digit
9250 CMP #&30 ; Below '0'? C clear if so
9252 .return_from_digit_test←2← 9246 BEQ← 924A BEQ
RTS ; Return: C set if '0'-'9'
9253 .not_a_digit←1← 924E BCS
CLC ; C=0: not a digit
9254 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 &9272. Called by check_and_setup_txcb for owner and public access.

On ExitAencoded access flags
9255 .get_access_bits←2← 9B0E JSR← 9B3A JSR
LDY #&0e ; Offset &0E in directory entry
9257 LDA (fs_options),y ; Load raw access byte
9259 AND #&3f ; Mask to 6 access bits
925B LDX #4 ; X=4: start encoding at bit 4
925D 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 (&9272). Called by send_txcb_swap_addrs and check_and_setup_txcb.

On EntryAraw protection bits (low 5 used)
On ExitAencoded protection flags
925F .get_prot_bits←2← 9A16 JSR← 9B57 JSR
AND #&1f ; Mask to 5 protection bits
9261 LDX #&ff ; X=&FF: start encoding at bit 0
9263 .begin_prot_encode←1← 925D BNE
STA fs_error_ptr ; Save remaining bits
9265 LDA #0 ; Clear encoded result
9267 .loop_encode_prot←1← 926F BNE
INX ; Advance to next table position
9268 LSR fs_error_ptr ; Shift out lowest source bit
926A BCC skip_clear_prot ; Bit clear: skip this position
926C ORA prot_bit_encode_table,x ; Bit set: OR in encoded value
926F .skip_clear_prot←1← 926A BCC
BNE loop_encode_prot ; More bits to process
9271 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).
9272 .prot_bit_encode_table←1← 926C ORA
EQUB &50 ; Bit 0: &50 = %01010000 (bits 4,6)
9273 EQUB &20 ; Bit 1: &20 = %00100000 (bit 5)
9274 EQUB &05 ; Bit 2: &05 = %00000101 (bits 0,2)
9275 EQUB &02 ; Bit 3: &02 = %00000010 (bit 1)
9276 EQUB &88 ; Bit 4: &88 = %10001000 (bits 3,7)
9277 EQUB &04 ; Bit 0: &04 = %00000100 (bit 2)
9278 EQUB &08 ; Bit 1: &08 = %00001000 (bit 3)
9279 EQUB &80 ; Bit 2: &80 = %10000000 (bit 7)
927A EQUB &10 ; Bit 3: &10 = %00010000 (bit 4)
927B EQUB &01 ; Bit 4: &01 = %00000001 (bit 0)
927C 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
927D .set_text_and_xfer_ptr←1← A0FC JSR
STX os_text_ptr ; Set text pointer low
927F 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
9281 .set_xfer_params←5← 8E1D JSR← 9921 JSR← 9D45 JSR← 9E26 JSR← AD6E JSR
STA fs_last_byte_flag ; Store transfer byte count
9283 STX fs_crc_lo ; Store source pointer low
9285 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
9287 .set_options_ptr←2← 9BB4 JSR← B979 JSR
STX fs_options ; Store options pointer low
9289 STY fs_block_offset ; Store options pointer high
928B .clear_escapable←1← 9870 JMP
PHP ; Save processor flags
928C LSR escapable ; Clear bit 0 of escape flag
928E PLP ; Restore processor flags
928F 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
9290 .cmp_5byte_handle←2← 9984 JSR← 9A89 JSR
LDX #4 ; Compare 5 bytes (indices 4 down to 1)
9292 .loop_cmp_handle←1← 9299 BNE
LDA addr_work,x ; Load byte from handle buffer
9294 EOR fs_load_addr_3,x ; Compare with channel handle
9296 BNE return_from_cmp_handle ; Mismatch: return Z=0
9298 DEX ; Next byte
9299 BNE loop_cmp_handle ; Loop until all compared
929B .return_from_cmp_handle←1← 9296 BNE
RTS ; Return: Z=1 if all 5 matched
929C .fscv_7_read_handles
LDX #&20 ; Unreachable code
929E LDY #&2f ; (dead)
92A0 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
92A1 .set_conn_active←2← 9C3A JSR← 9E83 JSR
PHP ; Save processor flags
92A2 PHA ; Save A
92A3 TXA ; Transfer X to A
92A4 PHA ; Save original X
92A5 TSX ; Get stack pointer
92A6 LDA stack_page_2,x ; Read original A from stack
92A9 JSR attr_to_chan_index ; Convert to channel index
92AC BMI done_conn_flag ; No channel found: skip
92AE LDA #&40 ; Bit 6: connection active flag
92B0 ORA chan_status,x ; Set active flag in channel table
92B3 STA chan_status,x ; Store updated status
92B6 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
92B8 .clear_conn_active←2← 9C9B JSR← 9E7E JSR
PHP ; Save processor flags
92B9 PHA ; Save A
92BA TXA ; Transfer X to A
92BB PHA ; Save original X
92BC TSX ; Get stack pointer
92BD LDA stack_page_2,x ; Read original A from stack
92C0 JSR attr_to_chan_index ; Convert to channel index
92C3 BMI done_conn_flag ; No channel found: skip
92C5 LDA #&bf ; Bit 6 clear mask (&BF = ~&40)
92C7 AND chan_status,x ; Clear active flag in channel table
92CA STA chan_status,x ; Store updated status
92CD .done_conn_flag←3← 92AC BMI← 92B6 BNE← 92C3 BMI
PLA ; Restore X
92CE TAX ; Transfer back to X
92CF PLA ; Restore A
92D0 PLP ; Restore processor flags
92D1 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.

92D2 .cmd_fs_operation
JSR copy_fs_cmd_name ; Copy command name to TX buffer
92D5 TXA ; Save buffer position
92D6 PHA ; Push it
92D7 JSR parse_quoted_arg ; Parse filename (handles quoting)
92DA JSR parse_access_prefix ; Parse owner/public access prefix
92DD PLA ; Restore buffer position
92DE TAX ; Transfer to X
92DF JSR check_not_ampersand ; Reject '&' character in filename
92E2 CMP #&0d ; End of line?
92E4 BNE read_filename_char ; No: copy filename chars to buffer
92E6 .error_bad_filename←4← 92FA BEQ← 93F5 JMP← AECD JMP← AF02 JMP
LDA #&cc ; Error number &CC
92E8 JSR error_bad_inline ; Raise 'Bad file name' error
92EB 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.

92F5 .check_not_ampersand←2← 92DF JSR← 92FD JSR
LDA fs_filename_buf ; Load first parsed character
92F8 CMP #&26 ; Is it '&'?
92FA BEQ error_bad_filename ; Yes: invalid filename
92FC RTS ; Return
92FD .read_filename_char←3← 92E4 BNE← 930B JMP← 93C6 JMP
JSR check_not_ampersand ; Reject '&' in current char
9300 STA fs_cmd_data,x ; Store character in TX buffer
9303 INX ; Advance buffer pointer
9304 CMP #&0d ; End of line?
9306 BEQ send_fs_request ; Yes: send request to file server
9308 JSR strip_token_prefix ; Strip BASIC token prefix byte
930B JMP read_filename_char ; Continue reading filename chars
930E .send_fs_request←2← 9306 BEQ← 93EE JMP
LDY #0 ; Y=0: no extra dispatch offset
9310 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)
9313 .copy_fs_cmd_name←2← 92D2 JSR← 9377 JSR
TYA ; Save command line offset
9314 PHA ; Push it
9315 .loop_scan_flag←1← 9319 BPL
DEX ; Scan backwards in command table
9316 LDA cmd_table_fs,x ; Load table byte
9319 BPL loop_scan_flag ; Bit 7 clear: keep scanning
931B INX ; Point past flag byte to name start
931C LDY #0 ; Y=0: TX buffer offset
931E .loop_copy_name←1← 9328 BNE
LDA cmd_table_fs,x ; Load command name character
9321 BMI append_space ; Bit 7 set: end of name
9323 STA fs_cmd_data,y ; Store character in TX buffer
9326 INX ; Advance table pointer
9327 INY ; Advance buffer pointer
9328 BNE loop_copy_name ; Continue copying name
932A .append_space←1← 9321 BMI
LDA #&20 ; Space separator
932C STA fs_cmd_data,y ; Append space after command name
932F INY ; Advance buffer pointer
9330 TYA ; Transfer length to A
9331 TAX ; And to X (buffer position)
9332 PLA ; Restore command line offset
9333 TAY ; Transfer to Y
9334 .return_from_copy_cmd_name←1← 9369 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.

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

9377 .cmd_rename
JSR copy_fs_cmd_name ; Copy 'Rename ' to TX buffer
937A TXA ; Save buffer position
937B PHA ; Push it
937C JSR mask_owner_access ; Set owner-only access mask
937F JSR parse_quoted_arg ; Parse first filename (quoted)
9382 JSR parse_access_prefix ; Parse access prefix
9385 PLA ; Restore buffer position
9386 TAX ; Transfer to X
9387 .loop_copy_rename←1← 93A5 JMP
LDA fs_filename_buf ; Load next parsed character
938A CMP #&0d ; End of line?
938C BNE store_rename_char ; No: store character
938E .error_bad_rename←1← 93C4 BNE
LDA #&b0 ; Error number &B0
9390 JSR error_bad_inline ; Raise 'Bad rename' error
9393 EQUS "rename."
939A .store_rename_char←1← 938C BNE
STA fs_cmd_data,x ; Store character in TX buffer
939D INX ; Advance buffer pointer
939E CMP #&20 ; Space (name separator)?
93A0 BEQ skip_rename_spaces ; Yes: first name complete
93A2 JSR strip_token_prefix ; Strip BASIC token prefix byte
93A5 JMP loop_copy_rename ; Continue copying first filename
93A8 .skip_rename_spaces←2← 93A0 BEQ← 93B0 BEQ
JSR strip_token_prefix ; Strip token from next char
93AB LDA fs_filename_buf ; Load next parsed character
93AE CMP #&20 ; Still a space?
93B0 BEQ skip_rename_spaces ; Yes: skip multiple spaces
93B2 LDA fs_lib_flags ; Save current FS options
93B5 PHA ; Push them
93B6 JSR mask_owner_access ; Reset access mask for second name
93B9 TXA ; Save buffer position
93BA PHA ; Push it
93BB JSR parse_access_prefix ; Parse access prefix for second name
93BE PLA ; Restore buffer position
93BF TAX ; Transfer to X
93C0 PLA ; Restore original FS options
93C1 CMP fs_lib_flags ; Options changed (cross-FS)?
93C4 BNE error_bad_rename ; Yes: error (can't rename across FS)
93C6 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.

93C9 .cmd_dir
LDA (fs_crc_lo),y ; Get first char of argument
93CB CMP #&26 ; Is it '&' (FS selector prefix)?
93CD BNE dir_pass_simple ; No: simple dir change
93CF INY ; Skip '&'
93D0 LDA (fs_crc_lo),y ; Get char after '&'
93D2 CMP #&0d ; End of line?
93D4 BEQ setup_fs_root ; Yes: '&' alone (root directory)
93D6 CMP #&20 ; Space?
93D8 BNE check_fs_dot ; No: check for '.' separator
93DA .setup_fs_root←1← 93D4 BEQ
LDY #&ff ; Y=&FF: pre-increment for loop
93DC .loop_copy_fs_num←1← 93E4 BNE
INY ; Advance index
93DD LDA (fs_crc_lo),y ; Load char from command line
93DF STA fs_cmd_data,y ; Copy to TX buffer
93E2 CMP #&26 ; Is it '&' (end of FS path)?
93E4 BNE loop_copy_fs_num ; No: keep copying
93E6 LDA #&0d ; Replace '&' with CR terminator
93E8 STA fs_cmd_data,y ; Store CR in buffer
93EB INY ; Point past CR
93EC TYA ; Transfer length to A
93ED TAX ; And to X (byte count)
93EE JMP send_fs_request ; Send directory request to server
93F1 .check_fs_dot←1← 93D8 BNE
CMP #&2e ; Is char after '&' a dot?
93F3 BEQ parse_fs_dot_dir ; Yes: &FS.dir format
93F5 JMP error_bad_filename ; No: invalid syntax
93F8 .parse_fs_dot_dir←1← 93F3 BEQ
INY ; Skip '.'
93F9 STY fs_load_addr ; Save dir path start position
93FB LDA #4 ; FS command 4: examine directory
93FD STA fs_cmd_data ; Store in TX buffer
9400 LDA fs_lib_flags ; Load FS flags
9403 ORA #&40 ; Set bit 6 (FS selection active)
9405 STA fs_lib_flags ; Store updated flags
9408 LDX #1 ; X=1: buffer offset
940A JSR copy_arg_validated ; Copy FS number to buffer
940D LDY #&12 ; Y=&12: select FS command code
940F JSR save_net_tx_cb ; Send FS selection command
9412 LDA fs_cmd_data ; Load reply status
9415 CMP #2 ; Status 2 (found)?
9417 BEQ dir_found_send ; Yes: proceed to dir change
9419 LDA #&d6 ; Error number &D6
941B JSR error_inline_log ; Raise 'Not found' error
941E EQUS "Not found."
9428 .dir_found_send←1← 9417 BEQ
LDA fs_csd_handle ; Load current FS station byte
942B STA fs_cmd_data ; Store in TX buffer
942E LDX #1 ; X=1: buffer offset
9430 LDY #7 ; Y=7: change directory command code
9432 JSR save_net_tx_cb ; Send directory change request
9435 LDX #1 ; X=1
9437 STX fs_cmd_data ; Store start marker in buffer
943A STX fs_func_code ; Store start marker in buffer+1
943D INX ; X=&02
943E LDY fs_load_addr ; Restore dir path start position
9440 JSR copy_arg_validated ; Copy directory path to buffer
9443 LDY #6 ; Y=6: set directory command code
9445 JSR save_net_tx_cb ; Send set directory command
9448 LDY fs_cmd_data ; Load reply handle
944B JMP fsreply_3_set_csd ; Select FS and return
944E .dir_pass_simple←1← 93CD BNE
JMP pass_send_cmd ; 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.

9451 .init_txcb_bye←1← 94DD 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
9453 .init_txcb_port←1← 9ACE JSR
JSR init_txcb ; Initialise TXCB from template
9456 STA txcb_port ; Set transmit port
9458 LDA #3 ; A=3: data start offset
945A STA txcb_start ; Set TXCB start offset
945C DEC txcb_ctrl ; Decrement control byte
945E RTS ; Return

Initialise TX control block from ROM template

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

945F .init_txcb←5← 8DF1 JSR← 9453 JSR← 94CC JSR← A8DE LDA← B92F JSR
PHA ; Save A
9460 LDY #&0b ; Y=&0B: template size - 1
9462 .loop_init_txcb←1← 9473 BPL
LDA txcb_init_template,y ; Load byte from TXCB template
9465 STA txcb_ctrl,y ; Store to TXCB workspace
9468 CPY #2 ; Index >= 2?
946A BPL skip_txcb_dest ; Yes: skip dest station copy
946C LDA fs_server_stn,y ; Load dest station byte
946F STA txcb_dest,y ; Store to TXCB destination
9472 .skip_txcb_dest←1← 946A BPL
DEY ; Decrement index
9473 BPL loop_init_txcb ; More bytes: continue
9475 PLA ; Restore A
9476 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, &947D)
; 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.
9477 .txcb_init_template←1← 9462 LDA
EQUB &80 ; Offset 0: txcb_ctrl = &80 (transmit)
9478 EQUB &99 ; Offset 1: txcb_port = &99 (FS reply)
9479 EQUB &00 ; Offset 2: txcb_dest lo (overwritten)
947A EQUB &00 ; Offset 3: txcb_dest hi (overwritten)
947B EQUB &00 ; Offset 4: txcb_start = 0
947C EQUB &0F ; Offset 5: buffer start hi (page &0F)
947D .bit_test_ff←22← 8C68 BIT← 9638 BIT← 9768 BIT← 9B2F BIT← 9D02 BIT← A086 BIT← A185 BIT← A2FE BIT← A329 BIT← A360 BIT← AA6E BIT← AF5F BIT← AF65 BIT← B005 BIT← B181 BIT← B1E1 BIT← B222 BIT← B2B2 BIT← B54E BIT← B58C BIT← B88B BIT← B988 BIT
EQUB &FF ; Offset 6: BIT target / buffer end lo
947E EQUB &FF ; Offset 7: txcb_pos = &FF
947F EQUB &FF ; Offset 8: txcb_end = &FF
9480 EQUB &0F ; Offset 9: buffer end hi (page &0F)
9481 EQUB &FF ; Offset 10: extended addr fill (&FF)
9482 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.

9483 .send_request_nowrite←1← 9F02 JSR
PHA ; Save A
9484 SEC ; Set carry (read-only mode)
9485 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.

9487 .send_request_write←2← 9944 JSR← 99F8 JSR
CLV ; Clear V
9488 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.

948A .cmd_bye
LDY #0 ; Y=0: close all files
948C JSR process_all_fcbs ; Process all file control blocks
948F LDA #osbyte_close_spool_exec ; OSBYTE &77: close spool/exec
9491 JSR osbyte ; Close any *SPOOL and *EXEC files
9494 JSR close_all_net_chans ; Close all network channels
9497 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.

9499 .save_net_tx_cb←26← 8E0E JSR← 940F JSR← 9432 JSR← 9445 JSR← 9B4B JSR← 9C34 JSR← 9C44 JSR← 9C92 JSR← 9D2B JSR← 9DA4 JSR← 9DD8 JSR← 9E70 JSR← 9E93 JSR← 9F57 JSR← A012 JSR← A1BE JSR← A1E6 JSR← A52A JSR← AD2F JMP← ADA6 JSR← ADE4 JSR← AE53 JSR← B365 JSR← B406 JSR← B6C9 JSR← B8C8 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.

949A .save_net_tx_cb_vset←3← 9B32 JSR← 9D08 JSR← AF62 JMP
LDA fs_urd_handle ; Copy FS station to TX control block
949D STA fs_cmd_urd ; Store in TXCB
94A0 .txcb_copy_carry_clr←1← 9488 BVC
CLC ; Clear carry
94A1 .txcb_copy_carry_set←1← 9485 BCS
PHP ; Save flags (carry = mode)
94A2 STY fs_cmd_y_param ; Store function code in TXCB
94A5 LDY #1 ; Copy 2 bytes (indices 0-1)
94A7 .loop_copy_vset_stn←1← 94AE BPL
LDA fs_csd_handle,y ; Load source byte
94AA STA fs_cmd_csd,y ; Store to TXCB
94AD DEY ; Next byte
94AE BPL loop_copy_vset_stn ; Loop until all copied
94B0 BIT fs_lib_flags ; Test library flag bits 6-7
94B3 BVS use_lib_station ; Bit 6 set: use station as port
94B5 BPL done_vset_station ; Bit 7 clear: skip port override
94B7 LDA fs_lib_handle ; Bit 7 set: load alternative port
94BA STA fs_cmd_csd ; Override TXCB port byte
94BD BVC done_vset_station ; ALWAYS branch
94BF .use_lib_station←1← 94B3 BVS
LDA fs_urd_handle ; Bit 6: load station byte
94C2 STA fs_cmd_csd ; Use station as TXCB port
94C5 .done_vset_station←2← 94B5 BPL← 94BD 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.

94C6 .prep_send_tx_cb←1← 9FB0 JSR
PHP ; Save flags
94C7 LDA #&90 ; Port &90: FS command port
94C9 STA txcb_reply_port ; Set reply port in TXCB
94CC JSR init_txcb ; Initialise TXCB workspace
94CF TXA ; Get TXCB data end offset
94D0 ADC #5 ; Add 5 for header size
94D2 STA txcb_end ; Set TXCB end pointer
94D4 PLP ; Restore flags
94D5 BCS handle_disconnect ; C set: send disconnect instead
94D7 PHP ; Save flags
94D8 JSR init_tx_ptr_and_send ; Initialise TX pointer and send
94DB 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.

94DC .recv_and_process_reply←2← 9A0B JSR← 9F43 JSR
PHP ; Save flags
94DD JSR init_txcb_bye ; Set up receive TXCB
94E0 JSR wait_net_tx_ack ; Wait for TX acknowledgment
94E3 PLP ; Restore flags
94E4 .loop_next_reply←1← 94F8 BCC
INY ; Advance to next reply byte
94E5 LDA (txcb_start),y ; Load reply byte
94E7 TAX ; Save in X
94E8 BEQ return_from_recv_reply ; Zero: no more replies, return
94EA BVC process_reply_code ; V clear: use code directly
94EC ADC #&2a ; V set: adjust reply code (+&2B)
94EE .process_reply_code←1← 94EA BVC
BNE store_reply_status ; Non-zero: process reply
94F0 .return_from_recv_reply←2← 94E8 BEQ← 955E BPL
RTS ; Return
94F1 .handle_disconnect←1← 94D5 BCS
PLA ; Discard saved flags
94F2 LDX #&c0 ; X=&C0: disconnect command
94F4 INY ; Advance reply offset
94F5 JSR send_disconnect_reply ; Send disconnect reply
94F8 BCC loop_next_reply ; Successful: process next reply
94FA .store_reply_status←1← 94EE BNE
STX fs_last_error ; Store reply status code
94FD LDA fs_eof_flags ; Load pending operation marker
9500 BNE check_data_loss ; Pending: go to data loss check
9502 CPX #&bf ; Reply &BF (normal bye response)?
9504 BNE build_error_block ; No: build error from reply
9506 .check_data_loss←1← 9500 BNE
LDA #&40 ; A=&40: initial data-loss flag
9508 PHA ; Push data-loss accumulator
9509 LDX #&0f ; Scan 16 channel entries (15 to 0)
950B .loop_scan_channels←1← 9519 BPL
PLA ; Pop accumulator
950C ORA fcb_status,x ; OR in channel status bits
950F PHA ; Push updated accumulator
9510 LDA fcb_status,x ; Load channel status
9513 AND #&c0 ; Keep only bits 6-7 (close flags)
9515 STA fcb_status,x ; Clear data bits, keep state flags
9518 DEX ; Next channel
9519 BPL loop_scan_channels ; Loop all 16 channels
951B JSR close_all_net_chans ; Close all network channels
951E PLA ; Pop data-loss accumulator
951F ROR ; Bit 0 to carry (data lost?)
9520 BCC reload_reply_status ; No data lost: skip message
9522 JSR print_inline ; Print 'Data Lost' + CR
9525 EQUS "Data Lost."
952F .reload_reply_status←1← 9520 BCC
LDX fs_last_error ; Reload reply status code
9532 LDA fs_eof_flags ; Check pending operation
9535 BEQ build_error_block ; No pending: build error from reply
9537 PLA ; Pending: clean up stack (3 bytes)
9538 PLA ; (second byte)
9539 PLA ; (third byte)
953A RTS ; Return to pending operation caller
953B .build_error_block←2← 9504 BNE← 9535 BEQ
LDY #1 ; Y=1: error code offset in reply
953D CPX #&a8 ; Reply code >= &A8?
953F BCS setup_error_copy ; Yes: keep server error code
9541 LDA #&a8 ; No: use minimum error code &A8
9543 STA (txcb_start),y ; Overwrite error code in reply
9545 .setup_error_copy←1← 953F BCS
LDY #&ff ; Y=&FF: pre-increment index
9547 .loop_copy_error←1← 954F BNE
INY ; Advance to next byte
9548 LDA (txcb_start),y ; Load reply byte
954A STA error_block,y ; Copy to error block
954D EOR #&0d ; Is it CR (end of message)?
954F BNE loop_copy_error ; No: copy next byte
9551 STA error_block,y ; Store null terminator (A=0 from EOR)
9554 DEY ; Get message length
9555 TYA ; Transfer to A
9556 TAX ; Length in X
9557 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.

955A .check_escape←2← 8DCB JSR← 9846 JSR
LDA escape_flag ; Load MOS escape flag
955C AND escapable ; Mask with escape-enabled flag
955E BPL return_from_recv_reply ; No escape: return
9560 .raise_escape_error←1← B42B JMP
LDA #osbyte_acknowledge_escape ; OSBYTE &7E: acknowledge escape
9562 JSR osbyte ; Clear escape condition and perform escape effects
9565 LDA #6 ; Error class 6: Escape
9567 JMP classify_reply_error ; Classify as network error
956A .lang_1_remote_boot
LDY #4 ; Offset 4: remote state byte
956C LDA (net_rx_ptr),y ; Load remote state
956E BEQ init_remote_session ; Zero: initialise remote session
9570 .done_commit_state←1← 95B6 BNE
JMP commit_state_byte ; Non-zero: commit state and return
9573 .init_remote_session←2← 956E BEQ← 95AC BEQ
ORA #9 ; Set bits 0,3: remote active flags
9575 STA (net_rx_ptr),y ; Store updated remote state
9577 LDX #&80 ; X=&80: flag for vector setup
9579 LDY #&80 ; Offset &80 in RX buffer
957B LDA (net_rx_ptr),y ; Load remote station low
957D PHA ; Save on stack
957E INY ; Y=&81
957F LDA (net_rx_ptr),y ; Load remote station high
9581 LDY #&0f ; Workspace offset &0F
9583 STA (nfs_workspace),y ; Store remote station high
9585 DEY ; Y=&0E Y=&0e
9586 PLA ; Restore remote station low
9587 STA (nfs_workspace),y ; Store remote station low
9589 JSR scan_remote_keys ; Set up remote keyboard scanning
958C JSR init_ws_copy_narrow ; Initialise workspace copy
958F LDX #1 ; X=1: disable keyboard
9591 LDY #0 ; Y=0
9593 LDA #osbyte_read_write_econet_keyboard_disable ; OSBYTE &C9: Econet keyboard disable
9595 JSR osbyte ; Disable keyboard (for Econet)
9598 .lang_3_execute_at_0100
JSR commit_state_byte ; Commit state change
959B LDA #0 ; Error code 0
959D JSR error_inline_log ; Generate 'Remoted' error
95A0 EQUS "Remoted."
95A8 .lang_4_remote_validated
LDY #4 ; Offset 4: remote state byte
95AA LDA (net_rx_ptr),y ; Load remote state
95AC BEQ init_remote_session ; Zero: reinitialise session
95AE LDY #&80 ; Offset &80: station low
95B0 LDA (net_rx_ptr),y ; Load station low from RX
95B2 LDY #&0e ; Workspace offset &0E
95B4 CMP (nfs_workspace),y ; Compare with stored station
95B6 BNE done_commit_state ; Different station: commit state
95B8 .lang_0_insert_remote_key
LDY #&82 ; Offset &82: keypress byte
95BA LDA (net_rx_ptr),y ; Load remote keypress
95BC TAY ; Key code to Y
95BD LDX #0 ; X=0: keyboard buffer
95BF JSR commit_state_byte ; Commit state change
95C2 LDA #osbyte_insert_input_buffer ; OSBYTE &99: insert into buffer
95C4 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.

95C7 .wait_net_tx_ack←6← 94E0 JSR← 999E JSR← 9AD8 JSR← A923 JMP← ABBF JSR← AC61 JSR
LDA rx_poll_count ; Save TX timeout counter
95CA PHA ; Push (used as outer loop counter)
95CB LDA econet_flags ; Save TX control state
95CE PHA ; Push (preserved during wait)
95CF LDA net_tx_ptr_hi ; Check if TX in progress
95D1 BNE init_poll_counters ; Non-zero: skip force-wait
95D3 ORA #&80 ; Set bit 7 to force wait mode
95D5 STA econet_flags ; Store updated control state
95D8 .init_poll_counters←1← 95D1 BNE
LDA #0 ; A=0: initial counter values
95DA PHA ; Push inner loop counter
95DB PHA ; Push middle loop counter
95DC TAY ; Y=&00
95DD TSX ; X=SP for stack-relative DECs
95DE .loop_poll_tx←3← 95E5 BNE← 95EA BNE← 95EF BNE
LDA (net_tx_ptr),y ; Poll TX completion status
95E0 BMI done_poll_tx ; Bit 7 set: TX complete
95E2 DEC error_text,x ; Decrement inner counter
95E5 BNE loop_poll_tx ; Not zero: keep polling
95E7 DEC stack_page_2,x ; Decrement middle counter
95EA BNE loop_poll_tx ; Not zero: keep polling
95EC DEC stack_page_4,x ; Decrement outer counter
95EF BNE loop_poll_tx ; Not zero: keep polling
95F1 .done_poll_tx←1← 95E0 BMI
PLA ; Discard inner counter
95F2 PLA ; Discard middle counter
95F3 PLA ; Restore l0d61 control state
95F4 STA econet_flags ; Write back TX control state
95F7 PLA ; Pop outer counter (0 if timed out)
95F8 BEQ build_no_reply_error ; Zero: TX timed out
95FA 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
95FB .cond_save_error_code←6← 9611 JSR← 964A JSR← 9666 JSR← 9690 JSR← 96A2 JSR← 96BB JSR
BIT fs_flags ; Test error logging flag
95FE BPL return_from_cond_save_err ; Bit 7 clear: skip save
9600 STA fs_last_error ; Save error code to workspace
9603 .return_from_cond_save_err←1← 95FE BPL
RTS ; Return
9604 .build_no_reply_error←1← 95F8 BEQ
LDX #8 ; X=8: 'No reply' error index
9606 LDY net_error_lookup_data,x ; Look up message table offset
9609 LDX #0 ; X=0: error text start
960B STX error_block ; Clear BRK byte in error block
960E LDA error_msg_table,y ; Load error number from table
9611 JSR cond_save_error_code ; Conditionally save error code
9614 .loop_copy_no_reply_msg←1← 961E BNE
LDA error_msg_table,y ; Load message byte
9617 STA error_text,x ; Store in error text buffer
961A BEQ done_no_reply_msg ; Null terminator?
961C INX ; Advance destination
961D INY ; Advance source
961E BNE loop_copy_no_reply_msg ; Loop until end of message
9620 .done_no_reply_msg←1← 961A BEQ
JSR append_drv_dot_num ; Append ' net.station' to message
9623 LDA #0 ; A=0: null terminator
9625 STA error_text,x ; Terminate error text
9628 JMP check_net_error_code ; Check and raise network error
962B .fixup_reply_status_a←1← 98B1 JMP
LDA (net_tx_ptr,x) ; Load first reply byte
962D CMP #&41 ; Is it 'A' (status &41)?
962F BNE skip_if_not_a ; No: keep original
9631 LDA #&42 ; Yes: change to 'B' (&42)
9633 .skip_if_not_a←1← 962F BNE
CLV ; Clear V flag
9634 BVC mask_error_class ; ALWAYS branch
9636 .load_reply_and_classify←1← 986A JMP
LDA (net_tx_ptr,x) ; Load first reply byte
9638 .classify_reply_error←2← 9567 JMP← 9DD0 JMP
BIT bit_test_ff ; Set V flag (via BIT &FF)
963B .mask_error_class←1← 9634 BVC
AND #7 ; Mask to error class (0-7)
963D PHA ; Save error class on stack
963E CMP #2 ; Class 2 (station error)?
9640 BNE build_simple_error ; No: build simple error message
9642 PHP ; Save flags (V state for suffix)
9643 TAX ; Error class to X
9644 LDY net_error_lookup_data,x ; Look up message table offset
9647 LDA error_msg_table,y ; Load error number from table
964A JSR cond_save_error_code ; Conditionally save error code
964D LDX #0 ; X=0: error text start
964F STX error_block ; Clear BRK byte
9652 .loop_copy_station_msg←1← 965C BNE
LDA error_msg_table,y ; Load message byte
9655 STA error_text,x ; Store in error text
9658 BEQ done_station_msg ; Null terminator?
965A INY ; Advance source
965B INX ; Advance destination
965C BNE loop_copy_station_msg ; Loop until end of message
965E .done_station_msg←1← 9658 BEQ
JSR append_drv_dot_num ; Append ' net.station' suffix
9661 PLP ; Restore flags
9662 BVS suffix_not_listening ; V set: append 'not listening'
9664 LDA #&a4 ; Error code &A4
9666 JSR cond_save_error_code ; Conditionally save error code
9669 STA error_text ; Replace error number in block
966C LDY #&0b ; Y=&0B: 'not present' suffix index
966E BNE load_suffix_offset ; ALWAYS branch
9670 .suffix_not_listening←1← 9662 BVS
LDY #9 ; Y=9: 'not listening' suffix index
9672 .load_suffix_offset←1← 966E BNE
LDA net_error_lookup_data,y ; Look up suffix table offset
9675 TAY ; Offset to Y for indexing
9676 .loop_copy_suffix←1← 9680 BNE
LDA error_msg_table,y ; Load suffix byte
9679 STA error_text,x ; Append to error text
967C BEQ done_suffix ; Null terminator?
967E INY ; Advance source
967F INX ; Advance destination
9680 BNE loop_copy_suffix ; Loop until end of suffix
9682 .done_suffix←1← 967C BEQ
BEQ check_msg_terminator ; ALWAYS branch to error dispatch
9684 .build_simple_error←1← 9640 BNE
TAX ; Error class to X
9685 LDY net_error_lookup_data,x ; Look up message table offset
9688 LDX #0 ; X=0: error text start
968A STX error_block ; Clear BRK byte
968D LDA error_msg_table,y ; Load error number from table
9690 JSR cond_save_error_code ; Conditionally save error code
9693 .loop_copy_error_msg←1← 969D BNE
LDA error_msg_table,y ; Load message byte
9696 STA error_text,x ; Store in error text
9699 .check_msg_terminator←1← 9682 BEQ
BEQ check_net_error_code ; Null terminator? Go to error
969B INY ; Advance source
969C INX ; Advance destination
969D .bad_str_anchor
BNE loop_copy_error_msg ; Loop until end of message
969F 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
96A2 .error_bad_inline←11← 8FCD JSR← 91F6 JSR← 9203 JSR← 9217 JSR← 9223 JSR← 9232 JSR← 92E8 JSR← 936D JSR← 9390 JSR← A247 JSR← BC5B JSR
JSR cond_save_error_code ; Conditionally log error code to workspace
96A5 TAY ; Save error number in Y
96A6 PLA ; Pop return address (low) — points to last byte of JSR
96A7 STA fs_load_addr ; Store return address low
96A9 PLA ; Pop return address (high)
96AA STA fs_load_addr_hi ; Store return address high
96AC LDX #0 ; X=0: start of prefix string
96AE .loop_copy_bad_prefix←1← 96B7 BNE
INX ; Copy 'Bad ' prefix from lookup table
96AF LDA bad_prefix,x ; Get next prefix character
96B2 STA error_text,x ; Store in error text buffer
96B5 CMP #&20 ; Is it space (end of 'Bad ')?
96B7 BNE loop_copy_bad_prefix ; No: copy next prefix character
96B9 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
96BB .error_inline_log←11← 941B JSR← 959D JSR← A25E JSR← ABEE JSR← AC00 JSR← B475 JSR← B4EC JSR← B538 JSR← B7E0 JSR← B81B JSR← B866 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
96BE .error_inline←4← A111 JSR← B9FC JSR← BB2C JSR← BBEE JSR
TAY ; Save error number in Y
96BF PLA ; Pop return address (low) — points to last byte of JSR
96C0 STA fs_load_addr ; Store return address low
96C2 PLA ; Pop return address (high)
96C3 STA fs_load_addr_hi ; Store return address high
96C5 LDX #0 ; X=0: error text index
96C7 .write_error_num_and_str←1← 96B9 BEQ
STY error_text ; Store error number in error block
96CA TYA ; Copy error number to A
96CB PHA ; Push error number on stack
96CC LDY #0 ; Y=0: inline string index
96CE STY error_block ; Zero the BRK byte at &0100
96D1 .loop_copy_inline_str←1← 96D8 BNE
INX ; Copy inline string into error block
96D2 INY ; Advance string index
96D3 LDA (fs_load_addr),y ; Read next byte from inline string
96D5 STA error_text,x ; Store byte in error block
96D8 BNE loop_copy_inline_str ; Loop until null terminator
96DA .check_net_error_code←4← 9557 JMP← 9628 JMP← 9699 BEQ← B966 JMP
LDY #&0e ; Y=&0E: offset to error code in RX buffer
96DC LDA (net_rx_ptr),y ; Load network error code from reply
96DE BNE handle_net_error ; Non-zero: network returned an error
96E0 PLA ; Pop saved error number
96E1 CMP #&de ; Was it &DE (file server error)?
96E3 BEQ append_error_number ; Yes: append error number and trigger BRK
96E5 .trigger_brk←1← 9736 BEQ
JMP error_block ; Jump to BRK via error block
96E8 .handle_net_error←1← 96DE BNE
STA net_context ; Store error code in workspace
96EB PHA ; Push error code
96EC TXA ; Save X (error text index)
96ED PHA ; Push X
96EE LDY #&0e ; Y=&0E: error code offset
96F0 LDA (net_rx_ptr),y ; Load error code from RX buffer
96F2 STA fs_load_addr ; Save to fs_load_addr as spool handle
96F4 LDA #0 ; A=0: clear error code in RX buffer
96F6 STA (net_rx_ptr),y ; Zero the error code byte in buffer
96F8 LDA #&c6 ; A=&C6: OSBYTE read spool handle
96FA JSR osbyte_x0 ; Read current spool file handle
96FD CPY fs_load_addr ; Compare Y result with saved handle
96FF BEQ close_exec_file ; Match: close the spool file
9701 CPX fs_load_addr ; Compare X result with saved handle
9703 BNE done_close_files ; No match: skip spool close
9705 PHA ; Push A (preserved)
9706 LDA #&c6 ; A=&C6: disable spool with OSBYTE
9708 BNE close_spool_exec ; ALWAYS branch to close spool ALWAYS branch
970A .close_exec_file←1← 96FF BEQ
PHA ; Push A (preserved)
970B LDA #&c7 ; A=&C7: disable exec with OSBYTE
970D .close_spool_exec←1← 9708 BNE
JSR osbyte_x0_y0 ; OSBYTE with X=0, Y=0 to close
9710 PLA ; Pull saved handle
9711 TAY ; Transfer to Y for OSFIND
9712 LDA #osfind_close ; A=0: close file
9714 JSR osfind ; Close the spool/exec file Close one or all files
9717 .done_close_files←1← 9703 BNE
PLA ; Pull saved X (error text index)
9718 TAX ; Restore X
9719 LDY #&0a ; Y=&0A: lookup index for 'on channel'
971B LDA net_error_lookup_data,y ; Load message offset from lookup table
971E TAY ; Transfer offset to Y
971F .loop_copy_channel_msg←1← 9729 BNE
LDA error_msg_table,y ; Load error message byte
9722 STA error_text,x ; Append to error text buffer
9725 BEQ append_error_number ; Null terminator: done copying
9727 INX ; Advance error text index
9728 INY ; Advance message index
9729 BNE loop_copy_channel_msg ; Loop until full message copied
972B .append_error_number←2← 96E3 BEQ← 9725 BEQ
STX fs_load_addr_2 ; Save error text end position
972D PLA ; Pull saved error number
972E JSR append_space_and_num ; Append ' nnn' error number suffix
9731 LDA #0 ; A=0: null terminator
9733 STA stack_page_2,x ; Terminate error text string
9736 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
9738 .append_drv_dot_num←2← 9620 JSR← 965E JSR
LDA #&20 ; A=' ': space separator
973A STA error_text,x ; Append space to error text
973D INX ; Advance error text index
973E STX fs_load_addr_2 ; Save position for number formatting
9740 LDY #3 ; Y=3: offset to network number in TX CB
9742 LDA (net_tx_ptr),y ; Load network number
9744 BEQ append_station_num ; Zero: skip network part (local)
9746 JSR append_decimal_num ; Append network number as decimal
9749 LDX fs_load_addr_2 ; Reload error text position
974B LDA #&2e ; A='.': dot separator
974D STA error_text,x ; Append dot to error text
9750 INC fs_load_addr_2 ; Advance past dot
9752 .append_station_num←1← 9744 BEQ
LDY #2 ; Y=2: offset to station number in TX CB
9754 LDA (net_tx_ptr),y ; Load station number
9756 JSR append_decimal_num ; Append station number as decimal
9759 LDX fs_load_addr_2 ; Reload error text position
975B 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)
975C .append_space_and_num←2← 972E JSR← B4CA JSR
TAY ; Save number in Y
975D LDA #&20 ; A=' ': space prefix
975F LDX fs_load_addr_2 ; Load current error text position
9761 STA error_text,x ; Append space to error text
9764 INC fs_load_addr_2 ; Advance position past space
9766 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)
9767 .append_decimal_num←2← 9746 JSR← 9756 JSR
TAY ; Save number in Y for division
9768 BIT bit_test_ff ; Set V: suppress leading zeros
976B LDA #&64 ; A=100: hundreds digit divisor
976D JSR append_decimal_digit ; Extract and append hundreds digit
9770 LDA #&0a ; A=10: tens digit divisor
9772 JSR append_decimal_digit ; Extract and append tens digit
9775 LDA #1 ; A=1: units digit (remainder)
9777 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
9778 .append_decimal_digit←2← 976D JSR← 9772 JSR
STA fs_load_addr_3 ; Store divisor
977A TYA ; Copy number to A for division
977B LDX #&2f ; X='0'-1: digit counter (ASCII offset)
977D PHP ; Save V flag (leading zero suppression)
977E SEC ; Set carry for subtraction
977F .loop_count_digit←1← 9782 BCS
INX ; Increment digit counter
9780 SBC fs_load_addr_3 ; Subtract divisor
9782 BCS loop_count_digit ; Not negative yet: continue counting
9784 ADC fs_load_addr_3 ; Add back divisor (restore remainder)
9786 PLP ; Restore V flag
9787 TAY ; Save remainder back to Y
9788 TXA ; Digit counter to A (ASCII digit)
9789 CMP #&30 ; Is digit '0'?
978B BNE store_digit ; Non-zero: always print
978D BVS return_from_store_digit ; V set (suppress leading zeros): skip
978F .store_digit←1← 978B BNE
CLV ; Clear V: first non-zero digit seen
9790 LDX fs_load_addr_2 ; Load current text position
9792 STA error_text,x ; Store ASCII digit in error text
9795 INC fs_load_addr_2 ; Advance text position
9797 .return_from_store_digit←1← 978D 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.
9798 .net_error_lookup_data←5← 9606 LDY← 9644 LDY← 9672 LDA← 9685 LDY← 971B LDA
EQUB error_msg_table - error_msg_table ; Class 0: &A0 "Line jammed"
9799 EQUB msg_net_error - error_msg_table ; Class 1: &A1 "Net error"
979A EQUB msg_station - error_msg_table ; Class 2: &A2 "Station"
979B EQUB msg_no_clock - error_msg_table ; Class 3: &A3 "No clock"
979C EQUB msg_escape - error_msg_table ; Class 4: &11 "Escape"
979D EQUB msg_escape - error_msg_table ; Class 5: &11 "Escape" (duplicate)
979E EQUB msg_escape - error_msg_table ; Class 6: &11 "Escape" (duplicate)
979F EQUB msg_bad_option - error_msg_table ; Class 7: &CB "Bad option"
97A0 EQUB msg_no_reply - error_msg_table ; Index 8: &A5 "No reply from station"
97A1 EQUB msg_not_listening - error_msg_table ; Index 9: " not listening" suffix
97A2 EQUB msg_on_channel - error_msg_table ; Index 10: " on channel" suffix
97A3 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".
97A4 .error_msg_table←8← 960E LDA← 9614 LDA← 9647 LDA← 9652 LDA← 9676 LDA← 968D LDA← 9693 LDA← 971F LDA
EQUB &A0 ; Error &A0: Line jammed
97A5 EQUS "Line jammed"
97B0 EQUB &00 ; Null terminator
97B1 .msg_net_error
EQUB &A1 ; Error &A1: Net error
97B2 EQUS "Net error"
97BB EQUB &00 ; Null terminator
97BC .msg_station
EQUB &A2 ; Error &A2: Station
97BD EQUS "Station"
97C4 EQUB &00 ; Null terminator
97C5 .msg_no_clock
EQUB &A3 ; Error &A3: No clock
97C6 EQUS "No clock"
97CE EQUB &00 ; Null terminator
97CF .msg_escape
EQUB &11 ; Error &11: Escape
97D0 EQUS "Escape"
97D6 EQUB &00 ; Null terminator
97D7 .msg_bad_option
EQUB &CB ; Error &CB: Bad option
97D8 EQUS "Bad option"
97E2 EQUB &00 ; Null terminator
97E3 .msg_no_reply
EQUB &A5 ; Error &A5: No reply from station
97E4 EQUS "No reply from station"
97F9 EQUB &00 ; Null terminator
97FA .msg_not_listening
EQUS " not listening" ; Suffix: " not listening"
9808 EQUB &00 ; Null terminator
9809 .msg_on_channel
EQUS " on channel" ; Suffix: " on channel"
9814 EQUB &00 ; Null terminator
9815 .msg_not_present
EQUS " not present" ; Suffix: " not present"
9821 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.

9822 .init_tx_ptr_and_send←2← 94D8 JSR← 9AC9 JSR
LDX #&c0 ; X=&C0: TX control block base (low)
9824 STX net_tx_ptr ; Set TX pointer low
9826 LDX #0 ; X=0: TX control block base (high)
9828 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.

982A .send_net_packet←7← A90C JSR← A965 JMP← A9C2 JSR← ABB4 JSR← AC3F JSR← B03B JSR← B216 JSR
LDA tx_retry_count ; Load retry count from workspace
982D BNE set_timeout ; Non-zero: use configured retry count
982F LDA #&ff ; A=&FF: default retry count (255)
9831 .set_timeout←1← 982D BNE
LDY #&60 ; Y=&60: timeout value
9833 PHA ; Push retry count
9834 TYA ; A=&60: copy timeout to A A=&60
9835 PHA ; Push timeout
9836 LDX #0 ; X=0: TX pointer index
9838 LDA (net_tx_ptr,x) ; Load first byte of TX control block
983A .start_tx_attempt←1← 985C BEQ
STA (net_tx_ptr,x) ; Write control byte back to CB
983C PHA ; Push control byte
983D JSR poll_adlc_tx_status ; Poll ADLC until line idle
9840 ASL ; Shift left: check bit 6 (success)
9841 BPL tx_success ; Bit 6 clear: transmission complete
9843 ASL ; Shift left: check bit 5 (fatal)
9844 BEQ tx_send_error ; Zero (bit 5 clear): fatal error
9846 JSR check_escape ; Check for escape condition
9849 PLA ; Pull control byte
984A TAX ; Restore to X
984B PLA ; Pull timeout
984C TAY ; Restore to Y
984D PLA ; Pull retry count
984E BEQ try_alternate_phase ; Zero retries remaining: try alternate
9850 .loop_retry_tx←1← 9867 BNE
SBC #1 ; Decrement retry counter
9852 PHA ; Push updated retry count
9853 TYA ; Copy timeout to A
9854 PHA ; Push timeout for delay loop
9855 TXA ; Copy control byte to A
9856 .loop_tx_delay←2← 9857 BNE← 985A BNE
DEX ; Inner delay: decrement X
9857 BNE loop_tx_delay ; Loop until X=0
9859 DEY ; Decrement outer counter Y
985A BNE loop_tx_delay ; Loop until Y=0
985C BEQ start_tx_attempt ; ALWAYS branch: retry transmission ALWAYS branch
985E .try_alternate_phase←1← 984E BEQ
CMP tx_retry_count ; Compare retry count with alternate
9861 BNE tx_send_error ; Different: go to error handling
9863 LDA #&80 ; A=&80: set escapable flag
9865 STA escapable ; Mark as escapable for second phase
9867 BNE loop_retry_tx ; ALWAYS branch: retry with escapable ALWAYS branch
9869 .tx_send_error←2← 9844 BEQ← 9861 BNE
TAX ; Result code to X
986A JMP load_reply_and_classify ; Jump to classify reply and return
986D .tx_success←1← 9841 BPL
PLA ; Pull control byte
986E PLA ; Pull timeout
986F PLA ; Pull retry count
9870 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.
9873 .pass_txbuf_init_table←2← 9889 LDX← 98E3 LDX
EQUB &88 ; Offset 0: ctrl = &88 (immediate TX)
9874 EQUB &00 ; Offset 1: port = &00 (immediate op)
9875 EQUB &FD ; Offset 2: &FD skip (preserve dest stn)
9876 EQUB &FD ; Offset 3: &FD skip (preserve dest net)
9877 EQUB &3A ; Offset 4: buf start lo (&3A)
9878 EQUB &0D ; Offset 5: buf start hi (&0D) -> &0D3A
9879 EQUB &FF ; Offset 6: extended addr fill (&FF)
987A EQUB &FF ; Offset 7: extended addr fill (&FF)
987B EQUB &3E ; Offset 8: buf end lo (&3E)
987C EQUB &0D ; Offset 9: buf end hi (&0D) -> &0D3E
987D EQUB &FF ; Offset 10: extended addr fill (&FF)
987E 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.

987F .init_tx_ptr_for_pass←1← 8DF4 JSR
LDY #&c0 ; Y=&C0: TX control block base (low)
9881 STY net_tx_ptr ; Set TX pointer low byte
9883 LDY #0 ; Y=0: TX control block base (high)
9885 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.

9887 .setup_pass_txbuf←1← ABB1 JSR
LDY #&0b ; Y=&0B: 12 bytes to process (0-11)
9889 .loop_copy_template←1← 9897 BPL
LDX pass_txbuf_init_table,y ; Load template byte for this offset
988C CPX #&fd ; Is it &FD (skip marker)?
988E BEQ skip_template_byte ; Yes: skip this offset, don't modify
9890 LDA (net_tx_ptr),y ; Load existing TX buffer byte
9892 PHA ; Save original value on stack
9893 TXA ; Copy template value to A
9894 STA (net_tx_ptr),y ; Store template value to TX buffer
9896 .skip_template_byte←1← 988E BEQ
DEY ; Next offset (descending)
9897 BPL loop_copy_template ; Loop until all 12 bytes processed
9899 LDA peek_retry_count ; Load pass-through control value
989C PHA ; Push control value
989D TYA ; A=&FF (Y is &FF after loop)
989E PHA ; Push &FF as timeout
989F LDX #0 ; X=0: TX pointer index
98A1 LDA (net_tx_ptr,x) ; Load control byte from TX CB
98A3 .start_pass_tx←1← 98DC BEQ
STA (net_tx_ptr,x) ; Write control byte to start TX
98A5 PHA ; Save control byte on stack
98A6 JSR poll_adlc_tx_status ; Poll ADLC until line idle
98A9 ASL ; Shift result: check bit 6 (success)
98AA BPL pass_tx_success ; Bit 6 clear: transmission complete
98AC ASL ; Shift result: check bit 5 (fatal)
98AD BNE restore_retry_state ; Non-zero (not fatal): retry
98AF .done_pass_retries←1← 98CE BEQ
LDX #0 ; X=0: clear error status
98B1 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.

98B4 .poll_adlc_tx_status←3← 983D JSR← 98A6 JSR← 98B7 BCC
ASL ws_0d60 ; Shift ws_0d60 left to poll ADLC
98B7 BCC poll_adlc_tx_status ; Bit not set: keep polling
98B9 LDA net_tx_ptr ; Copy TX pointer low to NMI TX block
98BB STA nmi_tx_block ; Store in NMI TX block low
98BD LDA net_tx_ptr_hi ; Copy TX pointer high
98BF STA nmi_tx_block_hi ; Store in NMI TX block high
98C1 JSR tx_begin ; Begin Econet frame transmission
98C4 .loop_poll_pass_tx←1← 98C6 BMI
LDA (net_tx_ptr,x) ; Read TX status byte
98C6 BMI loop_poll_pass_tx ; Bit 7 set: still transmitting
98C8 RTS ; Return with result in A
98C9 .restore_retry_state←1← 98AD BNE
PLA ; Pull control byte
98CA TAX ; Restore to X
98CB PLA ; Pull timeout
98CC TAY ; Restore to Y
98CD PLA ; Pull retry count
98CE BEQ done_pass_retries ; Zero retries: go to error handling
98D0 SBC #1 ; Decrement retry counter
98D2 PHA ; Push updated retry count
98D3 TYA ; Copy timeout to A
98D4 PHA ; Push timeout
98D5 TXA ; Copy control byte to A
98D6 .loop_pass_tx_delay←2← 98D7 BNE← 98DA BNE
DEX ; Inner delay loop: decrement X
98D7 BNE loop_pass_tx_delay ; Loop until X=0
98D9 DEY ; Decrement outer counter Y
98DA BNE loop_pass_tx_delay ; Loop until Y=0
98DC BEQ start_pass_tx ; ALWAYS branch: retry transmission ALWAYS branch
98DE .pass_tx_success←1← 98AA BPL
PLA ; Pull control byte (discard)
98DF PLA ; Pull timeout (discard)
98E0 PLA ; Pull retry count (discard)
98E1 LDY #0 ; Y=0: start restoring from offset 0
98E3 .loop_restore_txbuf←1← 98F0 BNE
LDX pass_txbuf_init_table,y ; Load template byte for this offset
98E6 CPX #&fd ; Is it &FD (skip marker)?
98E8 BEQ skip_restore_byte ; Yes: don't restore this offset
98EA PLA ; Pull original value from stack
98EB STA (net_tx_ptr),y ; Restore original TX buffer byte
98ED .skip_restore_byte←1← 98E8 BEQ
INY ; Next offset (ascending)
98EE CPY #&0c ; Processed all 12 bytes?
98F0 BNE loop_restore_txbuf ; No: continue restoring
98F2 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.

98F3 .load_text_ptr_and_parse←1← 9924 JSR
LDY #1 ; Y=1: start at second byte of pointer
98F5 .loop_copy_text_ptr←1← 98FB BPL
LDA (fs_options),y ; Load pointer byte from FS options
98F7 STA os_text_ptr,y ; Store in OS text pointer
98FA DEY ; Decrement index
98FB BPL loop_copy_text_ptr ; Loop until both bytes copied
98FD LDY #0 ; Y=0: reset command line offset
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.

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

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

9984 .send_txcb_swap_addrs←2← 9973 JSR← 9F40 JSR
JSR cmp_5byte_handle ; Compare 5-byte handle with current
9987 BEQ return_from_txcb_swap ; Match: no need to send, return
9989 LDA #&92 ; A=&92: FS reply port number
998B STA txcb_port ; Set TXCB port
998D .loop_swap_and_send←1← 99A9 BNE
LDX #3 ; X=3: copy 4 bytes
998F .loop_copy_start_end←1← 9998 BPL
LDA txcb_end,x ; Load TXCB end pointer byte
9991 STA txcb_start,x ; Store in TXCB start pointer
9993 LDA fs_work_4,x ; Load new end address from fs_work
9995 STA txcb_end,x ; Store in TXCB end pointer
9997 DEX ; Decrement counter
9998 BPL loop_copy_start_end ; Loop for all 4 bytes
999A LDA #&7f ; A=&7F: control byte for data transfer
999C STA txcb_ctrl ; Set TXCB control byte
999E JSR wait_net_tx_ack ; Wait for network TX acknowledgement
99A1 LDY #3 ; Y=3: compare 4 bytes
99A3 .loop_verify_addrs←1← 99AC BPL
LDA txcb_end,y ; Load TXCB end byte
99A6 EOR fs_work_4,y ; Compare with expected end address
99A9 BNE loop_swap_and_send ; Mismatch: resend from start
99AB DEY ; Decrement counter
99AC BPL loop_verify_addrs ; Loop until all 4 bytes match
99AE .return_from_txcb_swap←1← 9987 BEQ
RTS ; Return (all bytes match)
99AF .check_display_type←1← 992F BPL
BEQ setup_dir_display ; Z set: directory entry display
99B1 JMP dispatch_osword_op ; Non-zero: jump to OSWORD dispatch
99B4 .setup_dir_display←2← 99AF BEQ← 9AE6 JMP
LDX #4 ; X=4: loop counter for 4 iterations
99B6 LDY #&0e ; Y=&0E: FS options offset for addresses
99B8 SEC ; Set carry for subtraction
99B9 .loop_compute_diffs←1← 99D3 BNE
LDA (fs_options),y ; Load address byte from FS options
99BB STA port_ws_offset,y ; Save to workspace (port_ws_offset)
99BE JSR retreat_y_by_4 ; Y -= 4 to point to paired offset
99C1 SBC (fs_options),y ; Subtract paired value
99C3 STA fs_cmd_csd,y ; Store difference in l0f03 buffer
99C6 PHA ; Push difference
99C7 LDA (fs_options),y ; Load paired value from FS options
99C9 STA port_ws_offset,y ; Save to workspace
99CC PLA ; Pull difference back
99CD STA (fs_options),y ; Store in FS options for display
99CF JSR skip_one_and_advance5 ; Advance Y by 5 for next field
99D2 DEX ; Decrement loop counter
99D3 BNE loop_compute_diffs ; Loop for all 4 address pairs
99D5 LDY #9 ; Y=9: copy 9 bytes of options data
99D7 .loop_copy_fs_options←1← 99DD BNE
LDA (fs_options),y ; Load FS options byte
99D9 STA fs_cmd_csd,y ; Store in l0f03 buffer
99DC DEY ; Decrement index
99DD BNE loop_copy_fs_options ; Loop until all 9 bytes copied
99DF LDA #&91 ; A=&91: FS port for info request
99E1 STA escapable ; Set escapable flag
99E3 STA fs_cmd_urd ; Store port in TX buffer
99E6 STA fs_error_ptr ; Store in fs_error_ptr
99E8 LDX #&0b ; X=&0B: copy argument at offset 11
99EA JSR copy_arg_to_buf ; Copy argument to TX buffer
99ED LDY #1 ; Y=1: info sub-command
99EF LDA fs_last_byte_flag ; Load last byte flag
99F1 CMP #7 ; Is it 7 (catalogue info)?
99F3 PHP ; Save comparison result
99F4 BNE send_info_request ; Not 7: keep Y=1
99F6 LDY #&1d ; Y=&1D: extended info command
99F8 .send_info_request←1← 99F4 BNE
JSR send_request_write ; Send request to file server
99FB JSR format_filename_field ; Format filename for display
99FE PLP ; Restore comparison flags
99FF BNE setup_txcb_transfer ; Not catalogue info: show short format
9A01 LDX #0 ; X=0: start at first byte
9A03 BEQ store_result ; ALWAYS branch to store and display ALWAYS branch
9A05 .setup_txcb_transfer←1← 99FF BNE
LDA fs_cmd_data ; Load file handle from l0f05
9A08 JSR check_and_setup_txcb ; Check and set up TXCB for transfer
9A0B .recv_reply←1← 9981 JMP
JSR recv_and_process_reply ; Receive and process reply
9A0E .store_result←1← 9A03 BEQ
STX fs_reply_cmd ; Store result byte in l0f08
9A11 LDY #&0e ; Y=&0E: protection bits offset
9A13 LDA fs_cmd_data ; Load access byte from l0f05
9A16 JSR get_prot_bits ; Extract protection bit flags
9A19 BEQ store_prot_byte ; Zero: use reply buffer data
9A1B .loop_copy_file_info←1← 9A23 BNE
LDA fs_reply_data,y ; Load file info byte from l0ef7
9A1E .store_prot_byte←1← 9A19 BEQ
STA (fs_options),y ; Store in FS options at offset Y
9A20 INY ; Advance to next byte
9A21 CPY #&12 ; Y=&12: end of protection fields?
9A23 BNE loop_copy_file_info ; No: copy next byte
9A25 LDY fs_messages_flag ; Load display flag from l0e06
9A28 BEQ return_from_advance_y ; Zero: skip display, return
9A2A LDY #0 ; Y=0: filename character index
9A2C .loop_print_filename←1← 9A35 BNE
LDA filename_buf,y ; Load filename character from l10f3
9A2F JSR osasci ; Print character via OSASCI Write character
9A32 INY ; Advance to next character
9A33 CPY #&0c ; Printed all 12 characters?
9A35 BNE loop_print_filename ; No: print next character
9A37 LDY #5 ; Y=5: offset for access string
9A39 JSR print_5_hex_bytes ; Print 5 hex bytes (access info)
9A3C JSR print_load_exec_addrs ; Print load and exec addresses
9A3F JSR osnewl ; Print newline Write newline (characters 10 and 13)
9A42 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.

9A45 .print_load_exec_addrs←1← 9A3C JSR
LDY #9 ; Y=9: offset for exec address
9A47 JSR print_5_hex_bytes ; Print 5 hex bytes (exec address)
9A4A LDY #&0c ; Y=&0C: offset for length (3 bytes)
9A4C LDX #3 ; X=3: print 3 bytes only
9A4E 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)
9A50 .print_5_hex_bytes←2← 9A39 JSR← 9A47 JSR
LDX #4 ; X=4: print 5 bytes (4 to 0)
9A52 .loop_print_hex_byte←2← 9A4E BNE← 9A59 BNE
LDA (fs_options),y ; Load byte from FS options at offset Y
9A54 JSR print_hex_byte ; Print as 2-digit hex
9A57 DEY ; Decrement byte offset
9A58 DEX ; Decrement byte count
9A59 BNE loop_print_hex_byte ; Loop until all bytes printed
9A5B LDA #&20 ; A=' ': space separator
9A5D 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.

9A60 .copy_fsopts_to_zp←2← 994D JSR← 9958 JSR
LDY #5 ; Y=5: copy 4 bytes (offsets 2-5)
9A62 .loop_copy_fsopts_byte←1← 9A6A BCS
LDA (fs_options),y ; Load byte from FS options
9A64 STA work_ae,y ; Store in zero page at l00ae+Y
9A67 DEY ; Decrement index
9A68 CPY #2 ; Below offset 2?
9A6A 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.

9A6C .skip_one_and_advance5←1← 99CF 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
9A6D .advance_y_by_4←2← 9F1D JSR← B054 JSR
INY ; Y += 4
9A6E INY ; (continued)
9A6F INY ; (continued)
9A70 INY ; (continued)
9A71 .return_from_advance_y←1← 9A28 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.

9A72 .copy_workspace_to_fsopts←2← 9950 JSR← 9955 JSR
LDY #&0d ; Y=&0D: copy bytes from offset &0D down
9A74 TXA ; Transfer X to A
9A75 .loop_copy_ws_byte←1← 9A7D BCS
STA (fs_options),y ; Store byte in FS options at offset Y
9A77 LDA fs_cmd_urd,y ; Load next workspace byte from l0f02+Y
9A7A DEY ; Decrement index
9A7B CPY #2 ; Below offset 2?
9A7D 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
9A7F .retreat_y_by_4←1← 99BE 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
9A80 .retreat_y_by_3←2← 9AFC JSR← 9F25 JSR
DEY ; Y -= 3
9A81 DEY ; (continued)
9A82 DEY ; (continued)
9A83 RTS ; Return
9A84 .discard_handle_match←2← 9A8C BEQ← 9AD2 BCS
PLA ; Discard stacked value
9A85 LDY fs_block_offset ; Restore Y from fs_block_offset
9A87 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.

9A88 .check_and_setup_txcb←2← 9A08 JSR← 9F3B JSR
PHA ; Save port/sub-function on stack
9A89 JSR cmp_5byte_handle ; Compare 5-byte handle with current
9A8C BEQ discard_handle_match ; Match: discard port and return
9A8E .init_transfer_addrs←1← 9ADB BNE
LDX #0 ; X=0: loop start
9A90 LDY #4 ; Y=4: copy 4 bytes
9A92 STX fs_reply_cmd ; Clear l0f08 (transfer size low)
9A95 STX fs_load_vector ; Clear l0f09 (transfer size high)
9A98 CLC ; Clear carry for addition
9A99 .loop_copy_addr_offset←1← 9AA6 BNE
LDA fs_load_addr,x ; Load address byte from zero page
9A9B STA txcb_start,x ; Store in TXCB start pointer
9A9D ADC fs_func_code,x ; Add offset from l0f06
9AA0 STA txcb_end,x ; Store sum in TXCB end pointer
9AA2 STA fs_load_addr,x ; Also update load address
9AA4 INX ; Advance to next byte
9AA5 DEY ; Decrement counter
9AA6 BNE loop_copy_addr_offset ; Loop for all 4 bytes
9AA8 BCS clamp_end_to_limit ; Carry set: overflow, use limit
9AAA SEC ; Set carry for subtraction
9AAB .loop_check_vs_limit←1← 9AB3 BNE
LDA fs_load_addr,y ; Load computed end address
9AAE SBC fs_work_4,y ; Subtract maximum from fs_work_4
9AB1 INY ; Advance to next byte
9AB2 DEX ; Decrement counter
9AB3 BNE loop_check_vs_limit ; Loop for all bytes
9AB5 BCC set_port_and_ctrl ; Below limit: keep computed end
9AB7 .clamp_end_to_limit←1← 9AA8 BCS
LDX #3 ; X=3: copy 4 bytes of limit
9AB9 .loop_copy_limit←1← 9ABE BPL
LDA fs_work_4,x ; Load limit from fs_work_4
9ABB STA txcb_end,x ; Store as TXCB end
9ABD DEX ; Decrement counter
9ABE BPL loop_copy_limit ; Loop for all 4 bytes
9AC0 .set_port_and_ctrl←1← 9AB5 BCC
PLA ; Pull port from stack
9AC1 PHA ; Push back (keep for later)
9AC2 PHP ; Save flags (carry = overflow state)
9AC3 STA txcb_port ; Set TXCB port number
9AC5 LDA #&80 ; A=&80: control byte for data request
9AC7 STA txcb_ctrl ; Set TXCB control byte
9AC9 JSR init_tx_ptr_and_send ; Init TX pointer and send packet
9ACC LDA fs_error_ptr ; Load error pointer
9ACE JSR init_txcb_port ; Init TXCB port from error pointer
9AD1 PLP ; Restore overflow flags
9AD2 BCS discard_handle_match ; Carry set: discard and return
9AD4 LDA #&91 ; A=&91: FS reply port
9AD6 STA txcb_port ; Set TXCB port for reply
9AD8 JSR wait_net_tx_ack ; Wait for TX acknowledgement
9ADB BNE init_transfer_addrs ; Non-zero (not done): retry send
9ADD .dispatch_osword_op←1← 99B1 JMP
STA fs_cmd_data ; Store sub-operation code
9AE0 CMP #7 ; Compare with 7
9AE2 BCC dispatch_ops_1_to_6 ; Below 7: handle operations 1-6
9AE4 BNE skip_if_error ; Above 7: jump to handle via finalise
9AE6 JMP setup_dir_display ; Equal to 7: jump to directory display
9AE9 .dispatch_ops_1_to_6←1← 9AE2 BCC
CMP #6 ; Compare with 6
9AEB BEQ send_delete_request ; 6: delete file operation
9AED CMP #5 ; Compare with 5
9AEF BEQ read_cat_info ; 5: read catalogue info
9AF1 CMP #4 ; Compare with 4
9AF3 BEQ setup_write_access ; 4: write file attributes
9AF5 CMP #1 ; Compare with 1
9AF7 BEQ setup_save_access ; 1: read file info
9AF9 ASL ; Shift left twice: A*4
9AFA ASL ; A*4
9AFB TAY ; Copy to Y as index
9AFC JSR retreat_y_by_3 ; Y -= 3 to get FS options offset
9AFF LDX #3 ; X=3: copy 4 bytes
9B01 .loop_copy_fsopts_4←1← 9B08 BPL
LDA (fs_options),y ; Load byte from FS options at offset Y
9B03 STA fs_func_code,x ; Store in l0f06 buffer
9B06 DEY ; Decrement source offset
9B07 DEX ; Decrement byte count
9B08 BPL loop_copy_fsopts_4 ; Loop for all 4 bytes
9B0A LDX #5 ; X=5: copy arg to buffer at offset 5
9B0C BNE send_save_or_access ; ALWAYS branch to copy and send ALWAYS branch
9B0E .setup_save_access←1← 9AF7 BEQ
JSR get_access_bits ; Get access bits for file
9B11 STA fs_file_attrs ; Store access byte in l0f0e
9B14 LDY #9 ; Y=9: source offset in FS options
9B16 LDX #8 ; X=8: copy 8 bytes to buffer
9B18 .loop_copy_fsopts_8←1← 9B1F BNE
LDA (fs_options),y ; Load FS options byte
9B1A STA fs_cmd_data,x ; Store in l0f05 buffer
9B1D DEY ; Decrement source offset
9B1E DEX ; Decrement byte count
9B1F BNE loop_copy_fsopts_8 ; Loop for all 8 bytes
9B21 LDX #&0a ; X=&0A: buffer offset for argument
9B23 .send_save_or_access←2← 9B0C BNE← 9B42 BNE
JSR copy_arg_to_buf ; Copy argument to buffer
9B26 LDY #&13 ; Y=&13: OSWORD &13 (NFS operation)
9B28 BNE send_request_vset ; ALWAYS branch to send request ALWAYS branch
9B2A .send_delete_request←1← 9AEB BEQ
JSR copy_arg_to_buf_x0 ; Copy argument to buffer at X=0
9B2D LDY #&14 ; Y=&14: delete file command
9B2F .send_request_vset←1← 9B28 BNE
BIT bit_test_ff ; Set V flag (no directory check)
9B32 JSR save_net_tx_cb_vset ; Send request with V set
9B35 .skip_if_error←1← 9AE4 BNE
BCS done_osword_op ; Carry set: error, jump to finalise
9B37 JMP return_with_last_flag ; No error: return with last flag
9B3A .setup_write_access←1← 9AF3 BEQ
JSR get_access_bits ; Get access bits for file
9B3D STA fs_func_code ; Store in l0f06
9B40 LDX #2 ; X=2: buffer offset
9B42 BNE send_save_or_access ; ALWAYS branch to copy and send ALWAYS branch
9B44 .read_cat_info←1← 9AEF BEQ
LDX #1 ; X=1: buffer offset
9B46 JSR copy_arg_to_buf ; Copy argument to buffer
9B49 LDY #&12 ; Y=&12: open file command
9B4B JSR save_net_tx_cb ; Send open file request
9B4E LDA fs_obj_type ; Load reply handle from l0f11
9B51 STX fs_obj_type ; Clear l0f11
9B54 STX fs_len_clear ; Clear l0f14
9B57 JSR get_prot_bits ; Get protection bits
9B5A LDX fs_cmd_data ; Load file handle from l0f05
9B5D BEQ return_with_handle ; Zero: file not found, return
9B5F LDY #&0e ; Y=&0E: store access bits
9B61 STA (fs_options),y ; Store access byte in FS options
9B63 DEY ; Y=&0D Y=&0d
9B64 LDX #&0c ; X=&0C: copy 12 bytes of file info
9B66 .loop_copy_cat_info←1← 9B6D BNE
LDA fs_cmd_data,x ; Load reply byte from l0f05+X
9B69 STA (fs_options),y ; Store in FS options at offset Y
9B6B DEY ; Decrement destination offset
9B6C DEX ; Decrement source counter
9B6D BNE loop_copy_cat_info ; Loop for all 12 bytes
9B6F INX ; X=1 (INX from 0)
9B70 INX ; X=2
9B71 LDY #&11 ; Y=&11: FS options offset
9B73 .loop_copy_ext_info←1← 9B7A BPL
LDA fs_access_level,x ; Load extended info byte from l0f12
9B76 STA (fs_options),y ; Store in FS options
9B78 DEY ; Decrement destination offset
9B79 DEX ; Decrement source counter
9B7A BPL loop_copy_ext_info ; Loop until all copied
9B7C LDX fs_cmd_data ; Reload file handle
9B7F .return_with_handle←1← 9B5D BEQ
TXA ; Transfer to A
9B80 .done_osword_op←1← 9B35 BCS
JMP finalise_and_return ; Jump to finalise and return
; Unreachable dead code (3 bytes)
; Duplicate of the JMP at &9B80 immediately above.
; Unreachable after the unconditional JMP and
; unreferenced. Likely a development remnant.
9B83 JMP finalise_and_return ; Dead: duplicate JMP 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.

9B86 .format_filename_field←2← 9970 JSR← 99FB JSR
LDY #0 ; Y=0: destination index
9B88 LDX fs_cmd_csd ; Load source offset from l0f03
9B8B BNE copy_from_buf_entry ; Non-zero: copy from l0f05 buffer
9B8D .loop_copy_cmdline_char←1← 9B97 BNE
LDA (fs_crc_lo),y ; Load character from command line
9B8F CMP #&21 ; Below '!' (control/space)?
9B91 BCC pad_with_spaces ; Yes: pad with spaces
9B93 STA filename_buf,y ; Store printable character in l10f3
9B96 INY ; Advance to next character
9B97 BNE loop_copy_cmdline_char ; Loop for more characters
9B99 .pad_with_spaces←2← 9B91 BCC← 9BA1 BCC
LDA #&20 ; A=' ': space for padding
9B9B STA filename_buf,y ; Store space in display buffer
9B9E INY ; Advance index
9B9F CPY #&0c ; Filled all 12 characters?
9BA1 BCC pad_with_spaces ; No: pad more spaces
9BA3 RTS ; Return with field formatted
9BA4 .loop_copy_buf_char←1← 9BAC BPL
INX ; Advance source and destination
9BA5 INY ; INY
9BA6 .copy_from_buf_entry←1← 9B8B BNE
LDA fs_cmd_data,x ; Load byte from l0f05 buffer
9BA9 STA filename_buf,y ; Store in display buffer l10f3
9BAC BPL loop_copy_buf_char ; Bit 7 clear: more characters
9BAE RTS ; Return (bit 7 set = terminator)
9BAF JSR verify_ws_checksum ; Verify workspace checksum
9BB2 STA fs_last_byte_flag ; Store result as last byte flag
9BB4 JSR set_options_ptr ; Set FS options pointer
9BB7 ORA #0 ; OR with 0 to set flags
9BB9 BPL dispatch_osfind_op ; Positive: handle sub-operations
9BBB ASL ; Shift left to check bit 6
9BBC BEQ validate_chan_close ; Zero (was &80): close channel
9BBE JMP close_all_fcbs ; Other: process all FCBs first
9BC1 .validate_chan_close←1← 9BBC BEQ
TYA ; Transfer Y to A
9BC2 CMP #&20 ; Compare with &20 (space)
9BC4 BCS check_chan_range ; Above &20: check further
9BC6 .error_invalid_chan←1← 9BCB BCS
JMP err_net_chan_invalid ; Below &20: invalid channel char
9BC9 .check_chan_range←1← 9BC4 BCS
CMP #&30 ; Compare with '0'
9BCB BCS error_invalid_chan ; Above '0': invalid channel char
9BCD JSR process_all_fcbs ; Process all matching FCBs
9BD0 TYA ; Transfer Y to A (FCB index)
9BD1 PHA ; Push FCB index
9BD2 TAX ; Copy to X
9BD3 LDY #0 ; Y=0: clear counter
9BD5 STY fs_last_byte_flag ; Clear last byte flag
9BD7 STY fs_block_offset ; Clear block offset
9BD9 .loop_copy_fcb_fields←1← 9BE4 BNE
LDA fcb_attr_or_count_mid,x ; Load channel data from l1010+X
9BDC STA (fs_options),y ; Store in FS options at Y
9BDE JSR advance_x_by_8 ; Advance X by 8 (next FCB field)
9BE1 INY ; Advance destination index
9BE2 CPY #4 ; Copied all 4 channel fields?
9BE4 BNE loop_copy_fcb_fields ; No: copy next field
9BE6 PLA ; Pull saved FCB index
9BE7 STA fs_block_offset ; Restore to fs_block_offset
9BE9 .dispatch_osfind_op←1← 9BB9 BPL
CMP #5 ; Compare with 5
9BEB BCS done_return_flag ; 5 or above: return with last flag
9BED CPY #0 ; Compare Y with 0
9BEF BNE osfind_with_channel ; Non-zero: handle OSFIND with channel
9BF1 JMP osfind_close_or_open ; Y=0 (close): jump to OSFIND open
9BF4 .osfind_with_channel←1← 9BEF BNE
PHA ; Push sub-function
9BF5 TXA ; Transfer X to A
9BF6 PHA ; Push X (FCB slot)
9BF7 TYA ; Transfer Y to A
9BF8 PHA ; Push Y (channel char)
9BF9 JSR check_not_dir ; Check file is not a directory
9BFC PLA ; Pull channel char
9BFD LDY #&0e ; Y=&0E: error code offset
9BFF STA (net_rx_ptr),y ; Store channel char in RX buffer
9C01 LDA fcb_net_or_port,x ; Load FCB flag byte from l1030
9C04 STA fs_cmd_data ; Store in l0f05
9C07 PLA ; Pull X (FCB slot)
9C08 TAX ; Restore X
9C09 PLA ; Pull sub-function
9C0A LSR ; Shift right: check bit 0
9C0B BEQ osargs_ptr_dispatch ; Zero (OSFIND close): handle close
9C0D PHP ; Save flags (carry from LSR)
9C0E PHA ; Push sub-function
9C0F LDX fs_options ; Load FS options pointer low
9C11 LDY fs_block_offset ; Load block offset
9C13 JSR process_all_fcbs ; Process all matching FCBs
9C16 LDA fcb_attr_or_count_mid,y ; Load updated data from l1010
9C19 STA fs_cmd_data ; Store in l0f05
9C1C PLA ; Pull sub-function
9C1D STA fs_func_code ; Store in l0f06
9C20 PLP ; Restore flags
9C21 TYA ; Transfer Y to A
9C22 PHA ; Push Y (offset)
9C23 BCC osargs_read_op ; Carry clear: read operation
9C25 LDY #3 ; Y=3: copy 4 bytes
9C27 .loop_copy_zp_to_buf←1← 9C2E BPL
LDA zp_work_3,x ; Load zero page data
9C29 STA fs_data_count,y ; Store in l0f07 buffer
9C2C DEX ; Decrement source
9C2D DEY ; Decrement counter
9C2E BPL loop_copy_zp_to_buf ; Loop for all 4 bytes
9C30 LDY #&0d ; Y=&0D: TX buffer size
9C32 LDX #5 ; X=5: argument offset
9C34 JSR save_net_tx_cb ; Send TX control block to server
9C37 STX fs_last_byte_flag ; Store X in last byte flag
9C39 PLA ; Pull saved offset
9C3A JSR set_conn_active ; Set connection active flag
9C3D .done_return_flag←1← 9BEB BCS
JMP return_with_last_flag ; Return with last flag
9C40 .osargs_read_op←1← 9C23 BCC
LDY #&0c ; Y=&0C: TX buffer size (smaller)
9C42 LDX #2 ; X=2: argument offset
9C44 JSR save_net_tx_cb ; Send TX control block
9C47 STA fs_last_byte_flag ; Store A in last byte flag
9C49 LDX fs_options ; Load FS options pointer low
9C4B LDY #2 ; Y=2: zero page offset
9C4D STA zp_work_3,x ; Store A in zero page
9C4F .loop_copy_reply_to_zp←1← 9C56 BPL
LDA fs_cmd_data,y ; Load buffer byte from l0f05+Y
9C52 STA zp_work_2,x ; Store in zero page at offset
9C54 DEX ; Decrement source X
9C55 DEY ; Decrement counter Y
9C56 BPL loop_copy_reply_to_zp ; Loop until all bytes copied
9C58 PLA ; Pull saved offset
9C59 JMP return_with_last_flag ; Return with last flag
9C5C .osargs_ptr_dispatch←1← 9C0B BEQ
BCS osargs_write_ptr ; Carry set: write file pointer
9C5E LDA fs_block_offset ; Load block offset
9C60 JSR attr_to_chan_index ; Convert attribute to channel index
9C63 LDY fs_options ; Load FS options pointer
9C65 LDA fcb_count_lo,x ; Load FCB low byte from l1000
9C68 STA zp_ptr_lo,y ; Store in zero page pointer low
9C6B LDA fcb_attr_or_count_mid,x ; Load FCB high byte from l1010
9C6E STA zp_ptr_hi,y ; Store in zero page pointer high
9C71 LDA fcb_station_or_count_hi,x ; Load FCB extent from l1020
9C74 STA zp_work_2,y ; Store in zero page work area
9C77 LDA #0 ; A=0: clear high byte
9C79 STA zp_work_3,y ; Store zero in work area high
9C7C BEQ return_with_last_flag ; ALWAYS branch to return with flag ALWAYS branch
9C7E .osargs_write_ptr←1← 9C5C BCS
STA fs_func_code ; Store write value in l0f06
9C81 TXA ; Transfer X to A
9C82 PHA ; Push X (zero page offset)
9C83 LDY #3 ; Y=3: copy 4 bytes
9C85 .loop_copy_ptr_to_buf←1← 9C8C BPL
LDA zp_work_3,x ; Load zero page data at offset
9C87 STA fs_data_count,y ; Store in l0f07 buffer
9C8A DEX ; Decrement source
9C8B DEY ; Decrement counter
9C8C BPL loop_copy_ptr_to_buf ; Loop for all 4 bytes
9C8E LDY #&0d ; Y=&0D: TX buffer size
9C90 LDX #5 ; X=5: argument offset
9C92 JSR save_net_tx_cb ; Send TX control block
9C95 STX fs_last_byte_flag ; Store X in last byte flag
9C97 PLA ; Pull saved zero page offset
9C98 TAY ; Transfer to Y
9C99 LDA fs_block_offset ; Load block offset (attribute)
9C9B JSR clear_conn_active ; Clear connection active flag
9C9E JSR attr_to_chan_index ; Convert attribute to channel index
9CA1 LDA zp_ptr_lo,y ; Load zero page pointer low
9CA4 STA fcb_count_lo,x ; Store back to FCB l1000
9CA7 LDA zp_ptr_hi,y ; Load zero page pointer high
9CAA STA fcb_attr_or_count_mid,x ; Store back to FCB l1010
9CAD LDA zp_work_2,y ; Load zero page work byte
9CB0 STA fcb_station_or_count_hi,x ; Store back to FCB l1020
9CB3 JMP return_with_last_flag ; Return with last flag
9CB6 .close_all_fcbs←1← 9BBE JMP
JSR process_all_fcbs ; Process all matching FCBs first
9CB9 .return_with_last_flag←12← 9935 JMP← 9A42 JMP← 9B37 JMP← 9C3D JMP← 9C59 JMP← 9C7C BEQ← 9CB3 JMP← 9DAF JMP← 9E36 JMP← A2DF JMP← A2E5 JMP← A399 JMP
LDA fs_last_byte_flag ; Load last byte flag
9CBB .finalise_and_return←7← 9B80 JMP← 9B83 JMP← 9CD1 BNE← 9CE4 BPL← 9D7B JMP← 9EBD JMP← A1EE JMP
PHA ; Push result on stack
9CBC LDA #0 ; A=0: clear error flag
9CBE LDY #&0e ; Y=&0E: error code offset
9CC0 STA (net_rx_ptr),y ; Clear error code in RX buffer
9CC2 PLA ; Pull result back
9CC3 LDX fs_options ; Restore X from FS options pointer
9CC5 LDY fs_block_offset ; Restore Y from block offset
9CC7 RTS ; Return to caller
9CC8 .osfind_close_or_open←1← 9BF1 JMP
CMP #2 ; Compare with 2 (open for output)
9CCA BCS done_file_open ; 2 or above: handle file open
9CCC TAY ; Transfer to Y (Y=0 or 1)
9CCD BNE loop_copy_reply_data ; Non-zero (1 = read pointer): copy data
9CCF LDA #5 ; A=5: return code for close-all
9CD1 BNE finalise_and_return ; ALWAYS branch to finalise ALWAYS branch
9CD3 .loop_copy_reply_data←2← 9CCD BNE← 9CD9 BPL
LDA fs_cmd_context,y ; Load reply data byte at Y
9CD6 STA (fs_options),y ; Store in FS options
9CD8 DEY ; Decrement index
9CD9 BPL loop_copy_reply_data ; Loop until all bytes copied
9CDB STY zp_work_2,x ; Clear zero page work low
9CDD STY zp_work_3,x ; Clear zero page work high
9CDF .done_file_open←1← 9CCA BCS
BEQ shift_and_finalise ; Z set: jump to clear A and return
9CE1 .clear_result←2← 9CE8 BNE← 9FF4 JMP
LDA #0 ; A=0: clear result
9CE3 .shift_and_finalise←1← 9CDF BEQ
LSR ; Shift right (always positive)
9CE4 BPL finalise_and_return ; Positive: jump to finalise
9CE6 .alloc_fcb_for_open←1← 9D55 BEQ
AND #&3f ; Mask to 6-bit access value
9CE8 BNE clear_result ; Non-zero: clear A and finalise
9CEA TXA ; Transfer X to A (options pointer)
9CEB JSR alloc_fcb_or_error ; Allocate FCB slot or raise error
9CEE EOR #&80 ; Toggle bit 7
9CF0 ASL ; Shift left: build open mode
9CF1 STA fs_cmd_data ; Store open mode in l0f05
9CF4 ROL ; Rotate to complete mode byte
9CF5 STA fs_func_code ; Store in l0f06
9CF8 JSR parse_cmd_arg_y0 ; Parse command argument (Y=0)
9CFB LDX #2 ; X=2: buffer offset
9CFD JSR copy_arg_to_buf ; Copy argument to TX buffer
9D00 LDY #6 ; Y=6: open file command
9D02 BIT bit_test_ff ; Set V flag (skip directory check)
9D05 SEC ; Set carry
9D06 ROR escapable ; Rotate carry into escapable flag bit 7
9D08 JSR save_net_tx_cb_vset ; Send open request with V set
9D0B BCS done_osfind ; Carry set (error): jump to finalise
9D0D LDA #&ff ; A=&FF: mark as newly opened
9D0F LDY #&0e ; Y=&0E: error code offset
9D11 STA (net_rx_ptr),y ; Store &FF as error flag in RX buffer
9D13 LDA fs_cmd_data ; Load handle from l0f05
9D16 PHA ; Push handle
9D17 LDA #4 ; A=4: file info sub-command
9D19 STA fs_cmd_data ; Store sub-command
9D1C LDX #1 ; X=1: shift filename
9D1E .loop_shift_filename←1← 9D27 BNE
LDA fs_func_code,x ; Load filename byte from l0f06+X
9D21 STA fs_cmd_data,x ; Shift down to l0f05+X
9D24 INX ; Advance source index
9D25 CMP #&0d ; Is it CR (end of filename)?
9D27 BNE loop_shift_filename ; No: continue shifting
9D29 LDY #&12 ; Y=&12: file info request
9D2B JSR save_net_tx_cb ; Send file info request
9D2E LDA fs_last_byte_flag ; Load last byte flag
9D30 AND #&bf ; Clear bit 6 (read/write bits)
9D32 ORA fs_cmd_data ; OR with reply access byte
9D35 ORA #1 ; Set bit 0 (file is open)
9D37 TAY ; Transfer to Y (access flags)
9D38 AND #2 ; Check bit 1 (write access)
9D3A BEQ check_open_mode ; No write access: check read-only
9D3C PLA ; Pull handle from stack
9D3D JSR alloc_fcb_slot ; Allocate FCB slot for channel
9D40 BNE store_fcb_flags ; Non-zero: FCB allocated, store flags
9D42 JSR verify_ws_checksum ; Verify workspace checksum
9D45 JSR set_xfer_params ; Set up transfer parameters
9D48 TAX ; Transfer A to X
9D49 JSR mask_owner_access ; Set owner-only access mask
9D4C TXA ; Transfer X back to A
9D4D BEQ close_all_channels ; Zero: close file, process FCBs
9D4F JSR save_ptr_to_os_text ; Save text pointer for OS
9D52 LDY cur_dir_handle ; Load current directory handle
9D55 BEQ alloc_fcb_for_open ; Zero: allocate new FCB
9D57 TYA ; Transfer Y to A
9D58 LDX #0 ; X=0: clear directory handle
9D5A STX cur_dir_handle ; Store zero (clear handle)
9D5D BEQ done_osfind ; ALWAYS branch to finalise ALWAYS branch
9D5F .check_open_mode←1← 9D3A BEQ
LDA fs_func_code ; Load access/open mode byte
9D62 ROR ; Rotate right: check bit 0
9D63 BCS alloc_fcb_with_flags ; Carry set (bit 0): check read permission
9D65 ROR ; Rotate right: check bit 1
9D66 BCC alloc_fcb_with_flags ; Carry clear (no write): skip
9D68 BIT fs_data_count ; Test bit 7 of l0f07 (lock flag)
9D6B BPL alloc_fcb_with_flags ; Not locked: skip
9D6D TYA ; Transfer Y to A (flags)
9D6E ORA #&20 ; Set bit 5 (locked file flag)
9D70 TAY ; Transfer back to Y
9D71 .alloc_fcb_with_flags←3← 9D63 BCS← 9D66 BCC← 9D6B BPL
PLA ; Pull handle from stack
9D72 JSR alloc_fcb_slot ; Allocate FCB slot for channel
9D75 .store_fcb_flags←1← 9D40 BNE
TAX ; Transfer to X
9D76 TYA ; Transfer Y to A (flags)
9D77 STA fcb_flags,x ; Store flags in FCB table l1040
9D7A TXA ; Transfer X back to A (handle)
9D7B .done_osfind←2← 9D0B BCS← 9D5D BEQ
JMP finalise_and_return ; Jump to finalise and return
9D7E .close_all_channels←1← 9D4D BEQ
JSR process_all_fcbs ; Process all matching FCBs
9D81 TYA ; Transfer Y to A
9D82 BNE close_specific_chan ; Non-zero channel: close specific
9D84 LDA fs_options ; Load FS options pointer low
9D86 PHA ; Push (save for restore)
9D87 LDA #osbyte_close_spool_exec ; A=&77: OSBYTE close spool/exec files
9D89 JSR osbyte ; Close any *SPOOL and *EXEC files
9D8C PLA ; Pull saved options pointer
9D8D STA fs_options ; Restore FS options pointer
9D8F LDA #0 ; A=0: clear flags
9D91 STA fs_last_byte_flag ; Clear last byte flag
9D93 STA fs_block_offset ; Clear block offset
9D95 BEQ send_close_request ; ALWAYS branch to send close request ALWAYS branch
9D97 .close_specific_chan←1← 9D82 BNE
JSR check_chan_char ; Validate channel character
9D9A LDA fcb_net_or_port,x ; Load FCB flag byte from l1030
9D9D .send_close_request←1← 9D95 BEQ
STA fs_cmd_data ; Store as l0f05 (file handle)
9DA0 LDX #1 ; X=1: argument size
9DA2 LDY #7 ; Y=7: close file command
9DA4 JSR save_net_tx_cb ; Send close file request
9DA7 LDY fs_block_offset ; Load block offset
9DA9 BNE clear_single_fcb ; Non-zero: clear single FCB
9DAB CLV ; Clear V flag
9DAC JSR scan_fcb_flags ; Scan and clear all FCB flags
9DAF .done_close←3← 9DBA BEQ← 9DCC BCC← 9DE0 BPL
JMP return_with_last_flag ; Return with last flag
9DB2 .clear_single_fcb←1← 9DA9 BNE
LDA #0 ; A=0: clear FCB entry
9DB4 STA fcb_attr_or_count_mid,y ; Clear l1010 (FCB high byte)
9DB7 STA fcb_flags,y ; Clear l1040 (FCB flags)
9DBA BEQ done_close ; ALWAYS branch to return ALWAYS branch
9DBC .fscv_0_opt_entry
BEQ store_display_flag ; Z set: handle OSARGS 0
9DBE CPX #4 ; Compare X with 4 (number of args)
9DC0 BNE osargs_dispatch ; Not 4: check for error
9DC2 CPY #4 ; Compare Y with 4
9DC4 BCC send_osargs_request ; Below 4: handle special OSARGS
9DC6 .osargs_dispatch←1← 9DC0 BNE
DEX ; Decrement X
9DC7 BNE error_osargs ; X was 1: store display flag
9DC9 .store_display_flag←1← 9DBC BEQ
STY fs_messages_flag ; Store Y in display control flag l0e06
9DCC BCC done_close ; Carry clear: return with flag
9DCE .error_osargs←1← 9DC7 BNE
LDA #7 ; A=7: error code
9DD0 JMP classify_reply_error ; Jump to classify reply error
9DD3 .send_osargs_request←1← 9DC4 BCC
STY fs_cmd_data ; Store Y in l0f05
9DD6 LDY #&16 ; Y=&16: OSARGS save command
9DD8 JSR save_net_tx_cb ; Send OSARGS request
9DDB LDY fs_block_offset ; Reload block offset
9DDD STY fs_boot_option ; Store in l0e05
9DE0 BPL done_close ; Positive: return with flag
9DE2 .fscv_1_eof
JSR verify_ws_checksum ; Verify workspace checksum
9DE5 PHA ; Push result on stack
9DE6 LDA fs_block_offset ; Load block offset
9DE8 PHA ; Push block offset
9DE9 STX cur_chan_attr ; Store X in l10c9
9DEC JSR find_matching_fcb ; Find matching FCB entry
9DEF BEQ mark_not_found ; Zero: no match found
9DF1 LDA fcb_count_lo,y ; Load FCB low byte from l1000
9DF4 CMP fcb_buf_offset,x ; Compare with stored offset l1098
9DF7 BCC mark_not_found ; Below stored: no match
9DF9 LDX #&ff ; X=&FF: mark as found (all bits set)
9DFB BMI restore_and_return ; ALWAYS branch (negative) ALWAYS branch
9DFD .mark_not_found←2← 9DEF BEQ← 9DF7 BCC
LDX #0 ; X=0: mark as not found
9DFF .restore_and_return←1← 9DFB BMI
PLA ; Restore block offset from stack
9E00 TAY ; Transfer to Y
9E01 PLA ; Restore result from stack
9E02 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).

9E03 .update_addr_from_offset9←1← 9F48 JSR
LDY #9 ; Y=9: FS options offset for high address
9E05 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
9E08 .update_addr_from_offset1←1← A03B 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
9E0A .add_workspace_to_fsopts←1← 9E05 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
9E0B .adjust_fsopts_4bytes←2← 9F4E JSR← A047 JSR
LDX #&fc ; X=&FC: loop counter (-4 to -1)
9E0D .loop_adjust_byte←1← 9E20 BNE
LDA (fs_options),y ; Load FS options byte at offset Y
9E0F BIT fs_load_addr_2 ; Test fs_load_addr_2 bit 7 (add/subtract)
9E11 BMI subtract_ws_byte ; Bit 7 set: subtract instead
9E13 ADC fs_cmd_context,x ; Add workspace byte to FS options
9E16 JMP store_adjusted_byte ; Jump to store result
9E19 .subtract_ws_byte←1← 9E11 BMI
SBC fs_cmd_context,x ; Subtract workspace byte from FS options
9E1C .store_adjusted_byte←1← 9E16 JMP
STA (fs_options),y ; Store result back to FS options
9E1E INY ; Advance to next byte
9E1F INX ; Advance counter
9E20 BNE loop_adjust_byte ; Loop until 4 bytes processed
9E22 RTS ; Return
9E23 JSR verify_ws_checksum ; Verify workspace checksum
9E26 JSR set_xfer_params ; Set up transfer parameters
9E29 PHA ; Push transfer type on stack
9E2A JSR mask_owner_access ; Set owner-only access mask
9E2D PLA ; Pull transfer type
9E2E TAX ; Transfer to X
9E2F BEQ skip_if_out_of_range ; Zero: no valid operation, return
9E31 DEX ; Decrement (convert 1-based to 0-based)
9E32 CPX #8 ; Compare with 8 (max operation)
9E34 BCC valid_osgbpb_op ; Below 8: valid operation
9E36 .skip_if_out_of_range←1← 9E2F BEQ
JMP return_with_last_flag ; Out of range: return with flag
9E39 .valid_osgbpb_op←1← 9E34 BCC
TXA ; Transfer operation code to A
9E3A LDY #0 ; Y=0: buffer offset
9E3C PHA ; Push operation code
9E3D CMP #4 ; Compare with 4 (write operations)
9E3F BCC load_chan_handle ; Below 4: read operation
9E41 JMP write_block_entry ; 4 or above: write data block
9E44 .load_chan_handle←1← 9E3F BCC
LDA (fs_options),y ; Load channel handle from FS options
9E46 PHA ; Push handle
9E47 JSR check_not_dir ; Check file is not a directory
9E4A PLA ; Pull handle
9E4B TAY ; Transfer to Y
9E4C JSR process_all_fcbs ; Process all matching FCBs
9E4F LDA fcb_net_or_port,x ; Load FCB flag byte from l1030
9E52 STA fs_cmd_data ; Store file handle in l0f05
9E55 LDA #0 ; A=0: clear direction flag
9E57 STA fs_func_code ; Store in l0f06
9E5A LDA fcb_count_lo,x ; Load FCB low byte (position)
9E5D STA fs_data_count ; Store in l0f07
9E60 LDA fcb_attr_or_count_mid,x ; Load FCB high byte
9E63 STA fs_reply_cmd ; Store in l0f08
9E66 LDA fcb_station_or_count_hi,x ; Load FCB extent byte
9E69 STA fs_load_vector ; Store in l0f09
9E6C LDY #&0d ; Y=&0D: TX buffer size
9E6E LDX #5 ; X=5: argument count
9E70 JSR save_net_tx_cb ; Send TX control block to server
9E73 PLA ; Pull operation code
9E74 JSR setup_transfer_workspace ; Set up transfer workspace
9E77 PHP ; Save flags (carry from setup)
9E78 LDY #0 ; Y=0: index for channel handle
9E7A LDA (fs_options),y ; Load channel handle from FS options
9E7C BCS set_write_active ; Carry set (write): set active
9E7E JSR clear_conn_active ; Read: clear connection active
9E81 BPL setup_gbpb_request ; Branch to continue (always positive)
9E83 .set_write_active←1← 9E7C BCS
JSR set_conn_active ; Write: set connection active
9E86 .setup_gbpb_request←1← 9E81 BPL
STY fs_func_code ; Clear l0f06 (Y=0)
9E89 JSR lookup_cat_slot_data ; Look up channel slot data
9E8C STA fs_cmd_data ; Store flag byte in l0f05
9E8F LDY #&0c ; Y=&0C: TX buffer size (short)
9E91 LDX #2 ; X=2: argument count
9E93 JSR save_net_tx_cb ; Send TX control block
9E96 JSR lookup_cat_entry_0 ; Look up channel entry at Y=0
9E99 LDY #9 ; Y=9: FS options offset for position
9E9B LDA fs_cmd_data ; Load new position low from l0f05
9E9E STA fcb_count_lo,x ; Update FCB low byte in l1000
9EA1 STA (fs_options),y ; Store in FS options at Y=9
9EA3 INY ; Y=&0A Y=&0a
9EA4 LDA fs_func_code ; Load new position high from l0f06
9EA7 STA fcb_attr_or_count_mid,x ; Update FCB high byte in l1010
9EAA STA (fs_options),y ; Store in FS options at Y=&0A
9EAC INY ; Y=&0B Y=&0b
9EAD LDA fs_data_count ; Load new extent from l0f07
9EB0 STA fcb_station_or_count_hi,x ; Update FCB extent in l1020
9EB3 STA (fs_options),y ; Store in FS options at Y=&0B
9EB5 LDA #0 ; A=0: clear high byte of extent
9EB7 INY ; Y=&0C Y=&0c
9EB8 STA (fs_options),y ; Store zero in FS options at Y=&0C
9EBA PLP ; Restore flags
9EBB LDA #0 ; A=0: success
9EBD 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
9EC0 .lookup_cat_entry_0←2← 9E96 JSR← 9ECC JSR
LDY #0 ; Y=0: offset for channel handle
9EC2 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
9EC4 .lookup_cat_slot_data←1← 9E89 JSR
JSR lookup_chan_by_char ; Look up channel by character
9EC7 LDA fcb_net_or_port,x ; Load FCB flag byte from l1030
9ECA 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.

9ECB .setup_transfer_workspace←2← 9E74 JSR← B97C JMP
PHA ; Push operation code on stack
9ECC JSR lookup_cat_entry_0 ; Look up channel entry at Y=0
9ECF STA fs_cmd_data ; Store flag byte in l0f05
9ED2 LDY #&0b ; Y=&0B: source offset in FS options
9ED4 LDX #6 ; X=6: copy 6 bytes
9ED6 .loop_copy_opts_to_buf←1← 9EE2 BNE
LDA (fs_options),y ; Load FS options byte
9ED8 STA fs_func_code,x ; Store in l0f06 buffer
9EDB DEY ; Decrement source index
9EDC CPY #8 ; Skip offset 8?
9EDE BNE skip_struct_hole ; No: continue copy
9EE0 DEY ; Skip offset 8 (hole in structure)
9EE1 .skip_struct_hole←1← 9EDE BNE
DEX ; Decrement destination counter
9EE2 BNE loop_copy_opts_to_buf ; Loop until all 6 bytes copied
9EE4 PLA ; Pull operation code
9EE5 LSR ; Shift right: check bit 0 (direction)
9EE6 PHA ; Push updated code
9EE7 BCC store_direction_flag ; Carry clear: OSBGET (read)
9EE9 INX ; Carry set: OSBPUT (write), X=1
9EEA .store_direction_flag←1← 9EE7 BCC
STX fs_func_code ; Store direction flag in l0f06
9EED LDY #&0b ; Y=&0B: TX buffer size
9EEF LDX #&91 ; X=&91: port for OSBGET
9EF1 PLA ; Pull operation code
9EF2 PHA ; Push back (keep on stack)
9EF3 BEQ store_port_and_send ; Zero (OSBGET): keep port &91
9EF5 LDX #&92 ; X=&92: port for OSBPUT
9EF7 DEY ; Y=&0A: adjusted buffer size Y=&0a
9EF8 .store_port_and_send←1← 9EF3 BEQ
STX fs_cmd_urd ; Store port in l0f02
9EFB STX fs_error_ptr ; Store port in fs_error_ptr
9EFD LDX #8 ; X=8: argument count
9EFF LDA fs_cmd_data ; Load file handle from l0f05
9F02 JSR send_request_nowrite ; Send request (no write data)
9F05 LDX #0 ; X=0: index
9F07 LDA (fs_options,x) ; Load channel handle from FS options
9F09 TAX ; Transfer to X as index
9F0A LDA fcb_flags,x ; Load FCB flags from l1040
9F0D EOR #1 ; Toggle bit 0 (transfer direction)
9F0F STA fcb_flags,x ; Store updated flags
9F12 CLC ; Clear carry for addition
9F13 LDX #4 ; X=4: process 4 address bytes
9F15 .loop_setup_addr_bytes←1← 9F29 BNE
LDA (fs_options),y ; Load FS options address byte
9F17 STA addr_work,y ; Store in zero page address area
9F1A STA txcb_pos,y ; Store in TXCB position
9F1D JSR advance_y_by_4 ; Advance Y by 4
9F20 ADC (fs_options),y ; Add offset from FS options
9F22 STA addr_work,y ; Store computed end address
9F25 JSR retreat_y_by_3 ; Retreat Y by 3 for next pair
9F28 DEX ; Decrement byte counter
9F29 BNE loop_setup_addr_bytes ; Loop for all 4 address bytes
9F2B INX ; X=1 (INX from 0)
9F2C .loop_copy_offset←1← 9F33 BPL
LDA fs_cmd_csd,x ; Load offset from l0f03
9F2F STA fs_func_code,x ; Copy to l0f06
9F32 DEX ; Decrement counter
9F33 BPL loop_copy_offset ; Loop until both bytes copied
9F35 PLA ; Pull operation code
9F36 BNE send_with_swap ; Non-zero (OSBPUT): swap addresses
9F38 LDA fs_cmd_urd ; Load port from l0f02
9F3B JSR check_and_setup_txcb ; Check and set up TXCB
9F3E BCS recv_and_update ; Carry set: skip swap
9F40 .send_with_swap←1← 9F36 BNE
JSR send_txcb_swap_addrs ; Send TXCB and swap start/end addresses
9F43 .recv_and_update←1← 9F3E BCS
JSR recv_and_process_reply ; Receive and process reply
9F46 STX fs_load_addr_2 ; Store result in fs_load_addr_2
9F48 JSR update_addr_from_offset9 ; Update addresses from offset 9
9F4B DEC fs_load_addr_2 ; Decrement fs_load_addr_2
9F4D SEC ; Set carry for subtraction
9F4E JSR adjust_fsopts_4bytes ; Adjust FS options by 4 bytes
9F51 ASL fs_cmd_data ; Shift l0f05 left (update status)
9F54 RTS ; Return
9F55 .send_osbput_data←1← 9F85 BEQ
LDY #&15 ; Y=&15: TX buffer size for OSBPUT data
9F57 JSR save_net_tx_cb ; Send TX control block
9F5A LDA fs_boot_option ; Load display flag from l0e05
9F5D STA fs_boot_data ; Store in l0f16
9F60 STX fs_load_addr ; Clear fs_load_addr (X=0)
9F62 STX fs_load_addr_hi ; Clear fs_load_addr_hi
9F64 LDA #&12 ; A=&12: byte count for data block
9F66 STA fs_load_addr_2 ; Store in fs_load_addr_2
9F68 BNE write_data_block ; ALWAYS branch to write data block ALWAYS branch
9F6A .write_block_entry←1← 9E41 JMP
LDY #4 ; Y=4: offset for station comparison
9F6C LDA tube_present ; Load stored station from l0d63
9F6F BEQ store_station_result ; Zero: skip station check
9F71 CMP (fs_options),y ; Compare with FS options station
9F73 BNE store_station_result ; Mismatch: skip subtraction
9F75 DEY ; Y=3 Y=&03
9F76 SBC (fs_options),y ; Subtract FS options value
9F78 .store_station_result←2← 9F6F BEQ← 9F73 BNE
STA svc_state ; Store result in svc_state
9F7A .loop_copy_opts_to_ws←1← 9F80 BNE
LDA (fs_options),y ; Load FS options byte at Y
9F7C STA fs_last_byte_flag,y ; Store in workspace at fs_last_byte_flag+Y
9F7F DEY ; Decrement index
9F80 BNE loop_copy_opts_to_ws ; Loop until all bytes copied
9F82 PLA ; Pull operation code
9F83 AND #3 ; Mask to 2-bit sub-operation
9F85 BEQ send_osbput_data ; Zero: send OSBPUT data
9F87 LSR ; Shift right: check bit 0
9F88 BEQ handle_cat_update ; Zero (bit 0 clear): handle read
9F8A BCS update_cat_position ; Carry set: handle catalogue update
9F8C .handle_cat_update←1← 9F88 BEQ
TAY ; Transfer to Y (Y=0)
9F8D LDA fs_csd_handle,y ; Load data byte from l0e03
9F90 STA fs_cmd_csd ; Store in l0f03
9F93 LDA fs_lib_handle ; Load high data byte from l0e04
9F96 STA fs_cmd_lib ; Store in l0f04
9F99 LDA fs_urd_handle ; Load port from l0e02
9F9C STA fs_cmd_urd ; Store in l0f02
9F9F LDX #&12 ; X=&12: buffer size marker
9FA1 STX fs_cmd_y_param ; Store in l0f01
9FA4 LDA #&0d ; A=&0D: count value
9FA6 STA fs_func_code ; Store in l0f06
9FA9 STA fs_load_addr_2 ; Store in fs_load_addr_2
9FAB LSR ; Shift right (A=6)
9FAC STA fs_cmd_data ; Store in l0f05
9FAF CLC ; Clear carry for addition
9FB0 JSR prep_send_tx_cb ; Prepare and send TX control block
9FB3 STX fs_load_addr_hi ; Store X in fs_load_addr_hi (X=0)
9FB5 INX ; X=1 (INX)
9FB6 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.

9FB8 .write_data_block←2← 9F68 BNE← A030 JSR
LDA svc_state ; Load svc_state (tube flag)
9FBA BNE tube_write_setup ; Non-zero: write via tube
9FBC LDX fs_load_addr ; Load source index from fs_load_addr
9FBE LDY fs_load_addr_hi ; Load destination index from fs_load_addr_hi
9FC0 .loop_copy_to_host←1← 9FC9 BNE
LDA fs_cmd_data,x ; Load data byte from l0f05 buffer
9FC3 STA (fs_crc_lo),y ; Store to destination via fs_crc pointer
9FC5 INX ; Advance source index
9FC6 INY ; Advance destination index
9FC7 DEC fs_load_addr_2 ; Decrement byte counter
9FC9 BNE loop_copy_to_host ; Loop until all bytes transferred
9FCB BEQ tail_update_catalogue ; ALWAYS branch to update catalogue ALWAYS branch
9FCD .tube_write_setup←1← 9FBA BNE
JSR tube_claim_c3 ; Claim tube with call &C3
9FD0 LDA #1 ; A=1: tube transfer type (write)
9FD2 LDX fs_options ; Load destination low from fs_options
9FD4 LDY fs_block_offset ; Load destination high from fs_block_offset
9FD6 INX ; Increment low byte
9FD7 BNE set_tube_addr ; No wrap: skip high increment
9FD9 INY ; Carry: increment high byte
9FDA .set_tube_addr←1← 9FD7 BNE
JSR tube_addr_data_dispatch ; Set up tube transfer address
9FDD LDX fs_load_addr ; Load source index
9FDF .loop_write_to_tube←1← 9FED BNE
LDA fs_cmd_data,x ; Load data byte from buffer
9FE2 STA tube_data_register_3 ; Write to tube data register 3
9FE5 INX ; Advance source index
9FE6 LDY #6 ; Y=6: tube write delay
9FE8 .loop_tube_delay←1← 9FE9 BNE
DEY ; Delay loop: decrement Y
9FE9 BNE loop_tube_delay ; Loop until delay complete
9FEB DEC fs_load_addr_2 ; Decrement byte counter
9FED BNE loop_write_to_tube ; Loop until all bytes written to tube
9FEF LDA #&83 ; A=&83: release tube claim
9FF1 JSR tube_addr_data_dispatch ; Release tube
9FF4 .tail_update_catalogue←2← 9FCB BEQ← A058 JMP
JMP clear_result ; Jump to clear A and finalise return
9FF7 .update_cat_position←1← 9F8A BCS
LDY #9 ; Y=9: offset for position byte
9FF9 LDA (fs_options),y ; Load position from FS options
9FFB STA fs_func_code ; Store in l0f06
9FFE LDY #5 ; Y=5: offset for extent byte
A000 LDA (fs_options),y ; Load extent byte from FS options
A002 STA fs_data_count ; Store in l0f07
A005 LDX #&0d ; X=&0D: byte count
A007 STX fs_reply_cmd ; Store in l0f08
A00A LDY #2 ; Y=2: command sub-type
A00C STY fs_load_addr ; Store in fs_load_addr
A00E STY fs_cmd_data ; Store in l0f05
A011 INY ; Y=3: TX buffer command byte Y=&03
A012 JSR save_net_tx_cb ; Send TX control block
A015 STX fs_load_addr_hi ; Store X (0) in fs_load_addr_hi
A017 LDA fs_func_code ; Load data offset from l0f06
A01A STA (fs_options,x) ; Store as first byte of FS options
A01C LDA fs_cmd_data ; Load data count from l0f05
A01F LDY #9 ; Y=9: position offset in FS options
A021 ADC (fs_options),y ; Add to current position
A023 STA (fs_options),y ; Store updated position
A025 LDA txcb_end ; Load TXCB end byte
A027 SBC #7 ; Subtract 7 (header overhead)
A029 STA fs_func_code ; Store remaining data size
A02C STA fs_load_addr_2 ; Store in fs_load_addr_2 (byte count)
A02E BEQ clear_buf_after_write ; Zero bytes: skip write
A030 JSR write_data_block ; Write data block to host/tube
A033 .clear_buf_after_write←1← A02E BEQ
LDX #2 ; X=2: clear 3 bytes (indices 0-2)
A035 .loop_clear_buf←1← A039 BPL
STA fs_data_count,x ; Clear l0f07+X
A038 DEX ; Decrement index
A039 BPL loop_clear_buf ; Loop until all cleared
A03B JSR update_addr_from_offset1 ; Update addresses from offset 1
A03E SEC ; Set carry for subtraction
A03F DEC fs_load_addr_2 ; Decrement fs_load_addr_2
A041 LDA fs_cmd_data ; Load data count from l0f05
A044 STA fs_func_code ; Copy to l0f06
A047 JSR adjust_fsopts_4bytes ; Adjust FS options by 4 bytes (subtract)
A04A LDX #3 ; X=3: check 4 bytes
A04C LDY #5 ; Y=5: starting offset
A04E SEC ; Set carry for comparison
A04F .loop_check_remaining←1← A055 BPL
LDA (fs_options),y ; Load FS options byte
A051 BNE done_write_block ; Non-zero: more data remaining
A053 INY ; Advance to next byte
A054 DEX ; Decrement counter
A055 BPL loop_check_remaining ; Loop until all bytes checked
A057 CLC ; All zero: clear carry (transfer complete)
A058 .done_write_block←1← A051 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.

A05B .tube_claim_c3←3← 9FCD JSR← A060 BCC← A2CB JSR
LDA #&c3 ; A=&C3: tube claim protocol
A05D JSR tube_addr_data_dispatch ; Dispatch tube address/data claim
A060 BCC tube_claim_c3 ; Carry clear: claim failed, retry
A062 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
A063 .cmd_fs
LDA fs_server_stn ; Load current FS station high
A066 STA fs_work_5 ; Save to fs_work_5
A068 LDA fs_server_net ; Load current FS station low
A06B STA fs_work_6 ; Save to l00b6
A06D LDA (fs_crc_lo),y ; Get first character of argument
A06F CMP #&0d ; Is it CR (no argument)?
A071 BEQ print_current_fs ; No arg: print current FS info
A073 JSR parse_fs_ps_args ; Parse FS/PS station arguments
A076 LDA #1 ; A=1: write NFS info
A078 STA fs_work_4 ; Store OSWORD sub-function
A07A LDA #&13 ; OSWORD &13: NFS information
A07C LDX #<(fs_work_4) ; Parameter block low
A07E LDY #>(fs_work_4) ; Parameter block high
A080 JMP osword ; Read/Write NFS information (see https://beebwiki.mdfs.net/OSWORDs)
A083 .print_current_fs←1← A071 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.

A086 .print_fs_info_newline←1← B092 JSR
BIT bit_test_ff ; Set V (suppress padding)
A089 JSR print_station_addr ; Print station address
A08C 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.

A08F .parse_fs_ps_args←3← A073 JSR← AFF1 JSR← B1C4 JSR
TXA ; Save X on stack
A090 PHA ; Push X
A091 LDA #0 ; A=0: initialise dot-seen flag
A093 STA fs_work_4 ; Clear dot-seen flag
A095 JSR parse_addr_arg ; Parse first number (network)
A098 BCS skip_if_no_station ; C set: number found, check for dot
A09A TYA ; Save Y (command line offset)
A09B PHA ; Push Y
A09C JSR init_bridge_poll ; Initialise bridge polling
A09F EOR fs_load_addr_2 ; Compare bridge result with parsed value
A0A1 BEQ store_station_lo ; Same: keep bridge result
A0A3 LDA fs_load_addr_2 ; Different: use parsed value
A0A5 .store_station_lo←1← A0A1 BEQ
STA fs_work_6 ; Store station low byte
A0A7 PLA ; Restore Y
A0A8 TAY ; Transfer back to Y
A0A9 INY ; Skip dot separator
A0AA JSR parse_addr_arg ; Parse second number (station)
A0AD .skip_if_no_station←1← A098 BCS
BEQ done_parse_fs_ps ; Zero result: skip store
A0AF STA fs_work_5 ; Store station high byte
A0B1 .done_parse_fs_ps←1← A0AD BEQ
PLA ; Restore X
A0B2 TAX ; Transfer back to X
A0B3 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.

A0B4 .get_pb_ptr_as_index←2← A0D2 JSR← A0E2 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)
A0B6 .byte_to_2bit_index←4← 8F13 JSR← A5B9 JSR← A5D2 JSR← B0E4 JSR
ASL ; Shift left (A * 2)
A0B7 ASL ; Shift left (A * 4)
A0B8 PHA ; Save A * 4 on stack
A0B9 ASL ; Shift left (A * 8)
A0BA TSX ; Get stack pointer
A0BB PHP ; Save flags (carry from shift)
A0BC ADC error_text,x ; A*8 + A*4 (from stack) = A*12
A0BF ROR ; Divide by 2 with carry
A0C0 PLP ; Restore original flags
A0C1 ASL ; Shift left again
A0C2 TAY ; Result to Y as index
A0C3 PLA ; Pop saved A * 4
A0C4 CMP #&48 ; A * 4 >= &48 (out of range)?
A0C6 BCC return_from_2bit_index ; In range: return
A0C8 LDY #0 ; Out of range: Y=0
A0CA TYA ; A=&00
A0CB .return_from_2bit_index←1← A0C6 BCC
RTS ; Return with A=index, Y=index
A0CC .net_1_read_handle
LDY #&6f ; Y=&6F: source offset
A0CE LDA (net_rx_ptr),y ; Load byte from RX buffer
A0D0 BCC store_pb_result ; C clear: store directly
A0D2 .net_2_read_handle_entry
JSR get_pb_ptr_as_index ; Get index from PB pointer
A0D5 BCS return_zero_uninit ; C set (out of range): clear value
A0D7 LDA (nfs_workspace),y ; Load workspace byte at index
A0D9 CMP #&3f ; Is it '?' (uninitialised)?
A0DB BNE store_pb_result ; No: use value from RX buffer
A0DD .return_zero_uninit←1← A0D5 BCS
LDA #0 ; A=0: return zero for uninitialised
A0DF .store_pb_result←2← A0D0 BCC← A0DB BNE
STA osword_pb_ptr ; Store result to PB pointer
A0E1 RTS ; Return
A0E2 .net_3_close_handle
JSR get_pb_ptr_as_index ; Get index from PB pointer
A0E5 BCC mark_ws_uninit ; C clear: store to workspace
A0E7 ROR fs_flags ; Save carry to l0d6c bit 7
A0EA LDA osword_pb_ptr ; Load PB pointer value
A0EC ROL ; Shift carry back in
A0ED ROL fs_flags ; Restore l0d6c bit 7
A0F0 RTS ; Return
A0F1 .mark_ws_uninit←1← A0E5 BCC
ROR econet_flags ; Save carry to l0d61 bit 7
A0F4 LDA #&3f ; A='?': mark as uninitialised
A0F6 STA (nfs_workspace),y ; Store '?' to workspace
A0F8 ROL econet_flags ; Restore l0d61 bit 7
A0FB RTS ; Return
A0FC .fscv_3_star_cmd←1← 8CEE JMP
JSR set_text_and_xfer_ptr ; Set text and transfer pointers
A0FF LDY #&ff ; Y=&FF: prepare for INY to 0
A101 STY fs_spool_handle ; Clear spool handle (no spool active)
A103 STY escapable ; Set escapable flag (&FF)
A105 INY ; Y=&00
A106 LDX #&4a ; X=&4A: FS command table offset
A108 JSR match_fs_cmd ; Match command in FS table
A10B BCS dispatch_fs_cmd ; C set: command found
A10D .cmd_fs_reentry←1← 8C4F JMP
BVC dispatch_fs_cmd ; V clear: syntax error
A10F .error_syntax←1← AF4F JMP
LDA #&dc ; Error code &DC
A111 JSR error_inline ; Generate 'Syntax' error
A114 EQUS "Syntax."
A11B .dispatch_fs_cmd←2← A10B BCS← A10D BVC
LDA #0 ; A=0: clear service state
A11D STA svc_state ; Store cleared service state
A11F LDA cmd_table_fs_hi,x ; Load command handler address high
A122 PHA ; Push high byte
A123 LDA cmd_table_fs_lo,x ; Load command handler address low
A126 PHA ; Push low byte
A127 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.

A128 .match_fs_cmd←5← 8C4A JSR← 8C78 JSR← A108 JSR← B304 JSR← B331 JSR
TYA ; Save Y (command line offset)
A129 PHA ; Push on stack
A12A .restart_table_scan←1← A150 BNE
PLA ; Restore saved Y
A12B PHA ; Push back (keep on stack)
A12C TAY ; Transfer to Y
A12D LDA cmd_table_fs,x ; Load table entry byte
A130 BMI check_char_type ; Bit 7 set: end of table names
A132 .loop_match_char←1← A13F BNE
LDA cmd_table_fs,x ; Load table byte
A135 BMI check_separator ; Bit 7 set: end of this name
A137 EOR (fs_crc_lo),y ; Compare with command line char
A139 AND #&df ; Case-insensitive compare
A13B BNE skip_entry_chars ; Mismatch: skip to next entry
A13D INY ; Match: advance command line
A13E INX ; Advance table pointer
A13F BNE loop_match_char ; Loop for next character
A141 .skip_entry_chars←2← A13B BNE← A145 BPL
INX ; Advance past remaining table chars
A142 LDA cmd_table_fs,x ; Load next table byte
A145 BPL skip_entry_chars ; Bit 7 clear: more chars to skip
A147 LDA (fs_crc_lo),y ; Check command line terminator
A149 CMP #&2e ; Is it '.' (abbreviation)?
A14B BEQ skip_dot_and_spaces ; Yes: skip spaces after dot
A14D .loop_skip_to_next←1← A162 BNE
INX ; X += 3: skip flags and address bytes
A14E INX ; (continued)
A14F INX ; (continued)
A150 BNE restart_table_scan ; Try next table entry
A152 .check_separator←1← A135 BMI
TYA ; Save Y (end of matched name)
A153 PHA ; Push position
A154 LDA (fs_crc_lo),y ; Load char after matched portion
A156 LDY #9 ; Y=9: check 10 separator chars
A158 .loop_check_sep_table←1← A15E BPL
CMP sep_table_data,y ; Compare with separator table
A15B BEQ separator_matched ; Match: valid command separator
A15D DEY ; Try next separator
A15E BPL loop_check_sep_table ; Loop through separator list
A160 PLA ; No separator match: restore Y
A161 TAY ; Transfer back to Y
A162 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.
A164 .sep_table_data←1← A158 CMP
EQUB &20 ; Space
A165 EQUB &22 ; '"' double quote
A166 EQUB &23 ; '#' hash
A167 EQUB &24 ; '$' dollar
A168 EQUB &26 ; '&' ampersand
A169 EQUB &2A ; '*' asterisk
A16A EQUB &3A ; ':' colon
A16B EQUB &40 ; '@' at-sign
A16C EQUB &0D ; CR (carriage return)
A16D .separator_matched←1← A15B BEQ
PLA ; Restore saved Y
A16E TAY ; Transfer to Y
A16F .loop_skip_trail_spaces←1← A176 JMP
LDA (fs_crc_lo),y ; Load next char
A171 CMP #&20 ; Is it space?
A173 BNE check_cmd_flags ; No: done skipping
A175 .skip_dot_and_spaces←1← A14B BEQ
INY ; Advance past space
A176 JMP loop_skip_trail_spaces ; Loop for more spaces
A179 .check_cmd_flags←1← A173 BNE
LDA cmd_table_fs,x ; Load command flags byte
A17C ASL ; Shift: check 'no-arg' bit
A17D BPL clear_v_flag ; Bit clear: allow arguments
A17F LDA (fs_crc_lo),y ; Check if line ends here
A181 CMP #&0d ; Is it CR?
A183 BNE clear_v_flag ; No: argument present, V clear
A185 BIT bit_test_ff ; CR found: set V (no argument)
A188 BVS clear_c_flag ; V set: command is valid
A18A .clear_v_flag←2← A17D BPL← A183 BNE
CLV ; Clear V (argument present)
A18B .clear_c_flag←1← A188 BVS
CLC ; C=0: command not found
A18C .return_with_result←1← A1A7 BCS
PLA ; Pop saved Y from stack
A18D LDA (fs_crc_lo),y ; Load command line char at Y
A18F RTS ; Return (C and V set per result)
A190 .loop_scan_past_word←1← A19D BNE
INY ; Advance past character
A191 .check_char_type←1← A130 BMI
LDA (fs_crc_lo),y ; Load current char
A193 CMP #&0d ; Is it CR (end of line)?
A195 BEQ set_c_and_return ; Yes: end of input
A197 CMP #&2e ; Is it '.' (abbreviation dot)?
A199 BEQ skip_sep_spaces ; Yes: skip to next word
A19B CMP #&20 ; Is it space?
A19D BNE loop_scan_past_word ; No: keep scanning
A19F .skip_sep_spaces←2← A199 BEQ← A1A4 BEQ
INY ; Skip past separator
A1A0 LDA (fs_crc_lo),y ; Load next char
A1A2 CMP #&20 ; Is it space?
A1A4 BEQ skip_sep_spaces ; Yes: skip consecutive spaces
A1A6 .set_c_and_return←1← A195 BEQ
SEC ; C=1: have more text to match
A1A7 BCS return_with_result ; ALWAYS branch
A1A9 .fscv_2_star_run
JSR save_ptr_to_os_text ; Save text pointer
A1AC JSR mask_owner_access ; Set owner-only access mask
A1AF JSR parse_cmd_arg_y0 ; Parse command argument (Y=0)
A1B2 .open_file_for_run←1← A229 BNE
LDX #1 ; X=1: buffer index
A1B4 JSR copy_arg_to_buf ; Copy argument to buffer
A1B7 LDA #2 ; A=2: open for update
A1B9 STA fs_cmd_data ; Store open mode
A1BC LDY #&12 ; Y=&12: open file command
A1BE JSR save_net_tx_cb ; Send open request to server
A1C1 LDA fs_cmd_data ; Load reply status
A1C4 CMP #1 ; Status 1 (success)?
A1C6 BNE try_library_path ; No: file not found, try library
A1C8 LDX #3 ; X=3: check 4 handle bytes
A1CA .loop_check_handles←1← A1D3 BPL
INC fs_handle_check,x ; Increment handle byte
A1CD BEQ alloc_run_fcb ; Was &FF (overflow to 0): try next
A1CF JMP check_exec_addr ; Non-zero: handle valid, execute
A1D2 .alloc_run_fcb←1← A1CD BEQ
DEX ; Try next handle byte
A1D3 BPL loop_check_handles ; Loop until all checked
A1D5 JSR alloc_fcb_or_error ; Allocate new FCB or raise error
A1D8 LDX #1 ; X=1: open mode index
A1DA STX fs_cmd_data ; Store in l0f05
A1DD STX fs_func_code ; Store in l0f06
A1E0 INX ; X=2 X=&02
A1E1 JSR copy_arg_to_buf ; Copy argument to buffer
A1E4 LDY #6 ; Y=6: re-open command
A1E6 JSR save_net_tx_cb ; Send re-open request
A1E9 BCS done_run_dispatch ; C set: error on re-open
A1EB JMP alloc_run_channel ; C clear: finalise file opening
A1EE .done_run_dispatch←1← A1E9 BCS
JMP finalise_and_return ; Jump to finalise and return
A1F1 .try_library_path←1← A1C6 BNE
LDA fs_filename_buf ; Load first char of filename
A1F4 CMP #&24 ; Is it '$' (root dir)?
A1F6 BEQ error_bad_command ; Yes: no library search, error
A1F8 LDA fs_lib_flags ; Load library flag byte
A1FB BMI library_tried ; Bit 7 set: library already tried
A1FD ROL ; Rotate bits to check library state
A1FE ROL ; Rotate again
A1FF BMI restore_filename ; Bit 7 set: restore from backup
A201 BCS error_bad_command ; Carry set: bad command
A203 LDX #&ff ; X=&FF: pre-increment for loop
A205 .loop_find_name_end←1← A20B BNE
INX ; Find end of filename
A206 LDA fs_filename_buf,x ; Load filename byte
A209 CMP #&0d ; Is it CR (end)?
A20B BNE loop_find_name_end ; No: continue scanning
A20D .loop_shift_name_right←1← A214 BPL
LDA fs_filename_buf,x ; Shift filename right by 8 bytes
A210 STA fs_filename_backup,x ; Store shifted byte
A213 DEX ; Previous byte
A214 BPL loop_shift_name_right ; Loop until all shifted
A216 LDX #7 ; X=7: 'Library.' is 8 bytes
A218 .loop_copy_lib_prefix←1← A21F BPL
LDA library_dir_prefix,x ; Copy 'Library.' prefix
A21B STA fs_filename_buf,x ; Store prefix byte
A21E DEX ; Previous byte
A21F BPL loop_copy_lib_prefix ; Loop until prefix copied
A221 LDA fs_lib_flags ; Load library flag
A224 ORA #&60 ; Set bits 5-6: library path active
A226 STA fs_lib_flags ; Store updated flag
A229 .retry_with_library←1← A240 BNE
BNE open_file_for_run ; Retry file open with library path
A22B .restore_filename←1← A1FF BMI
LDX #&ff ; X=&FF: pre-increment for loop
A22D .loop_restore_name←1← A236 BNE
INX ; Restore original filename
A22E LDA fs_filename_backup,x ; Load backup byte
A231 STA fs_filename_buf,x ; Store to filename buffer
A234 EOR #&0d ; Is it CR (end)?
A236 BNE loop_restore_name ; No: continue restoring
A238 JSR mask_owner_access ; Set owner-only access mask
A23B ORA #&80 ; Set bit 7: library tried
A23D STA fs_lib_flags ; Store updated flag
A240 BNE retry_with_library ; ALWAYS branch
A242 .library_tried←1← A1FB BMI
JSR mask_owner_access ; Set owner-only access mask
A245 .error_bad_command←3← A1F6 BEQ← A201 BCS← B316 JMP
LDA #&fe ; Error code &FE
A247 JSR error_bad_inline ; Generate 'Bad command' error
A24A EQUS "command."
A252 .check_exec_addr←1← A1CF JMP
LDX #3 ; X=3: check 4 execution bytes
A254 .loop_check_exec_bytes←1← A25A BNE
INC fs_func_code,x ; Increment execution address byte
A257 BNE setup_oscli_arg ; Non-zero: valid, go to OSCLI
A259 DEX ; Try next byte
A25A BNE loop_check_exec_bytes ; Loop until all checked
A25C LDA #&93 ; Error code &93
A25E JSR error_inline_log ; Generate 'No!' error
A261 EQUS "No!."
A265 .alloc_run_channel←1← A1EB JMP
LDA fs_cmd_data ; Load open mode result
A268 JSR alloc_fcb_slot ; Allocate FCB slot
A26B TAY ; Transfer to Y
A26C LDA #0 ; A=0: clear channel status
A26E STA chan_status,x ; Clear status in channel table
A271 STY cur_dir_handle ; Store handle in l1070
A274 LDY #3 ; Y=3: OSCLI execution
A276 JMP boot_cmd_oscli ; Execute via boot/OSCLI path
A279 .library_dir_prefix←1← A218 LDA
EQUS "Library."
A281 .setup_oscli_arg←1← A257 BNE
JSR copy_arg_to_buf_x0 ; Copy argument to buffer (X=0)
A284 LDY #0 ; Y=0
A286 CLC ; C=0 for GSINIT
A287 JSR gsinit ; Initialise GS string read
A28A .loop_read_gs_string←1← A28D BCC
JSR gsread ; Read next GS character
A28D BCC loop_read_gs_string ; C clear: more chars
A28F DEY ; Back up one position
A290 .loop_skip_trailing←1← A295 BEQ
INY ; Skip trailing spaces
A291 LDA (os_text_ptr),y ; Load next char
A293 CMP #&20 ; Is it space?
A295 BEQ loop_skip_trailing ; Yes: skip it
A297 EOR #&0d ; Check for CR (end of line)
A299 CLC ; C=0 for addition
A29A TYA ; Transfer Y offset to A
A29B ADC os_text_ptr ; Add to text pointer low
A29D STA fs_cmd_context ; Store as command tail pointer low
A2A0 LDA os_text_ptr_hi ; Load text pointer high
A2A2 ADC #0 ; Add carry
A2A4 STA fs_context_hi ; Store as command tail pointer high
A2A7 JSR save_text_ptr ; Save text pointer for later
A2AA LDX #&0e ; X=&0E: OSWORD parameter offset
A2AC STX fs_block_offset ; Store as block offset high
A2AE LDA #&10 ; A=&10: OSWORD parameter size
A2B0 STA fs_options ; Store as options pointer
A2B2 STA fs_work_16 ; Store to l0e16
A2B5 LDX #&4a ; X=&4A: FS command table offset
A2B7 LDY #5 ; Y=5
A2B9 JSR do_fs_cmd_iteration ; Execute FS command iteration
A2BC LDA tube_present ; Load tube flag
A2BF BEQ dispatch_via_vector ; Zero: no tube transfer needed
A2C1 AND fs_load_upper ; AND with l0f0b
A2C4 AND fs_addr_check ; AND with l0f0c
A2C7 CMP #&ff ; All &FF?
A2C9 BEQ dispatch_via_vector ; Yes: no tube transfer needed
A2CB JSR tube_claim_c3 ; Claim tube for data transfer
A2CE LDX #9 ; X=9: parameter count
A2D0 LDY #&0f ; Y=&0F: parameter offset
A2D2 LDA #4 ; A=4: tube transfer type
A2D4 JMP tube_addr_data_dispatch ; Dispatch tube address/data
A2D7 .dispatch_via_vector←2← A2BF BEQ← A2C9 BEQ
LDA #1 ; A=1
A2D9 JMP (fs_load_vector) ; Dispatch via indirect vector
A2DC .fsreply_3_set_csd←1← 944B JMP
JSR find_station_bit3 ; Find station with bit 3 set
A2DF JMP return_with_last_flag ; Return with last flag state
A2E2 .fsreply_5_set_lib
JSR flip_set_station_boot ; Flip/set station boot config
A2E5 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.

A2E8 .find_station_bit2←1← A387 JSR
LDX #&10 ; X=&10: scan 16 slots (15 to 0)
A2EA CLV ; Clear V
A2EB .loop_search_stn_bit2←2← A2F1 BNE← A2F8 BEQ
DEX ; Try next slot
A2EC BMI done_search_bit2 ; All slots checked: not found
A2EE JSR match_station_net ; Compare station/network
A2F1 BNE loop_search_stn_bit2 ; No match: try next
A2F3 LDA chan_status,x ; Load slot status byte
A2F6 AND #4 ; Test bit 2 (PS active flag)?
A2F8 BEQ loop_search_stn_bit2 ; Not set: try next
A2FA TYA ; Transfer Y to A
A2FB STA fcb_net_or_port,x ; Store Y in slot data
A2FE BIT bit_test_ff ; Set V (found match)
A301 .done_search_bit2←1← A2EC BMI
STY fs_urd_handle ; Store Y to l0e02
A304 BVS set_flags_bit2 ; V set: found, skip allocation
A306 TYA ; Transfer Y to A
A307 JSR alloc_fcb_slot ; Allocate FCB slot
A30A STA handle_1_fcb ; Store allocation result
A30D BEQ jmp_restore_fs_ctx ; Zero: failed, restore context
A30F .set_flags_bit2←1← A304 BVS
LDA #&26 ; A=&26: station flags value
A311 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.

A313 .find_station_bit3←3← A2DC JSR← A345 JSR← A38D JSR
LDX #&10 ; X=&10: scan 16 slots (15 to 0)
A315 CLV ; Clear V
A316 .loop_search_stn_bit3←2← A31C BNE← A323 BEQ
DEX ; Try next slot
A317 BMI done_search_bit3 ; All slots checked: not found
A319 JSR match_station_net ; Compare station/network
A31C BNE loop_search_stn_bit3 ; No match: try next
A31E LDA chan_status,x ; Load slot status byte
A321 AND #8 ; Test bit 3 (FS active flag)?
A323 BEQ loop_search_stn_bit3 ; Not set: try next
A325 TYA ; Transfer Y to A
A326 STA fcb_net_or_port,x ; Store Y in slot data
A329 BIT bit_test_ff ; Set V (found match)
A32C .done_search_bit3←1← A317 BMI
STY fs_csd_handle ; Store Y to l0e03
A32F BVS set_flags_bit3 ; V set: found, skip allocation
A331 TYA ; Transfer Y to A
A332 JSR alloc_fcb_slot ; Allocate FCB slot
A335 STA handle_2_fcb ; Store allocation result
A338 BEQ jmp_restore_fs_ctx ; Zero: failed, restore context
A33A .set_flags_bit3←1← A32F BVS
LDA #&2a ; A=&2A: station flags value
A33C 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
A33E .cmd_flip
LDA fs_csd_handle ; Load current CSD handle
A341 PHA ; Save CSD handle
A342 LDY fs_lib_handle ; Load library handle into Y
A345 JSR find_station_bit3 ; Install library as new CSD
A348 PLA ; Restore original CSD handle
A349 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.

A34A .flip_set_station_boot←2← A2E2 JSR← A393 JSR
LDX #&10 ; X=&10: max 16 station entries
A34C CLV ; Clear V (no match found yet)
A34D .loop_search_stn_boot←2← A353 BNE← A35A BEQ
DEX ; Decrement station index
A34E BMI done_search_boot ; All searched: exit loop
A350 JSR match_station_net ; Check if station[X] matches
A353 BNE loop_search_stn_boot ; No match: try next station
A355 LDA chan_status,x ; Load station flags byte
A358 AND #&10 ; Test bit 4 (active flag)
A35A BEQ loop_search_stn_boot ; Not active: try next station
A35C TYA ; Transfer boot type to A
A35D STA fcb_net_or_port,x ; Store boot setting for station
A360 BIT bit_test_ff ; Set V flag (station match found)
A363 .done_search_boot←1← A34E BMI
STY fs_lib_handle ; Store boot type
A366 BVS set_flags_boot ; V set (matched): skip allocation
A368 TYA ; Boot type to A
A369 JSR alloc_fcb_slot ; Allocate FCB slot for new entry
A36C STA handle_3_fcb ; Store allocation result
A36F BEQ jmp_restore_fs_ctx ; Zero: allocation failed, exit
A371 .set_flags_boot←1← A366 BVS
LDA #&32 ; A=&32: station flags (active+boot)
A373 .store_stn_flags_restore←2← A311 BNE← A33C BNE
STA chan_status,x ; Store station flags
A376 .jmp_restore_fs_ctx←3← A30D BEQ← A338 BEQ← A36F BEQ
JMP restore_fs_context ; Restore FS context and return
A379 .fsreply_1_copy_handles_boot
JSR close_all_net_chans ; Close all network channels
A37C SEC ; Set carry flag
A37D LDA fs_reply_cmd ; Load reply boot type
A380 STA fs_boot_option ; Store as current boot type
A383 .fsreply_2_copy_handles
PHP ; Save processor status
A384 LDY fs_cmd_data ; Load station number from reply
A387 JSR find_station_bit2 ; Find station entry with bit 2
A38A LDY fs_func_code ; Load network number from reply
A38D JSR find_station_bit3 ; Find station entry with bit 3
A390 LDY fs_data_count ; Load boot type from reply
A393 JSR flip_set_station_boot ; Set boot config for station
A396 PLP ; Restore processor status
A397 BCS check_auto_boot_flag ; Carry set: proceed with boot
A399 JMP return_with_last_flag ; Return with last flag
A39C .check_auto_boot_flag←1← A397 BCS
LDA fs_lib_flags ; Load config flags
A39F TAX ; Save copy in X
A3A0 AND #4 ; Test bit 2 (auto-boot flag)
A3A2 PHP ; Save bit 2 test result
A3A3 TXA ; Restore full flags
A3A4 AND #&fb ; Clear bit 2 (consume flag)
A3A6 STA fs_lib_flags ; Store cleared flags
A3A9 PLP ; Restore bit 2 test result
A3AA BNE load_boot_type ; Bit 2 was set: skip to boot cmd
A3AC LDA #osbyte_scan_keyboard ; OSBYTE &79: scan keyboard
A3AE LDX #(255 - inkey_key_ctrl) EOR 128 ; X=internal key number EOR 128
A3B0 JSR osbyte ; Test for 'CTRL' key pressed (X=129)
A3B3 TXA ; X has top bit set if 'CTRL' pressed
A3B4 BPL load_boot_type ; CTRL not pressed: proceed to boot
A3B6 .boot_load_cmd←1← A3CE BEQ
RTS ; CTRL pressed: cancel boot, return
A3B7 EQUS "L.!BOOT"
A3BE EQUB &0D
A3BF .boot_exec_cmd
EQUS "E.!BOOT"
A3C6 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).
A3C7 .boot_oscli_lo_table←1← A3D0 LDX
EQUB &C6 ; Opt 0: &A3C6 (don't-care, unused)
A3C8 EQUB &B7 ; Opt 1: &A3B7 'L.!BOOT' (*LOAD)
A3C9 EQUB &B9 ; Opt 2: &A3B9 '!BOOT' (*RUN)
A3CA EQUB &BF ; Opt 3: &A3BF 'E.!BOOT' (*EXEC)
A3CB .load_boot_type←2← A3AA BNE← A3B4 BPL
LDY fs_boot_option ; Load boot type
A3CE BEQ boot_load_cmd ; Type 0: no command, just return
A3D0 .boot_cmd_oscli←1← A276 JMP
LDX boot_oscli_lo_table,y ; Look up boot command address low
A3D3 LDY #&a3 ; Boot command address high (&A3xx)
A3D5 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
A3D8 .cmd_table_fs←12← 8BA3 LDA← 8BB2 LDA← 8BBA LDA← 8BC7 LDA← 8BF5 LDA← 8BFC LDA← 9316 LDA← 931E LDA← A12D LDA← A132 LDA← A142 LDA← A179 LDA
EQUS "C" ; *Close (first char)
A3D9 .cmd_table_fs_lo←3← 8C88 LDA← A123 LDA← B30A ORA
EQUS "l" ; *Close cont (dispatch lo base)
A3DA .cmd_table_fs_hi←3← 8C84 LDA← A11F LDA← B337 AND
EQUS "ose" ; *Close cont (dispatch hi base)
A3DD EQUB &80 ; No syntax
A3DE EQUW cmd_close-1 ; Dispatch addr-1
A3E0 EQUS "Dump" ; *Dump
A3E4 EQUB &C4 ; V no arg; syn 4: <filename> ...
A3E5 EQUW cmd_dump-1 ; Dispatch addr-1
A3E7 EQUS "Net" ; *Net (select NFS)
A3EA EQUB &80 ; No syntax
A3EB EQUW cmd_net_fs-1 ; Dispatch addr-1
A3ED EQUS "Pollps" ; *Pollps
A3F3 EQUB &88 ; Syn 8: (<stn. id.>|<ps type>)
A3F4 EQUW cmd_pollps-1 ; Dispatch addr-1
A3F6 EQUS "Print" ; *Print
A3FB EQUB &CC ; V no arg; syn 12: <filename>
A3FC EQUW cmd_print-1 ; Dispatch addr-1
A3FE EQUS "Prot" ; *Prot
A402 EQUB &8E ; Syn 14: (attribute keywords)
A403 EQUW cmd_prot-1 ; Dispatch addr-1
A405 EQUS "PS" ; *PS; syn 8: (<stn. id.>|<ps type>)
A407 EQUB &88
A408 EQUW cmd_ps-1 ; Dispatch addr-1
A40A EQUS "Roff" ; *Roff
A40E EQUB &80 ; No syntax
A40F EQUW cmd_roff-1 ; Dispatch addr-1
A411 EQUS "Type" ; *Type
A415 EQUB &CC ; V no arg; syn 12: <filename>
A416 EQUW cmd_type-1 ; Dispatch addr-1
A418 EQUS "Unprot" ; *Unprot
A41E EQUB &8E ; Syn 14: (attribute keywords)
A41F EQUW cmd_unprot-1 ; Dispatch addr-1
A421 EQUB &80 ; End of utility sub-table
A422 .cmd_table_nfs
EQUS "Access" ; *Access
A428 EQUB &C9 ; V no arg; syn 9: <obj> (L)(W)(R)...
A429 EQUW cmd_fs_operation-1 ; Dispatch addr-1
A42B EQUS "Bye" ; *Bye
A42E EQUB &80 ; No syntax
A42F EQUW cmd_bye-1 ; Dispatch addr-1
A431 EQUS "Cdir" ; *Cdir
A435 EQUB &C6 ; V no arg; syn 6: <dir> (<number>)
A436 EQUW cmd_cdir-1 ; Dispatch addr-1
A438 EQUS "Delete" ; *Delete
A43E EQUB &C3 ; V no arg; syn 3: <object>
A43F EQUW cmd_fs_operation-1 ; Dispatch addr-1
A441 EQUS "Dir" ; *Dir
A444 EQUB &81 ; Syn 1: (<dir>)
A445 EQUW cmd_dir-1 ; Dispatch addr-1
A447 EQUS "Ex" ; *Ex; syn 1: (<dir>)
A449 EQUB &81
A44A EQUW cmd_ex-1 ; Dispatch addr-1
A44C EQUS "Flip" ; *Flip
A450 EQUB &80 ; No syntax
A451 EQUW cmd_flip-1 ; Dispatch addr-1
A453 EQUS "FS" ; *FS; syn 11: (<stn. id.>)
A455 EQUB &8B
A456 EQUW cmd_fs-1 ; Dispatch addr-1
A458 EQUS "Info" ; *Info
A45C EQUB &C3 ; V no arg; syn 3: <object>
A45D EQUW cmd_fs_operation-1 ; Dispatch addr-1
A45F .cmd_table_nfs_iam←1← 8DA4 LDA
EQUS "I am" ; *I am
A463 EQUB &C2 ; V no arg; syn 2: (<stn>) <user>...
A464 EQUW cmd_iam-1 ; Dispatch addr-1
A466 EQUS "Lcat" ; *Lcat
A46A EQUB &81 ; Syn 1: (<dir>)
A46B EQUW cmd_lcat-1 ; Dispatch addr-1
A46D EQUS "Lex" ; *Lex
A470 EQUB &81 ; Syn 1: (<dir>)
A471 EQUW cmd_lex-1 ; Dispatch addr-1
A473 EQUS "Lib" ; *Lib
A476 EQUB &C5 ; V no arg; syn 5: <dir>
A477 EQUW cmd_fs_operation-1 ; Dispatch addr-1
A479 EQUS "Pass" ; *Pass
A47D EQUB &C7 ; V no arg; syn 7: <pass> ...
A47E EQUW cmd_pass-1 ; Dispatch addr-1
A480 EQUS "Remove" ; *Remove
A486 EQUB &C3 ; V no arg; syn 3: <object>
A487 EQUW cmd_remove-1 ; Dispatch addr-1
A489 EQUS "Rename" ; *Rename
A48F EQUB &CA ; V no arg; syn 10: <file> <new file>
A490 EQUW cmd_rename-1 ; Dispatch addr-1
A492 EQUS "Wipe" ; *Wipe
A496 EQUB &81 ; Syn 1: (<dir>)
A497 EQUW cmd_wipe-1 ; Dispatch addr-1
A499 EQUB &80 ; End of NFS sub-table
A49A .cmd_table_help
EQUB &09, &8E ; &09/&8E: before help-only entries
A49C EQUS "Net" ; *Net (local)
A49F EQUB &80 ; No syntax
A4A0 EQUW help_net-1 ; Dispatch addr-1
A4A2 EQUS "Utils" ; *Utils
A4A7 EQUB &80 ; No syntax
A4A8 EQUW help_utils-1 ; Dispatch addr-1
A4AA 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
A4AB .cmd_table_copro
EQUS "Halt" ; Halt
A4AF EQUB &FC ; Flag &FC: V no arg, syn 28 (unused)
A4B0 EQUB &20 ; *Prot OR mask: bit 5
A4B1 EQUB &DF ; *Unprot AND mask: ~bit 5
A4B2 EQUS "JSR" ; JSR
A4B5 EQUB &FC ; Flag &FC: V no arg, syn 28 (unused)
A4B6 EQUB &04 ; *Prot OR mask: bit 2
A4B7 EQUB &FB ; *Unprot AND mask: ~bit 2
A4B8 EQUS "Peek" ; Peek
A4BC EQUB &FC ; Flag &FC: V no arg, syn 28 (unused)
A4BD EQUB &01 ; *Prot OR mask: bit 0
A4BE EQUB &FE ; *Unprot AND mask: ~bit 0
A4BF EQUS "Poke" ; Poke
A4C3 EQUB &FC ; Flag &FC: V no arg, syn 28 (unused)
A4C4 EQUB &02 ; *Prot OR mask: bit 1
A4C5 EQUB &FD ; *Unprot AND mask: ~bit 1
A4C6 EQUS "Proc" ; Proc
A4CA EQUB &FC ; Flag &FC: V no arg, syn 28 (unused)
A4CB EQUB &08 ; *Prot OR mask: bit 3
A4CC EQUB &F7 ; *Unprot AND mask: ~bit 3
A4CD EQUS "Utils" ; Utils
A4D2 EQUB &A9 ; Flag &A9: syn 9 (unused)
A4D3 EQUB &10 ; *Prot OR mask: bit 4
A4D4 EQUB &EF ; *Unprot AND mask: ~bit 4
A4D5 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.

A4D6 .svc_8_osword
CLC ; CLC so SBC subtracts value+1
A4D7 LDA osbyte_a_copy ; A = OSWORD number
A4D9 SBC #&0d ; A = OSWORD - &0E (CLC+SBC = -&0E)
A4DB BMI return_from_osword_setup ; Below &0E: not ours, return
A4DD CMP #7 ; Index >= 7? (OSWORD > &14)
A4DF BCS return_from_osword_setup ; Above &14: not ours, return
A4E1 JSR osword_setup_handler ; Set up dispatch and save state
A4E4 LDY #2 ; Copy 3 bytes (Y=2,1,0)
A4E6 .loop_copy_osword_data←1← A4EC BPL
LDA (net_rx_ptr),y ; Load from RX buffer
A4E8 STA osword_flag,y ; Store to osword_flag workspace
A4EB DEY ; Next byte down
A4EC BPL loop_copy_osword_data ; Loop for all 3 bytes
A4EE 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)
A4EF .osword_setup_handler←1← A4E1 JSR
TAX ; X = OSWORD index (0-6)
A4F0 LDA osword_dispatch_hi_table,x ; Load handler address high byte
A4F3 PHA ; Push high byte for RTS dispatch
A4F4 LDA osword_dispatch_lo_table,x ; Load handler address low byte
A4F7 PHA ; Push low byte for RTS dispatch
A4F8 LDY #2 ; Copy 3 bytes (Y=2,1,0)
A4FA .loop_copy_osword_flag←1← A500 BPL
LDA osword_flag,y ; Load from osword_flag workspace
A4FD STA (net_rx_ptr),y ; Store to RX buffer
A4FF DEY ; Next byte down
A500 BPL loop_copy_osword_flag ; Loop for all 3 bytes
A502 INY ; Y=0 (INY from -1)
A503 LDA (osword_pb_ptr),y ; Load PB byte 0 (OSWORD sub-code)
A505 STY svc_state ; Clear service state
A507 .return_from_osword_setup←2← A4DB BMI← A4DF 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.
A508 .osword_dispatch_lo_table←1← A4F4 LDA
EQUB <(osword_0e_handler-1) ; lo-&0E: Read clock
A509 EQUB <(return_from_osword_setup-1) ; lo-&0F: (unimplemented)
A50A EQUB <(osword_10_handler-1) ; lo-&10: Transmit
A50B EQUB <(osword_11_handler-1) ; lo-&11: Receive
A50C EQUB <(osword_12_handler-1) ; lo-&12: Read station info
A50D EQUB <(osword_13_dispatch-1) ; lo-&13: Misc operations
A50E EQUB <(osword_14_handler-1) ; lo-&14: Bridge/net config
A50F .osword_dispatch_hi_table←1← A4F0 LDA
EQUB >(osword_0e_handler-1) ; hi-&0E: Read clock
A510 EQUB >(return_from_osword_setup-1) ; hi-&0F: (unimplemented)
A511 EQUB >(osword_10_handler-1) ; hi-&10: Transmit
A512 EQUB >(osword_11_handler-1) ; hi-&11: Receive
A513 EQUB >(osword_12_handler-1) ; hi-&12: Read station info
A514 EQUB >(osword_13_dispatch-1) ; hi-&13: Misc operations
A515 EQUB >(osword_14_handler-1) ; hi-&14: Bridge/net config
A516 .osword_0e_handler
PHA ; Save A for later test
A517 BIT fs_flags ; Test station active flag
A51A BPL return_from_osword_0e ; Not active: just return
A51C PLA ; Restore A (OSWORD sub-code)
A51D CMP #4 ; Sub-code = 4? (read clock)
A51F BEQ save_txcb_and_convert ; Yes: handle clock read
A521 LDA #8 ; Other sub-codes: set state = 8
A523 STA svc_state ; Store service state
A525 .return_from_osword_0e←1← A51A BPL
RTS ; Return
A526 .save_txcb_and_convert←1← A51F BEQ
LDX #0 ; X=0: start of TX control block
A528 LDY #&10 ; Y=&10: length of TXCB to save
A52A JSR save_net_tx_cb ; Save current TX control block
A52D LDA fs_load_vector ; Load seconds from clock workspace
A530 JSR bin_to_bcd ; Convert binary to BCD
A533 STA fs_load_upper ; Store BCD seconds
A536 LDA fs_reply_cmd ; Load minutes from clock workspace
A539 JSR bin_to_bcd ; Convert binary to BCD
A53C STA fs_handle_check ; Store BCD minutes
A53F LDA fs_data_count ; Load hours from clock workspace
A542 JSR bin_to_bcd ; Convert binary to BCD
A545 STA fs_load_vector ; Store BCD hours
A548 LDA #0 ; Clear hours high position
A54A STA fs_reply_cmd ; Store zero
A54D LDA fs_func_code ; Load day+month byte
A550 PHA ; Save for later high nibble extract
A551 LDA fs_cmd_data ; Load day value
A554 JSR bin_to_bcd ; Convert day to BCD
A557 STA fs_data_count ; Store BCD day
A55A PLA ; Restore day+month byte
A55B PHA ; Save again for month extract
A55C AND #&0f ; Mask low nibble (month low bits)
A55E JSR bin_to_bcd ; Convert to BCD
A561 STA fs_func_code ; Store BCD month
A564 PLA ; Restore day+month byte
A565 LSR ; Shift high nibble down
A566 LSR ; Continue shifting
A567 LSR ; Continue shifting
A568 LSR ; 4th shift: isolate high nibble
A569 ADC #&51 ; Add &51 for year offset + carry
A56B JSR bin_to_bcd ; Convert year to BCD
A56E STA fs_cmd_data ; Store BCD year
A571 LDY #6 ; Copy 7 bytes (Y=6 down to 0)
A573 .loop_copy_bcd_to_pb←1← A579 BPL
LDA fs_cmd_data,y ; Load BCD byte from workspace
A576 STA (osword_pb_ptr),y ; Store to parameter block
A578 DEY ; Next byte down
A579 BPL loop_copy_bcd_to_pb ; Loop for all 7 bytes
A57B 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
A57C .bin_to_bcd←6← A530 JSR← A539 JSR← A542 JSR← A554 JSR← A55E JSR← A56B JSR
PHP ; Save processor flags (decimal mode)
A57D TAX ; X = binary count
A57E BEQ done_bcd_convert ; Zero: result is 0, skip loop
A580 SED ; Set decimal mode for BCD add
A581 LDA #0 ; Start BCD result at 0
A583 .loop_bcd_add←1← A587 BNE
CLC ; Clear carry for BCD add
A584 ADC #1 ; Add 1 in decimal mode
A586 DEX ; Count down binary value
A587 BNE loop_bcd_add ; Loop until zero
A589 .done_bcd_convert←1← A57E BEQ
PLP ; Restore flags (clears decimal mode)
A58A RTS ; Return with BCD result in A
A58B .osword_10_handler
ASL ws_0d60 ; Shift ws_0d60 left (status flag)
A58E TYA ; A = Y (saved index)
A58F BCS setup_ws_rx_ptrs ; C=1: transmit active path
A591 STA (osword_pb_ptr),y ; C=0: store Y to parameter block
A593 RTS ; Return (transmit not active)
A594 .setup_ws_rx_ptrs←1← A58F BCS
LDA net_rx_ptr_hi ; Set workspace high byte
A596 STA ws_ptr_hi ; Copy to ws_ptr_hi
A598 STA nmi_tx_block_hi ; Also set as NMI TX block high
A59A LDA #&6f ; Low byte = &6F
A59C STA ws_ptr_lo ; Set ws_ptr_lo
A59E STA nmi_tx_block ; Set NMI TX block low
A5A0 LDX #&0f ; X=&0F: byte count for copy
A5A2 JSR copy_pb_byte_to_ws ; Copy data and begin transmission
A5A5 JMP tx_begin ; Jump to begin Econet transmission
A5A8 .osword_11_handler
LDX nfs_workspace_hi ; Load NFS workspace page high byte
A5AA STX ws_ptr_hi ; Set workspace pointer high
A5AC STY ws_ptr_lo ; Set workspace pointer low from Y
A5AE ROR econet_flags ; Rotate Econet flags (save interrupt state)
A5B1 LDA (osword_pb_ptr),y ; Load PB byte 0 (OSWORD flag)
A5B3 STA osword_flag ; Store OSWORD flag
A5B5 BNE use_specified_slot ; Non-zero: use specified slot
A5B7 LDA #3 ; A=3: start searching from slot 3
A5B9 .loop_find_rx_slot←1← A5CB BNE
JSR byte_to_2bit_index ; Convert slot to 2-bit workspace index
A5BC BCS store_rx_result ; C set: slot invalid, store result
A5BE LSR ; Shift index right (divide by 4)
A5BF LSR ; Continue shift
A5C0 TAX ; Transfer to X as workspace offset
A5C1 LDA (ws_ptr_lo),y ; Load workspace byte at offset
A5C3 BEQ store_rx_result ; Zero: slot empty, store result
A5C5 CMP #&3f ; Compare with &3F ('?' marker)
A5C7 BEQ store_rx_slot_found ; Match: slot found for receive
A5C9 INX ; Try next slot index
A5CA TXA ; Transfer back to A
A5CB BNE loop_find_rx_slot ; Loop back (A != 0)
A5CD .store_rx_slot_found←1← A5C7 BEQ
TXA ; Transfer found slot to A
A5CE LDX #0 ; X=0: index for indirect store
A5D0 STA (osword_pb_ptr,x) ; Store slot number to PB byte 0
A5D2 .use_specified_slot←1← A5B5 BNE
JSR byte_to_2bit_index ; Convert specified slot to workspace index
A5D5 BCS store_rx_result ; C set: slot invalid, store result
A5D7 DEY ; Y=Y-1: adjust workspace offset
A5D8 STY ws_ptr_lo ; Update workspace pointer low
A5DA LDA #&c0 ; A=&C0: slot active marker
A5DC LDY #1 ; Y=1: workspace byte offset
A5DE LDX #&0b ; X=&0B: byte count for PB copy
A5E0 CPY osword_flag ; Compare Y with OSWORD flag
A5E2 ADC (ws_ptr_lo),y ; Add workspace byte (check slot state)
A5E4 BEQ copy_pb_and_mark ; Zero: slot ready, copy PB and mark
A5E6 BMI increment_and_retry ; Negative: slot busy, increment and retry
A5E8 .loop_copy_slot_data←1← A5F8 BNE
CLC ; Clear carry for PB copy
A5E9 .copy_pb_and_mark←1← A5E4 BEQ
JSR copy_pb_byte_to_ws ; Copy PB byte to workspace slot
A5EC BCS osword_11_done ; C set: copy done, finish
A5EE LDA #&3f ; A=&3F: mark slot as pending ('?')
A5F0 LDY #1 ; Y=1: workspace flag offset
A5F2 STA (ws_ptr_lo),y ; Store pending marker to workspace
A5F4 BNE osword_11_done ; ALWAYS branch
A5F6 .increment_and_retry←1← A5E6 BMI
ADC #1 ; Increment retry counter
A5F8 BNE loop_copy_slot_data ; Non-zero: retry copy loop
A5FA DEY ; Decrement Y (adjust offset)
A5FB .store_rx_result←3← A5BC BCS← A5C3 BEQ← A5D5 BCS
STA (osword_pb_ptr),y ; Store result A to PB via Y
A5FD .osword_11_done←2← A5EC BCS← A5F4 BNE
ROL econet_flags ; Rotate Econet flags back (restore state)
A600 RTS ; Return from OSWORD 11 handler

Store OSWORD parameter block pointer+1 to workspace

Computes PB pointer + 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 the PB low byte to compute the buffer end pointer, stored at workspace offset &20.

A601 .store_osword_pb_ptr←1← A8F0 JSR
LDY #&1c ; Y=&1C: workspace offset
A603 LDA osword_pb_ptr ; Load PB pointer low byte
A605 ADC #1 ; Add 1 (C from earlier operation)
A607 JSR store_ptr_at_ws_y ; Store ptr at workspace+Y
A60A LDY #1 ; Y=1: read PB byte 1
A60C LDA (osword_pb_ptr),y ; Load transfer length from PB
A60E LDY #&20 ; Y=&20: second workspace offset
A610 ADC osword_pb_ptr ; Add PB low byte to get end ptr
fall through ↓

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 osword_pb_ptr_hi plus carry, supporting pointer arithmetic across page boundaries.

On EntryApointer low byte
Yworkspace offset
Ccarry for high byte addition
A612 .store_ptr_at_ws_y←1← A607 JSR
STA (nfs_workspace),y ; Store low byte to workspace+Y
A614 INY ; Next byte
A615 LDA osword_pb_ptr_hi ; Load PB pointer high byte
A617 ADC #0 ; Add carry
A619 STA (nfs_workspace),y ; Store high byte to workspace+Y+1
A61B RTS ; Return
A61C .osword_12_handler
LDA net_rx_ptr_hi ; Set workspace from RX ptr high
A61E STA ws_ptr_hi ; Store to ws_ptr_hi
A620 LDY #&7f ; Y=&7F: last byte of RX buffer
A622 LDA (net_rx_ptr),y ; Load port/count from RX buffer
A624 INY ; Y=&80: set workspace pointer Y=&80
A625 STY ws_ptr_lo ; Store as ws_ptr_lo
A627 TAX ; X = port/count value
A628 DEX ; X-1: adjust count
A629 LDY #0 ; Y=0 for copy
A62B JSR copy_pb_byte_to_ws ; Copy workspace data
A62E JMP commit_state_byte ; Update state and return
A631 .osword_13_dispatch
TAX ; X = sub-code
A632 CMP #&13 ; Sub-code < &13?
A634 BCS return_from_osword_13 ; Out of range: return
A636 LDA osword_13_hi_table,x ; Load handler address high byte
A639 PHA ; Push high byte
A63A LDA osword_13_lo_table,x ; Load handler address low byte
A63D PHA ; Push low byte
A63E .return_from_osword_13←1← A634 BCS
RTS ; RTS dispatches to handler
A63F .osword_13_lo_table←1← A63A LDA
EQUB <(osword_13_read_station-1) ; lo-sub 0: read FS station
A640 EQUB <(osword_13_set_station-1) ; lo-sub 1: set FS station
A641 EQUB <(osword_13_read_ws_pair-1) ; lo-sub 2: read workspace pair
A642 EQUB <(osword_13_write_ws_pair-1) ; lo-sub 3: write workspace pair
A643 EQUB <(osword_13_read_prot-1) ; lo-sub 4: read protection mask
A644 EQUB <(osword_13_write_prot-1) ; lo-sub 5: write protection mask
A645 EQUB <(osword_13_read_handles-1) ; lo-sub 6: read FCB handles
A646 EQUB <(osword_13_set_handles-1) ; lo-sub 7: set FCB handles
A647 EQUB <(osword_13_read_rx_flag-1) ; lo-sub 8: read RX flag
A648 EQUB <(osword_13_read_rx_port-1) ; lo-sub 9: read RX port
A649 EQUB <(osword_13_read_error-1) ; lo-sub 10: read error flag
A64A EQUB <(osword_13_read_context-1) ; lo-sub 11: read context byte
A64B EQUB <(osword_13_read_csd-1) ; lo-sub 12: read CSD path
A64C EQUB <(osword_13_write_csd-1) ; lo-sub 13: write CSD path
A64D EQUB <(osword_13_read_free_bufs-1) ; lo-sub 14: read free buffers
A64E EQUB <(osword_13_read_ctx_3-1) ; lo-sub 15: read 3 context bytes
A64F EQUB <(osword_13_write_ctx_3-1) ; lo-sub 16: write 3 context bytes
A650 EQUB <(osword_13_bridge_query-1) ; lo-sub 17: query bridge status
A651 .osword_13_hi_table←1← A636 LDA
EQUB >(osword_13_read_station-1) ; hi-sub 0: read FS station
A652 EQUB >(osword_13_set_station-1) ; hi-sub 1: set FS station
A653 EQUB >(osword_13_read_ws_pair-1) ; hi-sub 2: read workspace pair
A654 EQUB >(osword_13_write_ws_pair-1) ; hi-sub 3: write workspace pair
A655 EQUB >(osword_13_read_prot-1) ; hi-sub 4: read protection mask
A656 EQUB >(osword_13_write_prot-1) ; hi-sub 5: write protection mask
A657 EQUB >(osword_13_read_handles-1) ; hi-sub 6: read FCB handles
A658 EQUB >(osword_13_set_handles-1) ; hi-sub 7: set FCB handles
A659 EQUB >(osword_13_read_rx_flag-1) ; hi-sub 8: read RX flag
A65A EQUB >(osword_13_read_rx_port-1) ; hi-sub 9: read RX port
A65B EQUB >(osword_13_read_error-1) ; hi-sub 10: read error flag
A65C EQUB >(osword_13_read_context-1) ; hi-sub 11: read context byte
A65D EQUB >(osword_13_read_csd-1) ; hi-sub 12: read CSD path
A65E EQUB >(osword_13_write_csd-1) ; hi-sub 13: write CSD path
A65F EQUB >(osword_13_read_free_bufs-1) ; hi-sub 14: read free buffers
A660 EQUB >(osword_13_read_ctx_3-1) ; hi-sub 15: read 3 context bytes
A661 EQUB >(osword_13_write_ctx_3-1) ; hi-sub 16: write 3 context bytes
A662 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.

A663 .osword_13_read_station
BIT fs_flags ; NFS active?
A666 BMI read_station_bytes ; Yes: read station data
A668 .nfs_inactive_exit←1← A679 BPL
JMP return_zero_in_pb ; No: return zero
A66B .read_station_bytes←1← A666 BMI
LDY #2 ; Y=2: copy 2 bytes
A66D .loop_copy_station←1← A673 BNE
LDA fs_server_base,y ; Load station byte
A670 STA (osword_pb_ptr),y ; Store to PB[Y]
A672 DEY ; Previous byte
A673 BNE loop_copy_station ; Loop for bytes 2..1
A675 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.

A676 .osword_13_set_station
BIT fs_flags ; NFS active?
A679 BPL nfs_inactive_exit ; No: return zero
A67B LDY #0 ; Y=0 for process_all_fcbs
A67D JSR process_all_fcbs ; Close all open FCBs
A680 LDY #2 ; Y=2: copy 2 bytes
A682 .loop_store_station←1← A688 BNE
LDA (osword_pb_ptr),y ; Load new station byte from PB
A684 STA fs_server_base,y ; Store to l0dff
A687 DEY ; Previous byte
A688 BNE loop_store_station ; Loop for bytes 2..1
A68A JSR clear_if_station_match ; Clear handles if station matches
A68D LDX #&0f ; X=&0F: scan 16 FCB entries
A68F .scan_fcb_entry←1← A6E8 BPL
LDA chan_status,x ; Load FCB flags
A692 TAY ; Save flags in Y
A693 AND #2 ; Test bit 1 (FCB allocated?)
A695 BEQ next_fcb_entry ; No: skip to next entry
A697 TYA ; Restore flags
A698 AND #&df ; Clear bit 5 (pending update)
A69A STA chan_status,x ; Store updated flags
A69D TAY ; Save in Y
A69E JSR match_station_net ; Does FCB match new station?
A6A1 BNE next_fcb_entry ; No match: skip to next
A6A3 CLC ; Clear carry for ADC
A6A4 TYA ; Restore flags
A6A5 AND #4 ; Test bit 2 (handle 1 active?)
A6A7 BEQ check_handle_2 ; No: check handle 2
A6A9 TYA ; Restore flags
A6AA ORA #&20 ; Set bit 5 (handle reassigned)
A6AC TAY ; Save updated flags
A6AD LDA fcb_net_or_port,x ; Get FCB high byte
A6B0 STA fs_urd_handle ; Store as handle 1 station
A6B3 TXA ; FCB index
A6B4 ADC #&20 ; Add &20 for FCB table offset
A6B6 STA handle_1_fcb ; Store as handle 1 FCB index
A6B9 .check_handle_2←1← A6A7 BEQ
TYA ; Restore flags
A6BA AND #8 ; Test bit 3 (handle 2 active?)
A6BC BEQ check_handle_3 ; No: check handle 3
A6BE TYA ; Restore flags
A6BF ORA #&20 ; Set bit 5
A6C1 TAY ; Save updated flags
A6C2 LDA fcb_net_or_port,x ; Get FCB high byte
A6C5 STA fs_csd_handle ; Store as handle 2 station
A6C8 TXA ; FCB index
A6C9 ADC #&20 ; Add &20 for FCB table offset
A6CB STA handle_2_fcb ; Store as handle 2 FCB index
A6CE .check_handle_3←1← A6BC BEQ
TYA ; Restore flags
A6CF AND #&10 ; Test bit 4 (handle 3 active?)
A6D1 BEQ store_updated_status ; No: store final flags
A6D3 TYA ; Restore flags
A6D4 ORA #&20 ; Set bit 5
A6D6 TAY ; Save updated flags
A6D7 LDA fcb_net_or_port,x ; Get FCB high byte
A6DA STA fs_lib_handle ; Store as handle 3 station
A6DD TXA ; FCB index
A6DE ADC #&20 ; Add &20 for FCB table offset
A6E0 STA handle_3_fcb ; Store as handle 3 FCB index
A6E3 .store_updated_status←1← A6D1 BEQ
TYA ; Store final flags for this FCB
A6E4 STA chan_status,x ; Update l1060[X]
A6E7 .next_fcb_entry←2← A695 BEQ← A6A1 BNE
DEX ; Next FCB entry
A6E8 BPL scan_fcb_entry ; Loop for all 16 entries
A6EA RTS ; Return

OSWORD &13 sub 12: read CSD path

Reads 5 current selected directory path bytes from the RX workspace at offset &1B into PB[1..5]. Sets carry clear to select the workspace-to-PB copy direction.

A6EB .osword_13_read_csd
CLC ; C=0: workspace-to-PB direction
A6EC 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 &1B. Sets carry to select the PB-to-workspace copy direction.

A6EE .osword_13_write_csd
SEC ; C=1: PB-to-workspace direction
A6EF .setup_csd_copy←1← A6EC BCC
LDA #&1b ; Workspace offset &1B
A6F1 STA ws_ptr_lo ; Set ws_ptr_lo
A6F3 LDA net_rx_ptr_hi ; Page from RX pointer high byte
A6F5 STA ws_ptr_hi ; Set ws_ptr_hi
A6F7 LDY #1 ; Y=1: first PB data byte
A6F9 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
A6FB .copy_pb_byte_to_ws←5← A5A2 JSR← A5E9 JSR← A62B JSR← A707 BPL← A714 BCC
BCC copy_ws_byte_to_pb ; C=0: skip PB-to-WS copy
A6FD LDA (osword_pb_ptr),y ; C=1: load from parameter block
A6FF STA (ws_ptr_lo),y ; Store to workspace
A701 .copy_ws_byte_to_pb←1← A6FB BCC
LDA (ws_ptr_lo),y ; Load from workspace
A703 STA (osword_pb_ptr),y ; Store to parameter block
A705 INY ; Next byte
A706 DEX ; Count down
A707 BPL copy_pb_byte_to_ws ; Loop for all bytes
A709 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.

A70A .osword_13_read_ws_pair
LDA nfs_workspace_hi ; Load workspace page high byte
A70C STA ws_ptr_hi ; Set ws_ptr_hi
A70E INY ; Y=1
A70F TYA ; A=1
A710 STA ws_ptr_lo ; Set ws_ptr_lo = 1
A712 TAX ; X=1: copy 2 bytes
A713 CLC ; C=0: workspace-to-PB direction
A714 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.

A716 .osword_13_write_ws_pair
INY ; Y=1: first PB data byte
A717 LDA (osword_pb_ptr),y ; Load PB[1]
A719 INY ; Y=2
A71A STA (nfs_workspace),y ; Store to (nfs_workspace)+2
A71C LDA (osword_pb_ptr),y ; Load PB[2]
A71E INY ; Y=3
A71F STA (nfs_workspace),y ; Store to (nfs_workspace)+3
A721 JSR init_bridge_poll ; Reinitialise bridge routing
A724 EOR (nfs_workspace),y ; Compare result with workspace
A726 BNE return_from_write_ws_pair ; Different: leave unchanged
A728 STA (nfs_workspace),y ; Same: clear workspace byte
A72A .return_from_write_ws_pair←1← A726 BNE
RTS ; Return

OSWORD &13 sub 4: read protection mask

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

A72B .osword_13_read_prot
LDA ws_0d68 ; Load protection mask
A72E 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.

A731 .osword_13_write_prot
INY ; Y=1: PB data offset
A732 LDA (osword_pb_ptr),y ; Load new mask from PB[1]
A734 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].

A737 .osword_13_read_handles
BIT fs_flags ; NFS active?
A73A BPL return_zero_in_pb ; No: return zero
A73C LDY #3 ; Y=3: copy 3 bytes
A73E .loop_copy_handles←1← A744 BNE
LDA fs_lib_flags,y ; Load handle byte
A741 STA (osword_pb_ptr),y ; Store to PB[Y]
A743 DEY ; Previous byte
A744 BNE loop_copy_handles ; Loop for bytes 3..1
A746 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.

A747 .osword_13_set_handles
BIT fs_flags ; NFS active?
A74A BMI start_set_handles ; Yes: process handles
A74C .return_zero_in_pb←2← A668 JMP← A73A BPL
LDA #0 ; A=0
A74E TAY ; Y=&00
A74F STA (osword_pb_ptr),y ; Store 0 to PB[0]
A751 RTS ; Return
A752 .start_set_handles←1← A74A BMI
LDY #1 ; Y=1: first handle in PB
A754 .validate_handle←1← A794 BNE
LDA (osword_pb_ptr),y ; Load handle value from PB[Y]
A756 CMP #&20 ; Must be >= &20
A758 BCC handle_invalid ; Below range: invalid
A75A CMP #&30 ; Must be < &30
A75C BCS handle_invalid ; Above range: invalid
A75E TAX ; X = handle value
A75F LDA fcb_attr_or_count_mid,x ; Load l1010[handle]
A762 BNE check_handle_alloc ; Non-zero: FCB exists
A764 .handle_invalid←3← A758 BCC← A75C BCS← A770 BEQ
LDA #0 ; Invalid: store 0 to PB[0]
A766 TAX ; X=&00
A767 STA (osword_pb_ptr,x) ; Clear PB[0] status
A769 BEQ next_handle_slot ; Skip to next handle ALWAYS branch
A76B .check_handle_alloc←1← A762 BNE
LDA fcb_flags,x ; Load l1040[handle] flags
A76E AND #2 ; Test bit 1 (allocated?)
A770 BEQ handle_invalid ; Not allocated: invalid
A772 TXA ; X = handle value
A773 STA fs_lib_flags,y ; Store handle to l1071+Y
A776 LDA fcb_attr_or_count_mid,x ; Load station from l1010
A779 STA fs_server_net,y ; Store station to l0e01+Y
A77C CPY #1 ; Is this handle 1 (Y=1)?
A77E BNE assign_handle_2 ; No: check handle 2
A780 TYA ; Save Y
A781 PHA ; Push Y
A782 LDY #4 ; Bit mask &04 for handle 1
A784 JSR update_fcb_flag_bits ; Update flags across all FCBs
A787 PLA ; Restore Y
A788 TAY ; Back to Y
A789 LDA fcb_flags,x ; Reload l1040 flags
A78C ORA #&24 ; Set bits 2+5 (active+updated)
A78E STA fcb_flags,x ; Store updated flags
A791 .next_handle_slot←3← A769 BEQ← A7AD BNE← A7C0 BNE
INY ; Next handle slot
A792 CPY #4 ; Done all 3 handles?
A794 BNE validate_handle ; No: process next handle
A796 DEY ; Y=3 for return
A797 RTS ; Return
A798 .assign_handle_2←1← A77E BNE
CPY #2 ; Is this handle 2 (Y=2)?
A79A BNE assign_handle_3 ; No: must be handle 3
A79C TYA ; Save Y
A79D PHA ; Push Y
A79E LDY #8 ; Bit mask &08 for handle 2
A7A0 JSR update_fcb_flag_bits ; Update flags across all FCBs
A7A3 PLA ; Restore Y
A7A4 TAY ; Back to Y
A7A5 LDA fcb_flags,x ; Reload l1040 flags
A7A8 ORA #&28 ; Set bits 3+5 (active+updated)
A7AA STA fcb_flags,x ; Store updated flags
A7AD BNE next_handle_slot ; Next handle slot ALWAYS branch
A7AF .assign_handle_3←1← A79A BNE
TYA ; Handle 3: save Y
A7B0 PHA ; Push Y
A7B1 LDY #&10 ; Bit mask &10 for handle 3
A7B3 JSR update_fcb_flag_bits ; Update flags across all FCBs
A7B6 PLA ; Restore Y
A7B7 TAY ; Back to Y
A7B8 LDA fcb_flags,x ; Reload l1040 flags
A7BB ORA #&30 ; Set bits 4+5 (active+updated)
A7BD STA fcb_flags,x ; Store updated flags
A7C0 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)
A7C2 .update_fcb_flag_bits←3← A784 JSR← A7A0 JSR← A7B3 JSR
TXA ; Save X (current FCB index)
A7C3 PHA ; Push X
A7C4 LDX #&0f ; X=&0F: scan 16 FCB entries
A7C6 .loop_scan_fcb_flags←1← A7E2 BPL
LDA chan_status,x ; Load FCB flags
A7C9 ROL ; Shift bits 6-7 into bits 7-0
A7CA ROL ; Bit 6 now in bit 7 (N flag)
A7CB BPL next_flag_entry ; Bit 6 clear: skip entry
A7CD TYA ; Restore Y (bit mask)
A7CE AND chan_status,x ; Test mask bits against flags
A7D1 BEQ no_flag_match ; Zero: no matching bits
A7D3 TYA ; Matching: restore Y
A7D4 ORA #&20 ; Set bit 5 (updated)
A7D6 BNE clear_flag_bits ; Skip clear path ALWAYS branch
A7D8 .no_flag_match←1← A7D1 BEQ
TYA ; No match: restore Y
A7D9 .clear_flag_bits←1← A7D6 BNE
EOR #&ff ; Invert all bits
A7DB AND chan_status,x ; Clear tested bits in flags
A7DE STA chan_status,x ; Store updated flags
A7E1 .next_flag_entry←1← A7CB BPL
DEX ; Next FCB entry
A7E2 BPL loop_scan_fcb_flags ; Loop for all 16 entries
A7E4 PLA ; Restore original X
A7E5 TAX ; Back to X
A7E6 RTS ; Return

OSWORD &13 sub 8: read RX control block flag

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

A7E7 .osword_13_read_rx_flag
LDY #5 ; Y=5: RX control block offset
A7E9 LDA (net_rx_ptr),y ; Load (net_rx_ptr)+5
A7EB LDY #0 ; Y=0
A7ED 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].

A7F0 .osword_13_read_rx_port
LDY #&7f ; Y=&7F: port byte offset
A7F2 LDA (net_rx_ptr),y ; Load (net_rx_ptr)+&7F
A7F4 LDY #1 ; Y=1
A7F6 STA (osword_pb_ptr),y ; Store to PB[1]
A7F8 INY ; Y=&02
A7F9 LDA #&80 ; A=&80
A7FB STA (osword_pb_ptr),y ; Store &80 to PB[2]
A7FD RTS ; Return

OSWORD &13 sub 10: read error flag

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

A7FE .osword_13_read_error
LDA fs_last_error ; Load error flag
A801 JMP store_a_to_pb_1 ; Store to PB[1] and return

OSWORD &13 sub 11: read context byte

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

A804 .osword_13_read_context
LDA net_context ; Load context byte
A807 JMP store_a_to_pb_1 ; Store to PB[1] and return

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.

A80A .osword_13_read_free_bufs
LDA #&6f ; Total buffers = &6F
A80C SEC ; Subtract used count
A80D SBC spool_buf_idx ; Free = &6F - l0d6b
A810 .store_a_to_pb_1←4← A72E JMP← A7ED JMP← A801 JMP← A807 JMP
INY ; Y=1
A811 STA (osword_pb_ptr),y ; Store A to PB[1]
A813 RTS ; Return

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.

A814 .osword_13_read_ctx_3←1← A81C BNE
INY ; Next byte offset
A815 LDA net_context,y ; Load l0d6d[Y]
A818 STA (osword_pb_ptr),y ; Store to PB[Y]
A81A CPY #3 ; Done 3 bytes?
A81C BNE osword_13_read_ctx_3 ; No: loop
A81E 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.

A81F .osword_13_write_ctx_3←1← A827 BNE
INY ; Next byte offset
A820 LDA (osword_pb_ptr),y ; Load PB[Y]
A822 STA net_context,y ; Store to l0d6d[Y]
A825 CPY #3 ; Done 3 bytes?
A827 BNE osword_13_write_ctx_3 ; No: loop
A829 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.

A82A .osword_13_bridge_query
JSR init_bridge_poll ; Poll for bridge
A82D LDY #0 ; Y=0
A82F LDA bridge_status ; Load bridge status
A832 CMP #&ff ; Is it &FF (no bridge)?
A834 BNE bridge_found ; No: bridge found
A836 TYA ; A=&00
A837 STA (osword_pb_ptr),y ; PB[0] = 0 (no bridge)
A839 RTS ; Return
A83A .bridge_found←1← A834 BNE
INY ; Y=1
A83B STA (osword_pb_ptr),y ; PB[1] = bridge status
A83D INY ; Y=2
A83E INY ; Y=3
A83F LDA (osword_pb_ptr),y ; Load PB[3] (caller value)
A841 BEQ use_default_station ; Zero: use default station
A843 .compare_bridge_status
EOR bridge_status ; Compare with bridge status
A846 BNE return_from_bridge_query ; Different: return unchanged
A848 BEQ store_bridge_station ; Same: confirm station ALWAYS branch
A84A .use_default_station←1← A841 BEQ
LDA fs_server_net ; Load default from l0e01
A84D .store_bridge_station←1← A848 BEQ
STA (osword_pb_ptr),y ; Store to PB[3]
A84F .return_from_bridge_query←1← A846 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.
A850 .bridge_txcb_init_table←1← A87D LDA
EQUB &82 ; TX 0: ctrl = &82 (immediate mode)
A851 EQUB &9C ; TX 1: port = &9C (bridge discovery)
A852 EQUB &FF ; TX 2: dest station = &FF (broadcast)
A853 EQUB &FF ; TX 3: dest network = &FF (all nets)
A854 EQUS "BRIDGE" ; TX 4-9: immediate data payload
A85A EQUB &9C ; TX 10: &9C (port echo)
A85B EQUB &00 ; TX 11: &00 (terminator)
A85C .bridge_rxcb_init_data
EQUB &7F ; RX 0: ctrl = &7F (receive)
A85D EQUB &9C ; RX 1: port = &9C (bridge discovery)
A85E EQUB &00 ; RX 2: station = &00 (any)
A85F EQUB &00 ; RX 3: network = &00 (any)
A860 EQUB &72 ; RX 4: buf start lo (&72)
A861 EQUB &0D ; RX 5: buf start hi (&0D) -> &0D72
A862 EQUB &FF ; RX 6: extended addr fill (&FF)
A863 EQUB &FF ; RX 7: extended addr fill (&FF)
A864 EQUB &74 ; RX 8: buf end lo (&74)
A865 EQUB &0D ; RX 9: buf end hi (&0D) -> &0D74
A866 EQUB &FF ; RX 10: extended addr fill (&FF)
A867 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.

A868 .init_bridge_poll←4← 8DFE JSR← A09C JSR← A721 JSR← A82A JSR
LDA bridge_status ; Check bridge status
A86B CMP #&ff ; Is it &FF (uninitialised)?
A86D BNE return_from_bridge_poll ; No: bridge already active, return
A86F TYA ; Save Y
A870 PHA ; Preserve Y on stack
A871 LDY #&18 ; Y=&18: workspace offset for init
A873 LDX #&0b ; X=&0B: 12 bytes to copy
A875 ROR econet_flags ; Rotate l0d61 right (save flag)
A878 .loop_copy_bridge_init←1← A884 BPL
LDA bridge_ws_init_data,y ; Load init data byte
A87B STA (nfs_workspace),y ; Store to workspace
A87D LDA bridge_txcb_init_table,x ; Load TXCB template byte
A880 STA txcb_ctrl,x ; Store to TX control block
A882 INY ; Next workspace byte
A883 DEX ; Next template byte
A884 BPL loop_copy_bridge_init ; Loop for all 12 bytes
A886 STX bridge_status ; Store X (-1) as bridge counter
A889 ROL econet_flags ; Restore l0d61 flag
A88C .loop_wait_ws_status←2← A88F BCC← A8BE BPL
ASL ws_0d60 ; Shift ws_0d60 left (check status)
A88F BCC loop_wait_ws_status ; C=0: status clear, retry
A891 LDA #&82 ; Control byte &82 for TX
A893 STA txcb_ctrl ; Set in TX control block
A895 LDA #&c0 ; Data block at &00C0
A897 STA nmi_tx_block ; Set NMI TX block low
A899 LDA #0 ; High byte = 0 (page 0)
A89B STA nmi_tx_block_hi ; Set NMI TX block high
A89D JSR tx_begin ; Begin Econet transmission
A8A0 .loop_wait_tx_done←1← A8A2 BMI
BIT txcb_ctrl ; Test TX control block bit 7
A8A2 BMI loop_wait_tx_done ; Negative: TX still in progress
A8A4 TAX ; X = result status
A8A5 PHA ; Save TX status
A8A6 LDA osword_pb_ptr_hi ; Save PB pointer high
A8A8 PHA ; Push for later restore
A8A9 LDX osword_pb_ptr ; X = PB pointer low
A8AB LDA #osbyte_vsync ; OSBYTE &13: wait for VSYNC
A8AD JSR osbyte ; Wait for vertical sync
A8B0 PLA ; Restore PB pointer high
A8B1 STA osword_pb_ptr_hi ; Restore to osword_pb_ptr_hi
A8B3 PLA ; Restore TX status
A8B4 TAX ; Back to X
A8B5 LDY #&18 ; Y=&18: check workspace response
A8B7 LDA (nfs_workspace),y ; Load bridge response
A8B9 BMI bridge_responded ; Negative: bridge responded
A8BB JSR advance_x_by_8 ; Advance retry counter by 8
A8BE BPL loop_wait_ws_status ; Positive: retry poll loop
A8C0 .bridge_responded←1← A8B9 BMI
LDA #&3f ; Set response to &3F (OK)
A8C2 STA (nfs_workspace),y ; Store to workspace
A8C4 PLA ; Restore saved Y
A8C5 TAY ; Back to Y
A8C6 LDA bridge_status ; Load bridge status
A8C9 TAX ; X = bridge status
A8CA EOR #&ff ; Complement status
A8CC BEQ return_from_bridge_poll ; Status was &FF: return (no bridge)
A8CE TXA ; Return bridge station in A
A8CF .return_from_bridge_poll←4← A86D BNE← A8CC BEQ← A8D7 BPL← A943 BEQ
RTS ; Return
A8D0 .osword_14_handler
CMP #1 ; Compare sub-code with 1
A8D2 BCS handle_tx_request ; Sub-code >= 1: handle TX request
A8D4 BIT fs_flags ; Test station active flag
A8D7 BPL return_from_bridge_poll ; Not active: return
A8D9 LDY #&23 ; Y=&23: workspace offset for params
A8DB JSR mask_owner_access ; Set owner access mask
A8DE .loop_copy_txcb_init←1← A8EB BNE
LDA init_txcb,y ; Load TXCB init byte
A8E1 BNE store_txcb_init_byte ; Non-zero: use template value
A8E3 LDA txcb_default_base,y ; Zero: use workspace default value
A8E6 .store_txcb_init_byte←1← A8E1 BNE
STA (nfs_workspace),y ; Store to workspace
A8E8 DEY ; Next byte down
A8E9 CPY #&17 ; Until Y reaches &17
A8EB BNE loop_copy_txcb_init ; Loop for all bytes
A8ED INY ; Y=&18 (INY from &17)
A8EE STY net_tx_ptr ; Set net_tx_ptr low byte
A8F0 JSR store_osword_pb_ptr ; Store PB pointer to workspace
A8F3 LDY #2 ; Y=2: parameter offset
A8F5 LDA #&90 ; Control byte &90
A8F7 STA escapable ; Set escapable flag
A8F9 STA (osword_pb_ptr),y ; Store control byte to PB
A8FB INY ; Y=&03
A8FC INY ; Y=&04
A8FD .loop_copy_ws_to_pb←1← A905 BNE
LDA osword_ws_base,y ; Load workspace data
A900 STA (osword_pb_ptr),y ; Store to parameter block
A902 INY ; Next byte
A903 CPY #7 ; Until Y reaches 7
A905 BNE loop_copy_ws_to_pb ; Loop for 3 bytes (Y=4,5,6)
A907 LDA nfs_workspace_hi ; Set TX pointer high byte
A909 STA net_tx_ptr_hi ; Store to net_tx_ptr_hi
A90B CLI ; Enable interrupts
A90C JSR send_net_packet ; Send the network packet
A90F LDY #&20 ; Y=&20: workspace offset
A911 LDA #&ff ; Set to &FF (pending)
A913 STA (nfs_workspace),y ; Mark send pending in workspace
A915 INY ; Y=&21
A916 STA (nfs_workspace),y ; Also mark offset &21
A918 LDY #&19 ; Y=&19: control offset
A91A LDA #&90 ; Control byte &90
A91C STA (nfs_workspace),y ; Store to workspace
A91E DEY ; Y=&18: RX control offset Y=&18
A91F LDA #&7f ; Control byte &7F
A921 STA (nfs_workspace),y ; Store RX control
A923 JMP wait_net_tx_ack ; Wait for TX acknowledgement
A926 .handle_tx_request←1← A8D2 BCS
PHP ; Save processor flags
A927 LDY #1 ; Y=1: PB offset for station
A929 LDA (osword_pb_ptr),y ; Load station number from PB
A92B TAX ; X = station number
A92C INY ; Y=&02
A92D LDA (osword_pb_ptr),y ; Load network number from PB
A92F INY ; Y=3: workspace start offset Y=&03
A930 STY ws_ptr_lo ; Store Y as ws_ptr_lo
A932 LDY #&72 ; Y=&72: workspace offset for dest
A934 STA (net_rx_ptr),y ; Store network to workspace
A936 DEY ; Y=&71
A937 TXA ; A = station (from X)
A938 STA (net_rx_ptr),y ; Store station to workspace
A93A PLP ; Restore flags from PHP
A93B BNE handle_burst_xfer ; Non-zero sub-code: handle burst
A93D .loop_send_pb_chars←1← A956 BNE
LDY ws_ptr_lo ; Load current offset
A93F INC ws_ptr_lo ; Advance offset for next byte
A941 LDA (osword_pb_ptr),y ; Load next char from PB
A943 BEQ return_from_bridge_poll ; Zero: end of data, return
A945 LDY #&7d ; Y=&7D: workspace char offset
A947 STA (net_rx_ptr),y ; Store char to RX buffer
A949 PHA ; Save char for later test
A94A JSR init_ws_copy_wide ; Init workspace copy for wide xfer
A94D JSR enable_irq_and_poll ; Enable IRQ and send packet
A950 .loop_bridge_tx_delay←1← A951 BNE
DEX ; Delay countdown
A951 BNE loop_bridge_tx_delay ; Loop for short delay
A953 PLA ; Restore char
A954 EOR #&0d ; Test if char was CR (&0D)
A956 BNE loop_send_pb_chars ; Not CR: send next char
A958 RTS ; CR sent: return
A959 .handle_burst_xfer←1← A93B BNE
JSR init_ws_copy_wide ; Init workspace for wide copy
A95C LDY #&7b ; Y=&7B: workspace offset
A95E LDA (net_rx_ptr),y ; Load buffer size
A960 ADC #3 ; Add 3 for header
A962 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.

A964 .enable_irq_and_poll←1← A94D JSR
CLI ; Enable interrupts
A965 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 (&8E74) in the extended vector data area.

A968 .netv_handler
PHP ; Save processor flags
A969 PHA ; Save A
A96A TXA ; Save X
A96B PHA ; Push X
A96C TYA ; Save Y
A96D PHA ; Push Y
A96E TSX ; Get stack pointer
A96F LDA stack_page_3,x ; Read OSWORD number from stack
A972 CMP #9 ; OSWORD >= 9?
A974 BCS restore_regs_return ; Yes: out of range, restore + return
A976 TAX ; X = OSWORD number
A977 JSR push_osword_handler_addr ; Push handler address for dispatch
A97A .restore_regs_return←1← A974 BCS
PLA ; Restore Y
A97B TAY ; Back to Y
A97C PLA ; Restore X
A97D TAX ; Back to X
A97E PLA ; Restore A
A97F PLP ; Restore processor flags
A980 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.

A981 .push_osword_handler_addr←1← A977 JSR
LDA osword_handler_hi_table,x ; Load handler high byte from table
A984 PHA ; Push for RTS dispatch
A985 LDA osword_handler_lo_table,x ; Load handler low byte from table
A988 PHA ; Push for RTS dispatch
A989 LDA osbyte_a_copy ; Reload OSWORD number for handler
A98B 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.
A98C .osword_handler_lo_table←1← A985 LDA
EQUB <(dispatch_rts-1) ; lo OSWORD 0: no-op (RTS)
A98D EQUB <(netv_print_data-1) ; lo OSWORD 1: printer spool data
A98E EQUB <(netv_print_data-1) ; lo OSWORD 2: printer spool data
A98F EQUB <(netv_print_data-1) ; lo OSWORD 3: printer spool data
A990 EQUB <(osword_4_handler-1) ; lo OSWORD 4: clear carry + abort
A991 EQUB <(netv_spool_check-1) ; lo OSWORD 5: spool buffer check
A992 EQUB <(dispatch_rts-1) ; lo OSWORD 6: no-op (RTS)
A993 EQUB <(netv_claim_release-1) ; lo OSWORD 7: claim/release handler
A994 EQUB <(osword_8_handler-1) ; lo OSWORD 8: copy PB + abort
A995 .osword_handler_hi_table←1← A981 LDA
EQUB >(dispatch_rts-1) ; hi OSWORD 0: no-op (RTS)
A996 EQUB >(netv_print_data-1) ; hi OSWORD 1: printer spool data
A997 EQUB >(netv_print_data-1) ; hi OSWORD 2: printer spool data
A998 EQUB >(netv_print_data-1) ; hi OSWORD 3: printer spool data
A999 EQUB >(osword_4_handler-1) ; hi OSWORD 4: clear carry + abort
A99A EQUB >(netv_spool_check-1) ; hi OSWORD 5: spool buffer check
A99B EQUB >(dispatch_rts-1) ; hi OSWORD 6: no-op (RTS)
A99C EQUB >(netv_claim_release-1) ; hi OSWORD 7: claim/release handler
A99D 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).

A99E .osword_4_handler
TSX ; Get stack pointer
A99F ROR stack_page_6,x ; Clear bit 0 of stacked P (carry)
A9A2 ASL stack_page_6,x ; Shift back (clears carry flag)
A9A5 TYA ; A = original Y
A9A6 LDY #&da ; Y=&DA: workspace offset
A9A8 STA (nfs_workspace),y ; Store Y to workspace
A9AA 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.

A9AC .tx_econet_abort←3← 8ADF JSR← A9FF JSR← AA65 JSR
LDY #&d9 ; Y=&D9: workspace abort offset
A9AE STA (nfs_workspace),y ; Store abort code to workspace
A9B0 LDA #&80 ; Control byte &80 (abort)
A9B2 LDY #&0c ; Y=&0C: control offset
A9B4 STA (nfs_workspace),y ; Store control byte
A9B6 LDA net_tx_ptr ; Save current TX ptr low
A9B8 PHA ; Push on stack
A9B9 LDA net_tx_ptr_hi ; Save current TX ptr high
A9BB PHA ; Push on stack
A9BC STY net_tx_ptr ; Set TX ptr to workspace offset
A9BE LDX nfs_workspace_hi ; Load workspace high byte
A9C0 STX net_tx_ptr_hi ; Set TX ptr high
A9C2 JSR send_net_packet ; Send the abort packet
A9C5 LDA #&3f ; Set status to &3F (complete)
A9C7 STA (net_tx_ptr,x) ; Store at TX ptr offset 0
A9C9 PLA ; Restore TX ptr high
A9CA STA net_tx_ptr_hi ; Back to net_tx_ptr_hi
A9CC PLA ; Restore TX ptr low
A9CD STA net_tx_ptr ; Back to net_tx_ptr
A9CF 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.

A9D0 .netv_claim_release
LDY osword_pb_ptr_hi ; Load PB pointer high
A9D2 CMP #&81 ; Compare with &81 (special case)
A9D4 BEQ process_match_result ; Match: skip to processing
A9D6 LDY #1 ; Y=1: first claim code position
A9D8 LDX #&0a ; X=&0A: 11 codes to check
A9DA JSR match_rx_code ; Search claim code table
A9DD BEQ process_match_result ; Found: skip to processing
A9DF DEY ; Try second table range
A9E0 DEY ; Y=-1: flag second range
A9E1 LDX #&11 ; X=&11: 18 codes to check
A9E3 JSR match_rx_code ; Search claim code table
A9E6 BEQ process_match_result ; Found: skip to processing
A9E8 INY ; Not found: increment Y
A9E9 .process_match_result←3← A9D4 BEQ← A9DD BEQ← A9E6 BEQ
LDX #2 ; X=2: default state
A9EB TYA ; A = Y (search result)
A9EC BEQ return_from_claim_release ; Zero: not found, return
A9EE PHP ; Save result flags
A9EF BPL save_tube_state ; Positive: use state X=2
A9F1 INX ; X=&03
A9F2 .save_tube_state←1← A9EF BPL
LDY #&dc ; Y=&DC: workspace offset for save
A9F4 .loop_save_tube_bytes←1← A9FC BPL
LDA tube_claimed_id,y ; Load tube claim ID byte
A9F7 STA (nfs_workspace),y ; Store to workspace
A9F9 DEY ; Next byte down
A9FA CPY #&da ; Until Y reaches &DA
A9FC BPL loop_save_tube_bytes ; Loop for 3 bytes
A9FE TXA ; A = state (2 or 3)
A9FF JSR tx_econet_abort ; Send abort with state code
AA02 PLP ; Restore flags
AA03 BPL return_from_claim_release ; Positive: return without poll
AA05 LDA #&7f ; Set control to &7F
AA07 LDY #&0c ; Y=&0C: control offset
AA09 STA (nfs_workspace),y ; Store control byte
AA0B .loop_poll_ws_status←1← AA0D BPL
LDA (nfs_workspace),y ; Load status from workspace
AA0D BPL loop_poll_ws_status ; Positive: keep waiting
AA0F TSX ; Get stack pointer
AA10 LDY #&dd ; Y=&DD: workspace result offset
AA12 LDA (nfs_workspace),y ; Load result byte
AA14 ORA #&44 ; Set bit 6 and bit 2
AA16 BNE store_stack_byte ; Always branch (NZ from ORA) ALWAYS branch
AA18 .loop_restore_stack←1← AA21 BNE
DEY ; Previous workspace byte
AA19 DEX ; Previous stack position
AA1A LDA (nfs_workspace),y ; Load workspace byte
AA1C .store_stack_byte←1← AA16 BNE
STA stack_page_6,x ; Store to caller's stack frame
AA1F CPY #&da ; Reached start of save area?
AA21 BNE loop_restore_stack ; No: copy next byte
AA23 .return_from_claim_release←2← A9EC BEQ← AA03 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
AA24 .match_rx_code←3← A9DA JSR← A9E3 JSR← AA2A BPL
CMP osword_claim_codes,x ; Compare A with code at index X
AA27 BEQ return_from_match_rx_code ; Match: return with Z set
AA29 DEX ; Try next code
AA2A BPL match_rx_code ; More codes: continue search
AA2C .return_from_match_rx_code←2← AA27 BEQ← AA47 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.
AA2D .osword_claim_codes←1← AA24 CMP
EQUB &04 ; Range 1+2: OSWORD &04
AA2E EQUB &09 ; Range 1+2: OSWORD &09
AA2F EQUB &0A ; Range 1+2: OSWORD &0A
AA30 EQUB &14 ; Range 1+2: OSWORD &14
AA31 EQUB &15 ; Range 1+2: OSWORD &15
AA32 EQUB &9A ; Range 1+2: OSWORD &9A
AA33 EQUB &9B ; Range 1+2: OSWORD &9B
AA34 EQUB &E1 ; Range 1+2: OSWORD &E1
AA35 EQUB &E2 ; Range 1+2: OSWORD &E2
AA36 EQUB &E3 ; Range 1+2: OSWORD &E3
AA37 EQUB &E4 ; Range 1+2: OSWORD &E4
AA38 EQUB &0B ; Range 2 only: OSWORD &0B
AA39 EQUB &0C ; Range 2 only: OSWORD &0C
AA3A EQUB &0F ; Range 2 only: OSWORD &0F
AA3B EQUB &79 ; Range 2 only: OSWORD &79
AA3C EQUB &7A ; Range 2 only: OSWORD &7A
AA3D EQUB &86 ; Range 2 only: OSWORD &86
AA3E 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.

AA3F .osword_8_handler
LDY #&0e ; Y=&0E: copy 15 bytes (0-14)
AA41 CMP #7 ; OSWORD 7?
AA43 BEQ copy_pb_to_ws ; Yes: handle
AA45 CMP #8 ; OSWORD 8?
AA47 BNE return_from_match_rx_code ; No: return
AA49 .copy_pb_to_ws←1← AA43 BEQ
LDX #&db ; Workspace low = &DB
AA4B STX nfs_workspace ; Set nfs_workspace low byte
AA4D .loop_copy_pb_to_ws←1← AA52 BPL
LDA (osword_pb_ptr),y ; Load PB[Y]
AA4F STA (nfs_workspace),y ; Store to workspace[Y]
AA51 DEY ; Next byte down
AA52 BPL loop_copy_pb_to_ws ; Loop for 15 bytes
AA54 INY ; Y=0
AA55 DEC nfs_workspace ; Workspace low = &DA
AA57 LDA osbyte_a_copy ; Load OSWORD number
AA59 STA (nfs_workspace),y ; Store at workspace+0 (= &DA)
AA5B STY nfs_workspace ; Workspace low = 0 (restore)
AA5D LDY #&14 ; Y=&14: control offset
AA5F LDA #&e9 ; Control value &E9
AA61 STA (nfs_workspace),y ; Store to workspace+&14
AA63 LDA #1 ; Abort code = 1
AA65 JSR tx_econet_abort ; Send abort packet
AA68 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.

AA6A .init_ws_copy_wide←2← A94A JSR← A959 JSR
LDX #&0d ; X=&0D: 14 bytes to copy
AA6C LDY #&7c ; Y=&7C: workspace destination offset
AA6E BIT bit_test_ff ; Test bit 6 via BIT (V flag check)
AA71 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.

AA73 .init_ws_copy_narrow←1← 958C JSR
LDY #&17 ; Y=&17: narrow mode dest offset
AA75 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.

AA77 .ws_copy_vclr_entry←1← AB38 JSR
CLV ; Clear V flag for narrow mode
AA78 .loop_copy_ws_template←2← AA71 BVS← AA99 BPL
LDA ws_txcb_template_data,x ; Load template byte
AA7B CMP #&fe ; Is it &FE? (end marker)
AA7D BEQ done_ws_template_copy ; Yes: finished, set TX ptr
AA7F CMP #&fd ; Is it &FD? (skip marker)
AA81 BEQ advance_template_idx ; Yes: skip store, just advance
AA83 CMP #&fc ; Is it &FC? (page ptr marker)
AA85 BNE select_store_target ; No: use literal value
AA87 LDA net_rx_ptr_hi ; &FC: load RX buffer page
AA89 BVS store_tx_ptr_hi ; V=1: use net_rx_ptr_hi
AA8B LDA nfs_workspace_hi ; V=0: use nfs_workspace_hi
AA8D .store_tx_ptr_hi←1← AA89 BVS
STA net_tx_ptr_hi ; Store as TX ptr high
AA8F .select_store_target←1← AA85 BNE
BVS store_via_rx_ptr ; V=1: store to net_rx_ptr target
AA91 STA (nfs_workspace),y ; V=0: store to nfs_workspace
AA93 BVC advance_template_idx ; Continue to next byte ALWAYS branch
AA95 .store_via_rx_ptr←1← AA8F BVS
STA (net_rx_ptr),y ; V=1: store to net_rx_ptr
AA97 .advance_template_idx←2← AA81 BEQ← AA93 BVC
DEY ; Advance workspace offset down
AA98 DEX ; Advance template index
AA99 BPL loop_copy_ws_template ; More bytes: continue copy
AA9B .done_ws_template_copy←1← AA7D BEQ
INY ; Adjust Y for start of TX data
AA9C STY net_tx_ptr ; Set net_tx_ptr from Y
AA9E 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.
AA9F .ws_txcb_template_data←1← AA78 LDA
EQUB &85 ; Wide &6F: ctrl=&85
AAA0 EQUB &00 ; Wide &70: port=&00
AAA1 EQUB &FD ; Wide &71: skip (dest station)
AAA2 EQUB &FD ; Wide &72: skip (dest network)
AAA3 EQUB &7D ; Wide &73: buf start lo=&7D
AAA4 EQUB &FC ; Wide &74: buf start hi=page ptr
AAA5 EQUB &FF ; Wide &75: buf start ext lo
AAA6 EQUB &FF ; Wide &76: buf start ext hi
AAA7 EQUB &7E ; Wide &77: buf end lo=&7E
AAA8 EQUB &FC ; Wide &78: buf end hi=page ptr
AAA9 EQUB &FF ; Wide &79: buf end ext lo
AAAA EQUB &FF ; Wide &7A: buf end ext hi
AAAB EQUB &00 ; Wide &7B: zero
AAAC EQUB &00 ; Wide &7C: zero
AAAD EQUB &FE ; Narrow stop (&FE terminator)
AAAE EQUB &80 ; Narrow &0C: ctrl=&80 (standard)
AAAF EQUB &93 ; Narrow &0D: port=&93
AAB0 EQUB &FD ; Narrow &0E: skip (dest station)
AAB1 EQUB &FD ; Narrow &0F: skip (dest network)
AAB2 EQUB &D9 ; Narrow &10: buf start lo=&D9
AAB3 EQUB &FC ; Narrow &11: buf start hi=page ptr
AAB4 EQUB &FF ; Narrow &12: buf start ext lo
AAB5 EQUB &FF ; Narrow &13: buf start ext hi
AAB6 EQUB &DE ; Narrow &14: buf end lo=&DE
AAB7 EQUB &FC ; Narrow &15: buf end hi=page ptr
AAB8 EQUB &FF ; Narrow &16: buf end ext lo
AAB9 EQUB &FF ; Narrow &17: buf end ext hi
AABA EQUB &FE ; Spool stop (&FE terminator)
AABB EQUB &D1 ; Spool &01: port=&D1
AABC EQUB &FD ; Spool &02: skip (dest station)
AABD EQUB &FD ; Spool &03: skip (dest network)
AABE EQUB &25 ; Spool &04: buf start lo=&25
AABF EQUB &FD ; Spool &05: skip (buf start hi)
AAC0 EQUB &FF ; Spool &06: buf start ext lo
AAC1 EQUB &FF ; Spool &07: buf start ext hi
AAC2 EQUB &FD ; Spool &08: skip (buf end lo)
AAC3 EQUB &FD ; Spool &09: skip (buf end hi)
AAC4 EQUB &FF ; Spool &0A: buf end ext lo
AAC5 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.

AAC6 .netv_spool_check
DEX ; X = X - 1
AAC7 CPX osword_pb_ptr ; Match osword_pb_ptr?
AAC9 BNE return_from_spool_reset ; No: return (not our PB)
AACB LDA vdu_status ; Load spool state byte
AACD ROR ; Rotate bit 0 into carry
AACE 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.

AAD0 .reset_spool_buf_state←2← 8F0E JSR← AB21 JMP
LDA #&25 ; Buffer start at &25
AAD2 STA spool_buf_idx ; Store as buffer pointer
AAD5 LDA #&41 ; Control state &41
AAD7 STA ws_0d6a ; Store as spool control state
AADA .return_from_spool_reset←4← AAC9 BNE← AACE BCS← AADD BNE← AAF1 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.

AADB .netv_print_data
CPY #4 ; Check Y == 4
AADD BNE return_from_spool_reset ; No: return
AADF TXA ; A = X (control byte)
AAE0 DEX ; Decrement X
AAE1 BNE handle_spool_ctrl_byte ; Non-zero: handle spool ctrl byte
AAE3 TSX ; Get stack pointer
AAE4 ORA stack_page_6,x ; OR with stack value
AAE7 STA stack_page_6,x ; Store back to stack
AAEA .loop_drain_printer_buf←2← AAF9 BCC← AAFE BCC
LDA #osbyte_read_buffer ; OSBYTE &91: read buffer
AAEC LDX #buffer_printer ; X=3: printer buffer
AAEE JSR osbyte ; Read character from buffer Get character from input buffer (C is set if the buffer is empty, otherwise Y=extracted character)
AAF1 BCS return_from_spool_reset ; C=1: buffer empty, return
AAF3 TYA ; A = extracted character Y is the character extracted from the buffer
AAF4 JSR append_byte_to_rxbuf ; Add byte to RX buffer
AAF7 CPY #&6e ; Buffer past &6E limit?
AAF9 BCC loop_drain_printer_buf ; No: read more from buffer
AAFB JSR process_spool_data ; Buffer full: send packet
AAFE 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
AB00 .append_byte_to_rxbuf←3← AAF4 JSR← AB1B JSR← ABD2 JSR
LDY spool_buf_idx ; Load current buffer index
AB03 STA (net_rx_ptr),y ; Store byte at buffer position
AB05 INC spool_buf_idx ; Advance buffer index
AB08 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.

AB09 .handle_spool_ctrl_byte←2← 8F3D JSR← AAE1 BNE
ROR ; Rotate bit 0 into carry
AB0A BCC check_spool_state ; Bit 0 clear: not active path
AB0C LDA ws_0d6a ; Load spool control state
AB0F PHA ; Save for bit test
AB10 ROR ; Rotate bit 0 into carry
AB11 PLA ; Restore state
AB12 BCS done_spool_ctrl ; C=1: already started, reset
AB14 ORA #3 ; Set bits 0-1 (active + pending)
AB16 STA ws_0d6a ; Store updated state
AB19 LDA #3 ; Control byte 3 for header
AB1B JSR append_byte_to_rxbuf ; Add to RX buffer
AB1E JSR process_spool_data ; Send current buffer
AB21 .done_spool_ctrl←1← AB12 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.

AB24 .process_spool_data←4← AAFB JSR← AB1E JSR← AB67 BCC← ABD5 JSR
LDY #8 ; Y=8: workspace offset for length
AB26 LDA spool_buf_idx ; Load buffer index (=length)
AB29 STA (nfs_workspace),y ; Store length to workspace
AB2B LDA net_rx_ptr_hi ; Set data page high byte
AB2D INY ; Y=&09
AB2E STA (nfs_workspace),y ; Store to workspace+9
AB30 LDY #5 ; Y=5: workspace offset
AB32 STA (nfs_workspace),y ; Store page to workspace+5
AB34 LDY #&0b ; Y=&0B: template start offset
AB36 LDX #&26 ; X=&26: template index
AB38 JSR ws_copy_vclr_entry ; Copy template to workspace
AB3B DEY ; Adjust Y down
AB3C LDA ws_0d6a ; Load spool control state
AB3F PHA ; Save state
AB40 ROL ; Rotate to get carry (bit 7)
AB41 PLA ; Restore state
AB42 EOR #&80 ; Toggle bit 7
AB44 STA ws_0d6a ; Store updated state
AB47 ROL ; Shift to get both flag bits
AB48 STA (nfs_workspace),y ; Store flags to workspace
AB4A LDA vdu_status ; Save l00d0 (exec flag)
AB4C PHA ; Push for later restore
AB4D AND #&fe ; Clear bit 0 of exec flag
AB4F STA vdu_status ; Store modified exec flag
AB51 LDY #&25 ; Reset buffer start to &25
AB53 STY spool_buf_idx ; Store reset buffer index
AB56 LDA #0 ; A=0 for disconnect reply
AB58 TAX ; X=0 X=&00
AB59 LDY nfs_workspace_hi ; Y = workspace page
AB5B CLI ; Enable interrupts
AB5C JSR send_disconnect_reply ; Send disconnect reply packet
AB5F PLA ; Restore exec flag
AB60 STA vdu_status ; Store original exec flag
AB62 RTS ; Return
AB63 .check_spool_state←1← AB0A BCC
LDA ws_0d6a ; Load spool control state
AB66 ROR ; Rotate bit 0 to carry
AB67 BCC process_spool_data ; C=0: send current buffer
AB69 LDA vdu_status ; Save exec flag
AB6B PHA ; Push for restore
AB6C AND #&fe ; Clear bit 0
AB6E STA vdu_status ; Store modified flag
AB70 LDA #&14 ; Control byte &14 (repeat count)
AB72 .start_spool_retry←1← ABE6 BNE
PHA ; Save retry count
AB73 LDX #&0b ; X=&0B: 12 bytes of template
AB75 LDY #&30 ; Y=&30: workspace offset for TXCB
AB77 .loop_copy_spool_tx←1← AB7E BPL
LDA tx_econet_txcb_template,x ; Load template byte
AB7A STA (net_rx_ptr),y ; Store to workspace
AB7C DEY ; Next byte down
AB7D DEX ; Next template byte
AB7E BPL loop_copy_spool_tx ; Loop for 12 bytes
AB80 STX escapable ; X=-1: disable escape checking
AB82 LDY #2 ; Y=2: workspace offset for station
AB84 LDA (nfs_workspace),y ; Load station number
AB86 PHA ; Save station
AB87 INY ; Y=&03
AB88 LDA (nfs_workspace),y ; Load network number
AB8A LDY #&28 ; Y=&28: TXCB dest network offset
AB8C STA (net_rx_ptr),y ; Store network to TXCB
AB8E DEY ; Y=&27
AB8F PLA ; Restore station
AB90 STA (net_rx_ptr),y ; Store station to TXCB
AB92 LDX #&0b ; X=&0B: 12 bytes of RX template
AB94 LDY #&0b ; Y=&0B: workspace RX offset
AB96 .loop_copy_spool_rx←1← ABA7 BPL
LDA rx_palette_txcb_template,x ; Load RX template byte
AB99 CMP #&fd ; Is it &FD? (skip marker)
AB9B BEQ advance_spool_rx_idx ; Yes: skip store
AB9D CMP #&fc ; Is it &FC? (page ptr marker)
AB9F BNE store_spool_rx_byte ; No: use literal value
ABA1 LDA net_rx_ptr_hi ; &FC: substitute RX buffer page
ABA3 .store_spool_rx_byte←1← AB9F BNE
STA (nfs_workspace),y ; Store to workspace
ABA5 .advance_spool_rx_idx←1← AB9B BEQ
DEY ; Next byte down
ABA6 DEX ; Next template byte
ABA7 BPL loop_copy_spool_rx ; Loop for 12 bytes
ABA9 LDA #&25 ; TX data start at &25
ABAB STA net_tx_ptr ; Set net_tx_ptr low
ABAD LDA net_rx_ptr_hi ; Set data page high byte
ABAF STA net_tx_ptr_hi ; Set net_tx_ptr high
ABB1 JSR setup_pass_txbuf ; Set up password in TX buffer
ABB4 JSR send_net_packet ; Send the packet
ABB7 LDA #0 ; Clear net_tx_ptr low (page base)
ABB9 STA net_tx_ptr ; Store zero
ABBB LDA nfs_workspace_hi ; Set TX high to workspace page
ABBD STA net_tx_ptr_hi ; Store workspace high byte
ABBF JSR wait_net_tx_ack ; Wait for TX acknowledgement
ABC2 LDY #&31 ; Y=&31: check reply status
ABC4 LDA (net_rx_ptr),y ; Load reply byte
ABC6 BEQ spool_tx_succeeded ; Zero: success
ABC8 CMP #3 ; Status = 3? (busy, can retry)
ABCA BNE spool_tx_retry ; Other error: handle failure
ABCC .spool_tx_succeeded←1← ABC6 BEQ
PLA ; Discard retry count
ABCD PLA ; Discard saved exec flag
ABCE STA vdu_status ; Restore l00d0
ABD0 LDA #0 ; A=0: null terminator
ABD2 JSR append_byte_to_rxbuf ; Add zero to RX buffer (end marker)
ABD5 JSR process_spool_data ; Send final buffer
ABD8 LDA ws_0d6a ; Load spool state
ABDB AND #&f0 ; Clear low nibble
ABDD STA ws_0d6a ; Store cleaned state
ABE0 RTS ; Return
ABE1 .spool_tx_retry←1← ABCA BNE
TAX ; X = error code
ABE2 PLA ; Restore retry count
ABE3 SEC ; Set carry for subtract
ABE4 SBC #1 ; Decrement retry count
ABE6 BNE start_spool_retry ; Non-zero: retry send
ABE8 CPX #1 ; Error code = 1? (busy)
ABEA BNE error_printer_jammed ; No: printer jammed error
ABEC .err_printer_busy←1← AFD5 JMP
LDA #&a6 ; A=&A6: printer busy error number
ABEE JSR error_inline_log ; Generate 'Printer busy' error
ABF1 EQUS "Printer busy."
ABFE .error_printer_jammed←1← ABEA BNE
LDA #&a7 ; A=&A7: printer jammed error number
AC00 JSR error_inline_log ; Generate 'Printer jammed' error
AC03 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.

AC12 .send_disconnect_reply←3← 94F5 JSR← AB5C JSR← B946 JSR
STX net_tx_ptr ; Set TX ptr low byte
AC14 STY net_tx_ptr_hi ; Set TX ptr high byte
AC16 PHA ; Save disconnect code
AC17 ORA #0 ; Test if zero
AC19 BEQ send_disconnect_status ; Zero: skip station search
AC1B LDX #&ff ; X=&FF: start search from -1
AC1D TAY ; Y = disconnect code
AC1E .loop_scan_disconnect←2← AC27 BNE← AC31 BNE
TYA ; A = disconnect code
AC1F INX ; Next station index
AC20 CMP fcb_net_or_port,x ; Compare with station table entry
AC23 BEQ verify_stn_match ; Match: verify station/network
AC25 CPX #&0f ; Past last station?
AC27 BNE loop_scan_disconnect ; No: try next
AC29 LDA #0 ; Not found: A=0
AC2B BEQ send_disconnect_status ; Skip to status update ALWAYS branch
AC2D .verify_stn_match←1← AC23 BEQ
TAY ; Y = disconnect code for compare
AC2E JSR match_station_net ; Check station and network match
AC31 BNE loop_scan_disconnect ; No match: try next station
AC33 LDA chan_status,x ; Load station status flags
AC36 AND #1 ; Isolate bit 0 (active flag)
AC38 .send_disconnect_status←2← AC19 BEQ← AC2B BEQ
LDY #0 ; Y=0: TX buffer status offset
AC3A ORA (net_tx_ptr),y ; OR with existing status byte
AC3C PHA ; Save combined status
AC3D STA (net_tx_ptr),y ; Store to TX buffer
AC3F JSR send_net_packet ; Send the packet
AC42 LDA #&ff ; Set end markers to &FF
AC44 LDY #8 ; Y=8: first end marker offset
AC46 STA (net_tx_ptr),y ; Store &FF
AC48 INY ; Y=&09
AC49 STA (net_tx_ptr),y ; Store &FF at offset 9 too
AC4B PLA ; Restore disconnect code
AC4C TAX ; X = status for control byte
AC4D LDY #&d1 ; Y=&D1: default control
AC4F PLA ; Check original disconnect code
AC50 PHA ; Peek but keep on stack
AC51 BEQ store_tx_ctrl_byte ; Zero: use &D1 control
AC53 LDY #&90 ; Non-zero: use &90 control
AC55 .store_tx_ctrl_byte←1← AC51 BEQ
TYA ; A = control byte (Y)
AC56 LDY #1 ; Y=1: control byte offset
AC58 STA (net_tx_ptr),y ; Store control byte
AC5A TXA ; A = X (status)
AC5B DEY ; Y=0 Y=&00
AC5C PHA ; Save status on stack
AC5D .loop_wait_disc_tx_ack←1← AC69 BCS
LDA #&7f ; Set status to &7F (waiting)
AC5F STA (net_tx_ptr),y ; Store at TX buffer offset 0
AC61 JSR wait_net_tx_ack ; Wait for TX acknowledgement
AC64 PLA ; Restore status
AC65 PHA ; Keep on stack for next check
AC66 EOR (net_tx_ptr),y ; Compare with current TX buffer
AC68 ROR ; Rotate result bit 0 to carry
AC69 BCS loop_wait_disc_tx_ack ; C=1: status changed, retry
AC6B PLA ; Done: discard status
AC6C PLA ; Discard disconnect code
AC6D RTS ; Return
; Spool TX control block template
; 12-byte TXCB template copied directly (no
; marker processing) to workspace at offset
; &25..&30. Destination station and network
; (&27/&28) are filled in from (nfs_workspace)
; after the copy.
AC6E .tx_econet_txcb_template←1← AB77 LDA
EQUB &80 ; ctrl=&80 (standard TX)
AC6F EQUB &9F ; port=&9F
AC70 EQUB &00 ; dest station=&00 (filled later)
AC71 EQUB &00 ; dest network=&00 (filled later)
AC72 EQUB &43 ; buf start lo=&43
AC73 EQUB &8E ; buf start hi=&8E
AC74 EQUB &FF ; buf start ext lo=&FF
AC75 EQUB &FF ; buf start ext hi=&FF
AC76 EQUB &4B ; buf end lo=&4B
AC77 EQUB &8E ; buf end hi=&8E
AC78 EQUB &FF ; buf end ext lo=&FF
AC79 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.
AC7A .rx_palette_txcb_template←1← AB96 LDA
EQUB &7F ; ctrl=&7F (RX listen)
AC7B EQUB &9E ; port=&9E
AC7C EQUB &FD ; skip: preserve dest station
AC7D EQUB &FD ; skip: preserve dest network
AC7E EQUB &31 ; buf start lo=&31
AC7F EQUB &FC ; buf start hi=page ptr (&FC)
AC80 EQUB &FF ; buf start ext lo=&FF
AC81 EQUB &FF ; buf start ext hi=&FF
AC82 EQUB &34 ; buf end lo=&34
AC83 EQUB &FC ; buf end hi=page ptr (&FC)
AC84 EQUB &FF ; buf end ext lo=&FF
AC85 EQUB &FF ; buf end ext hi=&FF
AC86 .lang_2_save_palette_vdu
LDA table_idx ; Save l00ad counter
AC88 PHA ; Push for later restore
AC89 LDA #&e9 ; Set workspace low to &E9
AC8B STA nfs_workspace ; Store to nfs_workspace low
AC8D LDY #0 ; Y=0: initial palette index
AC8F STY table_idx ; Clear palette counter
AC91 LDA vdu_screen_mode ; Load current screen mode
AC94 STA (nfs_workspace),y ; Store mode to workspace
AC96 INC nfs_workspace ; Advance workspace ptr
AC98 LDA vdu_display_start_hi ; Load video ULA copy
AC9B PHA ; Save for later restore
AC9C TYA ; A=0 for first palette entry A=&00
AC9D .loop_read_palette←1← ACBC BNE
STA (nfs_workspace),y ; Store logical colour to workspace
AC9F LDX nfs_workspace ; X = workspace ptr low
ACA1 LDY nfs_workspace_hi ; Y = workspace ptr high
ACA3 LDA #osword_read_palette ; OSWORD &0B: read palette
ACA5 JSR osword ; Read palette entry Read palette
ACA8 PLA ; Restore previous ULA value
ACA9 LDY #0 ; Y=0: reset index
ACAB STA (nfs_workspace),y ; Store ULA value to workspace
ACAD INY ; Y=1: physical colour offset Y=&01
ACAE LDA (nfs_workspace),y ; Load physical colour
ACB0 PHA ; Save for next iteration
ACB1 LDX nfs_workspace ; X = workspace ptr
ACB3 INC nfs_workspace ; Advance workspace ptr
ACB5 INC table_idx ; Advance palette counter
ACB7 DEY ; Y=0 Y=&00
ACB8 LDA table_idx ; Load counter
ACBA CPX #&f9 ; Reached &F9 workspace limit?
ACBC BNE loop_read_palette ; No: read next palette entry
ACBE PLA ; Discard last ULA value
ACBF STY table_idx ; Clear counter
ACC1 INC nfs_workspace ; Advance workspace ptr
ACC3 JSR serialise_palette_entry ; Store extra palette info
ACC6 INC nfs_workspace ; Advance workspace ptr again
ACC8 PLA ; Restore original l00ad
ACC9 STA table_idx ; 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.

ACCB .commit_state_byte←4← 9570 JMP← 9598 JSR← 95BF JSR← A62E JMP
LDA ws_0d69 ; Load current state
ACCE STA ws_0d68 ; Store as committed state
ACD1 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.

ACD2 .serialise_palette_entry←1← ACC3 JSR
LDA vdu_mode ; Load palette register value
ACD5 STA (nfs_workspace),y ; Store to workspace
ACD7 LDX vdu_mode ; X = palette register
ACDA JSR read_osbyte_to_ws ; Read OSBYTE for this mode
ACDD INC nfs_workspace ; Advance workspace ptr
ACDF TYA ; A=0
ACE0 STA (nfs_workspace,x) ; Store zero to workspace
ACE2 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.

ACE5 .read_osbyte_to_ws_x0←1← ACE2 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.

ACE7 .read_osbyte_to_ws←1← ACDA JSR
LDY table_idx ; Load OSBYTE code index
ACE9 INC table_idx ; Advance index counter
ACEB INC nfs_workspace ; Advance workspace ptr
ACED LDA osbyte_mode_read_codes,y ; Load OSBYTE number from table
ACF0 LDY #&ff ; Y=&FF: read current value
ACF2 JSR osbyte ; Call OSBYTE to read value
ACF5 TXA ; A = result (from X)
ACF6 LDX #0 ; X=0 for indexed store
ACF8 STA (nfs_workspace,x) ; Store result to workspace
ACFA 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.
ACFB .osbyte_mode_read_codes←1← ACED LDA
EQUB &85 ; OSBYTE &85: read display start addr
ACFC EQUB &C2 ; OSBYTE &C2: read video ULA ctrl
ACFD 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
ACFE .cmd_cdir
TYA ; Save command line offset
ACFF PHA ; Push onto stack
AD00 JSR mask_owner_access ; Set owner-only access mask
AD03 JSR skip_to_next_arg ; Skip to optional size argument
AD06 CMP #&0d ; End of line?
AD08 BNE parse_cdir_size ; No: parse size argument
AD0A LDX #2 ; Default allocation size index = 2
AD0C BNE done_cdir_size ; ALWAYS branch
AD0E .parse_cdir_size←1← AD08 BNE
LDA #&ff ; A=&FF: mark as decimal parse
AD10 STA fs_work_4 ; Store decimal parse flag
AD12 JSR parse_addr_arg ; Parse numeric size argument
AD15 LDX #&1b ; X=&1B: top of 26-entry size table
AD17 .loop_find_alloc_size←1← AD1B BCC
DEX ; Try next lower index
AD18 CMP cdir_alloc_size_table,x ; Compare size with threshold
AD1B BCC loop_find_alloc_size ; A < threshold: keep searching
AD1D .done_cdir_size←1← AD0C BNE
STX fs_cmd_data ; Store allocation size index
AD20 PLA ; Restore command line offset
AD21 TAY ; Transfer to Y
AD22 JSR save_ptr_to_os_text ; Save text pointer for filename parse
AD25 JSR parse_filename_arg ; Parse directory name argument
AD28 LDX #1 ; X=1: one argument to copy
AD2A JSR copy_arg_to_buf ; Copy directory name to TX buffer
AD2D LDY #&1b ; Y=&1B: *CDir FS command code
AD2F .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 overlaps with the JMP high byte at
; cdir_dispatch_col+2 (entry 0 = &94, 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.
AD32 EQUB &00 ; Index 1: threshold 0 (catch-all)
AD33 EQUB &0A ; Index 2: threshold 10 (default)
AD34 EQUB &14 ; Index 3: threshold 20
AD35 EQUB &1D ; Index 4: threshold 29
AD36 EQUB &27 ; Index 5: threshold 39
AD37 EQUB &31 ; Index 6: threshold 49
AD38 EQUB &3B ; Index 7: threshold 59
AD39 EQUB &45 ; Index 8: threshold 69
AD3A EQUB &4F ; Index 9: threshold 79
AD3B EQUB &58 ; Index 10: threshold 88
AD3C EQUB &62 ; Index 11: threshold 98
AD3D EQUB &6C ; Index 12: threshold 108
AD3E EQUB &76 ; Index 13: threshold 118
AD3F EQUB &80 ; Index 14: threshold 128
AD40 EQUB &8A ; Index 15: threshold 138
AD41 EQUB &94 ; Index 16: threshold 148
AD42 EQUB &9D ; Index 17: threshold 157
AD43 EQUB &A7 ; Index 18: threshold 167
AD44 EQUB &B1 ; Index 19: threshold 177
AD45 EQUB &BB ; Index 20: threshold 187
AD46 EQUB &C5 ; Index 21: threshold 197
AD47 EQUB &CF ; Index 22: threshold 207
AD48 EQUB &D8 ; Index 23: threshold 216
AD49 EQUB &E2 ; Index 24: threshold 226
AD4A EQUB &EC ; Index 25: threshold 236
AD4B EQUB &F6 ; Index 26: threshold 246
AD4C 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
AD4D .cmd_lcat
ROR fs_lib_flags ; Rotate carry into lib flag bit 7
AD50 SEC ; Set carry (= library directory)
AD51 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
AD53 .cmd_lex
ROR fs_lib_flags ; Rotate carry into lib flag bit 7
AD56 SEC ; Set carry (= library directory)
AD57 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
AD59 .cmd_ex
ROR fs_lib_flags ; Rotate carry into lib flag bit 7
AD5C CLC ; Clear carry (= current directory)
AD5D .ex_set_lib_flag←1← AD57 BCS
ROL fs_lib_flags ; Rotate carry back, clearing bit 7
AD60 LDA #&ff ; A=&FF: initial column counter
AD62 STA fs_spool_handle ; Store column counter
AD64 LDA #1 ; One entry per line (Ex format)
AD66 STA fs_work_7 ; Store entries per page
AD68 LDA #3 ; FS command code 3: Examine
AD6A STA fs_work_5 ; Store command code
AD6C BNE setup_ex_request ; ALWAYS branch
AD6E .fscv_5_cat
JSR set_xfer_params ; Set transfer parameters
AD71 LDY #0 ; Y=0: start from entry 0
AD73 ROR fs_lib_flags ; Rotate carry into lib flag
AD76 CLC ; Clear carry (= current directory)
AD77 .cat_set_lib_flag←1← AD51 BCS
ROL fs_lib_flags ; Rotate carry back, clearing bit 7
AD7A LDA #3 ; Three entries per column (Cat)
AD7C STA fs_spool_handle ; Store column counter
AD7E STA fs_work_7 ; Store entries per page
AD80 LDA #&0b ; FS command code &0B: Catalogue
AD82 STA fs_work_5 ; Store command code
AD84 .setup_ex_request←1← AD6C BNE
JSR save_ptr_to_os_text ; Save text pointer
AD87 LDA #&ff ; A=&FF: enable escape checking
AD89 STA escapable ; Set escapable flag
AD8B LDA #6 ; Command code 6
AD8D STA fs_cmd_data ; Store in TX buffer
AD90 JSR parse_filename_arg ; Parse directory argument
AD93 LDX #1 ; X=1: offset in buffer
AD95 JSR copy_arg_to_buf ; Copy argument to TX buffer
AD98 LDA fs_lib_flags ; Get library/FS flags
AD9B LSR ; Shift bit 0 to carry
AD9C BCC store_owner_flags ; Bit 0 clear: skip
AD9E ORA #&40 ; Set bit 6 (owner access flag)
ADA0 .store_owner_flags←1← AD9C BCC
ROL ; Rotate back
ADA1 STA fs_lib_flags ; Store modified flags
ADA4 LDY #&12 ; Y=&12: FS command for examine
ADA6 JSR save_net_tx_cb ; Send request to file server
ADA9 LDX #3 ; X=3: offset to directory title
ADAB JSR print_10_chars ; Print directory title (10 chars)
ADAE JSR print_inline ; Print '('
ADB1 EQUS "("
ADB2 LDA fs_reply_stn ; Get cycle number
ADB5 JSR print_decimal_3dig ; Print as 3-digit decimal
ADB8 JSR print_inline ; Print ') '
ADBB EQUS ") "
ADC1 LDY fs_access_level ; Get owner/public flag
ADC4 BNE print_public_label ; Non-zero: public access
ADC6 JSR print_inline ; Print 'Owner' + CR
ADC9 EQUS "Owner."
ADCF BNE send_dir_info_req ; Skip public; ALWAYS branch
ADD1 .print_public_label←1← ADC4 BNE
JSR print_inline ; Print 'Public' + CR
ADD4 EQUS "Public."
ADDB .send_dir_info_req←1← ADCF BNE
LDA fs_lib_flags ; Get flags
ADDE PHA ; Save flags
ADDF JSR mask_owner_access ; Mask owner access bits
ADE2 LDY #&15 ; Y=&15: FS command for dir info
ADE4 JSR save_net_tx_cb ; Send request to file server
ADE7 INX ; Advance X past header
ADE8 LDY #&10 ; Y=&10: print 16 chars
ADEA JSR print_chars_from_buf ; Print file entry
ADED JSR print_inline ; Print ' Option '
ADF0 EQUS " Option "
ADFB LDA fs_boot_option ; Get option byte
ADFE TAX ; Transfer to X for table lookup
ADFF JSR print_hex_byte ; Print option as hex
AE02 JSR print_inline ; Print ' ('
AE05 EQUS " ("
AE07 LDY option_str_offset_data,x ; Index into option string table
AE0A .loop_print_option_str←1← AE13 BNE
LDA roff_off_string,y ; Get option name character
AE0D BMI print_dir_header ; High bit set: end of string
AE0F JSR osasci ; Write character
AE12 INY ; Next character
AE13 BNE loop_print_option_str ; Loop; ALWAYS branch
AE15 .print_dir_header←1← AE0D BMI
JSR print_inline ; Print ')' + CR + 'Dir. '
AE18 EQUS ").Dir. "
AE1F LDX #&11 ; Offset &11: directory name
AE21 JSR print_10_chars ; Print directory name (10 chars)
AE24 JSR print_inline ; Print ' Lib. '
AE27 EQUS " Lib. "
AE31 LDX #&1b ; Offset &1B: library name
AE33 JSR print_10_chars ; Print library name (10 chars)
AE36 JSR osnewl ; Write newline (characters 10 and 13)
AE39 PLA ; Restore flags
AE3A STA fs_lib_flags ; Store restored flags
AE3D .setup_ex_pagination←1← AE6E BNE
STY fs_func_code ; Store entry count
AE40 STY fs_work_4 ; Also store in work_4
AE42 LDX fs_work_5 ; Get command code
AE44 STX fs_data_count ; Store in buffer
AE47 LDX fs_work_7 ; Get entries per page
AE49 STX fs_cmd_data ; Store in buffer
AE4C LDX #3 ; X=3: buffer offset
AE4E JSR copy_arg_to_buf ; Copy argument to buffer
AE51 LDY #3 ; Y=3: FS command for examine/cat
AE53 JSR save_net_tx_cb ; Send request to file server
AE56 INX ; Advance past header
AE57 LDA fs_cmd_data ; Get number of entries returned
AE5A BEQ jmp_osnewl ; Zero: no more entries
AE5C PHA ; Save entry count
AE5D .loop_scan_entry_data←1← AE61 BPL
INY ; Advance Y
AE5E LDA fs_cmd_data,y ; Get entry data byte
AE61 BPL loop_scan_entry_data ; Bit 7 clear: more data
AE63 STA fs_cmd_lib,y ; Store terminator byte
AE66 JSR ex_print_col_sep ; Print entry with column separator
AE69 PLA ; Restore entry count
AE6A CLC ; Clear carry for addition
AE6B ADC fs_work_4 ; Add entries processed
AE6D TAY ; Transfer to Y
AE6E 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
AE70 .print_10_chars←3← ADAB JSR← AE21 JSR← AE33 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
AE72 .print_chars_from_buf←2← ADEA JSR← AE7A BNE
LDA fs_cmd_data,x ; Get character from buffer
AE75 JSR osasci ; Write character
AE78 INX ; Next buffer position
AE79 DEY ; Decrement count
AE7A BNE print_chars_from_buf ; Loop until 10 printed
AE7C RTS ; Return
AE7D .jmp_osnewl←1← AE5A 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.

AE80 .parse_cmd_arg_y0←2← 9CF8 JSR← A1AF 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.

AE82 .parse_filename_arg←4← AD25 JSR← AD90 JSR← AF57 JSR← B347 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.

AE85 .parse_access_prefix←4← 92DA JSR← 9382 JSR← 93BB JSR← 992A JSR
LDA fs_filename_buf ; Get first parsed character
AE88 EOR #&26 ; Is it '&'?
AE8A BNE check_colon_prefix ; No: check for ':' prefix
AE8C LDA fs_lib_flags ; Get flags
AE8F ORA #&40 ; Set FS selection flag (bit 6)
AE91 STA fs_lib_flags ; Store updated flags
AE94 JSR strip_token_prefix ; Remove '&' prefix character
AE97 LDA fs_filename_buf ; Get next character
AE9A EOR #&2e ; Is it '.'?
AE9C BNE check_hash_prefix ; No: check for '#'
AE9E LDA fs_filename_buf_1 ; Get char after '.'
AEA1 EOR #&0d ; Is it CR (end of line)?
AEA3 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 ':'.

AEA5 .strip_token_prefix←5← 9308 JSR← 93A2 JSR← 93A8 JSR← AE94 JSR← AEE7 BNE
TXA ; Save X
AEA6 PHA ; Push X
AEA7 LDX #&ff ; X=&FF, will increment to 0
AEA9 .loop_shift_str_left←1← AEB2 BNE
INX ; Increment X
AEAA LDA fs_filename_buf_1,x ; Get character at offset+1
AEAD STA fs_filename_buf,x ; Store at offset (shift left)
AEB0 EOR #&0d ; Is it CR (end of line)?
AEB2 BNE loop_shift_str_left ; No: continue shifting
AEB4 TXA ; Get shifted string length
AEB5 BEQ done_strip_prefix ; Zero length: skip trailing trim
AEB7 .loop_trim_trailing←1← AEC4 BNE
LDA fs_filename_buf_m1,x ; Get character at end of string
AEBA EOR #&20 ; Is it a space?
AEBC BNE done_strip_prefix ; No: done trimming
AEBE LDA #&0d ; Replace trailing space with CR
AEC0 STA fs_filename_buf_m1,x ; Store CR
AEC3 DEX ; Move back
AEC4 BNE loop_trim_trailing ; Loop while more trailing spaces
AEC6 .done_strip_prefix←2← AEB5 BEQ← AEBC BNE
PLA ; Restore X
AEC7 TAX ; Transfer back to X
AEC8 .return_from_strip_prefix←3← AECB BEQ← AED2 BNE← AEDD BNE
RTS ; Return
AEC9 .check_hash_prefix←1← AE9C BNE
EOR #&23 ; Is it '#'?
AECB BEQ return_from_strip_prefix ; Yes: '#' prefix accepted
AECD .error_bad_prefix←1← AEA3 BEQ
JMP error_bad_filename ; Bad filename error
AED0 .check_colon_prefix←1← AE8A BNE
EOR #&1c ; Check for ':' prefix
AED2 BNE return_from_strip_prefix ; Neither '&' nor ':': no prefix
AED4 LDA fs_filename_buf_2 ; Get character after ':'
AED7 EOR #&2e ; Is it '.'?
AED9 BEQ set_fs_select_flag ; Yes: ':.' qualified prefix
AEDB EOR #&23 ; Is it CR (end of line)?
AEDD BNE return_from_strip_prefix ; No: no FS prefix, return
AEDF .set_fs_select_flag←1← AED9 BEQ
LDA fs_lib_flags ; Get flags
AEE2 ORA #&40 ; Set FS selection flag (bit 6)
AEE4 STA fs_lib_flags ; Store updated flags
AEE7 BNE strip_token_prefix ; ALWAYS branch
AEE9 .option_str_offset_data←1← AE07 LDY
BRK ; Data: option string offset table
AEEA EQUS "!.U"
AEED .roff_off_string←1← AE0A 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.

AEF0 .copy_arg_to_buf_x0←6← 8DB1 JSR← 8E0A JSR← 9938 JSR← 9B2A JSR← A281 JSR← AF5A 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.

AEF2 .copy_arg_to_buf←10← 99EA JSR← 9B23 JSR← 9B46 JSR← 9CFD JSR← A1B4 JSR← A1E1 JSR← AD2A JSR← AD95 JSR← AE4E JSR← B35C 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
AEF4 .copy_arg_validated←3← 8DAC JSR← 940A JSR← 9440 JSR
SEC ; Set carry: enable '&' validation
AEF5 .loop_copy_char←1← AF0B BNE
LDA (fs_crc_lo),y ; Get character from command line
AEF7 STA fs_cmd_data,x ; Store in TX buffer
AEFA BCC advance_positions ; Carry clear: skip validation
AEFC CMP #&21 ; Is it '!' or above?
AEFE EOR #&26 ; Is it '&'?
AF00 BNE restore_after_check ; No: continue copying
AF02 JMP error_bad_filename ; '&' in filename: bad filename
AF05 .restore_after_check←1← AF00 BNE
EOR #&26 ; Restore A (undo '&' EOR)
AF07 .advance_positions←1← AEFA BCC
INX ; Advance buffer position
AF08 INY ; Advance source position
AF09 EOR #&0d ; Is it CR (end of line)?
AF0B BNE loop_copy_char ; No: continue copying
AF0D .return_from_copy_arg←1← AF23 BMI
RTS ; Return
AF0E 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.

AF12 .mask_owner_access←13← 8E25 JSR← 937C JSR← 93B6 JSR← 9927 JSR← 9D49 JSR← 9E2A JSR← A1AC JSR← A238 JSR← A242 JSR← A8DB JSR← AD00 JSR← ADDF JSR← B33D JSR
LDA fs_lib_flags ; Get flags
AF15 AND #&1f ; Mask to low 5 bits only
AF17 STA fs_lib_flags ; Store masked flags
AF1A RTS ; Return
AF1B EQUS "Run"
AF1E .fsreply_0_print_dir
LDX #0 ; X=0: start from first entry
AF20 .loop_scan_entries←1← AF40 BNE
LDA fs_cmd_data,x ; Get entry byte from buffer
AF23 BMI return_from_copy_arg ; High bit set: end of entries
AF25 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.

AF27 .ex_print_col_sep←1← AE66 JSR
LDY fs_spool_handle ; Get column counter
AF29 BMI print_col_newline ; Negative: newline mode (Ex)
AF2B INY ; Increment column counter
AF2C TYA ; Transfer to A
AF2D AND #3 ; Modulo 4 (Cat: 3 per row)
AF2F STA fs_spool_handle ; Store updated counter
AF31 BEQ print_col_newline ; Zero: row full, print newline
AF33 JSR print_inline ; Print ' ' column separator
AF36 EQUS " "
AF38 BNE next_col_entry ; Skip newline; ALWAYS branch
AF3A .print_col_newline←2← AF29 BMI← AF31 BEQ
LDA #&0d ; CR character for newline
AF3C .print_entry_char←1← AF25 BNE
JSR osasci ; Write character 13
AF3F .next_col_entry←1← AF38 BNE
INX ; Advance to next entry
AF40 BNE loop_scan_entries ; Loop for more entries
AF42 EOR zp_0078 ; Embedded string data 'Exec'
AF44 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
AF46 .cmd_remove
TYA ; Save command line offset
AF47 PHA ; Push onto stack
AF48 JSR skip_to_next_arg ; Skip to check for extra arguments
AF4B CMP #&0d ; End of line?
AF4D BEQ done_extra_arg_check ; Yes: single arg, proceed
AF4F JMP error_syntax ; No: extra args, syntax error
AF52 .done_extra_arg_check←1← AF4D BEQ
PLA ; Restore command line offset
AF53 TAY ; Transfer to Y
AF54 JSR save_ptr_to_os_text ; Save text pointer for parsing
AF57 JSR parse_filename_arg ; Parse filename argument
AF5A JSR copy_arg_to_buf_x0 ; Copy filename to TX buffer
AF5D LDY #&14 ; Y=&14: *Delete FS command code
AF5F BIT bit_test_ff ; Set V flag (via BIT #&FF)
AF62 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)
AF65 .print_num_no_leading←1← 8FF3 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
AF68 .print_decimal_3dig←3← ADB5 JSR← B179 JSR← B190 JMP
TAY ; Transfer value to Y (remainder)
AF69 LDA #&64 ; A=100: hundreds divisor
AF6B JSR print_decimal_digit ; Print hundreds digit
AF6E LDA #&0a ; A=10: tens divisor
AF70 JSR print_decimal_digit ; Print tens digit
AF73 CLV ; Clear V (always print units)
AF74 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
AF76 .print_decimal_digit←2← AF6B JSR← AF70 JSR
STA fs_error_ptr ; Store divisor
AF78 TYA ; Get remaining value
AF79 LDX #&2f ; X='0'-1: digit counter
AF7B SEC ; Set carry for subtraction
AF7C PHP ; Save V flag for leading zero check
AF7D .loop_divide_digit←1← AF80 BCS
INX ; Count quotient digit
AF7E SBC fs_error_ptr ; Subtract divisor
AF80 BCS loop_divide_digit ; No underflow: continue dividing
AF82 ADC fs_error_ptr ; Add back divisor (get remainder)
AF84 TAY ; Remainder to Y for next digit
AF85 TXA ; Digit character to A
AF86 PLP ; Restore V flag
AF87 BVC print_nonzero_digit ; V clear: always print digit
AF89 CMP #&30 ; V set: is digit '0'?
AF8B BEQ return_from_print_digit ; Yes: suppress leading zero
AF8D .print_nonzero_digit←1← AF87 BVC
LDX fs_error_ptr ; Save divisor across OSASCI call
AF8F JSR osasci ; Write character
AF92 STX fs_error_ptr ; Restore divisor
AF94 .return_from_print_digit←1← AF8B 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.

AF95 .save_ptr_to_os_text←8← 9D4F JSR← A1A9 JSR← AD22 JSR← AD84 JSR← AF54 JSR← B018 JSR← B1F2 JSR← B344 JSR
PHA ; Save A
AF96 LDA fs_crc_lo ; Copy text pointer low byte
AF98 STA os_text_ptr ; To OS text pointer low
AF9A LDA fs_crc_hi ; Copy text pointer high byte
AF9C STA os_text_ptr_hi ; To OS text pointer high
AF9E PLA ; Restore A
AF9F RTS ; Return
AFA0 .loop_advance_char←1← AFAB 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
AFA1 .skip_to_next_arg←2← AD03 JSR← AF48 JSR
LDA (fs_crc_lo),y ; Load char from command line
AFA3 CMP #&20 ; Space?
AFA5 BEQ loop_skip_space_chars ; Yes: skip trailing spaces
AFA7 CMP #&0d ; CR (end of line)?
AFA9 BEQ return_from_skip_arg ; Yes: return (at end)
AFAB BNE loop_advance_char ; ALWAYS branch
AFAD .loop_skip_space_chars←2← AFA5 BEQ← AFB2 BEQ
INY ; Advance past space
AFAE LDA (fs_crc_lo),y ; Load next character
AFB0 CMP #&20 ; Still a space?
AFB2 BEQ loop_skip_space_chars ; Yes: skip multiple spaces
AFB4 .return_from_skip_arg←1← AFA9 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.

AFB5 .save_ptr_to_spool_buf←2← AFDB JSR← B1AE JSR
PHA ; Save A
AFB6 LDA fs_crc_lo ; Copy text pointer low byte
AFB8 STA fs_options ; To spool buffer pointer low
AFBA LDA fs_crc_hi ; Copy text pointer high byte
AFBC STA fs_block_offset ; To spool buffer pointer high
AFBE PLA ; Restore A
AFBF 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.

AFC0 .init_spool_drive←2← AFD8 JSR← B1A1 JSR
TYA ; Save Y
AFC1 PHA ; Push it
AFC2 JSR get_ws_page ; Get workspace page number
AFC5 STA addr_work ; Store as spool drive page high
AFC7 PLA ; Restore Y
AFC8 TAY ; Transfer to Y
AFC9 LDA #0 ; A=0
AFCB STA work_ae ; Clear spool drive page low
AFCD 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
AFCE .cmd_ps
LDA #1 ; A=1: check printer ready
AFD0 BIT ws_0d6a ; Test printer server workspace flag
AFD3 BNE done_ps_available ; Non-zero: printer available
AFD5 JMP err_printer_busy ; Printer not available: error
AFD8 .done_ps_available←1← AFD3 BNE
JSR init_spool_drive ; Initialise spool drive
AFDB JSR save_ptr_to_spool_buf ; Save pointer to spool buffer
AFDE LDA (fs_options),y ; Get first argument character
AFE0 CMP #&0d ; End of command line?
AFE2 BEQ no_ps_name_given ; Yes: no argument given
AFE4 CLV ; Clear V (= explicit PS name given)
AFE5 JSR is_decimal_digit ; Is first char a decimal digit?
AFE8 BCC save_ps_cmd_ptr ; Yes: station number, skip PS name
AFEA TYA ; PS name follows
AFEB PHA ; Save Y
AFEC JSR load_ps_server_addr ; Load PS server address
AFEF PLA ; Restore Y
AFF0 TAY ; Back to Y register
AFF1 JSR parse_fs_ps_args ; Parse FS/PS arguments
AFF4 JMP store_ps_station ; Jump to store station address

Copy printer server template at offset &1C

Sets Y=&1C 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.

AFF7 .copy_ps_data_y1c←1← 8EE0 JSR
LDY #&1c ; 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 (&8E43). This 6502 trick reaches data 248 bytes past the base label using a single instruction.

AFF9 .copy_ps_data←1← B1D4 JSR
LDX #&f8 ; X=&F8: offset into template
AFFB .loop_copy_ps_tmpl←1← B002 BNE
LDA ps_template_base,x ; Get template byte
AFFE STA (net_rx_ptr),y ; Store in RX buffer
B000 INY ; Next destination offset
B001 INX ; Next source offset
B002 BNE loop_copy_ps_tmpl ; Loop until X wraps to 0
B004 RTS ; Return
B005 .no_ps_name_given←1← AFE2 BEQ
BIT bit_test_ff ; Set V (= no explicit PS name)
B008 .save_ps_cmd_ptr←1← AFE8 BCC
STY ws_ptr_hi ; Save command line pointer
B00A BVS done_ps_name_parse ; V set: skip PS name parsing
B00C LDX #6 ; Max 6 characters for PS name
B00E LDY #&1c ; Buffer offset &1C for PS name
B010 LDA #&20 ; Space character
B012 .loop_pad_ps_name←1← B016 BNE
STA (net_rx_ptr),y ; Fill buffer with space
B014 INY ; Next position
B015 DEX ; Count down
B016 BNE loop_pad_ps_name ; Loop until 6 spaces filled
B018 JSR save_ptr_to_os_text ; Save text pointer
B01B LDY ws_ptr_hi ; Restore command line pointer
B01D JSR gsinit ; Initialise string reading
B020 BEQ done_ps_name_parse ; Empty string: skip to send
B022 LDX #6 ; Max 6 characters
B024 STY ws_ptr_hi ; Save updated string pointer
B026 LDY #&1c ; Buffer offset for PS name
B028 STY table_idx ; Save buffer position
B02A .loop_read_ps_char←1← B036 BNE
LDY ws_ptr_hi ; Restore string pointer
B02C JSR gsread ; Read next character
B02F STY ws_ptr_hi ; Save updated pointer
B031 BCS done_ps_name_parse ; End of string: go to send
B033 JSR store_char_uppercase ; Store char uppercased in buffer
B036 BNE loop_read_ps_char ; Loop for more characters
B038 .done_ps_name_parse←3← B00A BVS← B020 BEQ← B031 BCS
JSR reverse_ps_name_to_tx ; Copy reversed PS name to TX
B03B JSR send_net_packet ; Send PS status request
B03E JSR pop_requeue_ps_scan ; Pop and requeue PS scan
B041 JSR load_ps_server_addr ; Load PS server address
B044 LDA #0 ; A=0
B046 TAX ; X=&00
B047 LDY #&24 ; Offset &24 in buffer
B049 STA (net_rx_ptr),y ; Clear PS status byte
B04B .loop_pop_ps_slot←1← B073 BNE
PLA ; Get slot offset from stack
B04C BEQ done_ps_scan ; Zero: all slots done
B04E PHA ; Save slot offset
B04F TAY ; Transfer to Y
B050 LDA (nfs_workspace),y ; Read slot status
B052 BPL done_ps_slot_mark ; Bit 7 clear: slot inactive
B054 JSR advance_y_by_4 ; Advance Y by 4 (to status page)
B057 LDA (nfs_workspace),y ; Read status page pointer
B059 STA work_ae ; Store pointer low
B05B LDA (work_ae,x) ; Read printer status byte
B05D BNE done_ps_slot_mark ; Non-zero (busy): skip
B05F DEY ; Back to network number
B060 LDA (nfs_workspace),y ; Read network number
B062 STA fs_work_6 ; Store network number
B064 DEY ; Back to station number
B065 LDA (nfs_workspace),y ; Read station number
B067 STA fs_work_5 ; Store station low
B069 LDY #&24 ; Offset &24 in buffer
B06B STA (net_rx_ptr),y ; Store ready station in buffer
B06D .done_ps_slot_mark←2← B052 BPL← B05D BNE
PLA ; Retrieve slot offset
B06E TAY ; Transfer to Y
B06F LDA #&3f ; Mark slot as processed (&3F)
B071 STA (nfs_workspace),y ; Write marker to workspace
B073 BNE loop_pop_ps_slot ; ALWAYS branch
B075 .done_ps_scan←1← B04C BEQ
JSR print_printer_server_is ; Print 'Printer server is '
B078 LDY #&24 ; Offset &24: PS station number
B07A LDA (net_rx_ptr),y ; Get stored station number
B07C BNE print_ps_now ; Non-zero: server changed
B07E JSR print_inline ; Print 'still '
B081 EQUS "still "
B087 CLV ; Clear V
B088 BVC done_ps_status_msg ; ALWAYS branch
B08A .print_ps_now←1← B07C BNE
JSR print_inline ; Print 'now '
B08D EQUS "now "
B091 NOP ; Padding
B092 .done_ps_status_msg←1← B088 BVC
JSR print_fs_info_newline ; Print FS info and newline
B095 .store_ps_station←1← AFF4 JMP
LDY #2 ; Workspace offset 2
B097 LDA fs_work_5 ; Get station low
B099 STA (nfs_workspace),y ; Store in workspace
B09B INY ; Y=&03
B09C LDA fs_work_6 ; Get network number
B09E STA (nfs_workspace),y ; Store in workspace
B0A0 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.

B0A1 .print_file_server_is←1← A083 JSR
JSR print_inline ; Print 'File'
B0A4 EQUS "File"
B0A8 CLV ; Clear V
B0A9 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.

B0AB .print_printer_server_is←2← B075 JSR← B21C JSR
JSR print_inline ; Print 'Printer'
B0AE EQUS "Printer"
B0B5 NOP ; Padding
B0B6 .print_server_is_suffix←1← B0A9 BVC
JSR print_inline ; Print ' server is '
B0B9 EQUS " server is "
B0C4 NOP ; Padding
B0C5 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.

B0C6 .load_ps_server_addr←4← AFEC JSR← B041 JSR← B1BF JSR← B21F JSR
LDY #2 ; Workspace offset 2
B0C8 LDA (nfs_workspace),y ; Read station low
B0CA STA fs_work_5 ; Store station low
B0CC INY ; Y=&03
B0CD LDA (nfs_workspace),y ; Read network number
B0CF STA fs_work_6 ; Store network number
B0D1 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.

B0D2 .pop_requeue_ps_scan←2← B03E JSR← B219 JSR
PLA ; Pop return address low
B0D3 STA osword_flag ; Save return address low
B0D5 PLA ; Pop return address high
B0D6 STA ws_ptr_lo ; Save return address high
B0D8 LDA #0 ; Push 0 as end-of-list marker
B0DA PHA ; Push it
B0DB LDA #&84 ; Start scanning from offset &84
B0DD STA ws_ptr_hi ; Store scan position
B0DF LSR econet_flags ; Shift PS slot flags right
B0E2 LDA #3 ; Counter: 3 PS slots
B0E4 .loop_scan_ps_slots←1← B0F6 BNE
JSR byte_to_2bit_index ; Convert to 2-bit workspace index
B0E7 BCS done_ps_slot_scan ; Carry set: no more slots
B0E9 LSR ; Shift right twice
B0EA LSR ; To get slot offset
B0EB TAX ; Transfer to X
B0EC LDA (nfs_workspace),y ; Read slot status byte
B0EE BEQ done_ps_slot_scan ; Zero: empty slot, done
B0F0 CMP #&3f ; Is it processed marker (&3F)?
B0F2 BEQ reinit_ps_slot ; Yes: re-initialise this slot
B0F4 .skip_next_ps_slot←1← B11D JMP
INX ; Try next slot
B0F5 TXA ; Transfer slot index to A
B0F6 BNE loop_scan_ps_slots ; Loop for more slots
B0F8 .reinit_ps_slot←1← B0F2 BEQ
TYA ; Y = workspace offset of slot
B0F9 PHA ; Push slot offset for scan list
B0FA LDA #&7f ; Set active status (&7F)
B0FC STA (nfs_workspace),y ; Write status byte
B0FE INY ; Next byte
B0FF LDA #&9e ; Low byte: workspace page
B101 STA (nfs_workspace),y ; Write workspace pointer low
B103 LDA #0 ; A=0
B105 JSR write_two_bytes_inc_y ; Write two zero bytes + advance Y
B108 LDA ws_ptr_hi ; Get current scan page
B10A STA (nfs_workspace),y ; Write RX buffer page low
B10C CLC ; Clear carry for addition
B10D PHP ; Save processor status
B10E ADC #3 ; Advance by 3 pages
B110 PLP ; Restore processor status
B111 STA ws_ptr_hi ; Update scan position
B113 JSR write_ps_slot_byte_ff ; Write buffer page + &FF bytes
B116 LDA ws_ptr_hi ; Get updated scan position
B118 STA (nfs_workspace),y ; Write RX buffer page high
B11A .write_ps_slot_hi_link
JSR write_ps_slot_byte_ff ; Write another page + &FF bytes
B11D JMP skip_next_ps_slot ; Continue scanning slots
B120 .done_ps_slot_scan←2← B0E7 BCS← B0EE BEQ
ASL econet_flags ; Shift PS slot flags back
B123 LDA ws_ptr_lo ; Restore return address high
B125 PHA ; Push onto stack
B126 LDA osword_flag ; Restore return address low
B128 PHA ; Push onto stack
B129 LDA #&0a ; Delay counter: 10
B12B TAY ; Y=&0a
B12C TAX ; X=&0a
B12D STA fs_work_4 ; Outer loop counter = 10
B12F .loop_ps_delay←3← B130 BNE← B133 BNE← B137 BNE
DEY ; Decrement Y (inner loop)
B130 BNE loop_ps_delay ; Inner loop: 10 iterations
B132 DEX ; Decrement X (middle loop)
B133 BNE loop_ps_delay ; Middle loop: 10 iterations
B135 DEC fs_work_4 ; Decrement outer counter
B137 BNE loop_ps_delay ; Outer loop: ~1000 delay cycles
B139 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.

B13A .write_ps_slot_byte_ff←2← B113 JSR← B11A JSR
INY ; Advance Y
B13B LDA addr_work ; Get buffer page
B13D STA (nfs_workspace),y ; Store in workspace
B13F 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
B141 .write_two_bytes_inc_y←1← B105 JSR
INY ; Advance Y
B142 STA (nfs_workspace),y ; Write byte to workspace
B144 INY ; Advance Y
B145 STA (nfs_workspace),y ; Write byte to workspace
B147 INY ; Advance Y
B148 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.

B149 .reverse_ps_name_to_tx←2← B038 JSR← B1A6 JSR
LDY #&1c ; Start of PS name at offset &1C
B14B .loop_push_ps_name←1← B151 BNE
LDA (net_rx_ptr),y ; Load byte from RX buffer
B14D PHA ; Push to stack (for reversal)
B14E INY ; Next source byte
B14F CPY #&24 ; End of PS name field (&24)?
B151 BNE loop_push_ps_name ; No: continue pushing
B153 LDY #&1b ; End of TX name field at &1B
B155 .loop_pop_ps_name←1← B15B BNE
PLA ; Pop byte (reversed order)
B156 STA (net_rx_ptr),y ; Store in RX buffer
B158 DEY ; Previous position
B159 CPY #&13 ; Start of TX field (&13)?
B15B BNE loop_pop_ps_name ; No: continue popping
B15D LDA net_rx_ptr_hi ; Copy RX page to TX
B15F STA net_tx_ptr_hi ; Set TX pointer high
B161 LDA #&10 ; TX offset &10
B163 STA net_tx_ptr ; Set TX pointer low
B165 LDY #3 ; Copy 4 header bytes
B167 .loop_copy_tx_hdr←1← B16D BPL
LDA ps_tx_header_template,y ; Get header template byte
B16A STA (net_tx_ptr),y ; Store in TX buffer
B16C DEY ; Previous byte
B16D BPL loop_copy_tx_hdr ; Loop until all 4 copied
B16F 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.
B170 .ps_tx_header_template←1← B167 LDA
EQUB &80 ; Control byte &80 (immediate TX)
B171 EQUB &9F ; Port &9F (printer server)
B172 EQUB &FF ; Station &FF (any)
B173 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.

B174 .print_station_addr←4← A089 JSR← B225 JSR← B25D JSR← B2B5 JSR
PHP ; Save V flag (controls padding)
B175 LDA fs_work_6 ; Get network number
B177 BEQ skip_if_local_net ; Zero: no network prefix
B179 JSR print_decimal_3dig ; Print network as 3 digits
B17C LDA #&2e ; '.' separator
B17E JSR osasci ; Write character 46
B181 BIT bit_test_ff ; Set V (suppress station padding)
B184 .skip_if_local_net←1← B177 BEQ
BVS print_station_only ; V set: skip padding spaces
B186 JSR print_inline ; Print 4 spaces (padding)
B189 EQUS " "
B18D .print_station_only←1← B184 BVS
LDA fs_work_5 ; Get station number
B18F PLP ; Restore flags
B190 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 ; &B193.
; 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.
B193 .ps_slot_txcb_template
EQUB &80 ; Control byte &80 (immediate TX)
B194 EQUB &9F ; Port &9F (printer server)
B195 EQUB &00 ; Station 0 (filled in later)
B196 EQUB &00 ; Network 0 (filled in later)
B197 EQUB &14 ; Data buffer start lo (&14)
B198 EQUB &00 ; Data buffer start hi (= rx page)
B199 EQUB &FF ; Data buffer end lo (placeholder)
B19A EQUB &FF ; Data buffer end hi (placeholder)
B19B EQUB &1C ; Reply buffer start lo (&1C)
B19C EQUB &00 ; Reply buffer start hi (= rx page)
B19D EQUB &FF ; Reply buffer end lo (placeholder)
B19E 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
B19F .cmd_pollps
STY ws_ptr_hi ; Save command line pointer high
B1A1 JSR init_spool_drive ; Initialise spool/print drive
B1A4 STA fs_work_4 ; Save spool drive number
B1A6 JSR reverse_ps_name_to_tx ; Copy PS name to TX buffer
B1A9 JSR init_ps_slot_from_rx ; Init PS slot from RX data
B1AC LDY ws_ptr_hi ; Restore command line pointer
B1AE JSR save_ptr_to_spool_buf ; Save pointer to spool buffer
B1B1 LDA (fs_options),y ; Get first argument character
B1B3 CMP #&0d ; End of command line?
B1B5 BEQ no_poll_name_given ; Yes: no argument given
B1B7 CLV ; Clear V (= explicit PS name given)
B1B8 JSR is_decimal_digit ; Is first char a decimal digit?
B1BB BCC skip_if_no_poll_arg ; Yes: station number, skip PS name
B1BD TYA ; PS name follows
B1BE PHA ; Save Y
B1BF JSR load_ps_server_addr ; Load PS server address
B1C2 PLA ; Restore Y
B1C3 TAY ; Back to Y register
B1C4 JSR parse_fs_ps_args ; Parse FS/PS arguments
B1C7 LDY #&7a ; Offset &7A in slot buffer
B1C9 LDA fs_work_5 ; Get parsed station low
B1CB STA (work_ae),y ; Store station number low
B1CD INY ; Y=&7b
B1CE LDA fs_work_6 ; Get parsed network number
B1D0 STA (work_ae),y ; Store station number high
B1D2 LDY #&14 ; Offset &14 in TX buffer
B1D4 JSR copy_ps_data ; Copy PS data to TX buffer
B1D7 LDA addr_work ; Get buffer page high
B1D9 STA net_tx_ptr_hi ; Set TX pointer high byte
B1DB LDA #&78 ; Offset &78 in buffer
B1DD STA net_tx_ptr ; Set TX pointer low byte
B1DF BNE done_poll_name_parse ; ALWAYS branch
B1E1 .no_poll_name_given←1← B1B5 BEQ
BIT bit_test_ff ; Set V (= no explicit PS name)
B1E4 .skip_if_no_poll_arg←1← B1BB BCC
BVS done_poll_name_parse ; V set (no arg): skip to send
B1E6 LDX #6 ; Max 6 characters for PS name
B1E8 LDY #&14 ; Buffer offset for PS name
B1EA LDA #&20 ; Space character
B1EC .loop_pad_poll_name←1← B1F0 BNE
STA (net_rx_ptr),y ; Fill buffer position with space
B1EE INY ; Next position
B1EF DEX ; Count down
B1F0 BNE loop_pad_poll_name ; Loop until 6 spaces filled
B1F2 JSR save_ptr_to_os_text ; Save pointer to OS text
B1F5 LDY ws_ptr_hi ; Restore command line pointer
B1F7 JSR gsinit ; Initialise string reading
B1FA BEQ done_poll_name_parse ; Empty string: skip to send
B1FC LDX #6 ; Max 6 characters
B1FE STY ws_ptr_hi ; Save updated string pointer
B200 LDY #&14 ; Buffer offset for PS name
B202 STY table_idx ; Save buffer position
B204 .loop_read_poll_char←1← B210 BNE
LDY ws_ptr_hi ; Restore string pointer
B206 JSR gsread ; Read next char from string
B209 STY ws_ptr_hi ; Save updated string pointer
B20B BCS done_poll_name_parse ; End of string: go to send
B20D JSR store_char_uppercase ; Store char uppercased in buffer
B210 BNE loop_read_poll_char ; Loop if more chars to copy
B212 .done_poll_name_parse←4← B1DF BNE← B1E4 BVS← B1FA BEQ← B20B BCS
LDA #&80 ; Enable escape checking
B214 STA escapable ; Set escapable flag
B216 JSR send_net_packet ; Send the poll request packet
B219 JSR pop_requeue_ps_scan ; Pop and requeue PS scan
B21C JSR print_printer_server_is ; Print 'Printer server '
B21F JSR load_ps_server_addr ; Load PS server address
B222 BIT bit_test_ff ; Set V and N flags
B225 JSR print_station_addr ; Print station address
B228 JSR print_inline ; Print ' "'
B22B EQUS " ""
B22D LDY #&1c ; Start of PS name in buffer
B22F .loop_print_poll_name←1← B23B BNE
LDA (net_rx_ptr),y ; Get character from name field
B231 CMP #&20 ; Is it a space?
B233 BEQ done_poll_name_print ; Yes: end of name
B235 JSR osasci ; Write character
B238 INY ; Next character
B239 CPY #&22 ; Past end of name field?
B23B BNE loop_print_poll_name ; No: continue printing name
B23D .done_poll_name_print←1← B233 BEQ
JSR print_inline ; Print '"' + CR
B240 EQUS ""."
B242 NOP ; Padding byte
B243 .loop_pop_poll_slot←1← B2C1 BNE
PLA ; Get slot offset from stack
B244 BEQ return_from_poll_slots ; Zero: all slots done, return
B246 PHA ; Save slot offset
B247 TAY ; Transfer to Y
B248 LDA (nfs_workspace),y ; Read slot status byte
B24A BPL done_poll_slot_mark ; Bit 7 clear: slot inactive
B24C INY ; Advance to station number
B24D INY ; Offset+2 in slot
B24E LDA (nfs_workspace),y ; Read station number low
B250 STA fs_work_5 ; Store station low
B252 INY ; Next byte (offset+3)
B253 LDA (nfs_workspace),y ; Read network number
B255 STA fs_work_6 ; Store network number
B257 INY ; Next byte (offset+4)
B258 LDA (nfs_workspace),y ; Read status page pointer
B25A STA work_ae ; Store pointer low
B25C CLV ; Clear V flag
B25D JSR print_station_addr ; Print station address (V=0)
B260 JSR print_inline ; Print ' is '
B263 EQUS " is "
B267 LDX #0 ; X=0 for indirect indexed access
B269 LDA (work_ae,x) ; Read printer status byte
B26B BNE check_poll_jammed ; Non-zero: not ready
B26D JSR print_inline ; Print 'ready'
B270 EQUS "ready"
B275 CLV ; Clear V
B276 BVC done_poll_status_line ; ALWAYS branch
B278 .check_poll_jammed←1← B26B BNE
CMP #2 ; Status = 2?
B27A BNE check_poll_busy ; No: check for busy
B27C .print_poll_jammed←1← B28A BNE
JSR print_inline ; Print 'jammed'
B27F EQUS "jammed"
B285 CLV ; Clear V
B286 BVC done_poll_status_line ; ALWAYS branch
B288 .check_poll_busy←1← B27A BNE
CMP #1 ; Status = 1?
B28A BNE print_poll_jammed ; Not 1 or 2: default to jammed
B28C JSR print_inline ; Print 'busy'
B28F EQUS "busy"
B293 INC work_ae ; Advance past status byte
B295 LDA (work_ae,x) ; Read client station number
B297 STA fs_work_5 ; Store station low
B299 BEQ done_poll_status_line ; Zero: no client info, skip
B29B JSR print_inline ; Print ' with station '
B29E EQUS " with station "
B2AC INC work_ae ; Advance past station low
B2AE LDA (work_ae,x) ; Read client network number
B2B0 STA fs_work_6 ; Store network number
B2B2 BIT bit_test_ff ; Set V flag
B2B5 JSR print_station_addr ; Print client station address
B2B8 .done_poll_status_line←3← B276 BVC← B286 BVC← B299 BEQ
JSR osnewl ; Write newline (characters 10 and 13)
B2BB .done_poll_slot_mark←1← B24A BPL
PLA ; Retrieve slot offset
B2BC TAY ; Transfer to Y
B2BD LDA #&3f ; Mark slot as processed (&3F)
B2BF STA (nfs_workspace),y ; Write marker to workspace
B2C1 BNE loop_pop_poll_slot ; ALWAYS branch
B2C3 .return_from_poll_slots←1← B244 BEQ
RTS ; Return

Initialise PS slot buffer from template data

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

B2C4 .init_ps_slot_from_rx←1← B1A9 JSR
LDY #&78 ; Start at offset &78
B2C6 .loop_copy_slot_tmpl←1← B2D8 BNE
LDA write_ps_slot_link_addr,y ; Load template byte
B2C9 CPY #&7d ; At offset &7D?
B2CB BEQ subst_rx_page_byte ; Yes: substitute RX page
B2CD CPY #&81 ; At offset &81?
B2CF BNE store_slot_tmpl_byte ; No: use template byte
B2D1 .subst_rx_page_byte←1← B2CB BEQ
LDA net_rx_ptr_hi ; Use RX buffer page instead
B2D3 .store_slot_tmpl_byte←1← B2CF BNE
STA (work_ae),y ; Store byte in slot buffer
B2D5 INY ; Next offset
B2D6 CPY #&84 ; Past end of slot (&84)?
B2D8 BNE loop_copy_slot_tmpl ; No: continue copying
B2DA 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
B2DB .store_char_uppercase←2← B033 JSR← B20D JSR
LDY table_idx ; Y = current buffer position
B2DD AND #&7f ; Strip high bit
B2DF CMP #&61 ; Is it lowercase 'a' or above?
B2E1 BCC done_uppercase_store ; Below 'a': not lowercase
B2E3 CMP #&7b ; Above 'z'?
B2E5 BCS done_uppercase_store ; Yes: not lowercase
B2E7 AND #&5f ; Convert to uppercase
B2E9 .done_uppercase_store←2← B2E1 BCC← B2E5 BCS
STA (net_rx_ptr),y ; Store in RX buffer
B2EB INY ; Next buffer position
B2EC STY table_idx ; Update buffer position
B2EE DEX ; Decrement character count
B2EF 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
B2F0 .cmd_prot
LDA (fs_crc_lo),y ; Get next char from command line
B2F2 EOR #&0d ; Compare with CR (end of line)
B2F4 BNE parse_prot_keywords ; Not CR: attribute keywords follow
B2F6 LDA #&ff ; A=&FF: protect all attributes
B2F8 BNE store_prot_mask ; ALWAYS branch
B2FA .parse_prot_keywords←1← B2F4 BNE
LDA ws_0d68 ; Load current protection mask
B2FD PHA ; Save as starting value
B2FE .loop_match_prot_attr←1← B30E BNE
LDX #&d3 ; X=&D3: attribute keyword table offset
B300 LDA (fs_crc_lo),y ; Get next char from command line
B302 STA ws_page ; Save for end-of-args check
B304 JSR match_fs_cmd ; Match attribute keyword in table
B307 BCS prot_check_arg_end ; No match: check if end of arguments
B309 PLA ; Retrieve accumulated mask
B30A ORA cmd_table_fs_lo,x ; OR in attribute bit for keyword
B30D PHA ; Save updated mask
B30E BNE loop_match_prot_attr ; Always non-zero after ORA: loop
B310 .prot_check_arg_end←2← B307 BCS← B334 BCS
LDA ws_page ; Get the unmatched character
B312 EOR #&0d ; Is it CR?
B314 BEQ done_prot_args ; Yes: arguments ended correctly
B316 JMP error_bad_command ; No: invalid attribute keyword
B319 .done_prot_args←1← B314 BEQ
PLA ; Retrieve final protection mask
B31A .store_prot_mask←3← A734 JMP← B2F8 BNE← B325 BEQ
STA ws_0d68 ; Store protection mask
B31D STA ws_0d69 ; Store protection mask copy
B320 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
B321 .cmd_unprot
LDA (fs_crc_lo),y ; Get next char from command line
B323 EOR #&0d ; Compare with CR (end of line)
B325 BEQ store_prot_mask ; No args: A=0 clears all protection
B327 LDA ws_0d68 ; Load current protection mask
B32A PHA ; Save as starting value
B32B .loop_match_unprot_attr←1← B33B BCC
LDX #&d3 ; X=&D3: attribute keyword table offset
B32D LDA (fs_crc_lo),y ; Get next char from command line
B32F STA ws_page ; Save for end-of-args check
B331 JSR match_fs_cmd ; Match attribute keyword in table
B334 BCS prot_check_arg_end ; No match: check if end of arguments
B336 PLA ; Retrieve accumulated mask
B337 AND cmd_table_fs_hi,x ; AND to clear matched attribute bit
B33A PHA ; Save updated mask
B33B 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
B33D .cmd_wipe
JSR mask_owner_access ; Mask owner access flags to 5 bits
B340 LDA #0 ; Initialise file index to 0
B342 STA fs_work_5 ; Store file counter
B344 JSR save_ptr_to_os_text ; Save pointer to command text
B347 JSR parse_filename_arg ; Parse wildcard filename argument
B34A INX ; Advance past CR terminator
B34B STX fs_work_6 ; Save end-of-argument buffer position
B34D .request_next_wipe←1← B389 JMP
LDA #1 ; Command code 1 = examine directory
B34F STA fs_cmd_data ; Store command in TX buffer byte 0
B352 STA fs_data_count ; Store flag in TX buffer byte 2
B355 LDX fs_work_5 ; Load current file index
B357 STX fs_func_code ; Store file index in TX buffer byte 1
B35A LDX #3 ; X=3: copy from TX buffer offset 3
B35C JSR copy_arg_to_buf ; Copy filename argument to TX buffer
B35F LDY #3 ; Function code 3 = examine
B361 LDA #&80 ; Flag &80 = escapable
B363 STA escapable ; Mark operation as escapable
B365 JSR save_net_tx_cb ; Send examine request to file server
B368 LDA fs_cmd_data ; Get server response status
B36B BNE check_wipe_attr ; Non-zero: file found, process it
B36D LDA #osbyte_flush_buffer_class ; OSBYTE &0F: flush buffer class
B36F LDX #1 ; X=1: flush input buffers
B371 JSR osbyte ; Flush keyboard buffer Flush input buffers (X non-zero)
B374 LDA #osbyte_scan_keyboard_from_16 ; OSBYTE &7A: keyboard scan from 16
B376 JSR osbyte ; Scan keyboard to clear state Keyboard scan starting from key 16
B379 LDY #0 ; Y=0: no key pressed Y=key
B37B LDA #osbyte_write_keys_pressed ; OSBYTE &78: write keys pressed
B37D JMP osbyte ; Clear keyboard state and return Write current keys pressed (X and Y)
B380 .check_wipe_attr←1← B36B BNE
LDA fs_exam_attr_char ; Load first attribute char of response
B383 .loop_check_if_locked←1← B393 BNE
CMP #&4c ; Is file locked?
B385 BNE check_wipe_dir ; No: check if directory
B387 .skip_wipe_locked←1← B40E JMP
INC fs_work_5 ; Skip locked file, advance index
B389 JMP request_next_wipe ; Request next file from server
B38C .check_wipe_dir←1← B385 BNE
CMP #&44 ; Is it a directory entry?
B38E BNE show_wipe_prompt ; No: regular file, show prompt
B390 LDA fs_exam_dir_flag ; Check directory contents flag
B393 BNE loop_check_if_locked ; Non-empty dir: treat as locked, skip
B395 .show_wipe_prompt←1← B38E BNE
LDX #1 ; X=1: start from response byte 1
B397 LDY fs_work_6 ; Y = destination index in delete buffer
B399 .loop_copy_wipe_name←1← B3A6 BNE
LDA fs_func_code,x ; Load filename char from response
B39C JSR osasci ; Print filename character to screen Write character
B39F STA fs_filename_buf,y ; Store in delete command buffer too
B3A2 INY ; Advance destination index
B3A3 INX ; Advance source index
B3A4 CPX #&0c ; Copied all 11 filename characters?
B3A6 BNE loop_copy_wipe_name ; No: continue copying
B3A8 JSR print_inline ; Print '(Y/N/?) ' prompt
B3AB EQUS "(Y/N/?) "
B3B3 NOP ; Inline string terminator (NOP)
B3B4 JSR flush_and_read_char ; Read user response character
B3B7 CMP #&3f ; User pressed '?'?
B3B9 BNE check_wipe_response ; No: check for Y/N response
B3BB LDA #&0d ; Carriage return before full info
B3BD JSR oswrch ; Print CR Write character 13
B3C0 LDX #2 ; X=2: start from response byte 2
B3C2 .loop_print_wipe_info←1← B3CB BNE
LDA fs_cmd_data,x ; Load file info character
B3C5 JSR osasci ; Print file info character Write character
B3C8 INX ; Advance to next character
B3C9 CPX #&3e ; Printed all &3C info bytes?
B3CB BNE loop_print_wipe_info ; No: continue printing
B3CD JSR print_inline ; Print ' (Y/N) ' prompt (no '?')
B3D0 EQUS " (Y/N) "
B3D7 NOP ; Inline string terminator (NOP)
B3D8 JSR flush_and_read_char ; Read user response (Y/N only)
B3DB .check_wipe_response←1← B3B9 BNE
AND #&df ; Force uppercase
B3DD CMP #&59 ; User said 'Y' (yes)?
B3DF BNE skip_wipe_to_next ; No: print newline, skip to next file
B3E1 JSR osasci ; Echo 'Y' to screen Write character
B3E4 LDX #0 ; X=0: start of stored filename
B3E6 LDA fs_filename_buf,x ; Check first byte of stored name
B3E9 CMP #&0d ; Is first byte CR (empty first field)?
B3EB BEQ use_wipe_leaf_name ; Yes: use second filename field
B3ED .loop_build_wipe_cmd←1← B402 BNE
LDA fs_filename_buf,x ; Load byte from stored filename
B3F0 CMP #&0d ; Is it CR (field separator)?
B3F2 BNE skip_if_not_space ; No: check for space
B3F4 LDA #&2e ; Replace CR with '.' directory sep
B3F6 .skip_if_not_space←1← B3F2 BNE
CMP #&20 ; Is it a space (name terminator)?
B3F8 BNE store_wipe_tx_char ; No: keep character as-is
B3FA .set_wipe_cr_end←1← B41D BEQ
LDA #&0d ; Replace space with CR (end of name)
B3FC .store_wipe_tx_char←1← B3F8 BNE
STA fs_cmd_data,x ; Store in delete command TX buffer
B3FF INX ; Advance to next character
B400 CMP #&0d ; Was it the CR terminator?
B402 BNE loop_build_wipe_cmd ; No: continue building delete command
B404 LDY #&14 ; Function code &14 = delete file
B406 JSR save_net_tx_cb ; Send delete request to file server
B409 DEC fs_work_5 ; Adjust file index after deletion
B40B .skip_wipe_to_next←1← B3DF BNE
JSR osnewl ; Print newline after user response Write newline (characters 10 and 13)
B40E JMP skip_wipe_locked ; Advance index, process next file
B411 .use_wipe_leaf_name←1← B3EB BEQ
DEX ; DEX to offset following INX
B412 .loop_copy_wipe_leaf←1← B41B BNE
INX ; Advance to next byte
B413 LDA fs_filename_buf_1,x ; Load byte from second field
B416 STA fs_cmd_data,x ; Store in delete command TX buffer
B419 CMP #&20 ; Is it a space (field terminator)?
B41B BNE loop_copy_wipe_leaf ; No: continue copying second field
B41D BEQ set_wipe_cr_end ; Space found: terminate with CR ALWAYS branch

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

B41F .flush_and_read_char←2← B3B4 JSR← B3D8 JSR
LDA #osbyte_flush_buffer_class ; OSBYTE &0F: flush buffer class
B421 LDX #1 ; X=1: flush input buffers
B423 JSR osbyte ; Flush keyboard buffer before read Flush input buffers (X non-zero)
B426 JSR osrdch ; Read character from input stream Read a character from the current input stream
B429 BCC return_from_flush_read ; C clear: character read OK
B42B JMP raise_escape_error ; Escape pressed: raise error
B42E .return_from_flush_read←1← B429 BCC
RTS ; Return with character in A

Dead code: clear 120 bytes of workspace

Unreferenced subroutine. Zeroes offsets &00-&77 (120 bytes) of the workspace page pointed to by l00cc. Superseded by loop_zero_workspace (&8ED5) which clears a full 256-byte page via both l00cc and nfs_workspace pointers.

B42F .unused_clear_ws_78
LDA #0 ; A=0: clear value
B431 LDY #&78 ; Y=&78: clear offsets &00-&77
B433 .loop_clear_ws_78←1← B436 BNE
DEY ; Decrement index
B434 STA (fs_ws_ptr),y ; Clear workspace byte via l00cc
B436 BNE loop_clear_ws_78 ; Loop until Y=0
B438 RTS ; Return

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

B439 .init_channel_table←1← 8B6A JSR
LDA #0 ; A=0: clear value
B43B TAY ; Y=0: start index Y=&00
B43C .loop_clear_chan_table←1← B440 BNE
STA fcb_count_lo,y ; Clear channel table entry
B43F INY ; Next entry
B440 BNE loop_clear_chan_table ; Loop until all 256 bytes cleared
B442 LDY #&0f ; Offset &0F in receive buffer
B444 LDA (net_rx_ptr),y ; Get number of available channels
B446 SEC ; Prepare subtraction
B447 SBC #&5a ; Subtract 'Z' to get negative count
B449 TAY ; Y = negative channel count (index)
B44A LDA #&40 ; Channel marker &40 (available)
B44C .loop_mark_chan_avail←1← B452 BPL
STA fcb_count_lo,y ; Mark channel slot as available
B44F DEY ; Previous channel slot
B450 CPY #&b8 ; Reached start of channel range?
B452 BPL loop_mark_chan_avail ; No: continue marking channels
B454 INY ; Point to first channel slot
B455 LDA #&c0 ; Active channel marker &C0
B457 STA fcb_count_lo,y ; Mark first channel as active
B45A 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
B45B .attr_to_chan_index←5← 92A9 JSR← 92C0 JSR← 9C60 JSR← 9C9E JSR← B745 JSR
PHP ; Save flags
B45C SEC ; Prepare subtraction
B45D SBC #&20 ; Subtract &20 to get table index
B45F BMI error_chan_out_of_range ; Negative: out of valid range
B461 CMP #&10 ; Above maximum channel index &0F?
B463 BCC return_chan_index ; In range: valid index
B465 .error_chan_out_of_range←1← B45F BMI
LDA #&ff ; Out of range: return &FF (invalid)
B467 .return_chan_index←1← B463 BCC
PLP ; Restore flags
B468 TAX ; X = channel index (or &FF)
B469 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
B46A .check_chan_char←2← 9D97 JSR← B4E3 JSR
CMP #&20 ; Below space?
B46C BCC err_net_chan_invalid ; Yes: invalid channel character
B46E CMP #&30 ; Below '0'?
B470 BCC lookup_chan_by_char ; In range &20-&2F: look up channel
B472 .err_net_chan_invalid←2← 9BC6 JMP← B46C BCC
PHA ; Save channel character
B473 .error_chan_not_found←1← B4A5 BEQ
LDA #&de ; Error code &DE
B475 .err_net_chan_not_found
JSR error_inline_log ; Generate 'Net channel' error
B478 EQUS "Net channel."
B484 JSR false_ref_6f6e ; Error string continuation (unreachable)
B487 EQUS "t on this file server"
B49C 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
B49D .lookup_chan_by_char←2← 9EC4 JSR← B470 BCC
PHA ; Save channel character
B49E SEC ; Prepare subtraction
B49F SBC #&20 ; Convert char to table index
B4A1 TAX ; X = channel table index
B4A2 LDA fcb_net_or_port,x ; Look up network number for channel
B4A5 BEQ error_chan_not_found ; Zero: channel not found, raise error
B4A7 JSR match_station_net ; Check station/network matches current
B4AA BNE error_chan_not_here ; No match: build detailed error msg
B4AC PLA ; Discard saved channel character
B4AD LDA chan_status,x ; Load channel status flags
B4B0 RTS ; Return; A = channel flags
B4B1 .error_chan_not_here←1← B4AA BNE
LDA #&de ; Error code &DE
B4B3 STA error_text ; Store error code in error block
B4B6 LDA #0 ; BRK opcode
B4B8 STA error_block ; Store BRK at start of error block
B4BB TAX ; X=0: copy index X=&00
B4BC .loop_copy_chan_err_str←1← B4C3 BNE
INX ; Advance copy position
B4BD LDA net_channel_err_string,x ; Load 'Net channel' string byte
B4C0 STA error_text,x ; Copy to error text
B4C3 BNE loop_copy_chan_err_str ; Continue until NUL terminator
B4C5 STX fs_load_addr_2 ; Save end-of-string position
B4C7 STX fs_work_4 ; Save for suffix append
B4C9 PLA ; Retrieve channel character
B4CA JSR append_space_and_num ; Append ' N' (channel number)
B4CD LDY fs_work_4 ; Load 'Net channel' end position
B4CF .loop_append_err_suffix←1← B4D7 BNE
INY ; Skip past NUL to suffix string
B4D0 INX ; Advance destination position
B4D1 LDA net_channel_err_string,y ; Load ' not on this...' suffix byte
B4D4 STA error_text,x ; Append to error message
B4D7 BNE loop_append_err_suffix ; Continue until NUL
B4D9 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.

B4DC .store_result_check_dir←2← B7D4 JSR← B85C JSR
LDA cur_chan_attr ; Load current channel attribute
B4DF LDY #&0e ; Offset &0E in receive buffer
B4E1 STA (net_rx_ptr),y ; Store attribute in receive 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.

B4E3 .check_not_dir←2← 9BF9 JSR← 9E47 JSR
JSR check_chan_char ; Validate and look up channel
B4E6 AND #2 ; Test directory flag (bit 1)
B4E8 BEQ return_from_dir_check ; Not a directory: return OK
B4EA LDA #&a8 ; Error code &A8
B4EC JSR error_inline_log ; Generate 'Is a dir.' error
B4EF EQUS "Is a dir.."
B4F9 .return_from_dir_check←1← B4E8 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
B4FA .alloc_fcb_slot←7← 9D3D JSR← 9D72 JSR← A268 JSR← A307 JSR← A332 JSR← A369 JSR← B531 JSR
PHA ; Save channel attribute
B4FB LDX #&20 ; Start scanning from FCB slot &20
B4FD .loop_scan_fcb_slots←1← B505 BNE
LDA fcb_attr_or_count_mid,x ; Load FCB station byte
B500 BEQ done_found_free_slot ; Zero: slot is free, use it
B502 INX ; Try next slot
B503 CPX #&30 ; Past last FCB slot &2F?
B505 BNE loop_scan_fcb_slots ; No: check next slot
B507 PLA ; No free slot: discard saved attribute
B508 LDA #0 ; A=0: return failure (Z set)
B50A RTS ; Return
B50B .done_found_free_slot←1← B500 BEQ
PLA ; Restore channel attribute
B50C STA fcb_attr_or_count_mid,x ; Store attribute in FCB slot
B50F LDA #0 ; A=0: clear value
B511 STA fcb_xfer_count_lo,x ; Clear FCB transfer count low
B514 STA fcb_xfer_count_mid,x ; Clear FCB transfer count mid
B517 STA fcb_count_lo,x ; Clear FCB transfer count high
B51A LDA fs_server_stn ; Load current station number
B51D STA fcb_station_or_count_hi,x ; Store station in FCB
B520 LDA fs_server_net ; Load current network number
B523 STA fcb_net_or_port,x ; Store network in FCB
B526 TXA ; Get FCB slot index
B527 PHA ; Save slot index
B528 SEC ; Prepare subtraction
B529 SBC #&20 ; Convert slot to channel index (0-&0F)
B52B TAX ; X = channel index
B52C PLA ; Restore A = FCB slot index
B52D 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.

B52E .alloc_fcb_or_error←2← 9CEB JSR← A1D5 JSR
PHA ; Save argument
B52F LDA #0 ; A=0: allocate any available slot
B531 JSR alloc_fcb_slot ; Try to allocate an FCB slot
B534 BNE return_alloc_success ; Success: slot allocated
B536 LDA #&c0 ; Error code &C0
B538 JSR error_inline_log ; Generate 'No more FCBs' error
B53B EQUS "No more FCBs."
B548 .return_alloc_success←1← B534 BNE
PLA ; Restore argument
B549 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
B54A .close_all_net_chans←3← 9494 JSR← 951B JSR← A379 JSR
CLC ; C=0: close all matching channels
B54B BCC skip_set_carry ; Branch always to scan entry ALWAYS branch
B54D SEC ; C=1: close with write-flush
B54E .skip_set_carry←1← B54B BCC
BIT bit_test_ff ; 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.

B551 .scan_fcb_flags←1← 9DAC JSR
LDX #&10 ; Start from FCB slot &10
B553 .loop_scan_fcb_down←4← B55F BVC← B569 BVS← B56E BNE← B578 BEQ
DEX ; Previous FCB slot
B554 BPL skip_if_slots_done ; More slots to check
B556 RTS ; All FCB slots processed, return
B557 .skip_if_slots_done←1← B554 BPL
LDA chan_status,x ; Load channel flags for this slot
B55A TAY ; Save flags in Y
B55B AND #2 ; Test active flag (bit 1)
B55D BEQ done_check_station ; Not active: check station match
B55F BVC loop_scan_fcb_down ; V clear (close all): next slot
B561 BCC done_check_station ; C clear: check station match
B563 TYA ; Restore original flags
B564 AND #&df ; Clear write-pending flag (bit 5)
B566 STA chan_status,x ; Update channel flags
B569 BVS loop_scan_fcb_down ; Next slot (V always set here) ALWAYS branch
B56B .done_check_station←2← B55D BEQ← B561 BCC
JSR match_station_net ; Check if channel belongs to station
B56E BNE loop_scan_fcb_down ; No match: skip to next slot
B570 LDA #0 ; A=0: clear channel
B572 STA chan_status,x ; Clear channel flags (close it)
B575 STA fcb_net_or_port,x ; Clear network number
B578 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
B57A .match_station_net←7← A2EE JSR← A319 JSR← A350 JSR← A69E JSR← AC2E JSR← B4A7 JSR← B56B JSR
LDA fcb_flags,x ; Load FCB station number
B57D EOR fs_server_stn ; Compare with current station (EOR)
B580 BNE return_from_match_stn ; Different: Z=0, no match
B582 LDA fcb_net_num,x ; Load FCB network number
B585 EOR fs_server_net ; Compare with current network (EOR)
B588 .return_from_match_stn←1← B580 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.

B589 .find_open_fcb←2← B68A JSR← B788 JSR
LDX cur_fcb_index ; Load current FCB index
B58C BIT bit_test_ff ; Set V flag (first pass marker)
B58F .loop_find_fcb←4← B59E BVC← B5A4 BPL← B5C1 BVS← B5C8 BNE
INX ; Next FCB slot
B590 CPX #&10 ; Past end of table (&10)?
B592 BNE skip_if_no_wrap ; No: continue checking
B594 LDX #0 ; Wrap around to slot 0
B596 .skip_if_no_wrap←1← B592 BNE
CPX cur_fcb_index ; Back to starting slot?
B599 BNE done_check_fcb_status ; No: check this slot
B59B BVC loop_scan_empty_fcb ; V clear (second pass): scan empties
B59D CLV ; Clear V for second pass
B59E BVC loop_find_fcb ; Continue scanning ALWAYS branch
B5A0 .done_check_fcb_status←1← B599 BNE
LDA fcb_status,x ; Load FCB status flags
B5A3 ROL ; Shift bit 7 (in-use) into carry
B5A4 BPL loop_find_fcb ; Not in use: skip
B5A6 AND #4 ; Test bit 2 (modified flag)
B5A8 BNE skip_if_modified_fcb ; Modified: check further conditions
B5AA .done_select_fcb←1← B5CA BEQ
DEX ; Adjust for following INX
B5AB .loop_scan_empty_fcb←2← B59B BVC← B5B6 BPL
INX ; Next FCB slot
B5AC CPX #&10 ; Past end of table?
B5AE BNE done_test_empty_slot ; No: continue
B5B0 LDX #0 ; Wrap around to slot 0
B5B2 .done_test_empty_slot←1← B5AE BNE
LDA fcb_status,x ; Load FCB status flags
B5B5 ROL ; Shift bit 7 into carry
B5B6 BPL loop_scan_empty_fcb ; Not in use: continue scanning
B5B8 SEC ; Set carry for ROR restore
B5B9 ROR ; Restore original flags
B5BA STA fcb_status,x ; Save flags back (mark as found)
B5BD LDX cur_fcb_index ; Restore original FCB index
B5C0 RTS ; Return with found slot in X
B5C1 .skip_if_modified_fcb←1← B5A8 BNE
BVS loop_find_fcb ; V set (first pass): skip modified
B5C3 LDA fcb_status,x ; Load FCB status flags
B5C6 AND #&20 ; Test bit 5 (offset pending)
B5C8 BNE loop_find_fcb ; Bit 5 set: skip this slot
B5CA 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)
B5CC .init_wipe_counters←2← B60F JSR← B6CC JSR
LDY #1 ; Initial pass count = 1
B5CE STY xfer_pass_count ; Store pass counter
B5D1 DEY ; Y=0 Y=&00
B5D2 STY xfer_count_lo ; Clear byte counter low
B5D5 STY xfer_offset ; Clear offset counter
B5D8 STY xfer_flag ; Clear transfer flag
B5DB TYA ; A=0 A=&00
B5DC LDX #2 ; Clear 3 counter bytes
B5DE .loop_clear_counters←1← B5E2 BPL
STA xfer_counter,x ; Clear counter byte
B5E1 DEX ; Next byte
B5E2 BPL loop_clear_counters ; Loop for indices 2, 1, 0
B5E4 STX xfer_sentinel_1 ; Store &FF as sentinel in l10cd
B5E7 STX xfer_sentinel_2 ; Store &FF as sentinel in l10ce
B5EA LDX #&ca ; X=&CA: workspace offset
B5EC LDY #&10 ; Y=&10: page &10
B5EE 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
B5EF .start_wipe_pass←2← B681 JSR← B7BA JSR
JSR verify_ws_checksum ; Verify workspace checksum integrity
B5F2 STX cur_fcb_index ; Save current FCB index
B5F5 LDA fcb_status,x ; Load FCB status flags
B5F8 ROR ; Shift bit 0 (active) into carry
B5F9 BCC done_clear_fcb_active ; Not active: clear status and return
B5FB LDA work_stn_lo ; Save current station low to stack
B5FE PHA ; Push station low
B5FF LDA work_stn_hi ; Save current station high
B602 PHA ; Push station high
B603 LDA fcb_stn_lo,x ; Load FCB station low
B606 STA work_stn_lo ; Set as working station low
B609 LDA fcb_stn_hi,x ; Load FCB station high
B60C STA work_stn_hi ; Set as working station high
B60F JSR init_wipe_counters ; Reset transfer counters
B612 DEC xfer_offset ; Set offset to &FF (no data yet)
B615 DEC xfer_pass_count ; Set pass counter to 0 (flush mode)
B618 LDX cur_fcb_index ; Reload FCB index
B61B TXA ; Transfer to A
B61C CLC ; Prepare addition
B61D ADC #&11 ; Add &11 for buffer page offset
B61F STA fcb_buf_page ; Store buffer address high byte
B622 LDA fcb_status,x ; Load FCB status flags
B625 AND #&20 ; Test bit 5 (has saved offset)
B627 BEQ done_restore_offset ; No offset: skip restore
B629 LDA fcb_buf_offset,x ; Load saved byte offset
B62C STA xfer_offset ; Restore offset counter
B62F .done_restore_offset←1← B627 BEQ
LDA fcb_attr_ref,x ; Load FCB attribute reference
B632 STA cur_attr_ref ; Store as current reference
B635 TAX ; Transfer to X
B636 LDY #&0e ; Offset &0E in receive buffer
B638 LDA (net_rx_ptr),y ; Save current receive attribute
B63A PHA ; Push to stack
B63B TXA ; Restore attribute to A
B63C STA (net_rx_ptr),y ; Set attribute in receive buffer
B63E LDX #&ca ; X=&CA: workspace offset
B640 LDY #&10 ; Y=&10: page &10
B642 LDA #0 ; A=0: standard transfer mode
B644 JSR send_and_receive ; Send data and receive response
B647 LDX cur_fcb_index ; Reload FCB index
B64A PLA ; Restore saved receive attribute
B64B LDY #&0e ; Offset &0E
B64D STA (net_rx_ptr),y ; Restore receive attribute
B64F PLA ; Restore station high
B650 STA work_stn_hi ; Store station high
B653 PLA ; Restore station low
B654 STA work_stn_lo ; Store station low
B657 .done_clear_fcb_active←1← B5F9 BCC
LDA #&dc ; Mask &DC: clear bits 0, 1, 5
B659 AND fcb_status,x ; Clear active and offset flags
B65C STA fcb_status,x ; Update FCB status
B65F 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)
B660 .save_fcb_context←2← B735 JSR← B8A2 JSR
LDX #&0c ; Copy 13 bytes (indices 0 to &0C)
B662 .loop_save_tx_context←1← B66C BPL
LDA txcb_reply_port,x ; Load TX buffer byte
B665 STA fcb_ctx_save,x ; Save to context buffer at &10D9
B668 LDA fs_load_addr,x ; Load workspace byte from fs_load_addr
B66A PHA ; Save to stack
B66B DEX ; Next byte down
B66C BPL loop_save_tx_context ; Loop for all 13 bytes
B66E CPY #0 ; Y=0? (no FCB to process)
B670 BNE done_save_context ; Non-zero: scan and process FCBs
B672 JMP loop_restore_workspace ; Y=0: skip to restore workspace
B675 .done_save_context←1← B670 BNE
PHP ; Save flags
B676 LDX #&ff ; X=&FF: start scanning from -1
B678 .loop_find_pending_fcb←2← B67C BPL← B67F BPL
INX ; Next FCB slot
B679 LDA fcb_status,x ; Load FCB status flags
B67C BPL loop_find_pending_fcb ; Bit 7 clear: not pending, skip
B67E ASL ; Shift bit 6 to bit 7
B67F BPL loop_find_pending_fcb ; Bit 6 clear: skip
B681 JSR start_wipe_pass ; Flush this FCB's pending data
B684 LDA #&40 ; Pending marker &40
B686 STA fcb_status,x ; Mark FCB as pending-only
B689 PHP ; Save flags
B68A JSR find_open_fcb ; Find next available FCB slot
B68D PLP ; Restore flags
B68E LDA cur_chan_attr ; Load current channel attribute
B691 STA cur_attr_ref ; Store as current reference
B694 PHA ; Save attribute
B695 TAY ; Y = attribute index
B696 LDA fcb_attr_or_count_mid,y ; Load station for this attribute
B699 STA fs_cmd_data ; Store station in TX buffer
B69C PLA ; Restore attribute
B69D STA fcb_attr_ref,x ; Store attribute in FCB slot
B6A0 LDA work_stn_lo ; Load working station low
B6A3 STA fs_reply_cmd ; Store in TX buffer
B6A6 STA fcb_stn_lo,x ; Store station low in FCB
B6A9 LDA work_stn_hi ; Load working station high
B6AC STA fs_load_vector ; Store in TX buffer
B6AF STA fcb_stn_hi,x ; Store station high in FCB
B6B2 TXA ; Get FCB slot index
B6B3 CLC ; Prepare addition
B6B4 ADC #&11 ; Add &11 for buffer page offset
B6B6 STA fcb_buf_page ; Store buffer address high byte
B6B9 PLP ; Restore flags
B6BA BVC done_init_wipe ; V clear: skip directory request
B6BC LDX #0 ; Command byte = 0
B6BE STX fs_func_code ; Store in TX buffer
B6C1 INX ; X=1: flag byte X=&01
B6C2 STX fs_data_count ; Store in TX buffer
B6C5 LDY #&0d ; Function code &0D
B6C7 LDX #5 ; X=5: copy 5 bytes to TX
B6C9 JSR save_net_tx_cb ; Send directory request to server
B6CC .done_init_wipe←1← B6BA BVC
JSR init_wipe_counters ; Reset transfer counters
B6CF LDY #&0e ; Offset &0E
B6D1 LDA (net_rx_ptr),y ; Save current receive attribute
B6D3 PHA ; Push to stack
B6D4 LDA cur_attr_ref ; Load current reference
B6D7 STA (net_rx_ptr),y ; Set in receive buffer
B6D9 LDY #&10 ; Y=&10: page &10
B6DB LDA #2 ; A=2: transfer mode 2
B6DD JSR send_and_receive ; Send and receive data
B6E0 PLA ; Restore receive attribute
B6E1 LDY #&0e ; Offset &0E
B6E3 STA (net_rx_ptr),y ; Restore receive attribute
B6E5 LDX cur_fcb_index ; Reload FCB index
B6E8 LDA xfer_pass_count ; Load pass counter
B6EB BNE done_calc_offset ; Non-zero: data received, calc offset
B6ED LDA xfer_offset ; Load offset counter
B6F0 BEQ done_set_fcb_active ; Zero: no data received at all
B6F2 .done_calc_offset←1← B6EB BNE
LDA xfer_offset ; Load offset counter
B6F5 EOR #&ff ; Negate (ones complement)
B6F7 CLC ; Clear carry for add
B6F8 ADC #1 ; Complete twos complement negation
B6FA STA fcb_buf_offset,x ; Store negated offset in FCB
B6FD LDA #&20 ; Set bit 5 (has saved offset)
B6FF ORA fcb_status,x ; Add to FCB flags
B702 STA fcb_status,x ; Update FCB status
B705 LDA fcb_buf_page ; Load buffer address high byte
B708 STA fs_load_addr_3 ; Set pointer high byte
B70A LDA #0 ; A=0: pointer low byte and clear val
B70C STA fs_load_addr_2 ; Set pointer low byte
B70E LDY fcb_buf_offset,x ; Load negated offset (start of clear)
B711 .loop_clear_buffer←1← B714 BNE
STA (fs_load_addr_2),y ; Clear buffer byte
B713 INY ; Next byte
B714 BNE loop_clear_buffer ; Loop until page boundary
B716 .done_set_fcb_active←1← B6F0 BEQ
LDA #2 ; Set bit 1 (active flag)
B718 ORA fcb_status,x ; Add active flag to status
B71B STA fcb_status,x ; Update FCB status
B71E LDY #0 ; Y=0: start restoring workspace
B720 .loop_restore_workspace←2← B672 JMP← B727 BNE
PLA ; Restore workspace byte from stack
B721 STA fs_load_addr,y ; Store to fs_load_addr workspace
B724 INY ; Next byte
B725 CPY #&0d ; Restored all 13 bytes?
B727 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.

B729 .restore_catalog_entry←1← B8D3 JSR
LDY #&0c ; Copy 13 bytes (indices 0 to &0C)
B72B .loop_restore_tx_buf←1← B732 BPL
LDA fcb_ctx_save,y ; Load saved catalog byte from &10D9
B72E STA txcb_reply_port,y ; Restore to TX buffer
B731 DEY ; Next byte down
B732 BPL loop_restore_tx_buf ; Loop for all bytes
B734 RTS ; Return
B735 .loop_save_before_match←1← B754 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
B738 .find_matching_fcb←3← 9DEC JSR← B7EF JSR← B88E JSR
LDX #&ff ; X=&FF: start scanning from -1
B73A .loop_reload_attr←2← B774 BNE← B77C BNE
LDY cur_chan_attr ; Load channel attribute to match
B73D .loop_next_fcb_slot←2← B75C BEQ← B762 BNE
INX ; Next FCB slot
B73E CPX #&10 ; Past end of table (&10)?
B740 BNE done_test_fcb_active ; No: check this slot
B742 LDA cur_chan_attr ; Load channel attribute
B745 JSR attr_to_chan_index ; Convert to channel index
B748 LDA fcb_station_or_count_hi,x ; Load station for this channel
B74B STA work_stn_hi ; Store as match target station high
B74E LDA fcb_attr_or_count_mid,x ; Load port for this channel
B751 STA work_stn_lo ; Store as match target station low
B754 JMP loop_save_before_match ; Save context and rescan from start
B757 .done_test_fcb_active←1← B740 BNE
LDA fcb_status,x ; Load FCB status flags
B75A AND #2 ; Test active flag (bit 1)
B75C BEQ loop_next_fcb_slot ; Not active: skip to next
B75E TYA ; Get attribute to match
B75F CMP fcb_attr_ref,x ; Compare with FCB attribute ref
B762 BNE loop_next_fcb_slot ; No attribute match: skip
B764 STX cur_fcb_index ; Save matching FCB index
B767 SEC ; Prepare subtraction
B768 SBC #&20 ; Convert attribute to channel index
B76A TAY ; Y = channel index
B76B LDX cur_fcb_index ; Reload FCB index
B76E LDA fcb_attr_or_count_mid,y ; Load channel station byte
B771 CMP fcb_stn_lo,x ; Compare with FCB station
B774 BNE loop_reload_attr ; Station mismatch: try next
B776 LDA fcb_station_or_count_hi,y ; Load channel network byte
B779 CMP fcb_stn_hi,x ; Compare with FCB network
B77C BNE loop_reload_attr ; Network mismatch: try next
B77E LDA fcb_status,x ; Load FCB flags
B781 BPL return_test_offset ; Bit 7 clear: no pending flush
B783 AND #&7f ; Clear pending flag (bit 7)
B785 STA fcb_status,x ; Update FCB status
B788 JSR find_open_fcb ; Find new open FCB slot
B78B LDA fcb_status,x ; Reload FCB flags
B78E .return_test_offset←1← B781 BPL
AND #&20 ; Test bit 5 (has offset data)
B790 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
B791 .inc_fcb_byte_count←2← B836 JSR← B910 JSR
INC fcb_count_lo,x ; Increment byte count low
B794 BNE return_from_inc_fcb_count ; No overflow: done
B796 INC fcb_attr_or_count_mid,x ; Increment byte count mid
B799 BNE return_from_inc_fcb_count ; No overflow: done
B79B INC fcb_station_or_count_hi,x ; Increment byte count high
B79E .return_from_inc_fcb_count←2← B794 BNE← B799 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)
B79F .process_all_fcbs←9← 8D7C JSR← 8F87 JSR← 948C JSR← 9BCD JSR← 9C13 JSR← 9CB6 JSR← 9D7E JSR← 9E4C JSR← A67D JSR
TXA ; Save X
B7A0 PHA ; Push X to stack
B7A1 TYA ; Save Y
B7A2 PHA ; Push Y to stack
B7A3 LDA fs_options ; Save fs_options
B7A5 PHA ; Push fs_options
B7A6 LDA fs_block_offset ; Save fs_block_offset
B7A8 PHA ; Push fs_block_offset
B7A9 LDX #&0f ; Start from FCB slot &0F
B7AB STX cur_fcb_index ; Store as current FCB index
B7AE .loop_process_fcb←1← B7C2 BPL
LDX cur_fcb_index ; Load current FCB index
B7B1 TYA ; Get filter attribute
B7B2 BEQ done_flush_fcb ; Zero: process all FCBs
B7B4 CMP fcb_attr_ref,x ; Compare with FCB attribute ref
B7B7 BNE done_advance_fcb ; No match: skip this FCB
B7B9 .done_flush_fcb←1← B7B2 BEQ
PHA ; Save filter attribute
B7BA JSR start_wipe_pass ; Flush pending data for this FCB
B7BD PLA ; Restore filter
B7BE TAY ; Y = filter attribute
B7BF .done_advance_fcb←1← B7B7 BNE
DEC cur_fcb_index ; Previous FCB index
B7C2 BPL loop_process_fcb ; More slots: continue loop
B7C4 PLA ; Restore fs_block_offset
B7C5 STA fs_block_offset ; Store fs_block_offset
B7C7 PLA ; Restore fs_options
B7C8 STA fs_options ; Store fs_options
B7CA PLA ; Restore Y
B7CB TAY ; Y restored
B7CC PLA ; Restore X
B7CD TAX ; X restored
B7CE RTS ; Return
B7CF STY cur_chan_attr ; Save channel attribute
B7D2 TXA ; Save caller's X
B7D3 PHA ; Push X
B7D4 JSR store_result_check_dir ; Store result and check not directory
B7D7 LDA chan_status,x ; Load channel flags
B7DA AND #&20 ; Test write-only flag (bit 5)
B7DC BEQ done_read_fcb_byte ; Not write-only: proceed with read
B7DE LDA #&d4 ; Error code &D4
B7E0 JSR error_inline_log ; Generate 'Write only' error
B7E3 EQUS "Write only."
B7EE .done_read_fcb_byte←1← B7DC BEQ
CLV ; Clear V (first-pass matching)
B7EF JSR find_matching_fcb ; Find FCB matching this channel
B7F2 BEQ done_load_from_buf ; No offset: read byte from buffer
B7F4 LDA fcb_count_lo,y ; Load byte count for matching FCB
B7F7 CMP fcb_buf_offset,x ; Compare with buffer offset limit
B7FA BCC done_load_from_buf ; Below offset: data available
B7FC LDA chan_status,y ; Load channel flags for FCB
B7FF TAX ; Transfer to X for testing
B800 AND #&40 ; Test bit 6 (EOF already signalled)
B802 BNE error_end_of_file ; EOF already set: raise error
B804 TXA ; Restore flags
B805 ORA #&40 ; Set EOF flag (bit 6)
B807 STA chan_status,y ; Update channel flags with EOF
B80A LDA #0 ; A=0: clear receive attribute
B80C LDY #&0e ; Offset &0E
B80E STA (net_rx_ptr),y ; Clear attribute in receive buffer
B810 PLA ; Restore caller's X
B811 TAX ; X restored
B812 LDA #&fe ; A=&FE: EOF marker byte
B814 LDY cur_chan_attr ; Restore channel attribute
B817 SEC ; C=1: end of file
B818 RTS ; Return
B819 .error_end_of_file←1← B802 BNE
LDA #&df ; Error code &DF
B81B JSR error_inline_log ; Generate 'End of file' error
B81E EQUS "End of file."
B82A .done_load_from_buf←2← B7F2 BEQ← B7FA BCC
LDA fcb_count_lo,y ; Load current byte count (= offset)
B82D PHA ; Save byte count
B82E TYA ; Get FCB slot index
B82F TAX ; X = FCB slot for byte count inc
B830 LDA #0 ; A=0: clear receive attribute
B832 LDY #&0e ; Offset &0E
B834 STA (net_rx_ptr),y ; Clear attribute in receive buffer
B836 JSR inc_fcb_byte_count ; Increment byte count for this FCB
B839 PLA ; Restore byte count (= buffer offset)
B83A TAY ; Y = offset into data buffer
B83B LDA cur_fcb_index ; Load current FCB index
B83E CLC ; Prepare addition
B83F ADC #&11 ; Add &11 for buffer page offset
B841 STA fs_load_addr_3 ; Set pointer high byte
B843 LDA #0 ; A=0: pointer low byte
B845 STA fs_load_addr_2 ; Set pointer low byte
B847 PLA ; Restore caller's X
B848 TAX ; X restored
B849 LDA (fs_load_addr_2),y ; Read data byte from buffer
B84B LDY cur_chan_attr ; Restore channel attribute
B84E CLC ; C=0: byte read successfully
B84F RTS ; Return; A = data byte
B850 STY cur_chan_attr ; Save channel attribute
B853 PHA ; Save data byte
B854 TAY ; Y = data byte
B855 TXA ; Save caller's X
B856 PHA ; Push X
B857 TYA ; Restore data byte to A
B858 PHA ; Push data byte for later
B859 STA osbput_saved_byte ; Save data byte in workspace
B85C JSR store_result_check_dir ; Store result and check not directory
B85F LDA chan_status,x ; Load channel flags
B862 BMI done_test_write_flag ; Bit 7 set: channel open, proceed
B864 LDA #&c1 ; Error &C1: Not open for update
B866 JSR error_inline_log ; Raise error with inline string
B869 EQUS "Not open for update."
B87D .done_test_write_flag←1← B862 BMI
AND #&20 ; Test write flag (bit 5)
B87F BEQ done_find_write_fcb ; Not write-capable: use buffer path
B881 LDY fcb_net_or_port,x ; Load reply port for this channel
B884 PLA ; Restore data byte
B885 JSR send_wipe_request ; Send byte directly to server
B888 JMP done_inc_byte_count ; Update byte count and return
B88B .done_find_write_fcb←1← B87F BEQ
BIT bit_test_ff ; Set V flag (alternate match mode)
B88E JSR find_matching_fcb ; Find matching FCB for channel
B891 LDA fcb_count_lo,y ; Load byte count for FCB
B894 CMP #&ff ; Buffer full (&FF bytes)?
B896 BNE done_check_buf_offset ; No: store byte in buffer
B898 TXA ; Save X
B899 PHA ; Push X
B89A TYA ; Save Y (FCB slot)
B89B PHA ; Push Y
B89C LDA fcb_net_or_port,y ; Load reply port for FCB
B89F PHA ; Save reply port
B8A0 LDY #0 ; Y=0: no nested context
B8A2 JSR save_fcb_context ; Save context and flush FCB data
B8A5 PLA ; Restore reply port
B8A6 STA fs_cmd_data ; Store reply port in TX buffer
B8A9 TAX ; X = reply port
B8AA PLA ; Restore Y (FCB slot)
B8AB TAY ; Y restored
B8AC PHA ; Save Y again for later restore
B8AD TXA ; A = reply port
B8AE PHA ; Save reply port for send
B8AF LDX #0 ; Command byte = 0
B8B1 STX fs_func_code ; Store in TX buffer
B8B4 DEX ; X=&FF: flag byte X=&ff
B8B5 STX fs_data_count ; Store &FF in TX buffer
B8B8 LDA fcb_attr_or_count_mid,y ; Load station for FCB
B8BB STA fs_reply_cmd ; Store in TX buffer
B8BE LDA fcb_station_or_count_hi,y ; Load network for FCB
B8C1 STA fs_load_vector ; Store in TX buffer
B8C4 LDY #&0d ; Function code &0D
B8C6 LDX #5 ; X=5: copy 5 bytes to TX
B8C8 JSR save_net_tx_cb ; Send flush request to server
B8CB PLA ; Restore reply port
B8CC TAY ; Y = reply port
B8CD LDA osbput_saved_byte ; Load saved data byte
B8D0 JSR send_wipe_request ; Send data byte to server
B8D3 JSR restore_catalog_entry ; Restore TX buffer from saved context
B8D6 PLA ; Restore Y (FCB slot)
B8D7 TAY ; Y restored
B8D8 PLA ; Restore X
B8D9 TAX ; X restored
B8DA LDA fcb_count_lo,y ; Reload byte count after flush
B8DD .done_check_buf_offset←1← B896 BNE
CMP fcb_buf_offset,x ; Compare count with buffer offset
B8E0 BCC done_set_dirty_flag ; Below offset: skip offset update
B8E2 ADC #0 ; Add carry (count + 1)
B8E4 STA fcb_buf_offset,x ; Update buffer offset in FCB
B8E7 BNE done_set_dirty_flag ; Non-zero: keep offset flag
B8E9 LDA #&df ; Mask &DF: clear bit 5
B8EB AND fcb_status,x ; Clear offset flag
B8EE STA fcb_status,x ; Update FCB status
B8F1 .done_set_dirty_flag←2← B8E0 BCC← B8E7 BNE
LDA #1 ; Set bit 0 (dirty/active)
B8F3 ORA fcb_status,x ; Add to FCB flags
B8F6 STA fcb_status,x ; Update FCB status
B8F9 LDA fcb_count_lo,y ; Load byte count (= write position)
B8FC PHA ; Save count
B8FD TYA ; Get FCB slot index
B8FE TAX ; X = FCB slot
B8FF PLA ; Restore byte count
B900 TAY ; Y = buffer write offset
B901 LDA cur_fcb_index ; Load current FCB index
B904 CLC ; Prepare addition
B905 ADC #&11 ; Add &11 for buffer page offset
B907 STA fs_load_addr_3 ; Set pointer high byte
B909 LDA #0 ; A=0: pointer low byte
B90B STA fs_load_addr_2 ; Set pointer low byte
B90D PLA ; Restore data byte
B90E STA (fs_load_addr_2),y ; Write data byte to buffer
B910 .done_inc_byte_count←1← B888 JMP
JSR inc_fcb_byte_count ; Increment byte count for this FCB
B913 LDA #0 ; A=0: clear receive attribute
B915 LDY #&0e ; Offset &0E
B917 STA (net_rx_ptr),y ; Clear attribute in receive buffer
B919 PLA ; Restore caller's X
B91A TAX ; X restored
B91B PLA ; Discard saved data byte
B91C LDY cur_chan_attr ; Restore channel attribute
B91F 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
B920 .send_wipe_request←2← B885 JSR← B8D0 JSR
STY fs_handle_mask ; Store reply port
B923 STA fs_error_flags ; Store data byte
B926 TYA ; Save Y
B927 PHA ; Push Y to stack
B928 TXA ; Save X
B929 PHA ; Push X to stack
B92A LDA #&90 ; Function code &90
B92C STA fs_putb_buf ; Store in send buffer
B92F JSR init_txcb ; Initialise TX control block
B932 LDA #&dc ; TX start address low = &DC
B934 STA txcb_start ; Set TX start in control block
B936 LDA #&e0 ; TX end address low = &E0
B938 STA txcb_end ; Set TX end in control block
B93A LDA #9 ; Expected reply port = 9
B93C STA fs_getb_buf ; Store reply port in buffer
B93F LDX #&c0 ; TX control = &C0
B941 LDY #0 ; Y=0: no timeout
B943 LDA fs_handle_mask ; Load reply port for addressing
B946 JSR send_disconnect_reply ; Send packet to server
B949 LDA fs_getb_buf ; Load reply status
B94C BEQ done_toggle_station ; Zero: success
B94E STA fs_last_error ; Store error code
B951 LDX #0 ; X=0: copy index
B953 .loop_copy_wipe_err_msg←1← B95E BNE
LDA fs_putb_buf,x ; Load error message byte
B956 STA error_block,x ; Copy to error block
B959 CMP #&0d ; Is it CR (end of message)?
B95B BEQ done_terminate_wipe_err ; Yes: terminate string
B95D INX ; Next byte
B95E BNE loop_copy_wipe_err_msg ; Continue copying error message
B960 .done_terminate_wipe_err←1← B95B BEQ
LDA #0 ; NUL terminator
B962 STA error_block,x ; Terminate error string in block
B965 DEX ; Back up position for error check
B966 JMP check_net_error_code ; Process and raise network error
B969 .done_toggle_station←1← B94C BEQ
LDX cur_chan_attr ; Load channel attribute index
B96C LDA fcb_flags,x ; Load station number for channel
B96F EOR #1 ; Toggle bit 0 (alternate station)
B971 STA fcb_flags,x ; Update station number
B974 PLA ; Restore X
B975 TAX ; X restored
B976 PLA ; Restore Y
B977 TAY ; Y restored
B978 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
B979 .send_and_receive←2← B644 JSR← B6DD JSR
JSR set_options_ptr ; Set up FS options pointer
B97C JMP setup_transfer_workspace ; Set up transfer workspace and 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.

B97F .cmd_close
LDA #osfind_close ; A=0: close all open files
B981 TAY ; Y=&00
B982 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
B985 .cmd_type
CLV ; Clear V for unconditional BVC
B986 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
B988 .cmd_print
BIT bit_test_ff ; Set V flag (= print mode)
B98B .open_and_read_file←1← B986 BVC
JSR open_file_for_read ; Open file for reading
B98E LDY ws_page ; Y=file handle
B990 LDA #0 ; A = 0
B992 STA table_idx ; Clear previous-character tracker
B994 PHP ; Save V flag (print/type mode)
B995 .loop_read_print_byte←3← B9B5 JMP← B9D5 JMP← B9E8 BEQ
JSR osbget ; Read a single byte from an open file Y
B998 BCC done_print_escape ; Branch if not end of file
B99A PLP ; EOF: restore processor status
B99B JSR close_ws_file ; Close the file
B99E JMP osnewl ; Write newline (characters 10 and 13)
B9A1 .done_print_escape←1← B998 BCC
JSR abort_if_escape ; Check for escape key pressed
B9A4 PLP ; Restore V (print/type mode)
B9A5 PHP ; Re-save for next iteration
B9A6 BVS done_store_prev_char ; Print mode: skip CR/LF handling
B9A8 CMP #&0d ; Is it a carriage return?
B9AA BEQ done_handle_line_end ; Yes: handle line ending
B9AC CMP #&0a ; Is it a line feed?
B9AE BEQ done_handle_line_end ; Yes: handle line ending
B9B0 .done_store_prev_char←1← B9A6 BVS
STA table_idx ; Save as previous character
B9B2 .loop_write_char←1← B9C3 BNE
JSR oswrch ; Write character
B9B5 JMP loop_read_print_byte ; Loop for next byte
B9B8 .done_handle_line_end←2← B9AA BEQ← B9AE BEQ
PHA ; Save the CR or LF character
B9B9 LDX vdu_queue_count ; Check output destination flag
B9BC BEQ done_normalise_crlf ; Zero: normalise line endings
B9BE LDA #0 ; Non-zero: output raw
B9C0 STA table_idx ; Clear previous-character tracker
B9C2 PLA ; Retrieve CR/LF
B9C3 BNE loop_write_char ; Output it directly; ALWAYS branch
B9C5 .done_normalise_crlf←1← B9BC BEQ
LDA table_idx ; Get previous character
B9C7 CMP #&0d ; Was previous a CR?
B9C9 BEQ done_check_cr_lf ; Yes: check for CR+LF pair
B9CB CMP #&0a ; Was previous a LF?
B9CD BEQ done_check_lf_cr ; Yes: check for LF+CR pair
B9CF PLA ; Retrieve CR/LF from stack
B9D0 STA table_idx ; Save as previous character
B9D2 .done_write_newline←2← B9DD BNE← B9E2 BNE
JSR osnewl ; Write newline (characters 10 and 13)
B9D5 JMP loop_read_print_byte ; Loop for next byte
B9D8 .done_check_cr_lf←1← B9C9 BEQ
PLA ; Retrieve current character
B9D9 CMP #&0a ; Is it LF? (CR+LF pair)
B9DB BEQ done_consume_pair ; Yes: consume LF, no extra newline
B9DD BNE done_write_newline ; No: output extra newline ALWAYS branch
B9DF .done_check_lf_cr←1← B9CD BEQ
PLA ; Retrieve current character
B9E0 CMP #&0d ; Is it CR? (LF+CR pair)
B9E2 BNE done_write_newline ; No: output extra newline
B9E4 .done_consume_pair←1← B9DB BEQ
LDA #0 ; Pair consumed: A = 0
B9E6 STA table_idx ; Clear previous-character tracker
B9E8 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.

B9EA .abort_if_escape←2← B9A1 JSR← BA1E JSR
BIT escape_flag ; Test bit 7 of escape flag
B9EC BMI error_escape_pressed ; Escape pressed: handle abort
B9EE RTS ; No escape: return
B9EF .error_escape_pressed←1← B9EC BMI
JSR close_ws_file ; Close the open file
B9F2 JSR osnewl ; Write newline (characters 10 and 13)
B9F5 LDA #osbyte_acknowledge_escape ; Acknowledge escape condition
B9F7 JSR osbyte ; Clear escape condition and perform escape effects
B9FA LDA #&11 ; Error number &11
B9FC JSR error_inline ; Generate 'Escape' BRK error
B9FF 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
BA06 .cmd_dump
JSR open_file_for_read ; Open file for reading, set ws_page
BA09 LDX #&14 ; 21 bytes to push (0-&14)
BA0B LDA #0 ; Zero fill value
BA0D .loop_push_zero_buf←1← BA0F BPL
PHA ; Push zero onto stack
BA0E DEX ; Count down
BA0F BPL loop_push_zero_buf ; Loop until all 21 bytes pushed
BA11 TSX ; X = stack pointer (buffer base - 1)
BA12 JSR init_dump_buffer ; Set up buffer pointer and parse args
BA15 LDA (work_ae),y ; Load display address low byte
BA17 AND #&f0 ; Test high nibble
BA19 BEQ loop_dump_line ; Skip header if 16-byte aligned
BA1B JSR print_dump_header ; Print column header for offset start
BA1E .loop_dump_line←2← BA19 BEQ← BAC6 JMP
JSR abort_if_escape ; Check for Escape key
BA21 LDA #&ff ; Start byte counter at -1
BA23 STA osword_flag ; Reset counter
BA25 .loop_read_dump_byte←1← BA34 BNE
LDY ws_page ; Y=file handle
BA27 JSR osbget ; Read a single byte from an open file Y
BA2A BCS done_check_dump_eof ; C=1 from OSBGET: end of file
BA2C INC osword_flag ; Increment byte counter (0-15)
BA2E LDY osword_flag ; Use counter as buffer index
BA30 STA (work_ae),y ; Store byte in data buffer
BA32 CPY #&0f ; Read 16 bytes? (index 0-15)
BA34 BNE loop_read_dump_byte ; No: read next byte
BA36 CLC ; C=0: not EOF, full line read
BA37 .done_check_dump_eof←1← BA2A BCS
PHP ; Save C: EOF status
BA38 LDA osword_flag ; Check byte counter
BA3A BPL done_check_boundary ; Counter >= 0: have data to display
BA3C LDX #&15 ; 22 bytes to pop (21 buffer + PHP)
BA3E .loop_pop_stack_buf←2← BA40 BPL← BACB JMP
PLA ; Pop one byte from stack
BA3F DEX ; Count down
BA40 BPL loop_pop_stack_buf ; Loop until stack cleaned up
BA42 JMP close_ws_file ; Close file and return
BA45 .done_check_boundary←1← BA3A BPL
LDY #&10 ; Point to display address low byte
BA47 LDA (work_ae),y ; Load display address low byte
BA49 AND #&f0 ; Test high nibble
BA4B BNE done_start_dump_addr ; Non-zero: header already current
BA4D JSR print_dump_header ; Crossed 256-byte boundary: new header
BA50 .done_start_dump_addr←1← BA4B BNE
LDY #&13 ; Start from highest address byte
BA52 .loop_print_addr_byte←1← BA5C BNE
LDA (work_ae),y ; Load address byte
BA54 PHA ; Save for address increment later
BA55 JSR print_hex_byte ; Print as two hex digits
BA58 PLA ; Restore address byte
BA59 DEY ; Next byte down
BA5A CPY #&0f ; Printed all 4 address bytes?
BA5C BNE loop_print_addr_byte ; No: print next address byte
BA5E INY ; Y=&10: point to address byte 0
BA5F CLC ; Prepare for 16-byte add
BA60 ADC #&10 ; Add 16 to lowest address byte
BA62 PHP ; Save carry for propagation
BA63 .loop_inc_dump_addr←1← BA6E BNE
PLP ; Restore carry from previous byte
BA64 STA (work_ae),y ; Store updated address byte
BA66 INY ; Next address byte up
BA67 LDA (work_ae),y ; Load next address byte
BA69 ADC #0 ; Add carry
BA6B PHP ; Save carry for next byte
BA6C CPY #&14 ; Past all 4 address bytes?
BA6E BNE loop_inc_dump_addr ; No: continue propagation
BA70 PLP ; Discard final carry
BA71 JSR print_inline ; Print address/data separator
BA74 EQUS " : "
BA77 LDY #0 ; Start from first data byte
BA79 LDX osword_flag ; X = bytes read (counter for display)
BA7B .loop_print_dump_hex←1← BA8B BPL
LDA (work_ae),y ; Load data byte from buffer
BA7D JSR print_hex_byte ; Print as two hex digits
BA80 LDA #&20 ; Space separator
BA82 JSR osasci ; Print space between hex bytes Write character 32
BA85 .loop_next_dump_col←1← BA98 JMP
INY ; Next column
BA86 CPY #&10 ; All 16 columns done?
BA88 BEQ done_print_separator ; Yes: go to ASCII separator
BA8A DEX ; Decrement remaining data bytes
BA8B BPL loop_print_dump_hex ; More data: print next hex byte
BA8D TYA ; Save column position
BA8E PHA ; Preserve Y across print
BA8F JSR print_inline ; Print 3-space padding
BA92 EQUS " "
BA95 NOP ; Inline string terminator (NOP)
BA96 PLA ; Restore column position
BA97 TAY ; Back to Y
BA98 JMP loop_next_dump_col ; Check next column
BA9B .done_print_separator←1← BA88 BEQ
DEX ; Adjust X for advance_x_by_8
BA9C JSR print_inline ; Print hex/ASCII separator
BA9F EQUS ": "
BAA1 NOP ; Inline string terminator (NOP)
BAA2 JSR advance_x_by_8 ; X += 16: restore byte count for ASCII
BAA5 LDY #0 ; Start from first data byte
BAA7 .loop_print_dump_ascii←1← BABE BPL
LDA (work_ae),y ; Load data byte
BAA9 AND #&7f ; Strip high bit
BAAB CMP #&20 ; Printable? (>= space)
BAAD BCS done_test_del ; Yes: check for DEL
BAAF .skip_non_printable←1← BAB3 BEQ
LDA #&2e ; Non-printable: substitute '.'
BAB1 .done_test_del←1← BAAD BCS
CMP #&7f ; Is it DEL (&7F)?
BAB3 BEQ skip_non_printable ; Yes: substitute '.'
BAB5 JSR osasci ; Print ASCII character Write character
BAB8 INY ; Next column
BAB9 CPY #&10 ; All 16 columns done?
BABB BEQ done_end_dump_line ; Yes: end of line
BABD DEX ; Decrement remaining data bytes
BABE BPL loop_print_dump_ascii ; More data: print next ASCII char
BAC0 .done_end_dump_line←1← BABB BEQ
JSR osnewl ; Print newline Write newline (characters 10 and 13)
BAC3 PLP ; Restore EOF status from &BA37
BAC4 BCS done_dump_eof ; C=1: EOF reached, clean up
BAC6 JMP loop_dump_line ; Not EOF: continue with next line
BAC9 .done_dump_eof←1← BAC4 BCS
LDX #&14 ; 21 bytes to pop (buffer only, PHP done)
BACB 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.

BACE .print_dump_header←2← BA1B JSR← BA4D JSR
LDA (work_ae),y ; Load display address low byte
BAD0 PHA ; Save as starting column number
BAD1 JSR print_inline ; Print header label with leading CR
BAD4 EQUS ".Address : "
BAE0 NOP ; Inline string terminator (NOP)
BAE1 PLA ; Restore starting column number
BAE2 LDX #&0f ; 16 column headers to print
BAE4 .loop_print_col_num←1← BAF4 BPL
PHA ; Save current column number
BAE5 JSR print_hex_byte ; Print as two hex digits
BAE8 LDA #&20 ; Space separator
BAEA JSR osasci ; Print space after column number Write character 32
BAED PLA ; Restore column number
BAEE SEC ; SEC for +1 via ADC
BAEF ADC #0 ; Increment column number (SEC+ADC 0=+1)
BAF1 AND #&0f ; Wrap to low nibble (0-F)
BAF3 DEX ; Count down
BAF4 BPL loop_print_col_num ; Loop for all 16 columns
BAF6 JSR print_inline ; Print trailer with ASCII label
BAF9 EQUS ": ASCII data.."
BB0A NOP ; Inline string terminator (NOP)
BB0B RTS ; Return

Close file handle stored in workspace

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

BB0C .close_ws_file←6← B99B JSR← B9EF JSR← BA42 JMP← BBAF JSR← BBE9 JSR← BC56 JSR
LDY ws_page ; Y = file handle from ws_page
BB0E LDA #osfind_close ; A=0: close file
BB10 JMP osfind ; Close file and return 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.

BB13 .open_file_for_read←2← B98B JSR← BA06 JSR
PHP ; Save processor flags
BB14 TYA ; A = filename offset
BB15 CLC ; Add to command text pointer
BB16 ADC os_text_ptr ; Low byte of filename address
BB18 PHA ; Save on stack for later restore
BB19 TAX ; X = filename address low
BB1A LDA #0 ; Carry into high byte
BB1C ADC os_text_ptr_hi ; High byte of filename address
BB1E PHA ; Save on stack for later restore
BB1F TAY ; Y = filename address high
BB20 LDA #osfind_open_input ; Open for input
BB22 JSR osfind ; OSFIND: open file Open file for input (A=64)
BB25 TAY ; A=file handle (or zero on failure)
BB26 STA ws_page ; Store file handle
BB28 BNE done_restore_text_ptr ; Non-zero: file opened OK
BB2A LDA #&d6 ; Error number &D6
BB2C JSR error_inline ; Generate 'Not found' error
BB2F EQUS "Not found."
BB39 .done_restore_text_ptr←1← BB28 BNE
PLA ; Restore saved text pointer high
BB3A STA os_text_ptr_hi ; Restore os_text_ptr high byte
BB3C PLA ; Restore saved text pointer low
BB3D STA os_text_ptr ; Restore os_text_ptr low byte
BB3F LDY #0 ; Start scanning from offset 0
BB41 .loop_skip_filename←1← BB4A BNE
INY ; Advance past current char
BB42 LDA (os_text_ptr),y ; Load next char from command line
BB44 CMP #&0d ; CR: end of command line
BB46 BEQ return_with_fn_offset ; Yes: done scanning
BB48 CMP #&20 ; Space: end of filename
BB4A BNE loop_skip_filename ; No: keep scanning filename
BB4C .loop_skip_fn_spaces←1← BB51 BEQ
INY ; Advance past space
BB4D LDA (os_text_ptr),y ; Load next char
BB4F CMP #&20 ; Still a space?
BB51 BEQ loop_skip_fn_spaces ; Yes: skip it
BB53 .return_with_fn_offset←1← BB46 BEQ
PLP ; Restore processor flags
BB54 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.

BB55 .parse_dump_range←2← BBC5 JSR← BC51 JSR
TYA ; Save command line offset to X
BB56 TAX ; X tracks current position
BB57 LDA #0 ; Zero for clearing accumulator
BB59 TAY ; Y=0 for buffer indexing Y=&00
BB5A .loop_clear_hex_accum←1← BB5F BNE
STA (work_ae),y ; Clear accumulator byte
BB5C INY ; Next byte
BB5D CPY #4 ; All 4 bytes cleared?
BB5F BNE loop_clear_hex_accum ; No: clear next
BB61 .loop_parse_hex_digit←1← BBA8 JMP
TXA ; Restore pre-increment offset to A
BB62 INX ; Advance X to next char position
BB63 TAY ; Y = pre-increment offset for indexing
BB64 LDA (os_text_ptr),y ; Load character from command line
BB66 CMP #&0d ; CR: end of input
BB68 BEQ done_test_hex_space ; Done: skip trailing spaces
BB6A CMP #&20 ; Space: end of this parameter
BB6C BEQ done_test_hex_space ; Done: skip trailing spaces
BB6E CMP #&30 ; Below '0'?
BB70 BCC error_bad_hex_value ; Yes: not a hex digit, error
BB72 CMP #&3a ; Below ':'? (i.e. '0'-'9')
BB74 BCC done_mask_hex_digit ; Yes: is a decimal digit
BB76 AND #&5f ; Force uppercase for A-F
BB78 ADC #&b8 ; Map 'A'-'F' → &FA-&FF (C=0 here)
BB7A BCS error_bad_hex_value ; Carry set: char > 'F', error
BB7C CMP #&fa ; Below &FA? (i.e. was < 'A')
BB7E BCC error_bad_hex_value ; Yes: gap between '9' and 'A', error
BB80 .done_mask_hex_digit←1← BB74 BCC
AND #&0f ; Mask to low nibble (0-15)
BB82 PHA ; Save hex digit value
BB83 TXA ; Save current offset
BB84 PHA ; Preserve on stack
BB85 LDX #4 ; 4 bits to shift in
BB87 .loop_shift_nibble←1← BB9D BNE
LDY #0 ; Start from byte 0 (LSB)
BB89 TYA ; Clear A; C from PHA/PLP below A=&00
BB8A .loop_rotate_hex_accum←1← BB96 BNE
PHA ; Transfer carry bit to flags via stack
BB8B PLP ; PLP: C = bit shifted out of prev iter
BB8C LDA (work_ae),y ; Load accumulator byte
BB8E ROL ; Rotate left through carry
BB8F STA (work_ae),y ; Store shifted byte
BB91 PHP ; Save carry for next byte
BB92 PLA ; Transfer to A for PHA/PLP trick
BB93 INY ; Next accumulator byte
BB94 CPY #4 ; All 4 bytes rotated?
BB96 BNE loop_rotate_hex_accum ; No: rotate next byte
BB98 PHA ; Transfer carry to flags
BB99 PLP ; C = overflow bit
BB9A BCS error_hex_overflow ; Overflow: address too large
BB9C DEX ; Count bits shifted
BB9D BNE loop_shift_nibble ; 4 bits shifted? No: shift again
BB9F PLA ; Restore command line offset
BBA0 TAX ; Back to X
BBA1 PLA ; Restore hex digit value
BBA2 LDY #0 ; Point to LSB of accumulator
BBA4 ORA (work_ae),y ; OR digit into low nibble
BBA6 STA (work_ae),y ; Store updated LSB
BBA8 JMP loop_parse_hex_digit ; Parse next character
BBAB .error_hex_overflow←1← BB9A BCS
PLA ; Discard saved offset
BBAC PLA ; Discard saved digit
BBAD SEC ; C=1: overflow
BBAE RTS ; Return with C=1
BBAF .error_bad_hex_value←3← BB70 BCC← BB7A BCS← BB7E BCC
JSR close_ws_file ; Close open file before error
BBB2 JMP err_bad_hex ; Generate 'Bad hex' error
BBB5 .loop_skip_hex_spaces←1← BBBA BEQ
INY ; Advance past space
BBB6 .done_test_hex_space←2← BB68 BEQ← BB6C BEQ
LDA (os_text_ptr),y ; Load next char
BBB8 CMP #&20 ; Space?
BBBA BEQ loop_skip_hex_spaces ; Yes: skip it
BBBC CLC ; C=0: valid parse (no overflow)
BBBD 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.

BBBE .init_dump_buffer←1← BA12 JSR
INX ; X+1: first byte of buffer
BBBF STX work_ae ; Set buffer pointer low byte
BBC1 LDX #1 ; Buffer is on stack in page 1
BBC3 STX addr_work ; Set buffer pointer high byte
BBC5 JSR parse_dump_range ; Parse start offset from command line
BBC8 BCS error_outside_file ; Overflow: 'Outside file' error
BBCA TYA ; A = command line offset after parse
BBCB PHA ; Save for later (past start addr)
BBCC LDY ws_page ; Y=file handle
BBCE LDX #&aa ; X=zero page address for result
BBD0 LDA #2 ; A=2: read file extent (length)
BBD2 JSR osargs ; Get length of file into zero page address X (A=2)
BBD5 LDY #3 ; Check from MSB down
BBD7 .loop_cmp_file_length←1← BBDF BPL
LDA osword_flag,y ; Load file length byte
BBDA CMP (work_ae),y ; Compare with start offset byte
BBDC BNE done_check_outside ; Mismatch: check which is larger
BBDE DEY ; Next byte down
BBDF BPL loop_cmp_file_length ; More bytes to compare
BBE1 BMI done_advance_start ; All equal: start = length, within file ALWAYS branch
BBE3 .done_check_outside←1← BBDC BNE
BCC error_outside_file ; Length < start: outside file
BBE5 LDY #&ff ; Y=&FF: length > start, flag for later
BBE7 BNE done_advance_start ; Continue to copy start address ALWAYS branch
BBE9 .error_outside_file←2← BBC8 BCS← BBE3 BCC
JSR close_ws_file ; Close file before error
BBEC LDA #&b7 ; Error number &B7
BBEE JSR error_inline ; Generate 'Outside file' error
BBF1 EQUS "Outside file."
BBFE .loop_copy_start_addr←1← BC06 BNE
LDA (work_ae),y ; Load start address byte from buffer
BC00 STA osword_flag,y ; Store to osword_flag (&AA-&AD)
BC03 .done_advance_start←2← BBE1 BMI← BBE7 BNE
INY ; Next byte
BC04 CPY #4 ; All 4 bytes copied?
BC06 BNE loop_copy_start_addr ; No: copy next byte
BC08 LDX #&aa ; X=zero page address to write from
BC0A LDY ws_page ; Y=file handle
BC0C LDA #1 ; A=1: write file pointer
BC0E JSR osargs ; OSARGS: set file pointer Write sequential file pointer from zero page address X (A=1)
BC11 PLA ; Restore saved command line offset
BC12 TAY ; Back to Y for command line indexing
BC13 LDA (os_text_ptr),y ; Load next char from command line
BC15 CMP #&0d ; End of command? (CR)
BC17 BNE done_parse_disp_base ; No: parse display base address
BC19 LDY #1 ; Copy 2 bytes: os_text_ptr to buffer
BC1B .loop_copy_osfile_ptr←1← BC21 BPL
LDA os_text_ptr,y ; Load os_text_ptr byte
BC1E STA (work_ae),y ; Store as filename pointer in OSFILE CB
BC20 DEY ; Next byte
BC21 BPL loop_copy_osfile_ptr ; Copy both low and high bytes
BC23 LDA #osfile_read_catalogue_info ; Read catalogue information
BC25 LDX work_ae ; X = control block low
BC27 LDY addr_work ; Y = control block high
BC29 JSR osfile ; OSFILE: read file info Read catalogue information (A=5)
BC2C LDY #2 ; Start at OSFILE +2 (load addr byte 0)
BC2E .loop_shift_osfile_data←1← BC39 BNE
LDA (work_ae),y ; Load from OSFILE result offset
BC30 DEY ; Y-2: destination is 2 bytes earlier
BC31 DEY ; Continue decrement
BC32 STA (work_ae),y ; Store to buf[Y-2]
BC34 INY ; Y += 3: advance source index
BC35 INY ; (continued)
BC36 INY ; (continued)
BC37 CPY #6 ; Copied all 4 load address bytes?
BC39 BNE loop_shift_osfile_data ; No: copy next byte
BC3B DEY ; Y=6 after loop exit
BC3C DEY ; Y=4: check from buf[4] downward
BC3D .loop_check_ff_addr←1← BC44 BPL
LDA (work_ae),y ; Load address byte
BC3F CMP #&ff ; Is it &FF?
BC41 BNE done_add_disp_base ; No: valid load address, use it
BC43 DEY ; Check next byte down
BC44 BPL loop_check_ff_addr ; More bytes to check
BC46 LDY #3 ; Clear all 4 bytes
BC48 LDA #0 ; Zero value
BC4A .loop_zero_load_addr←1← BC4D BPL
STA (work_ae),y ; Clear byte
BC4C DEY ; Next byte down
BC4D BPL loop_zero_load_addr ; Loop for all 4 bytes
BC4F BMI done_add_disp_base ; Continue to compute display address ALWAYS branch
BC51 .done_parse_disp_base←1← BC17 BNE
JSR parse_dump_range ; Parse second hex parameter
BC54 BCC done_add_disp_base ; Valid: use as display base
BC56 JSR close_ws_file ; Invalid: close file before error
BC59 LDA #&fc ; Error number &FC
BC5B JSR error_bad_inline ; Generate 'Bad address' error
BC5E EQUS "address."
BC66 .done_add_disp_base←3← BC41 BNE← BC4F BMI← BC54 BCC
LDY #0 ; Start from LSB
BC68 LDX #4 ; 4 bytes to add
BC6A CLC ; Clear carry for addition
BC6B .loop_add_disp_bytes←1← BC75 BNE
LDA (work_ae),y ; Load display base byte
BC6D ADC osword_flag,y ; Add start offset byte
BC70 STA osword_flag,y ; Store result in osword_flag
BC73 INY ; Next byte
BC74 DEX ; Count down
BC75 BNE loop_add_disp_bytes ; Loop for all 4 bytes
BC77 LDY #&14 ; Point past end of address area
BC79 LDX #3 ; Start from MSB (byte 3)
BC7B .loop_store_disp_addr←1← BC81 BPL
DEY ; Pre-decrement Y
BC7C LDA osword_flag,x ; Load computed display address byte
BC7E STA (work_ae),y ; Store to buf[&10-&13]
BC80 DEX ; Next byte down
BC81 BPL loop_store_disp_addr ; Loop for all 4 bytes
BC83 RTS ; Return; Y=&10 (address low byte)

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.

BC84 .advance_x_by_8←3← 9BDE JSR← A8BB JSR← BAA2 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.

BC87 .advance_x_by_4←1← BC84 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
BC8A .inx4←1← BC87 JSR
INX ; X += 4
BC8B INX ; (continued)
BC8C INX ; (continued)
BC8D INX ; (continued)
BC8E RTS ; Return
BC8F EQUB &FF ; Padding; next byte is reloc_p5_src
BC90 0500 .tube_r2_dispatch_table←2← 0050 JMP← BEA3 STA
EQUW tube_osrdch ; R2 cmd 0: OSRDCH
BC92 0502 EQUW tube_oscli ; R2 cmd 1: OSCLI
BC94 0504 EQUW tube_osbyte_2param ; R2 cmd 2: OSBYTE (2-param)
BC96 0506 EQUW tube_osbyte_long ; R2 cmd 3: OSBYTE (3-param)
BC98 0508 EQUW tube_osword ; R2 cmd 4: OSWORD
BC9A 050A EQUW tube_osword_rdln ; R2 cmd 5: OSWORD 0 (read line)
BC9C 050C EQUW tube_osargs ; R2 cmd 6: OSARGS
BC9E 050E EQUW tube_osbget ; R2 cmd 7: OSBGET
BCA0 0510 EQUW tube_osbput ; R2 cmd 8: OSBPUT
BCA2 0512 EQUW tube_osfind ; R2 cmd 9: OSFIND
BCA4 0514 EQUW tube_osfile ; R2 cmd 10: OSFILE
BCA6 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.
BCA8 0518 .tube_ctrl_values←1← 0453 LDA
EQUB &86 ; Type 0: set I+J (1-byte R3, parasite to host)
BCA9 0519 EQUB &88 ; Type 1: set M (1-byte R3, host to parasite)
BCAA 051A EQUB &96 ; Type 2: set V+I+J (2-byte R3, parasite to host)
BCAB 051B EQUB &98 ; Type 3: set V+M (2-byte R3, host to parasite)
BCAC 051C EQUB &18 ; Type 4: clear V+M (execute code at address)
BCAD 051D EQUB &18 ; Type 5: clear V+M (release address claim)
BCAE 051E EQUB &82 ; Type 6: set I (define event handler)
BCAF 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.
BCB0 0520 .tube_osbput
JSR tube_read_r2 ; Read channel handle from R2 for BPUT
BCB3 0523 TAY ; Y=channel handle for OSBPUT
BCB4 0524 JSR tube_read_r2 ; Read data byte from R2 for OSBPUT
BCB7 0527 .tube_poll_r1_wrch
JSR osbput ; Write a single byte A to an open file Y
BCBA 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.
BCBD 052D .tube_osbget
JSR tube_read_r2 ; Read channel handle from R2 for BGET
BCC0 0530 TAY ; Y=file handle
BCC1 0531 JSR osbget ; Read a single byte from an open file Y
BCC4 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.
BCC7 0537 .tube_osrdch
JSR osrdch ; Read a character from the current input stream
BCCA 053A .tube_rdch_reply←2← 0534 JMP← 05EF JMP
ROR ; ROR A: encode carry (error flag) into bit 7
BCCB 053B JSR tube_send_r2 ; Send carry+data byte to Tube R2
BCCE 053E ROL ; ROL A: restore carry flag
BCCF 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.
BCD2 0542 .tube_osfind
JSR tube_read_r2 ; Read open mode from R2 for OSFIND
BCD5 0545 BEQ tube_osfind_close ; Mode=0: close file(s)
BCD7 0547 PHA ; Save open mode on stack
BCD8 0548 JSR tube_read_string ; Read filename string from R2
BCDB 054B PLA ; Restore open mode
BCDC 054C JSR osfind ; Open or close file(s)
BCDF 054F JMP tube_reply_byte ; Reply with file handle via R2
BCE2 0552 .tube_osfind_close←1← 0545 BEQ
JSR tube_read_r2 ; OSFIND close: read handle from R2
BCE5 0555 TAY ; Transfer handle to Y
BCE6 0556 LDA #osfind_close ; A=0: close file
BCE8 0558 JSR osfind ; Close one or all files
BCEB 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.
BCEE 055E .tube_osargs
JSR tube_read_r2 ; Read file handle from R2 for OSARGS
BCF1 0561 TAY ; Y=file handle for OSARGS
BCF2 0562 .tube_read_params
LDX #4 ; Read 4-byte arg + reason from R2 into ZP
BCF4 0564 .read_osargs_params←1← 056A BNE
JSR tube_read_r2 ; Read next param byte from R2
BCF7 0567 STA escape_flag,x ; Store param at ZP+X (escape_flag downward)
BCF9 0569 DEX ; Decrement index
BCFA 056A BNE read_osargs_params ; More params: continue reading
BCFC 056C JSR tube_read_r2 ; Read OSARGS reason code from R2
BCFF 056F JSR osargs ; Read or write a file's attributes
BD02 0572 JSR tube_send_r2 ; Send result A via R2
BD05 0575 LDX #3 ; X=3: send 4 result bytes
BD07 0577 .send_osargs_result←1← 057D BPL
LDA zp_ptr_lo,x ; Load result byte from zero page
BD09 0579 JSR tube_send_r2 ; Send result byte via R2
BD0C 057C DEX ; Decrement byte counter
BD0D 057D BPL send_osargs_result ; More bytes: continue sending
BD0F 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)
BD12 0582 .tube_read_string←3← 0548 JSR← 0596 JSR← 05B3 JSR
LDX #0 ; X=0: initialise string buffer index
BD14 0584 LDY #0 ; Y=0: initialise string offset
BD16 0586 .strnh←1← 0591 BNE
JSR tube_read_r2 ; Read next string byte from R2
BD19 0589 STA string_buf,y ; Store in string buffer at &0700+Y
BD1C 058C INY ; Advance string index
BD1D 058D BEQ string_buf_done ; Buffer full (256 bytes): done
BD1F 058F CMP #&0d ; Check for CR terminator
BD21 0591 BNE strnh ; Not CR: continue reading
BD23 0593 .string_buf_done←1← 058D BEQ
LDY #7 ; Y=7: set XY=&0700 for OSCLI/OSFIND
BD25 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.
BD26 0596 .tube_oscli
JSR tube_read_string ; Read command string from R2 into &0700
BD29 0599 JSR oscli ; Execute command string via OSCLI
BD2C 059C .tube_reply_ack←3← 0489 JMP← 052A JMP← 055B JMP
LDA #&7f ; &7F = success acknowledgement
BD2E 059E .tube_reply_byte←4← 053F JMP← 054F JMP← 05A1 BVC← 067D JMP
BIT tube_status_register_2 ; Poll R2 status until ready
BD31 05A1 BVC tube_reply_byte ; Bit 6 clear: not ready, loop
BD33 05A3 STA tube_data_register_2 ; Write byte to R2 data register
BD36 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.
BD39 05A9 .tube_osfile
LDX #&10 ; Read 16-byte OSFILE control block from R2
BD3B 05AB .argsw←1← 05B1 BNE
JSR tube_read_r2 ; Read next control block byte from R2
BD3E 05AE STA zp_ptr_hi,x ; Store at ZP+X (control block)
BD40 05B0 DEX ; Decrement index
BD41 05B1 BNE argsw ; More bytes: continue reading
BD43 05B3 JSR tube_read_string ; Read filename string from R2
BD46 05B6 STX zp_ptr_lo ; Set filename ptr low = 0
BD48 05B8 STY zp_ptr_hi ; Set filename ptr high = &07
BD4A 05BA LDY #0 ; Y=0: OSFILE reason code index
BD4C 05BC JSR tube_read_r2 ; Read OSFILE reason code from R2
BD4F 05BF JSR osfile ; Execute OSFILE
BD52 05C2 JSR tube_send_r2 ; Send result A via R2
BD55 05C5 LDX #&10 ; X=&10: send 16 result bytes
BD57 05C7 .send_osfile_ctrl_blk←1← 05CD BNE
LDA zp_ptr_hi,x ; Load control block byte
BD59 05C9 JSR tube_send_r2 ; Send control block byte via R2
BD5C 05CC DEX ; Decrement byte counter
BD5D 05CD BNE send_osfile_ctrl_blk ; More bytes: continue sending
BD5F 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.
BD61 05D1 .tube_osgbpb
LDX #&0d ; X=&0D: read 13-byte OSGBPB ctrl block
BD63 05D3 .read_osgbpb_ctrl_blk←1← 05D9 BNE
JSR tube_read_r2 ; Read next control block byte from R2
BD66 05D6 STA escape_flag,x ; Store at ZP+X (escape_flag downward)
BD68 05D8 DEX ; Decrement index
BD69 05D9 BNE read_osgbpb_ctrl_blk ; More bytes: continue reading
BD6B 05DB JSR tube_read_r2 ; Read OSGBPB reason code from R2
BD6E 05DE LDY #0 ; Y=0: OSGBPB direction/count
BD70 05E0 JSR osgbpb ; Read or write multiple bytes to an open file
BD73 05E3 PHA ; Save result A on stack
BD74 05E4 LDX #&0c ; X=&0C: send 12 result bytes
BD76 05E6 .send_osgbpb_result←1← 05EC BPL
LDA zp_ptr_lo,x ; Load result byte from zero page
BD78 05E8 JSR tube_send_r2 ; Send result byte via R2
BD7B 05EB DEX ; Decrement byte counter
BD7C 05EC BPL send_osgbpb_result ; More bytes: continue sending
BD7E 05EE PLA ; Recover completion status from stack
BD7F 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.
BD82 05F2 .tube_osbyte_2param
JSR tube_read_r2 ; Read X parameter from R2
BD85 05F5 TAX ; Transfer to X register
BD86 05F6 JSR tube_read_r2 ; Read A (OSBYTE function code) from R2
BD89 05F9 JSR osbyte ; Execute OSBYTE A,X
BD8C 05FC .tube_poll_r2_result←2← 05FF ref← 0625 BVS
BIT tube_status_register_2 ; Poll R2 status for result send
BD8F 05FF EQUB &50 ; BVC: page 5/6 boundary straddle
BD90 0600 .tube_osbyte_reply_block←1← BEA9 STA
EQUB &FB ; Send carry+status to co-processor via R2
BD91 0601 STX tube_data_register_2 ; Send X result for 2-param OSBYTE
BD94 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.
BD97 0607 .tube_osbyte_long
JSR tube_read_r2 ; Read X, Y, A from R2 for 3-param OSBYTE
BD9A 060A TAX ; Save in X
BD9B 060B JSR tube_read_r2 ; Read Y parameter from co-processor
BD9E 060E TAY ; Save in Y
BD9F 060F JSR tube_read_r2 ; Read A (OSBYTE function code)
BDA2 0612 JSR osbyte ; Execute OSBYTE A,X,Y
BDA5 0615 EOR #&9d ; Test for OSBYTE &9D (fast Tube BPUT)
BDA7 0617 BEQ bytex ; OSBYTE &9D (fast Tube BPUT): no result needed
BDA9 0619 ROR ; Encode carry (error flag) into bit 7
BDAA 061A JSR tube_send_r2 ; Send carry+status byte via R2
BDAD 061D .tube_osbyte_send_y←1← 0620 BVC
BIT tube_status_register_2 ; Poll R2 status for ready
BDB0 0620 BVC tube_osbyte_send_y ; Not ready: keep polling
BDB2 0622 STY tube_data_register_2 ; Send Y result, then fall through to send X
BDB5 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.
BDB7 0627 .tube_osword
JSR tube_read_r2 ; Overlapping entry: &20 = JSR c06c5 (OSWORD)
BDBA 062A TAY ; Save OSWORD number in Y
BDBB 062B .tube_osword_read←1← 062E BPL
BIT tube_status_register_2 ; Poll R2 status for data ready
BDBE 062E BPL tube_osword_read ; Not ready: keep polling
BDC0 0630 .tube_osbyte_send_x
LDX tube_data_register_2 ; Read param block length from R2
BDC3 0633 DEX ; DEX: length 0 means no params to read
BDC4 0634 BMI skip_param_read ; No params (length=0): skip read loop
BDC6 0636 .tube_osword_read_lp←2← 0639 BPL← 0642 BPL
BIT tube_status_register_2 ; Poll R2 status for data ready
BDC9 0639 BPL tube_osword_read_lp ; Not ready: keep polling
BDCB 063B LDA tube_data_register_2 ; Read param byte from R2
BDCE 063E STA tube_osword_pb,x ; Store param bytes into block at &0128
BDD1 0641 DEX ; Next param byte (descending)
BDD2 0642 BPL tube_osword_read_lp ; Loop until all params read
BDD4 0644 TYA ; Restore OSWORD number from Y
BDD5 0645 .skip_param_read←1← 0634 BMI
LDX #<(tube_osword_pb) ; XY=&0128: param block address for OSWORD
BDD7 0647 LDY #>(tube_osword_pb) ; Y=&01: param block at &0128
BDD9 0649 JSR osword ; Execute OSWORD with XY=&0128
BDDC 064C .poll_r2_osword_result←1← 064F BPL
BIT tube_status_register_2 ; Poll R2 status for ready
BDDF 064F BPL poll_r2_osword_result ; Not ready: keep polling
BDE1 0651 LDX tube_data_register_2 ; Read result block length from R2
BDE4 0654 DEX ; Decrement result byte counter
BDE5 0655 BMI tube_return_main ; No results to send: return to main loop
BDE7 0657 .tube_osword_write←1← 0663 BPL
LDY tube_osword_pb,x ; Send result block bytes from &0128 via R2
BDEA 065A .tube_osword_write_lp←1← 065D BVC
BIT tube_status_register_2 ; Poll R2 status for ready
BDED 065D BVC tube_osword_write_lp ; Not ready: keep polling
BDEF 065F STY tube_data_register_2 ; Send result byte via R2
BDF2 0662 DEX ; Next result byte (descending)
BDF3 0663 BPL tube_osword_write ; Loop until all results sent
BDF5 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).
BDF8 0668 .tube_osword_rdln
LDX #4 ; Read 5-byte OSWORD 0 control block from R2
BDFA 066A .read_rdln_ctrl_block←1← 0670 BPL
JSR tube_read_r2 ; Read control block byte from R2
BDFD 066D STA zp_ptr_lo,x ; Store in zero page params
BDFF 066F DEX ; Next byte (descending)
BE00 0670 BPL read_rdln_ctrl_block ; Loop until all 5 bytes read
BE02 0672 INX ; X=0 after loop, A=0 for OSWORD 0 (read line)
BE03 0673 LDY #0 ; Y=0 for OSWORD 0
BE05 0675 TXA ; A=0: OSWORD 0 (read line)
BE06 0676 JSR osword ; Read input line from keyboard
BE09 0679 BCC tube_rdln_send_line ; C=0: line read OK; C=1: escape pressed
BE0B 067B LDA #&ff ; &FF = escape/error signal to co-processor
BE0D 067D JMP tube_reply_byte ; Escape: send &FF error to co-processor
BE10 0680 .tube_rdln_send_line←1← 0679 BCC
LDX #0 ; X=0: start of input buffer at &0700
BE12 0682 LDA #&7f ; &7F = line read successfully
BE14 0684 JSR tube_send_r2 ; Send &7F (success) to co-processor
BE17 0687 .tube_rdln_send_loop←1← 0690 BNE
LDA string_buf,x ; Load char from input buffer
BE1A 068A .tube_rdln_send_byte
JSR tube_send_r2 ; Send char to co-processor
BE1D 068D INX ; Next character
BE1E 068E CMP #&0d ; Check for CR terminator
BE20 0690 BNE tube_rdln_send_loop ; Loop until CR terminator sent
BE22 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)
BE25 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)
BE28 0698 BVC tube_send_r2 ; Not ready: keep polling
BE2A 069A STA tube_data_register_2 ; Write A to Tube R2 data register
BE2D 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)
BE2E 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)
BE31 06A1 BVC tube_send_r4 ; Not ready: keep polling
BE33 06A3 STA tube_data_register_4 ; Write A to Tube R4 data register
BE36 06A6 RTS ; Return to caller
BE37 06A7 .tube_escape_check←1← 0403 JMP
LDA escape_flag ; Check OS escape flag at &FF
BE39 06A9 SEC ; SEC+ROR: put bit 7 of &FF into carry+bit 7
BE3A 06AA ROR ; ROR: shift escape bit 7 to carry
BE3B 06AB BMI tube_send_r1 ; Escape set: forward to co-processor via R1
BE3D 06AD .tube_event_handler
PHA ; EVNTV: forward event A, Y, X to co-processor
BE3E 06AE LDA #0 ; Send &00 prefix (event notification)
BE40 06B0 JSR tube_send_r1 ; Send zero prefix via R1
BE43 06B3 TYA ; Y value for event
BE44 06B4 JSR tube_send_r1 ; Send Y via R1
BE47 06B7 TXA ; X value for event
BE48 06B8 JSR tube_send_r1 ; Send X via R1
BE4B 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)
BE4C 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)
BE4F 06BF BVC tube_send_r1 ; Not ready: keep polling
BE51 06C1 STA tube_data_register_1 ; Write A to Tube R1 data register
BE54 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
BE55 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)
BE58 06C8 BPL tube_read_r2 ; Not ready: keep polling
BE5A 06CA LDA tube_data_register_2 ; Read data byte from R2
BE5D 06CD RTS ; Return with byte in A
BE5E 06CE CMP #&fe ; Is byte &FE (VDU stream start)?
BE60 06D0 BCC tube_vdu_normal_byte ; Below &FE: normal byte
BE62 06D2 BNE setup_tube_vectors ; &FF: set up event/break vectors
BE64 06D4 CPY #0 ; &FE: check Y parameter
BE66 06D6 BEQ tube_vdu_normal_byte ; Y=0: treat as normal byte
BE68 06D8 LDX #6 ; X=6: six extra pages
BE6A 06DA LDA #osbyte_explode_chars ; OSBYTE &14: explode char defs
BE6C 06DC JSR osbyte ; Explode character definition RAM (six extra pages), can redefine all characters 32-255 (X=6)
BE6F 06DF .loop_poll_r1_vdu←1← 06E2 BPL
BIT tube_status_1_and_tube_control ; Poll R1 status (bit 6 = ready)
BE72 06E2 BPL loop_poll_r1_vdu ; Not ready: keep polling
BE74 06E4 LDA tube_data_register_1 ; Read byte from Tube R1
BE77 06E7 BEQ tube_vdu_stream_end ; Zero: end of VDU stream
BE79 06E9 JSR oswrch ; Write character
BE7C 06EC .svc_11_nmi_claim
JMP loop_poll_r1_vdu_rom ; Loop back to read next R1 byte
BE7F 06EF .setup_tube_vectors←1← 06D2 BNE
LDA #&ad ; EVNTV low byte (&AD)
BE81 06F1 STA evntv ; Store in EVNTV vector low
BE84 06F4 LDA #6 ; EVNTV high byte (page 6)
BE86 06F6 STA evntv+1 ; Store in EVNTV vector high
BE89 06F9 LDA #&16 ; BRKV low byte (&16)
BE8B 06FB STA brkv ; Store in BRKV vector
BE8E 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.
BE90 STA brkv+1 ; Store BRK vector high byte
BE93 LDA #&8e ; A=&8E: Tube control register value
BE95 STA tube_status_1_and_tube_control ; Write Tube control register
BE98 LDY #0 ; Y=0: copy 256 bytes per page
BE9A .loop_copy_reloc_pages←1← BEAD BNE
LDA reloc_p4_src,y ; Load page 4 source byte
BE9D STA tube_page4_vectors,y ; Store to page 4 destination
BEA0 LDA reloc_p5_src,y ; Load page 5 source byte
BEA3 STA tube_r2_dispatch_table,y ; Store to page 5 destination
BEA6 LDA reloc_p6_src,y ; Load page 6 source byte
BEA9 STA tube_osbyte_reply_block,y ; Store to page 6 destination
BEAC DEY ; Decrement byte counter
BEAD BNE loop_copy_reloc_pages ; Non-zero: continue copying
BEAF JSR clear_tube_claim ; Clear tube claim state
BEB2 LDX #&41 ; X=&41: copy 66 bytes of ZP workspace
BEB4 .loop_copy_zp_workspace←1← BEBA BPL
LDA reloc_zp_src,x ; Load ZP source byte from ROM
BEB7 STA nmi_workspace_start,x ; Store to NMI workspace at &16+X
BEB9 DEX ; Decrement byte counter
BEBA BPL loop_copy_zp_workspace ; More bytes: continue copying
BEBC LDA #0 ; A=0: return success
BEBE RTS ; Return to caller
BEBF 0016 .nmi_workspace_start←1← BEB7 STA
LDA #&ff ; A=&FF: signal error to co-processor via R4
BEC1 0018 JSR tube_send_r4 ; Send &FF error signal to Tube R4
BEC4 001B LDA tube_data_register_2 ; Flush any pending R2 byte
BEC7 001E LDA #0 ; A=0: send zero prefix to R2
BEC9 0020 .tube_send_zero_r2
JSR tube_send_r2 ; Send zero prefix byte via R2
BECC 0023 TAY ; Y=0: start of error block at (&FD)
BECD 0024 LDA (brk_ptr),y ; Load error number from (&FD),0
BECF 0026 .tube_send_error_num
JSR tube_send_r2 ; Send error number via R2
BED2 0029 .tube_brk_send_loop←1← 0030 BNE
INY ; Advance to next error string byte
BED3 002A .tube_send_error_byte
LDA (brk_ptr),y ; Load next error string byte
BED5 002C JSR tube_send_r2 ; Send error string byte via R2
BED8 002F TAX ; Zero byte = end of error string
BED9 0030 BNE tube_brk_send_loop ; Loop until zero terminator sent
BEDB 0032 .tube_reset_stack←1← 0477 JMP
LDX #&ff ; Reset stack pointer to top
BEDD 0034 TXS ; TXS: set stack pointer from X
BEDE 0035 CLI ; Enable interrupts for main loop
BEDF 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
BEE2 0039 BPL tube_poll_r2 ; R1 not ready: check R2 instead
BEE4 003B .tube_handle_wrch←1← 0049 BMI
LDA tube_data_register_1 ; Read character from Tube R1 data
BEE7 003E JSR oswrch ; Write character
BEEA 0041 .tube_poll_r2←1← 0039 BPL
BIT tube_status_register_2 ; BIT R2 status: check command byte
BEED 0044 BPL tube_main_loop ; R2 not ready: loop back to R1 check
BEEF 0046 BIT tube_status_1_and_tube_control ; Re-check R1: WRCH has priority over R2
BEF2 0049 BMI tube_handle_wrch ; R1 ready: handle WRCH first
BEF4 004B LDX tube_data_register_2 ; Read command byte from Tube R2 data
BEF7 004E STX tube_cmd_lo ; Self-modify JMP low byte for dispatch
BEF9 0050 .tube_dispatch_cmd
JMP (tube_r2_dispatch_table) ; Dispatch to handler via indirect JMP
BEFC 0053 .tube_transfer_addr←2← 04DE STY← 04EE STA
EQUB &00 ; Tube transfer address low byte
BEFD 0054 .tube_xfer_page←3← 04B6 INC← 04D4 STA← 04F3 STA
EQUB &80 ; Tube transfer page (default &80)
BEFE 0055 .tube_xfer_addr_2←2← 04BA INC← 04FD STY
EQUB &00 ; Tube transfer address byte 2
BEFF 0056 .tube_xfer_addr_3←2← 04BE INC← 04FB STA
EQUB &00 ; Tube transfer address byte 3
BF00 0400 .tube_page4_vectors←1← BE9D STA
JMP tube_begin ; JMP to BEGIN startup entry
BF03 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)
BF06 0406 .tube_addr_data_dispatch←10← 049A JSR← 04CF JMP← 8371 JSR← 8445 JSR← 8929 JSR← 8931 JSR← 9FDA JSR← 9FF1 JSR← A05D JSR← A2D4 JMP
CMP #&80 ; A>=&80: address claim; A<&80: data transfer
BF08 0408 BCC tube_transfer_setup ; A<&80: data transfer setup (SENDW)
BF0A 040A CMP #&c0 ; A>=&C0: new address claim from another host
BF0C 040C BCS addr_claim_external ; C=1: external claim, check ownership
BF0E 040E ORA #&40 ; Map &80-&BF range to &C0-&FF for comparison
BF10 0410 CMP tube_claimed_id ; Is this for our currently-claimed address?
BF12 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.
BF14 0414 .tube_release_claim←1← 0471 JSR
PHP ; PHP: save interrupt state for release
BF15 0415 SEI ; SEI: disable interrupts during R4 protocol
BF16 0416 LDA #5 ; R4 cmd 5: release our address claim
BF18 0418 JSR tube_send_r4 ; Send release command to co-processor
BF1B 041B LDA tube_claimed_id ; Load our currently-claimed address
BF1D 041D JSR tube_send_r4 ; Send our address as release parameter
BF20 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.
BF21 0421 .clear_tube_claim←1← BEAF JSR
LDA #&80 ; &80 sentinel: clear address claim
BF23 0423 STA tube_claimed_id ; &80 sentinel = no address currently claimed
BF25 0425 STA tube_claim_flag ; Store to claim-in-progress flag
BF27 0427 RTS ; Return from tube_post_init
BF28 0428 .addr_claim_external←1← 040C BCS
ASL tube_claim_flag ; Another host claiming; check if we're owner
BF2A 042A BCS accept_new_claim ; C=1: we have an active claim
BF2C 042C CMP tube_claimed_id ; Compare with our claimed address
BF2E 042E BEQ return_tube_init ; Match: return (we already have it)
BF30 0430 CLC ; Not ours: CLC = we don't own this address
BF31 0431 RTS ; Return with C=0 (claim denied)
BF32 0432 .accept_new_claim←1← 042A BCS
STA tube_claimed_id ; Accept new claim: update our address
BF34 0434 .return_tube_init←2← 0412 BNE← 042E BEQ
RTS ; Return with address updated
BF35 0435 .tube_transfer_setup←1← 0408 BCC
PHP ; PHP: save interrupt state
BF36 0436 SEI ; SEI: disable interrupts for R4 protocol
BF37 0437 .setup_data_transfer
STY tube_data_ptr_hi ; Save 16-bit transfer address from (X,Y)
BF39 0439 STX tube_data_ptr ; Store address pointer low byte
BF3B 043B JSR tube_send_r4 ; Send transfer type byte to co-processor
BF3E 043E TAX ; X = transfer type for table lookup
BF3F 043F LDY #3 ; Y=3: send 4 bytes (address + claimed addr)
BF41 0441 LDA tube_claimed_id ; Send our claimed address + 4-byte xfer addr
BF43 0443 JSR tube_send_r4 ; Send transfer address byte
BF46 0446 .send_xfer_addr_bytes←1← 044C BPL
LDA (tube_data_ptr),y ; Load transfer address byte from (X,Y)
BF48 0448 JSR tube_send_r4 ; Send address byte to co-processor via R4
BF4B 044B DEY ; Previous byte (big-endian: 3,2,1,0)
BF4C 044C BPL send_xfer_addr_bytes ; Loop for all 4 address bytes
BF4E 044E LDY #&18 ; Y=&18: enable Tube control register
BF50 0450 STY tube_status_1_and_tube_control ; Enable Tube interrupt generation
BF53 0453 LDA tube_ctrl_values,x ; Look up Tube control bits for this xfer type
BF56 0456 STA tube_status_1_and_tube_control ; Apply transfer- specific control bits
BF59 0459 LSR ; LSR: check bit 2 (2-byte flush needed?)
BF5A 045A LSR ; LSR: shift bit 2 to carry
BF5B 045B BCC skip_r3_flush ; C=0: no flush needed, skip R3 reads
BF5D 045D BIT tube_data_register_3 ; Dummy R3 reads: flush for 2-byte transfers
BF60 0460 BIT tube_data_register_3 ; Second dummy read to flush R3 FIFO
BF63 0463 .skip_r3_flush←1← 045B BCC
JSR tube_send_r4 ; Trigger co-processor ack via R4
BF66 0466 .poll_r4_copro_ack←1← 0469 BVC
BIT tube_status_register_4_and_cpu_control ; Poll R4 status for co- processo r response
BF69 0469 BVC poll_r4_copro_ack ; Bit 6 clear: not ready, keep polling
BF6B 046B BCS copro_ack_nmi_check ; R4 bit 7: co-processor acknowledged transfer
BF6D 046D CPX #4 ; Type 4 = SENDW (host-to-parasite word xfer)
BF6F 046F BNE skip_nmi_release ; Not SENDW type: skip release path
BF71 0471 .tube_sendw_complete←1← 0496 BEQ
JSR tube_release_claim ; SENDW complete: release, sync, restart
BF74 0474 JSR tube_send_r2 ; Sync via R2 send
BF77 0477 JMP tube_reset_stack ; Restart Tube main loop
BF7A 047A .copro_ack_nmi_check←1← 046B BCS
LSR ; LSR: check bit 0 (NMI used?)
BF7B 047B BCC skip_nmi_release ; C=0: NMI not used, skip NMI release
BF7D 047D LDY #&88 ; Release Tube NMI (transfer used interrupts)
BF7F 047F STY tube_status_1_and_tube_control ; Write &88 to Tube control to release NMI
BF82 0482 .skip_nmi_release←2← 046F BNE← 047B BCC
PLP ; Restore interrupt state
BF83 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.
BF84 0484 .tube_begin←1← 0400 JMP
CLI ; BEGIN: enable interrupts for Tube host code
BF85 0485 BCS claim_addr_ff ; C=1: hard break, claim addr &FF
BF87 0487 BNE check_break_type ; C=0, A!=0: re-init path
BF89 0489 JMP tube_reply_ack ; Z=1 from C=0 path: just acknowledge
BF8C 048C .check_break_type←1← 0487 BNE
LDX #0 ; X=0 for OSBYTE
BF8E 048E LDY #&ff ; Y=&FF for OSBYTE
BF90 0490 LDA #osbyte_read_write_last_break_type ; OSBYTE &FD: what type of reset was this?
BF92 0492 JSR osbyte ; Read type of last reset
BF95 0495 TXA ; X=value of type of last reset
BF96 0496 BEQ tube_sendw_complete ; Soft break (X=0): re-init Tube and restart
BF98 0498 .claim_addr_ff←2← 0485 BCS← 049D BCC
LDA #&ff ; Claim address &FF (startup = highest prio)
BF9A 049A JSR tube_addr_data_dispatch ; Request address claim from Tube system
BF9D 049D BCC claim_addr_ff ; C=0: claim failed, retry
BF9F 049F JSR tube_init_reloc ; Init reloc pointers from ROM header
BFA2 04A2 .next_rom_page←1← 04C4 BVC
LDA #7 ; R4 cmd 7: SENDW to send ROM to parasite
BFA4 04A4 JSR tube_claim_default ; Set up Tube for SENDW transfer
BFA7 04A7 LDY #0 ; Y=0: start at beginning of page
BFA9 04A9 STY zp_ptr_lo ; Store to zero page pointer low byte
BFAB 04AB .send_rom_page_bytes←1← 04B4 BNE
LDA (zp_ptr_lo),y ; Send 256-byte page via R3, byte at a time
BFAD 04AD STA tube_data_register_3 ; Write byte to Tube R3 data register
BFB0 04B0 NOP ; Timing delay: Tube data register needs NOPs
BFB1 04B1 NOP ; NOP delay (2)
BFB2 04B2 NOP ; NOP delay (3)
BFB3 04B3 INY ; Next byte in page
BFB4 04B4 BNE send_rom_page_bytes ; Loop for all 256 bytes
BFB6 04B6 INC tube_xfer_page ; Increment 24-bit destination addr
BFB8 04B8 BNE skip_addr_carry ; No carry: skip higher bytes
BFBA 04BA INC tube_xfer_addr_2 ; Carry into second byte
BFBC 04BC BNE skip_addr_carry ; No carry: skip third byte
BFBE 04BE INC tube_xfer_addr_3 ; Carry into third byte
BFC0 04C0 .skip_addr_carry←2← 04B8 BNE← 04BC BNE
INC zp_ptr_hi ; Increment page counter
BFC2 04C2 BIT zp_ptr_hi ; Bit 6 set = all pages transferred
BFC4 04C4 BVC next_rom_page ; More pages: loop back to SENDW
BFC6 04C6 JSR tube_init_reloc ; Re-init reloc pointers for final claim
BFC9 04C9 LDA #4 ; A=4: transfer type for final address claim
; Claim default Tube transfer address
; Sets Y=0, X=&53 (address &0053), then JMP ; tube_addr_claim
; to initiate a Tube address claim for the default ; transfer
; address. Called from the BEGIN startup path and after ; the
; page transfer loop completes.
BFCB 04CB .tube_claim_default←1← 04A4 JSR
LDY #0 ; Y=0: transfer address low byte
BFCD 04CD LDX #&53 ; X=&53: transfer address high byte (&0053)
BFCF 04CF 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 04D2 .tube_init_reloc←2← 049F JSR← 04C6 JSR
LDA #&80 ; Init: start sending from &8000
BFD4 04D4 STA tube_xfer_page ; Store &80 as source page high byte
BFD6 04D6 STA zp_ptr_hi ; Store &80 as page counter initial value
BFD8 04D8 LDA #&20 ; A=&20: bit 5 mask for ROM type check
BFDA 04DA AND rom_type ; ROM type bit 5: reloc address in header?
BFDD 04DD TAY ; Y = 0 or &20 (reloc flag)
BFDE 04DE STY tube_transfer_addr ; Store as transfer address selector
BFE0 04E0 BEQ store_xfer_end_addr ; No reloc addr: use defaults
BFE2 04E2 LDX copyright_offset ; Skip past copyright string to find reloc addr
BFE5 04E5 .scan_copyright_end←1← 04E9 BNE
INX ; Skip past null-terminated copyright string
BFE6 04E6 LDA rom_header,x ; Load next byte from ROM header
BFE9 04E9 BNE scan_copyright_end ; Loop until null terminator found
BFEB 04EB LDA rom_header_byte1,x ; Read 4-byte reloc address from ROM header
BFEE 04EE STA tube_transfer_addr ; Store reloc addr byte 1 as transfer addr
BFF0 04F0 LDA rom_header_byte2,x ; Load reloc addr byte 2
BFF3 04F3 STA tube_xfer_page ; Store as source page start
BFF5 04F5 LDY service_entry,x ; Load reloc addr byte 3
BFF8 04F8 LDA service_handler_lo,x ; Load reloc addr byte 4 (highest)
BFFB 04FB .store_xfer_end_addr←1← 04E0 BEQ
STA tube_xfer_addr_3 ; Store high byte of end address
BFFD 04FD STY tube_xfer_addr_2 ; Store byte 3 of end address
BFFF 04FF RTS ; Return with pointers initialised