Acorn ADFS 1.30

Updated 1 Apr 2026

← All Acorn ADFS versions

; Sideways ROM header
8000 .rom_header
.language_entry
.pydis_start
EQUB &00, &00, &00
8003 .service_entry
JMP service_handler ; ROM service call handler
8006 .rom_type
EQUB &82
8007 .copyright_offset
EQUB copyright - rom_header
8008 .binary_version
EQUB &30
8009 .title
EQUS "Acorn ADFS"
8013 .version
EQUB &00
8014 EQUS "1.30"
8018 .copyright
EQUB &00
8019 EQUS "(C)1983 Acorn."

Claim Tube if present

Claim the Tube for a data transfer if a Tube is present. Copies the 4-byte transfer address from the control block to workspace and sets the Tube-in-use flag.

8027 .claim_tube←3← 8111 JSR← 8B61 JSR← BB67 JSR
LDY #4 ; Y=4: copy 4 bytes
8029 BIT zp_adfs_flags ; Is Tube present?
802B BPL return_1 ; No, return immediately
802D .copy_tube_addr_loop←1← 8033 BNE
LDA (zp_ctrl_blk_lo),y ; Copy 4-byte transfer address
802F STA wksp_tube_transfer_addr,y ; Store in Tube transfer workspace
8032 DEY ; Next byte
8033 BNE copy_tube_addr_loop ; Loop for 4 bytes
8035 LDA zp_adfs_flags ; Set bit 6: Tube in use
8037 ORA #&40 ; Set Tube-in-use flag
8039 STA zp_adfs_flags ; Store updated flags
803B .claim_tube_retry←4← 8040 BCC← A438 JSR← B839 JSR← B9A3 JSR
LDA #&c4 ; Claim Tube with A=&C4
803D JSR tube_entry ; Call Tube host to claim
8040 BCC claim_tube_retry ; Loop until claim succeeds
8042 .return_1←1← 802B BPL
RTS ; Return

Release Tube if in use

Release the Tube after a data transfer if it was claimed. Checks zp_adfs_flags bit 6 and clears it after release.

8043 .release_tube←5← 818A JSR← 8402 JSR← B8DB JSR← B9FB JMP← BFE0 JSR
BIT zp_adfs_flags ; Check Tube-in-use flag
8045 BVC return_2 ; Not in use, return immediately
8047 LDA #&84 ; Release Tube with A=&84
8049 JSR tube_entry ; Call Tube host to release
804C PHP ; Save interrupt state
804D SEI ; Disable interrupts
804E LDA zp_adfs_flags ; Clear bit 6: Tube no longer in use
8050 AND #&bf ; Clear Tube-in-use bit
8052 STA zp_adfs_flags ; Store updated flags
8054 PLP ; Restore interrupt state
8055 .return_2←1← 8045 BVC
RTS ; Return

Read SCSI status with settling

Read the SCSI status register, waiting for the value to settle. Reads the status twice and loops until consecutive reads match. Also stores result in zp_scsi_status.

On ExitAsettled SCSI status byte
Xcorrupted
Ycorrupted
8056 .scsi_get_status←5← 806A JSR← 8078 JSR← 8197 JSR← 8310 JSR← AB80 JSR
PHP ; Save processor flags
8057 .scsi_read_settle_loop←1← 8061 BNE
LDA fred_hard_drive_1 ; Read SCSI status register
805A STA zp_scsi_status ; Store first reading
805C LDA fred_hard_drive_1 ; Read SCSI status register again
805F CMP zp_scsi_status ; Has it settled?
8061 BNE scsi_read_settle_loop ; No, try again
8063 PLP ; Restore processor flags
8064 RTS ; Return

SCSI bus selection and command phase

Select a SCSI device on the bus and begin the command phase. Asserts the target's SCSI ID on the data bus and waits for the BSY signal to be asserted by the target.

8065 .scsi_start_command←3← 80F6 JSR← 823A JSR← 8B46 JSR
LDY #0 ; Y=0 for normal start
8067 .scsi_start_command2←1← AACA JSR
LDA #1 ; SCSI ID bit pattern = 1 (drive 0)
8069 PHA ; Save SCSI ID on stack
806A .wait_bus_free_loop←1← 806F BNE
JSR scsi_get_status ; Wait for BSY to deassert
806D AND #2 ; Check BSY bit
806F BNE wait_bus_free_loop ; Loop while BSY asserted
8071 PLA ; Retrieve SCSI ID
8072 STA fred_hard_drive_0 ; Assert ID on SCSI data bus
8075 STA fred_hard_drive_2 ; Assert SEL to select target
8078 .wait_target_bsy_loop←1← 807D BEQ
JSR scsi_get_status ; Wait for target to assert BSY
807B AND #2 ; Check BSY bit
807D BEQ wait_target_bsy_loop ; Loop until BSY asserted
807F .return_3←1← 80A7 BEQ
RTS ; Return

Set retry count for disc operation

Set the retry counter to the default value (16).

8080 .command_set_retries←4← 809F JSR← 8AF8 JSR← AB2D JSR← AC96 JSR
LDA wksp ; Default retry count from workspace
8083 STA zp_retry_count ; Store in retry counter
8085 RTS ; Return
8086 .escape_during_retry←1← 80B1 BMI
JMP error_escape_ack_invalidate_reload_fsm ; Escape during retry: abort

Execute disc command with control block at (X,Y)

Execute a disc operation using the control block pointed to by X (low) and Y (high). Handles both hard drive (SCSI) and floppy disc operations with retry logic.

On EntryXcontrol block address low byte
Ycontrol block address high byte
On ExitAresult code (0 = success, Z set)
Xcontrol block address low (preserved)
Ycontrol block address high (preserved)
8089 .command_exec_xy←6← 828B JSR← 8A74 JSR← 8ABF JSR← 9D52 JSR← A0D7 JSR← A16B JSR
JSR wait_ensuring ; Wait if files being ensured
808C STX zp_ctrl_blk_lo ; Store control block address low
808E STY zp_ctrl_blk_hi ; Store control block address high
8090 JSR check_dir_loaded ; Ensure directory is loaded
8093 LDY #5 ; Byte 5 of control block = command
8095 LDA (zp_ctrl_blk_lo),y ; Get command byte from control block
8097 CMP #&2f ; Format track?
8099 BEQ command_exec_start_exec ; Yes, skip retries
809B CMP #&1b ; Seek?
809D BEQ command_exec_start_exec ; Yes, skip retries
809F JSR command_set_retries ; Set default retry count
80A2 BPL dispatch_hd_or_floppy ; Always branch (retry count >= 0)
80A4 .command_exec_retry_loop←1← 80C4 BPL
JSR command_exec_start_exec ; Execute the disc operation
80A7 BEQ return_3 ; Success, return
80A9 CMP #4 ; Not-ready error?
80AB BNE dispatch_hd_or_floppy ; No, check if retries exhausted
80AD LDY #&19 ; Delay loop for not-ready
80AF .check_escape_during_retry←3← 80B6 BNE← 80B9 BNE← 80BC BNE
BIT zp_escape_flag ; Check for Escape during delay
80B1 BMI escape_during_retry ; Escape pressed, abort
80B3 SEC ; Set carry for subtraction
80B4 SBC #1 ; Decrement delay low byte
80B6 BNE check_escape_during_retry ; Inner loop not done
80B8 DEX ; Decrement delay mid byte
80B9 BNE check_escape_during_retry ; Mid loop not done
80BB DEY ; Decrement delay high byte
80BC BNE check_escape_during_retry ; Outer loop not done
80BE .dispatch_hd_or_floppy←2← 80A2 BPL← 80AB BNE
CMP #&40 ; Drive-not-present error?
80C0 BEQ command_exec_start_exec ; Yes, no point retrying
80C2 DEC zp_retry_count ; Decrement retry counter
80C4 BPL command_exec_retry_loop ; More retries remaining
80C6 .command_exec_start_exec←4← 8099 BEQ← 809D BEQ← 80A4 JSR← 80C0 BEQ
LDA zp_adfs_flags ; Check zp_flags for hard drive
80C8 AND #&20 ; Bit 5: hard drive present?
80CA BNE hd_command ; Yes, use hard drive command
80CC .command_exec_floppy_op←1← 80F4 BMI
JSR floppy_command_ind ; Floppy disc operation
80CF BEQ return_4 ; Success, return
80D1 PHA ; Save error code
80D2 LDY #6 ; Byte 6: drive + sector high
80D4 LDA (zp_ctrl_blk_lo),y ; Get drive+sector byte from blk
80D6 ORA wksp_current_drive ; Combine with current drive number
80D9 STA wksp_err_sector_hi ; Store in error sector workspace
80DC INY ; Y=&07
80DD LDA (zp_ctrl_blk_lo),y ; Byte 7: sector mid
80DF STA wksp_err_sector_mid ; Store sector mid byte
80E2 INY ; Y=&08
80E3 LDA (zp_ctrl_blk_lo),y ; Byte 8: sector low
80E5 STA wksp_err_sector ; Store sector low byte
80E8 PLA ; Retrieve error code
80E9 STA wksp_err_code ; Store error code
80EC .return_4←1← 80CF BEQ
RTS ; Return

Execute hard drive SCSI command

Execute a disc operation via the SCSI hard drive interface. Sends the SCSI command bytes from the control block at (&B0), performs data transfer (direct or via Tube), and reads the SCSI status and message phases.

Falls back to floppy if drive bit 7 is set.

On ExitAresult code (0 = success, Z set)
Xcontrol block address low (restored)
Ycontrol block address high (restored)
80ED .hd_command←1← 80CA BNE
LDY #6 ; Byte 6: drive + sector b16-b20
80EF LDA (zp_ctrl_blk_lo),y ; Get byte from control block
80F1 ORA wksp_current_drive ; Combine with current drive
80F4 BMI command_exec_floppy_op ; Bit 7 set = floppy drive
80F6 JSR scsi_start_command ; Select SCSI device and begin command
80F9 INY ; Byte 7: sector b8-b15
80FA LDA (zp_ctrl_blk_lo),y ; Get byte from control block
80FC STA zp_mem_ptr_lo ; Store as memory address low
80FE INY ; Byte 8: sector b0-b7
80FF LDA (zp_ctrl_blk_lo),y ; Get byte from control block
8101 STA zp_mem_ptr_hi ; Store as memory address high
8103 INY ; Byte 9: transfer address high
8104 LDA (zp_ctrl_blk_lo),y ; Get byte from control block
8106 CMP #&fe ; Address >= &FE00?
8108 BCC skip_tube_claim ; No, claim Tube for normal transfer
810A INY ; Byte 10: next address byte
810B LDA (zp_ctrl_blk_lo),y ; Get byte from control block
810D CMP #&ff ; Address = &FFxx (host memory)?
810F BEQ send_scsi_command_bytes ; Yes, skip Tube claim
8111 .skip_tube_claim←1← 8108 BCC
JSR claim_tube ; Claim Tube if present
8114 .send_scsi_command_bytes←1← 810F BEQ
LDY #5 ; Byte 5: SCSI command byte
8116 LDA (zp_ctrl_blk_lo),y ; Get byte from control block
8118 JSR scsi_send_byte_a ; Send SCSI command byte
811B INY ; Byte 6: drive + sector high
811C LDA (zp_ctrl_blk_lo),y ; Get byte from control block
811E ORA wksp_current_drive ; Combine with current drive for LUN
8121 STA wksp_current_drive_hi ; Save combined drive/LUN
8124 JMP send_next_cmd_byte ; Jump into command send loop
8127 .send_cmd_byte_loop←1← 8134 BNE
LDA (zp_ctrl_blk_lo),y ; Get next command byte
8129 .send_next_cmd_byte←1← 8124 JMP
JSR scsi_send_byte_a ; Send command byte to target
812C JSR scsi_wait_for_req ; Wait for SCSI REQ signal
812F BPL check_256_byte_transfer ; Status phase? Done sending command
8131 BVS check_256_byte_transfer ; Message phase? Done sending command
8133 INY ; Next command byte
8134 BNE send_cmd_byte_loop ; More command bytes to send
8136 .check_256_byte_transfer←2← 812F BPL← 8131 BVS
LDY #5 ; Check for 256-byte sector transfer
8138 LDA (zp_ctrl_blk_lo),y ; Get command byte
813A AND #&fd ; Mask to read/write bits
813C EOR #8 ; Is it a read/write 256-byte command?
813E BEQ hd_data_transfer_256 ; Yes, use optimised transfer
8140 JSR scsi_wait_for_req ; Wait for data phase
8143 CLC ; C=0: write direction
8144 BVC start_byte_transfer ; I/O bit clear? Writing
8146 SEC ; C=1: read direction
8147 .start_byte_transfer←1← 8144 BVC
LDY #0 ; Y=0: byte counter for 256-byte page
8149 BIT zp_adfs_flags ; Tube in use?
814B BVC wait_data_phase ; No, direct memory transfer
814D LDX #&27 ; X=&27: Tube workspace addr low
814F LDY #&10 ; Y=&10: Tube workspace page
8151 LDA #0 ; A=0 (direction flag)
8153 PHP ; Save direction flag
8154 ROL ; Rotate carry into bit 0
8155 JSR tube_start_xfer ; Start Tube transfer
8158 PLP ; Restore processor flags
8159 .wait_data_phase←5← 814B BVC← 8171 BNE← 8175 JMP← 8180 BCC← 8188 BCS
JSR scsi_wait_for_req ; Wait for SCSI REQ
815C BMI command_done ; Status phase, transfer done
815E BIT zp_adfs_flags ; Tube in use?
8160 BVS read_scsi_via_tube ; Yes, use Tube path
8162 BCS read_scsi_to_memory ; Reading from SCSI?
8164 LDA (zp_mem_ptr_lo),y ; Writing: get byte from memory
8166 STA fred_hard_drive_0 ; Write to SCSI data register
8169 BCC advance_memory_page ; Always branch to increment
816B .read_scsi_to_memory←1← 8162 BCS
LDA fred_hard_drive_0 ; Reading: get byte from SCSI
816E STA (zp_mem_ptr_lo),y ; Store in memory
8170 .advance_memory_page←1← 8169 BCC
INY ; Next byte
8171 BNE wait_data_phase ; Continue until page done
8173 INC zp_mem_ptr_hi ; Increment page pointer
8175 JMP wait_data_phase ; Continue transfer
8178 .read_scsi_via_tube←1← 8160 BVS
BCS write_tube_to_scsi ; Reading from SCSI via Tube?
817A LDA tube_data_register_3 ; Writing via Tube: read from Tube R3
817D STA fred_hard_drive_0 ; Write to SCSI data register
8180 BCC wait_data_phase ; Always branch back
8182 .write_tube_to_scsi←1← 8178 BCS
LDA fred_hard_drive_0 ; Reading via Tube: read from SCSI
8185 STA tube_data_register_3 ; Write to Tube R3
8188 BCS wait_data_phase ; Always branch back
fall through ↓

Complete SCSI command and read status

Release the Tube, then read the SCSI status and message bytes to determine the outcome of the command.

On ExitAresult code (0 = success, &7F-masked error)
Xcontrol block address low (restored)
Ycontrol block address high (restored)
818A .command_done←7← 815C BMI← 81C1 BMI← 8205 JMP← 8328 JMP← 8BB0 JMP← AB59 JSR← ACC6 JSR
JSR release_tube ; Release Tube if claimed
818D .wait_status_phase←1← 819C BEQ
JSR scsi_wait_for_req ; Wait for SCSI REQ (status phase)
8190 LDA fred_hard_drive_0 ; Read status byte from SCSI data
8193 JSR scsi_wait_for_req ; Wait for SCSI REQ (message phase)
8196 TAY ; Save status in Y
8197 JSR scsi_get_status ; Read SCSI status register
819A AND #1 ; Check BSY still asserted
819C BEQ wait_status_phase ; Loop until bus free
819E TYA ; Retrieve status byte
819F LDX fred_hard_drive_0 ; Read final data byte
81A2 BEQ check_scsi_error_bit ; Status OK?
81A4 JMP unrecoverable_scsi_error ; No, return error &FF
81A7 .check_scsi_error_bit←1← 81A2 BEQ
TAX ; Transfer status to X
81A8 AND #2 ; Check error bit in status
81AA BEQ return_scsi_result ; No error, return success
81AC JMP scsi_request_sense ; Error: do SCSI Request Sense
81AF .return_scsi_result←1← 81AA BEQ
LDA #0 ; A=0: success return code
81B1 .mask_error_code←2← 827F JMP← 8284 JMP
LDX zp_ctrl_blk_lo ; Restore control block pointer
81B3 LDY zp_ctrl_blk_hi ; Restore Y
81B5 AND #&7f ; Mask to 7-bit error code
81B7 RTS ; Return

SCSI 256-byte sector data transfer

Transfer complete 256-byte sectors between SCSI bus and memory (direct or via Tube). Optimised inner loop with no per-byte SCSI REQ polling.

81B8 .hd_data_transfer_256←1← 813E BEQ
LDY #0 ; Y=0: byte counter
81BA BIT zp_adfs_flags ; Tube in use?
81BC BVS setup_tube_write_256 ; Yes, use Tube 256-byte transfer
81BE .wait_req_and_transfer←2← 81CF BVC← 81DB BVS
JSR scsi_wait_for_req ; Wait for SCSI REQ
81C1 BMI command_done ; Status phase, done
81C3 BVS read_sector_byte_loop ; I/O bit: reading from SCSI?
81C5 .write_sector_byte_loop←1← 81CB BNE
LDA (zp_mem_ptr_lo),y ; Writing: get byte from memory
81C7 STA fred_hard_drive_0 ; Write to SCSI data register
81CA INY ; Next byte
81CB BNE write_sector_byte_loop ; Continue for 256 bytes
81CD INC zp_mem_ptr_hi ; Next page
81CF BVC wait_req_and_transfer ; Continue transfer
81D1 .read_sector_byte_loop←2← 81C3 BVS← 81D7 BNE
LDA fred_hard_drive_0 ; Reading: get byte from SCSI
81D4 STA (zp_mem_ptr_lo),y ; Store in memory
81D6 INY ; Next byte
81D7 BNE read_sector_byte_loop ; Continue for 256 bytes
81D9 INC zp_mem_ptr_hi ; Next page
81DB BVS wait_req_and_transfer ; Continue transfer
81DD .increment_tube_xfer_addr←2← 821C JSR← 8234 JSR
INC wksp_tube_xfer_addr_2 ; Increment low byte of transfer addr
81E0 BNE load_tube_workspace_ptr ; No wrap: skip mid byte increment
81E2 INC wksp_tube_xfer_addr_3 ; Increment mid byte
81E5 BNE load_tube_workspace_ptr ; No wrap: skip high byte increment
81E7 INC wksp_csd_drive_temp ; Increment high byte
81EA .load_tube_workspace_ptr←2← 81E0 BNE← 81E5 BNE
LDX #&27 ; X=&27: Tube workspace addr low
81EC LDY #&10 ; Y=&10: Tube workspace page
81EE RTS ; Return

Start Tube transfer with interrupts disabled

Disable interrupts then call the Tube host code at &0406 to initiate a data transfer.

On EntryATube transfer type (6=write, 7=read)
81EF .tube_start_xfer_sei←2← 820D JSR← 8225 JSR
SEI ; Disable interrupts for Tube xfer
fall through ↓

Start Tube transfer

Call the Tube host code at &0406 to initiate a data transfer. Followed by a delay for Tube synchronisation.

On EntryATube transfer type
81F0 .tube_start_xfer←1← 8155 JSR
JSR tube_entry ; Call Tube host code at &0406
81F3 LDY #0 ; Y=0
81F5 .tube_delay
JSR tube_delay2 ; Delay for Tube synchronisation
81F8 .tube_delay2←3← 81F5 JSR← 8BA2 JSR← B9E2 JSR
JSR return_5 ; Nested JSR/RTS delay
81FB .return_5←1← 81F8 JSR
RTS ; Return (also used as delay)
81FC .setup_tube_write_256←1← 81BC BVS
LDX #&27 ; X=&27: Tube workspace addr low
81FE LDY #&10 ; Y=&10: Tube workspace page
8200 .wait_tube_data_phase←2← 8220 BVC← 8238 BVS
JSR scsi_wait_for_req ; Wait for SCSI REQ
8203 BPL set_tube_write_direction ; Data phase?
8205 JMP command_done ; No, status phase - done
8208 .set_tube_write_direction←1← 8203 BPL
BVS set_tube_read_direction ; I/O bit: reading from SCSI?
820A PHP ; Save flags before SEI
820B LDA #6 ; Tube transfer type 6 (write)
820D JSR tube_start_xfer_sei ; Start Tube transfer with SEI
8210 .tube_write_byte_loop←1← 821A BNE
NOP ; NOP timing delay for Tube
8211 NOP ; NOP timing delay
8212 NOP ; NOP timing delay
8213 LDA tube_data_register_3 ; Read byte from Tube R3
8216 STA fred_hard_drive_0 ; Write to SCSI data register
8219 INY ; Next byte
821A BNE tube_write_byte_loop ; Continue for 256 bytes
821C JSR increment_tube_xfer_addr ; Increment transfer address
821F PLP ; Restore flags, continue transfer
8220 BVC wait_tube_data_phase ; Continue outer transfer loop
8222 .set_tube_read_direction←1← 8208 BVS
PHP ; Save flags for read path
8223 LDA #7 ; Tube transfer type 7 (read)
8225 JSR tube_start_xfer_sei ; Start Tube transfer with SEI
8228 .tube_read_byte_loop←1← 8232 BNE
NOP ; NOP timing delay for Tube
8229 NOP ; NOP timing delay
822A NOP ; NOP timing delay
822B LDA fred_hard_drive_0 ; Read byte from SCSI data register
822E STA tube_data_register_3 ; Write to Tube R3
8231 INY ; Next byte
8232 BNE tube_read_byte_loop ; Continue for 256 bytes
8234 JSR increment_tube_xfer_addr ; Increment transfer address
8237 PLP ; Restore flags, continue transfer
8238 BVS wait_tube_data_phase ; Continue outer transfer loop
fall through ↓

SCSI Request Sense command

Send a SCSI Request Sense command (opcode 3) to retrieve extended error information after a failed operation. Stores the 4-byte sense data in the error workspace.

On ExitAerror code from sense data (&FF if unrecoverable)
Xcorrupted
Ycorrupted
823A .scsi_request_sense←1← 81AC JMP
JSR scsi_start_command ; Select SCSI device
823D LDA #3 ; SCSI Request Sense command = 3
823F TAX ; X=3: receive 4 sense bytes
8240 TAY ; Y=3: send 3 more command bytes
8241 JSR scsi_send_byte_a ; Send command byte
8244 LDA wksp_current_drive_hi ; Get LUN bits from drive number
8247 AND #&e0 ; Isolate LUN (bits 5-7)
8249 JSR scsi_send_byte_a ; Send LUN byte
824C .send_zero_bytes_loop←1← 8250 BPL
JSR scsi_send_byte_a ; Send remaining zero bytes
824F DEY ; Decrement byte counter
8250 BPL send_zero_bytes_loop ; Loop for 3 bytes
8252 .receive_sense_data_loop←1← 825C BPL
JSR scsi_wait_for_req ; Receive sense data bytes
8255 LDA fred_hard_drive_0 ; Read sense data from SCSI bus
8258 STA wksp_err_sector,x ; Store in error workspace
825B DEX ; Next byte
825C BPL receive_sense_data_loop ; Loop for 4 bytes
825E LDA wksp_current_drive_hi ; Get drive LUN bits
8261 AND #&e0 ; Isolate LUN
8263 ORA wksp_err_sector_hi ; Merge with error sector high byte
8266 STA wksp_err_sector_hi ; Store back
8269 JSR scsi_wait_for_req ; Wait for status phase
826C LDX wksp_err_code ; Get error code from workspace
826F LDA fred_hard_drive_0 ; Read status byte
8272 JSR scsi_wait_for_req ; Wait for message phase
8275 LDY fred_hard_drive_0 ; Read message byte
8278 BNE unrecoverable_scsi_error ; Message byte non-zero? Error
827A AND #2 ; Check status error bit
827C BNE unrecoverable_scsi_error ; Error bit set? Return error
827E TXA ; Transfer error code to A
827F JMP mask_error_code ; Return with error code
8282 .unrecoverable_scsi_error←3← 81A4 JMP← 8278 BNE← 827C BNE
LDA #&ff ; Unrecoverable SCSI error
8284 JMP mask_error_code ; Return &FF

Execute disc command from workspace control block

Execute a disc command using the control block at &1015. Generates a BRK error if the command fails.

8287 .exec_disc_op_from_wksp←8← 89CA JSR← 8A1A JSR← 8FAE JSR← 94C9 JMP← 9722 JSR← 973C JSR← A7F2 JMP← A813 JSR
LDX #&15 ; Point to workspace disc op block
8289 LDY #&10 ; Y=&10: workspace page
fall through ↓

Execute disc command and check for error

Execute disc command via command_exec_xy. On error, generate a BRK (never returns). On success, restore saved drive and return.

On EntryXcontrol block address low byte
Ycontrol block address high byte
828B .exec_disc_command←8← 8888 JSR← 88A5 JSR← 89E7 JSR← 8FD3 JSR← 97A5 JMP← A81A JMP← B50A JSR← B571 JSR
JSR command_exec_xy ; Execute disc command
828E BNE generate_error ; Error? Generate BRK
8290 RTS ; Return (success)
8291 .restore_drive_after_op←2← 829C BEQ← 82A0 BEQ
LDA wksp_saved_drive ; Restore saved drive number
8294 STA wksp_current_drive ; Set current drive
8297 JMP bad_parms_error ; Restore drive and raise error

Generate a BRK error

Generate a BRK error from the disc error code in A. Never returns to caller.

On EntryASCSI/disc error code
829A .generate_error←6← 828E BNE← 82FE BNE← 8A42 JMP← AB48 JMP← AB60 JMP← ACAF JMP
CMP #&25 ; Error code &25 = drive not present?
829C BEQ restore_drive_after_op ; Yes, restore drive and raise error
829E CMP #&65 ; Error code &65 = volume error?
82A0 BEQ restore_drive_after_op ; Yes, restore drive and raise error
82A2 CMP #&6f ; Error code &6F = drive overrun?
82A4 BNE check_escape_condition ; No, check other error codes
82A6 .error_escape_ack_invalidate_reload_fsm←1← 8086 JMP
LDA #osbyte_acknowledge_escape ; Acknowledge Escape condition
82A8 JSR osbyte ; Clear escape condition and perform escape effects
82AB JSR invalidate_fsm_and_dir ; Invalidate FSM and directory
82AE JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
82B1 EQUB &11 ; Error &11: Escape
82B2 EQUS "Escape."
82B9 .check_escape_condition←1← 82A4 BNE
CMP #4 ; Error code &04 = drive not ready?
82BB BNE translate_scsi_error ; Not drive overrun, check other codes
82BD JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
82C0 EQUB &CD ; Error &CD: Drive not ready
82C1 EQUS "Drive not ready."
82D1 .translate_scsi_error←1← 82BB BNE
CMP #&40 ; Error code &40 = write protected?
82D3 BEQ store_error_sector ; Yes, generate Disc protected error
82D5 JSR save_wksp_and_return ; Convert SCSI error to disc error
82D8 TAX ; X = suffix control
82D9 JSR generate_error_suffix_x ; Generate error with suffix control in X
82DC EQUB &C7 ; Error &C7: Disc error
82DD EQUS "Disc error."
82E8 .store_error_sector←1← 82D3 BEQ
JSR generate_disc_error ; Write protected: save drive state
82EB EQUB &C9 ; Error &C9: Disc protected
82EC EQUS "Disc protected."
fall through ↓

Send one byte during SCSI command phase

Wait for SCSI REQ, then write byte A to the SCSI data bus. Returns only on success; generates BRK on error.

On EntryASCSI command byte to send
82FB .scsi_send_cmd_byte←6← AACE JSR← AAD7 JSR← AADD JSR← AAE3 JSR← AAE8 JSR← AAED JMP
JSR scsi_send_byte_wrapper ; Send byte A via SCSI
82FE BNE generate_error ; Non-zero result: SCSI error
8300 RTS ; Return (success)
8301 .scsi_send_byte_wrapper←1← 82FB JSR
JSR scsi_send_byte_a ; Send byte and return status
8304 RTS ; Return

Wait while files are being ensured

If zp_flags bit 0 (ensuring) is set, loop until it clears. This prevents disc operations during file ensure operations.

8305 .wait_ensuring←9← 8089 JSR← 830C BNE← 8AF5 JSR← 9E7F JSR← A347 JSR← A98F JSR← AAC7 JSR← B00D JSR← B39B JSR
LDA #1 ; A=1: test bit 0 of zp_flags
8307 PHP ; Save flags
8308 CLI ; Enable interrupts briefly
8309 PLP ; Restore flags
830A BIT zp_adfs_flags ; Bit 0 set (ensuring)?
830C BNE wait_ensuring ; Yes, keep waiting
830E RTS ; Return

Wait for SCSI REQ signal

Poll the SCSI status register until the REQ bit is asserted, indicating the target is ready for the next bus phase. Preserves A; N and V flags reflect SCSI bus phase.

On ExitApreserved
Xpreserved
Ypreserved
NC/D bit from SCSI status (set = command phase)
VMSG bit from SCSI status (set = message phase)
830F .scsi_wait_for_req←15← 812C JSR← 8140 JSR← 8159 JSR← 818D JSR← 8193 JSR← 81BE JSR← 8200 JSR← 8252 JSR← 8269 JSR← 8272 JSR← 831B JSR← 8B92 JSR← AB54 JSR← AB99 JSR← ACB7 JSR
PHA ; Save A on stack
8310 .poll_req_loop←1← 8315 BEQ
JSR scsi_get_status ; Read SCSI status
8313 AND #&20 ; Check REQ bit (bit 5)
8315 BEQ poll_req_loop ; Loop until REQ asserted
8317 PLA ; Restore A
8318 BIT zp_scsi_status ; Test C/D and MSG bits via BIT
831A RTS ; Return

Send byte A on SCSI bus after REQ

Wait for SCSI REQ then write A to the SCSI data register. May not return if MSG phase detected (unwinds call stack to command_done).

On EntryAbyte to send on SCSI bus
831B .scsi_send_byte_a←7← 8118 JSR← 8129 JSR← 8241 JSR← 8249 JSR← 824C JSR← 8301 JSR← 8B77 JSR
JSR scsi_wait_for_req ; Wait for SCSI REQ
831E BVS write_scsi_data_byte ; MSG phase? Abort command
8320 STA fred_hard_drive_0 ; Write data byte to SCSI bus
8323 LDA #0 ; A=0: success
8325 RTS ; Return (byte sent OK)
8326 .write_scsi_data_byte←1← 831E BVS
PLA ; Pop 2 return addresses from stack
8327 PLA ; Pop one return address
8328 JMP command_done ; Jump to status/message phase handler

Generate disc error with state recovery

Save the current drive state, reload FSM and directory, then generate a BRK error. The inline error number and message string follow the JSR instruction.

832B .generate_disc_error←6← 82E8 JSR← 85C8 JSR← 8656 JSR← 8664 JSR← 8FFA JSR← A6F9 JSR
LDX wksp_saved_drive ; Check if drive was already saved
832E INX ; Non-zero means drive already saved
832F BNE reload_fsm_and_dir_then_brk ; Already saved, just raise the error
8331 LDX wksp_alt_sector_hi ; Check alternative workspace
8334 INX ; Increment: non-zero?
8335 BNE check_for_on_channel ; Yes, skip CSD restore
8337 LDY #2 ; Copy CSD sector info to workspace
8339 .copy_error_string_loop←1← 8340 BPL
LDA wksp_csd_sector_lo,y ; Get CSD sector byte
833C STA wksp_csd_drive_sector,y ; Copy to CSD drive sector workspace
833F DEY ; Next byte
8340 BPL copy_error_string_loop ; Loop for 3 bytes
8342 .check_for_on_channel←1← 8335 BNE
LDA wksp_current_drive ; Save current drive for error message
8345 STA wksp_saved_drive ; Save current drive for error msg
fall through ↓

Reload FSM and directory then raise error

Reload the free space map and current directory from disc, then generate a BRK error. Used after operations that may have left the in-memory copies inconsistent.

8348 .reload_fsm_and_dir_then_brk←26← 82AE JSR← 82BD JSR← 832F BNE← 8737 JSR← 8982 JSR← 8BD7 JSR← 8BF0 JSR← 8D16 JSR← 8D53 JSR← 8DDE JSR← 8E1E JSR← 915C JSR← 91AD JSR← 91D7 JSR← 95A4 JSR← 99DA JSR← A00A JSR← A29B JSR← A389 JSR← A3F7 JSR← AA35 JSR← ACE9 JSR← AD5B JSR← B09D JSR← B1EB JSR← B4AE JSR
JSR save_wksp_and_return ; Ensure directory/FSM state is clean
834B LDA zp_adfs_flags ; Clear FSM-inconsistent flag (bit 4)
834D AND #&ef ; Clear FSM inconsistent flag
834F STA zp_adfs_flags ; Store updated flags
fall through ↓

Generate error without drive/sector suffix

Generate a BRK error from the disc error code without appending the drive:sector suffix.

8351 .generate_error_no_suffix←2← A6CD JSR← A73D JSR
LDX #0 ; X=non-zero to append drive:sector suffix
fall through ↓

Generate error with suffix control in X

Generate a BRK error from the inline error data following the JSR. X controls whether the drive:sector suffix is appended. Never returns.

On EntryXnon-zero to append drive:sector suffix
8353 .generate_error_suffix_x←2← 82D9 JSR← ABB2 JSR
PLA ; Pop return address (inline data ptr)
8354 STA zp_mem_ptr_lo ; Store inline data pointer low
8356 PLA ; High byte of inline data address
8357 STA zp_mem_ptr_hi ; Store inline data pointer high
8359 LDA zp_adfs_flags ; Clear FSM-inconsistent flag (bit 4)
835B AND #&ef ; Mask off bit 4
835D STA zp_adfs_flags ; Store cleared flags
835F LDY #0 ; Y=0: index into inline error data
8361 .copy_error_msg_loop←1← 8367 BNE
INY ; Copy inline error message to page 1
8362 LDA (zp_mem_ptr_lo),y ; Read error msg byte from inline data
8364 STA brk_error_block,y ; Store in error block on page 1
8367 BNE copy_error_msg_loop ; Loop until zero terminator
8369 TXA ; X=0 means no suffix wanted
836A BEQ generate_error_skip_no_suffix ; Skip suffix, go to channel check
836C LDA #&20 ; Append space before suffix
836E STA brk_error_block,y ; Store space in error block
8371 TXA ; Check if suffix is hex or decimal
8372 CMP #&30 ; Suffix value >= '0'?
8374 BCS check_colon_suffix ; Yes, check for colon
8376 .append_hex_suffix←1← 837E BCS
JSR error_append_hex ; Append as hex number
8379 JMP append_drive_sector_suffix ; Jump to hex formatting
837C .check_colon_suffix←1← 8374 BCS
CMP #&3a ; Suffix value >= ':'?
837E BCS append_hex_suffix ; Yes, append as hex
8380 JSR error_append_dec ; Append as decimal number
8383 .append_drive_sector_suffix←1← 8379 JMP
LDX #4 ; Copy reversed ' at :' suffix
8385 .copy_at_string_loop←1← 838D BPL
INY ; Next position
8386 LDA str_at,x ; Get char from reversed string
8389 STA brk_error_block,y ; Store in error block
838C DEX ; Next character in reversed string
838D BPL copy_at_string_loop ; Loop for 5 chars of ' at :'
838F LDA wksp_err_sector_hi ; Get drive number from error sector
8392 ASL ; Shift drive bits into low nibble
8393 ROL ; Rotate drive bits to low nibble
8394 ROL ; Second rotate
8395 ROL ; Third rotate
8396 JSR hex_digit ; Convert to hex digit character
8399 INY ; Advance position
839A STA brk_error_block,y ; Store drive digit
839D LDA #&2f ; Append '/' separator
839F INY ; Advance to next position
83A0 STA brk_error_block,y ; Store separator in error block
83A3 LDA wksp_err_sector_hi ; Get sector high byte
83A6 AND #&1f ; Mask to 5-bit sector address
83A8 LDX #2 ; X=2: output 3 bytes of sector addr
83AA BNE append_sector_hex ; Always branch to loop entry
83AC .append_sector_bytes_loop←1← 83B3 BPL
LDA wksp_err_sector,x ; Get next sector byte from workspace
83AF .append_sector_hex←1← 83AA BNE
JSR error_append_hex ; Append as two hex digits
83B2 DEX ; Next byte
83B3 BPL append_sector_bytes_loop ; Loop for 3 sector bytes
83B5 INY ; Advance past suffix
83B6 LDA #0 ; Zero-terminate the error string
83B8 STA brk_error_block,y ; Store zero terminator
83BB .generate_error_skip_no_suffix←1← 836A BEQ
LDA wksp_cur_channel ; Check for open channel suffix
83BE BEQ raise_brk_error ; No channel active, skip
83C0 LDX #&0b ; X=&0B: copy 12-char ' on channel '
83C2 DEY ; Back up one position
83C3 .append_channel_suffix_loop←1← 83CB BPL
LDA str_on_channel,x ; Get char from reversed string
83C6 INY ; Advance position
83C7 STA brk_error_block,y ; Store in error block
83CA DEX ; Next character in reversed string
83CB BPL append_channel_suffix_loop ; Loop for 12 chars
83CD LDA wksp_cur_channel ; Get channel number
83D0 JSR error_append_dec ; Append as decimal digits
83D3 TYA ; Save current position
83D4 PHA ; Push Y on stack
83D5 LDA #&c6 ; OSBYTE &C6: read EXEC file handle
83D7 STA wksp_compaction_reported ; Store OSBYTE number in workspace
83DA JSR osbyte_y_ff_x_00 ; OSBYTE &C6: read/write EXEC handle
83DD CPX wksp_cur_channel ; Is EXEC on this channel?
83E0 PHP ; Save flags for comparison result
83E1 LDX #&99 ; X=&99: EXEC string address
83E3 PLP ; Restore flags
83E4 BEQ close_exec_or_spool ; Yes, close EXEC file
83E6 CPY wksp_cur_channel ; Is SPOOL on this channel?
83E9 BNE restore_error_position ; No, skip
83EB LDX #&9c ; Close SPOOL file (ptr at &9C)
83ED .close_exec_or_spool←1← 83E4 BEQ
JSR oscli_at_x ; Execute close via OSCLI
83F0 .restore_error_position←1← 83E9 BNE
PLA ; Restore Y (position)
83F1 TAY ; Transfer back to Y
83F2 .raise_brk_error←1← 83BE BEQ
LDA wksp_error_suppress ; Check for additional error handling
83F5 BNE copy_brk_block_loop ; Non-zero: skip workspace update
83F7 JSR load_dir_for_drive ; Update workspace checksum
83FA .copy_brk_block_loop←1← 83F5 BNE
LDA #0 ; Store BRK opcode at start of page 1
83FC STA brk_error_block ; Store BRK opcode (&00) at start
83FF STA brk_error_block_1,y ; Zero-terminate after channel suffix
8402 JSR release_tube ; Release Tube before raising error
8405 LDA brk_error_block_1 ; Check error code
8408 CMP #&c7 ; Is it &C7 (Disc error)?
840A BNE run_exec_or_spool ; No, just execute the BRK
840C LDX #&9c ; Close SPOOL before disc error
840E JSR oscli_at_x ; Execute OSCLI with string at X
8411 LDX #&99 ; Close EXEC before disc error
8413 JSR oscli_at_x ; Execute OSCLI with string at X
8416 JSR invalidate_fsm_and_dir ; Invalidate FSM/dir after disc error
8419 .run_exec_or_spool←1← 840A BNE
JMP brk_error_block ; Jump to BRK block on page 1

Error suffix string constants

Reversed string constants used when building error messages. str_at contains ': ta ' (reversed ' at :') appended to disc error messages, and str_on_channel contains ' lennahc no ' (reversed ' on channel') for channel-specific errors.

841C .str_at←1← 8386 LDA
EQUS ": ta "
8421 .str_on_channel←1← 83C3 LDA
EQUS " lennahc no "

Append byte as two hex digits to error block

Write the byte in A as two ASCII hex digits into the error block at the current position Y.

On EntryAbyte value to convert to hex
Yindex into brk_error_block
On ExitAASCII hex digit of low nibble
Xpreserved
Yadvanced by 2
842D .error_append_hex←2← 8376 JSR← 83AF JSR
PHA ; Save byte value
842E LSR ; Shift high nibble to low nibble
842F LSR ; (continued)
8430 LSR ; (continued)
8431 LSR ; (continued)
8432 JSR store_hex_nibble ; Output high nibble as hex digit
8435 PLA ; Restore original byte
8436 .store_hex_nibble←1← 8432 JSR
JSR hex_digit ; Convert low nibble and output
8439 INY ; Advance position in error block
843A STA brk_error_block,y ; Store hex digit character
843D RTS ; Return

Convert 4-bit value to ASCII hex digit

Convert a 4-bit value in A to an ASCII hex character ('0'-'9' or 'A'-'F'). The low nibble of A is used.

On EntryAvalue with hex digit in low nibble
On ExitAASCII hex character ('0'-'9' or 'A'-'F')
Xpreserved
Ypreserved
843E .hex_digit←3← 8396 JSR← 8436 JSR← 9324 JSR
AND #&0f ; Isolate low nibble
8440 ORA #&30 ; Merge with &30 for ASCII '0'-'?'
8442 CMP #&3a ; Result > '9' (i.e. A-F)?
8444 BCC return_6 ; No, digit is 0-9, done
8446 ADC #6 ; Add 7 to get 'A'-'F' (6 + carry)
8448 .return_6←1← 8444 BCC
RTS ; Return

Append byte as decimal digits to error block

Write the byte in A as up to three decimal digits into the error block at the current position Y, suppressing leading zeros.

On EntryAbyte value to convert to decimal
Yindex into brk_error_block
On ExitAcorrupted
Xcorrupted
Yadvanced past decimal digits
8449 .error_append_dec←2← 8380 JSR← 83D0 JSR
BIT divide_loop ; Set V flag for leading zero suppress
844C LDX #&64 ; X=100: divide by hundreds
844E JSR print_decimal_digit ; Output hundreds digit
8451 LDX #&0a ; X=10: divide by tens
8453 JSR print_decimal_digit ; Output tens digit
8456 CLV ; Clear V: always show units digit
8457 LDX #1 ; X=1: divide by ones
8459 .print_decimal_digit←2← 844E JSR← 8453 JSR
PHP ; Save V flag (leading zero suppress)
845A STX zp_mem_ptr_hi ; Store divisor
845C LDX #&2f ; X='/': ASCII digit will be X+1
845E SEC ; Set carry for subtraction
845F .divide_loop←2← 8449 BIT← 8462 BCS
INX ; Increment quotient digit
8460 SBC zp_mem_ptr_hi ; Subtract divisor
8462 BCS divide_loop ; Loop while result >= 0
8464 ADC zp_mem_ptr_hi ; Add divisor back (went too far)
8466 PLP ; Restore V flag
8467 PHA ; Save remainder
8468 TXA ; Get ASCII digit
8469 BVC store_digit ; V set: suppress leading zeros
846B CMP #&30 ; Is it '0'?
846D BEQ skip_leading_zero ; Yes, skip (suppress leading zero)
846F CLV ; Not zero: clear V, show from now on
8470 .store_digit←1← 8469 BVC
INY ; Advance position
8471 STA brk_error_block,y ; Store decimal digit
8474 .skip_leading_zero←1← 846D BEQ
PLA ; Restore remainder
8475 RTS ; Return

Mark FSM and directory as invalid

Set flags to indicate that the in-memory free space map and directory buffer may be stale and need reloading from disc.

On ExitAzero
Xcorrupted
Yzero
8476 .invalidate_fsm_and_dir←3← 82AB JSR← 8416 JSR← 9C0F JSR
LDX #&0c ; X=&0C: clear 12 workspace bytes
8478 LDA #&ff ; A=&FF: invalid marker
847A .invalidate_sectors_loop←1← 8481 BNE
STA wksp_csd_sector_temp,x ; Invalidate drive/sector workspace
847D STA wksp_csd_sector,x ; Invalidate CSD/lib/prev sectors
8480 DEX ; Next byte
8481 BNE invalidate_sectors_loop ; Loop for 12 bytes
8483 JSR copy_default_dir_name ; Reset CSD name to default
8486 JSR copy_default_dir_name ; Copy default directory name to workspace
8489 LDY #0 ; Y=0: loop counter
848B TYA ; A=0: zero fill
848C .zero_buffers_loop←1← 8496 BNE
STA fsm_sector_1,y ; Zero FSM sector 1 buffer
848F STA fsm_sector_0,y ; Zero FSM sector 0 buffer
8492 STA dir_buffer,y ; Zero directory buffer
8495 INY ; Next byte
8496 BNE zero_buffers_loop ; Loop for 256 bytes
8498 RTS ; Return

OSCLI abbreviation strings

CR-terminated command abbreviation strings passed to OSCLI: str_exec_abbrev = 'E.' (*EXEC), str_spool_abbrev = 'SP.' (*SPOOL). Also includes str_yes (reversed 'YES' + CR for *DESTROY confirmation) and str_hugo (NUL + 'Hugo' directory identity string).

8499 .str_exec_abbrev
EQUS "E.." ; "E." + CR: *EXEC abbreviation
849C .str_spool_abbrev
EQUS "SP.." ; "SP." + CR: *SPOOL abbreviation

Call OSBYTE to read current value

Call OSBYTE with Y=&FF and X=0 to read the current value of the variable specified in A.

On EntryAOSBYTE number
On ExitAcorrupted
XOSBYTE result low byte
YOSBYTE result high byte
84A0 .osbyte_y_ff_x_00←3← 83DA JSR← 9BA7 JSR← 9C79 JSR
LDY #&ff ; Y=&FF: read current value
84A2 .osbyte_x_00
LDX #0 ; X=0: no modification
84A4 JMP osbyte ; Call OSBYTE

Execute OSCLI with string at X

Call OSCLI with the command string address in X (low byte).

On EntryXlow byte of command string address in page &84
On ExitAcorrupted
Xcorrupted
Ycorrupted
84A7 .oscli_at_x←3← 83ED JSR← 840E JSR← 8413 JSR
LDY #&84 ; Y=&84: high byte (string in this ROM)
84A9 JMP oscli ; Call OSCLI with (X,Y) address
84AC .str_yes←1← 9A18 CMP
EQUS "." ; CR + "SEY": reversed "YES" + CR
84AD EQUS "SEY"
84B0 .str_hugo←2← 95F9 LDA← A6F1 LDA
EQUS "." ; NUL + "Hugo": directory identity
84B1 EQUS "Hugo"

Release disc space back to free space map

Return the disc space occupied by the object at wksp_object_sector (3 bytes) with size at &1037-&1039 (3 bytes) back to the free space map. Searches for the correct position in the sorted FSM and merges with adjacent free entries where possible.

On EntryNOTEwksp_object_sector and wksp_object_size set in workspace
On ExitAcorrupted
Xcorrupted
Ycorrupted
84B5 .release_disc_space←5← 8F49 JMP← 9235 JSR← 9892 JSR← AF08 JSR← B45F JSR
LDA wksp_object_size ; Check if object has non-zero size
84B8 ORA wksp_object_size_mid ; OR with size mid byte
84BB ORA wksp_object_size_hi ; OR with size high byte
84BE BNE find_fsm_position ; Size is zero, nothing to release
84C0 RTS ; Size is zero: return
84C1 .find_fsm_position←1← 84BE BNE
LDX #0 ; X=0: start of FSM entries
84C3 .scan_fsm_entries_loop←1← 84DA BNE
CPX fsm_s1_total_sectors_lo ; Past end of free space list?
84C6 BCS insert_new_fsm_entry ; Yes, insert at end
84C8 INX ; Advance X by 3 (entry size)
84C9 INX ; Advance X (2nd byte of entry)
84CA INX ; Advance X (3rd byte of entry)
84CB STX zp_mem_ptr_lo ; Save X for backtrack
84CD LDY #2 ; Y=2: compare 3-byte address
84CF .compare_fsm_addr_loop←1← 84DF BPL
DEX ; Back up X to compare bytes
84D0 LDA fsm_sector_0,x ; Get FSM entry address byte
84D3 CMP wksp_object_sector,y ; Compare with object sector byte
84D6 BCS check_exact_match ; FSM entry >= object? Found position
84D8 LDX zp_mem_ptr_lo ; Restore X, try next entry
84DA BNE scan_fsm_entries_loop ; Try next FSM entry
84DC .check_exact_match←1← 84D6 BCS
BNE found_insertion_point ; Exact match on this byte?
84DE DEY ; Compare next byte down
84DF BPL compare_fsm_addr_loop ; Continue comparing bytes
84E1 .found_insertion_point←1← 84DC BNE
LDX zp_mem_ptr_lo ; Back to entry start
84E3 DEX ; Back up to entry start
84E4 DEX ; 2nd byte back
84E5 DEX ; 3rd byte back
84E6 STX zp_mem_ptr_lo ; Save entry index for merge check
84E8 CLC ; C=0 for addition
84E9 PHP ; Save carry for multi-byte add
84EA LDY #0 ; Y=0: compare 3 address bytes
84EC .check_adjacent_to_next_loop←1← 8501 BNE
PLP ; Restore carry
84ED LDA wksp_object_sector,y ; Object sector + object size
84F0 ADC wksp_object_size,y ; Add object size byte
84F3 PHP ; Save carry
84F4 CMP fsm_sector_0,x ; Compare with FSM entry address
84F7 BEQ adjacent_next_byte ; Match? Object is adjacent to entry
84F9 PLP ; Restore carry after mismatch
84FA .insert_new_fsm_entry←1← 84C6 BCS
JMP check_merge_with_prev ; No match, insert new entry
84FD .adjacent_next_byte←1← 84F7 BEQ
INX ; Next compare byte
84FE INY ; Next object sector byte
84FF CPY #3 ; Compared all 3 bytes?
8501 BNE check_adjacent_to_next_loop ; No, continue comparing
8503 PLP ; Restore carry from addition
8504 LDX zp_mem_ptr_lo ; Get FSM entry index back
8506 BEQ add_size_to_existing_entry ; Entry 0: no preceding entry to merge
8508 CLC ; Clear carry for addition
8509 PHP ; Save carry for multi-byte add
850A LDY #0 ; Y=0: compare bytes of prev+size
850C .check_adjacent_to_prev_loop←1← 8523 BNE
PLP ; Restore carry
850D LDA fsm_s0_pre3,x ; Get prev entry address byte
8510 ADC fsm_s0_boot_option,x ; Add prev entry length byte
8513 PHP ; Save carry
8514 CMP wksp_object_sector,y ; Compare prev+size with object sector
8517 BEQ adjacent_prev_byte ; Match: prev is adjacent (merge back)
8519 LDX zp_mem_ptr_lo ; No match: insert new entry
851B PLP ; Restore carry
851C JMP add_size_to_existing_entry ; Not adjacent: insert new entry
851F .adjacent_prev_byte←1← 8517 BEQ
INX ; Next byte
8520 INY ; Next object sector byte
8521 CPY #3 ; Compared all 3 bytes?
8523 BNE check_adjacent_to_prev_loop ; No, continue
8525 PLP ; Adjacent to prev: merge backward
8526 LDX zp_mem_ptr_lo ; Restore FSM index
8528 LDY #0 ; Y=0: add released size to prev length
852A CLC ; Clear carry for addition
852B PHP ; Save carry
852C .merge_with_prev_loop←1← 853B BNE
PLP ; Restore carry
852D LDA fsm_s0_boot_option,x ; Get prev entry length byte
8530 ADC wksp_object_size,y ; Add released size byte
8533 STA fsm_s0_boot_option,x ; Store updated length
8536 PHP ; Save carry for next byte
8537 INX ; Next entry byte
8538 INY ; Next size byte
8539 CPY #3 ; All 3 bytes?
853B BNE merge_with_prev_loop ; No, continue adding
853D PLP ; Restore carry
853E LDY #2 ; Y=2: check if merged entry is now
8540 LDX zp_mem_ptr_lo ; adjacent to the NEXT entry too
8542 CLC ; Clear carry for addition
8543 .check_triple_merge_loop←1← 854E BPL
LDA fsm_s0_boot_option,x ; Get merged entry address byte
8546 ADC fsm_sector_1,x ; Add merged entry length byte
8549 STA fsm_s0_boot_option,x ; Store sum (prev+released+next?)
854C INX ; Next byte
854D DEY ; Decrement counter
854E BPL check_triple_merge_loop ; Loop for 3 bytes
8550 .shift_entries_down_loop←1← 8562 BNE
CPX fsm_s1_total_sectors_lo ; Check if past end of FSM list
8553 BCS shrink_fsm_list ; Yes: shrink list by removing entry
8555 LDA fsm_sector_1,x ; Get next entry length
8558 STA fsm_s0_boot_option,x ; Store over current (shift down)
855B LDA fsm_sector_0,x ; Get next entry address
855E STA fsm_s0_pre3,x ; Store over current (shift down)
8561 INX ; Next entry
8562 BNE shift_entries_down_loop ; Loop shifting entries
8564 .shrink_fsm_list←1← 8553 BCS
DEX ; Adjust end-of-list pointer
8565 DEX ; Back 3 bytes
8566 DEX ; Back 3 bytes total
8567 STX fsm_s1_total_sectors_lo ; Store new end-of-list pointer
856A RTS ; Return

Add released size to FSM entry

Copy the object sector address and add the released block size to an existing FSM length entry, merging adjacent free regions.

On EntryXFSM entry index into sector 0/1 buffers
On ExitAcorrupted
Xcorrupted
Y3
856B .add_size_to_existing_entry←2← 8506 BEQ← 851C JMP
LDY #0 ; Y=0: copy+add 3-byte address+length
856D CLC ; Clear carry for addition
856E PHP ; Save carry for multi-byte operation
856F .merge_forward_loop←1← 8584 BNE
LDA wksp_object_sector,y ; Get object sector byte
8572 STA fsm_sector_0,x ; Store as FSM entry address
8575 PLP ; Restore carry from prev iteration
8576 LDA fsm_sector_1,x ; Get current FSM length byte
8579 ADC wksp_object_size,y ; Add released size byte
857C STA fsm_sector_1,x ; Store updated length
857F PHP ; Save carry
8580 INY ; Next byte
8581 INX ; Next FSM byte
8582 CPY #3 ; All 3 bytes?
8584 BNE merge_forward_loop ; No, continue
8586 PLP ; Restore final carry
8587 RTS ; Return (merge complete)
8588 .check_merge_with_prev←1← 84FA JMP
LDX zp_mem_ptr_lo ; Get FSM entry index
858A BEQ insert_new_entry ; Entry 0: no predecessor, insert new
858C CLC ; Clear carry for addition
858D PHP ; Save carry for multi-byte add
858E LDY #0 ; Y=0: compare prev+size with object
8590 .compare_prev_plus_size_loop←1← 85A5 BNE
PLP ; Restore carry
8591 LDA fsm_s0_pre3,x ; Get prev entry address byte
8594 ADC fsm_s0_boot_option,x ; Add prev entry length byte
8597 PHP ; Save carry
8598 CMP wksp_object_sector,y ; Compare with object sector byte
859B BEQ merge_size_into_prev ; Match: prev is adjacent
859D PLP ; Restore carry, no match
859E JMP insert_new_entry ; Not adjacent: insert new entry
85A1 .merge_size_into_prev←1← 859B BEQ
INX ; Next byte
85A2 INY ; Next object byte
85A3 CPY #3 ; All 3 bytes?
85A5 BNE compare_prev_plus_size_loop ; No, continue
85A7 PLP ; Restore carry (all matched)
85A8 LDY #0 ; Y=0: add released size to prev
85AA LDX zp_mem_ptr_lo ; Get FSM entry index
85AC CLC ; Clear carry for addition
85AD PHP ; Save carry for multi-byte add
85AE .add_size_to_prev_loop←1← 85BD BNE
PLP ; Restore carry
85AF LDA fsm_s0_boot_option,x ; Get prev entry length byte
85B2 ADC wksp_object_size,y ; Add released size byte
85B5 STA fsm_s0_boot_option,x ; Store updated length
85B8 PHP ; Save carry
85B9 INX ; Next FSM byte
85BA INY ; Next size byte
85BB CPY #3 ; All 3 bytes?
85BD BNE add_size_to_prev_loop ; No, continue
85BF PLP ; Restore carry
85C0 RTS ; Return (merge with prev complete)

Insert new entry into FSM

Check for room in the FSM. If full, raise Map full error. Otherwise shift entries up and insert the new entry at the correct sorted position.

On EntryNOTEzp_mem_ptr_lo = insertion point index in FSM
On ExitAcorrupted
Xcorrupted
Ycorrupted
85C1 .insert_new_entry←2← 858A BEQ← 859E JMP
LDA fsm_s1_total_sectors_lo ; Get end-of-list pointer
85C4 CMP #&f6 ; Room for new entry (< &F6)?
85C6 BCC shift_entries_up_start ; Yes: proceed with insert
85C8 JSR generate_disc_error ; Save drive state and raise error
85CB EQUB &99 ; Error &99: Map full
85CC EQUS "Map full."
85D5 .shift_entries_up_start←1← 85C6 BCC
LDX fsm_s1_total_sectors_lo ; Get end-of-list pointer
85D8 .shift_entries_up_loop←1← 85E9 JMP
CPX zp_mem_ptr_lo ; Reached insertion point?
85DA BEQ store_new_entry ; Yes: insert here
85DC DEX ; Shift entries up by 3 bytes
85DD LDA fsm_sector_0,x ; Get FSM address byte to shift
85E0 STA fsm_s0_first_length,x ; Store 3 bytes higher
85E3 LDA fsm_sector_1,x ; Get FSM length byte to shift
85E6 STA fsm_s1_first_length,x ; Store 3 bytes higher
85E9 JMP shift_entries_up_loop ; Continue shifting
85EC .store_new_entry←1← 85DA BEQ
LDY #0 ; Y=0: store new entry at gap
85EE .store_new_entry_loop←1← 85FE BNE
LDA wksp_object_sector,y ; Get object sector byte
85F1 STA fsm_sector_0,x ; Store as FSM entry address
85F4 LDA wksp_object_size,y ; Get released size byte
85F7 STA fsm_sector_1,x ; Store as FSM entry length
85FA INX ; Next byte
85FB INY ; Next source byte
85FC CPY #3 ; All 3 bytes?
85FE BNE store_new_entry_loop ; No, continue
8600 LDA fsm_s1_total_sectors_lo ; Get end-of-list pointer
8603 ADC #2 ; Add 3 (new entry size)
8605 STA fsm_s1_total_sectors_lo ; Store updated pointer
8608 .return_7←1← 8617 BEQ
RTS ; Return

Sum all free space in FSM

Walk the FSM entries accumulating the 3-byte length of each free extent into workspace &105D-&105F.

8609 .sum_free_space←2← 8642 JSR← A1B7 JSR
LDX #0 ; X=0: start scanning FSM
860B STX wksp_access_accum ; Clear accumulator low byte
860E STX wksp_access_accum_1 ; Clear accumulator mid byte
8611 STX wksp_free_space_total ; Clear accumulator high byte
8614 .sum_fsm_entries_loop←1← 862F JMP
CPX fsm_s1_total_sectors_lo ; Past end of FSM entries?
8617 BEQ return_7 ; Yes: return total
8619 LDY #0 ; Y=0: sum this 3-byte entry
861B CLC ; Clear carry for addition
861C PHP ; Save carry for multi-byte add
861D .sum_entry_bytes_loop←1← 862C BNE
PLP ; Restore carry
861E LDA fsm_sector_1,x ; Get FSM length byte
8621 ADC wksp_access_accum,y ; Add to accumulator
8624 STA wksp_access_accum,y ; Store updated accumulator
8627 PHP ; Save carry for next byte
8628 INY ; Next accumulator byte
8629 INX ; Next FSM byte
862A CPY #3 ; All 3 bytes?
862C BNE sum_entry_bytes_loop ; No, continue
862E PLP ; Restore carry
862F JMP sum_fsm_entries_loop ; Loop for next FSM entry

Allocate disc space from free space map

Find the best-fit free entry for the requested size at &103D-&103F. Generates Disc full or Compaction required errors if allocation is not possible.

8632 .allocate_disc_space←3← 8F55 JSR← 9895 JSR← AF0B JSR
LDX #&ff ; X=&FF: no best-fit entry yet
8634 STX zp_mem_ptr_hi ; Store as best-fit index
8636 INX ; X=&00
8637 .scan_for_best_fit←1← 8705 JMP
CPX fsm_s1_total_sectors_lo ; Past end of FSM entries?
863A BCC compare_entry_size ; No: check this entry
863C LDX zp_mem_ptr_hi ; Get best-fit index
863E CPX #&ff ; Still &FF (no fit found)?
8640 BNE use_best_fit_entry ; Found a fit: use it
8642 JSR sum_free_space ; No fit: sum all free space
8645 LDY #0 ; Y=0: compare total vs requested
8647 LDX #2 ; X=2: compare 3 bytes
8649 SEC ; Set carry for subtraction
864A .compare_total_vs_requested←1← 8652 BPL
LDA wksp_access_accum,y ; Get total free space byte
864D SBC wksp_alloc_size,y ; Subtract requested size byte
8650 INY ; Next byte
8651 DEX ; Next requested byte
8652 BPL compare_total_vs_requested ; All 3 bytes?
8654 BCS compaction_required_error ; Total >= requested: space exists
8656 .disc_full_error←2← 8F3A JMP← AED4 JMP
JSR generate_disc_error ; Not enough: Disc full error
8659 EQUB &C6 ; Error &C6: Disc full
865A EQUS "Disc full."
8664 .compaction_required_error←1← 8654 BCS
JSR generate_disc_error ; Compaction needed: error
8667 EQUB &98 ; Error &98: Compaction required
8668 EQUS "Compaction required."
867C .use_best_fit_entry←1← 8640 BNE
LDY #2 ; Y=2: copy best-fit entry sector addr
867E .copy_allocated_sector_loop←1← 8686 BPL
DEX ; Back up to entry start
867F LDA fsm_sector_0,x ; Get FSM address byte
8682 STA wksp_alloc_sector,y ; Store as allocated sector
8685 DEY ; Next byte
8686 BPL copy_allocated_sector_loop ; Loop for 3 bytes
8688 INY ; Y=1 (adjusted for carry)
8689 LDX zp_mem_ptr_hi ; Restore best-fit index
868B CLC ; Clear carry for addition
868C PHP ; Save carry
868D .advance_entry_addr_loop←1← 869C BNE
PLP ; Restore carry
868E LDA fsm_s0_pre3,x ; Get entry address byte
8691 ADC wksp_alloc_size,y ; Add requested size to advance addr
8694 STA fsm_s0_pre3,x ; Store updated entry address
8697 PHP ; Save carry
8698 INX ; Next entry byte
8699 INY ; Next requested byte
869A CPY #3 ; All 3 bytes?
869C BNE advance_entry_addr_loop ; No, continue
869E PLP ; Restore carry
869F LDY #0 ; Y=0: subtract requested from length
86A1 LDX zp_mem_ptr_hi ; Get best-fit index
86A3 SEC ; Set carry for subtraction
86A4 PHP ; Save carry
86A5 .subtract_from_length_loop←1← 86B4 BNE
PLP ; Restore carry
86A6 LDA fsm_s0_boot_option,x ; Get entry length byte
86A9 SBC wksp_alloc_size,y ; Subtract requested size
86AC STA fsm_s0_boot_option,x ; Store reduced length
86AF PHP ; Save carry
86B0 INX ; Next entry byte
86B1 INY ; Next requested byte
86B2 CPY #3 ; All 3 bytes?
86B4 BNE subtract_from_length_loop ; No, continue
86B6 PLP ; Restore carry
86B7 RTS ; Return (allocation complete)
86B8 .compare_entry_size←1← 863A BCC
LDY #2 ; Y=2: compare entry length backwards
86BA INX ; Advance X to entry+3
86BB INX ; 2nd byte
86BC INX ; 3rd byte
86BD STX zp_mem_ptr_lo ; Save entry end index
86BF .compare_size_bytes_loop←1← 86CB BPL
DEX ; Back up one byte
86C0 LDA fsm_sector_1,x ; Get entry length byte
86C3 CMP wksp_alloc_size,y ; Compare with requested size
86C6 BCC continue_scanning ; Entry < requested: too small
86C8 BNE check_if_first_fit ; Not equal: entry is larger
86CA DEY ; Next byte (decreasing Y)
86CB BPL compare_size_bytes_loop ; Loop for 3 bytes
86CD LDX zp_mem_ptr_lo ; Exact match: use this entry
86CF LDY #2 ; Y=2: copy entry address
86D1 .copy_exact_match_addr_loop←1← 86D9 BPL
DEX ; Back up
86D2 LDA fsm_sector_0,x ; Get entry address byte
86D5 STA wksp_alloc_sector,y ; Store as allocated sector
86D8 DEY ; Next byte
86D9 BPL copy_exact_match_addr_loop ; Loop for 3 bytes
86DB LDX zp_mem_ptr_lo ; Restore entry index
86DD .remove_exact_entry_loop←1← 86EF BNE
CPX fsm_s1_total_sectors_lo ; Past end of entries?
86E0 BCS shrink_list_after_exact ; Yes: shrink list
86E2 LDA fsm_sector_0,x ; Shift entries down
86E5 STA fsm_s0_pre3,x ; Store 3 bytes lower (addresses)
86E8 LDA fsm_sector_1,x ; Get length entry to shift
86EB STA fsm_s0_boot_option,x ; Store 3 bytes lower (lengths)
86EE INX ; Next entry
86EF BNE remove_exact_entry_loop ; Loop shifting entries
86F1 .shrink_list_after_exact←1← 86E0 BCS
LDA fsm_s1_total_sectors_lo ; Get end-of-list pointer
86F4 SBC #3 ; Subtract 3 (removed entry)
86F6 STA fsm_s1_total_sectors_lo ; Store updated pointer
86F9 RTS ; Return (exact match used)
86FA .check_if_first_fit←1← 86C8 BNE
LDX zp_mem_ptr_hi ; Get current best-fit
86FC INX ; X+1: was &FF (no fit yet)?
86FD BNE continue_scanning ; Non-zero: this entry is new best
86FF LDA zp_mem_ptr_lo ; No previous fit: store this one
8701 STA zp_mem_ptr_hi ; Store as best-fit index
8703 .continue_scanning←2← 86C6 BCC← 86FD BNE
LDX zp_mem_ptr_lo ; Restore entry index
8705 JMP scan_for_best_fit ; Continue scanning

Advance text pointer by one character

Increment the 16-bit text pointer at (&B4) by one, handling page crossing.

8708 .advance_text_ptr←3← 885A JSR← 8872 JSR← 88C1 JSR
INC zp_text_ptr_lo ; Increment pointer low byte
870A BNE return_8 ; No page crossing: return
870C INC zp_text_ptr_hi ; Increment pointer high byte
870E .return_8←1← 870A BNE
RTS ; Return

Parse argument and set up directory search

Skip leading spaces, set up directory search state, and clear the search result workspace. Falls through to check_char_is_terminator.

On ExitAfirst non-space character (Z set if terminator)
X0 if terminator, else preserved
Y0
870F .parse_and_setup_search←2← 884C JSR← 8851 JSR
JSR skip_spaces ; Skip leading spaces in command argument
8712 JSR set_up_directory_search ; Set up directory for search
8715 LDY #0 ; Y=0: clear search flag
8717 STY wksp_search_flag ; Store in workspace
fall through ↓

Check if character is a filename terminator

Test whether the character at (&B4),Y is a filename terminator: space, dot, double-quote, or control character.

On EntryYindex into text at (&B4)
On ExitAcharacter with bit 7 stripped (Z set if terminator)
X0 if terminator, else preserved
Ypreserved
871A .check_char_is_terminator←18← 872F JSR← 8767 JSR← 8782 JSR← 8787 JSR← 879C JSR← 8869 JSR← 88BA JSR← 88C6 JSR← 88DB JSR← 893B JSR← 8D70 JSR← 8D80 JSR← 8D9F JSR← 8DAB JSR← 8DD6 JSR← A4B9 JSR← A534 JSR← A867 JSR
LDA (zp_text_ptr_lo),y ; Get character, strip bit 7
871C AND #&7f ; Strip bit 7 of character
871E CMP #&2e ; Is it '.'?
8720 BEQ set_terminator_flag ; Yes, terminator
8722 CMP #&22 ; Is it a double-quote?
8724 BEQ set_terminator_flag ; Yes, terminator
8726 CMP #&20 ; Is it >= space?
8728 BCS return_9 ; Yes, not a terminator
872A .set_terminator_flag←2← 8720 BEQ← 8724 BEQ
LDX #0 ; X=0: signal terminator found
872C .return_9←1← 8728 BCS
RTS ; Return (not a terminator)

Check filename is within 10-character limit

Scan up to 10 characters of filename at (&B4),Y. Raises Bad name error if no terminator found within 10 characters. Then copies the directory entry name to the object name workspace.

On ExitAcorrupted (Z set if match after compare)
Xcorrupted
Ycorrupted
872D .check_filename_length←2← 87F6 JSR← 896F JSR
LDY #&0a ; Y=&0A: check up to 10 characters
872F .check_name_char_loop←1← 8735 BPL
JSR check_char_is_terminator ; Check next character
8732 BEQ name_length_ok ; Terminator found, ok
8734 DEY ; Decrement character count
8735 BPL check_name_char_loop ; Continue checking
8737 .bad_name_error←6← 8785 BNE← 8849 JMP← 8BD0 JMP← 8DDB JMP← A4E2 JMP← A86C JMP
JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
873A EQUB &CC ; Error &CC: Bad name
873B EQUS "Bad name."
8744 .name_length_ok←1← 8732 BEQ
LDY #9 ; Y=9: copy 10 bytes of entry name
8746 .copy_entry_name_loop←1← 874E BPL
LDA (zp_entry_ptr_lo),y ; Get name byte from directory entry
8748 AND #&7f ; Strip bit 7 (access bit)
874A STA wksp_object_name,y ; Store in object name workspace
874D DEY ; Next byte in entry name
874E BPL copy_entry_name_loop ; Loop for 10 bytes
8750 INY ; Y=1: start of object name
8751 LDX #0 ; X=0: pattern index
fall through ↓

Compare filename against pattern with wildcards

Compare the object name in workspace against the pattern at (&B4),Y. Supports '#' (match one char) and '*' (match rest) wildcards. Case-insensitive comparison.

On EntryXindex into wksp_object_name
Yindex into pattern at (&B4)
On ExitAcorrupted (Z set if match)
Xcorrupted
Ycorrupted
8753 .compare_filename←2← 877F BNE← 87BA JSR
CPX #&0a ; X >= 10? End of name reached
8755 BCS check_both_exhausted ; Yes, check pattern is also done
8757 LDA wksp_object_name,x ; Get object name character
875A CMP #&21 ; Control char in name? End of name
875C BCC check_both_exhausted ; Yes, name ended early
875E ORA #&20 ; Convert name char to lowercase
8760 STA wksp_csd_sector_temp ; Store for comparison
8763 CPY #&0a ; Y >= 10? Pattern exhausted
8765 BCS check_pattern_exhausted ; Yes, check if pattern terminated
8767 JSR check_char_is_terminator ; Check if pattern char is terminator
876A BEQ check_hash_wildcard ; Yes, compare lengths
876C CMP #&2a ; Pattern char is '*' wildcard?
876E BEQ begin_star_match ; Yes, match rest of name
8770 CMP #&23 ; Pattern char is '#' wildcard?
8772 BEQ advance_pattern_index ; Yes, match any single char
8774 ORA #&20 ; Convert pattern char to lowercase
8776 CMP wksp_csd_sector_temp ; Compare with name char
8779 BCC check_hash_wildcard ; Pattern < name? No match
877B BNE return_10 ; Pattern != name? No match
877D .advance_pattern_index←1← 8772 BEQ
INX ; Match: advance both pointers
877E INY ; Advance Y (pattern index)
877F BNE compare_filename ; Compare filename against pattern with wildcards
8781 .return_10←3← 877B BNE← 879A BEQ← 879F BEQ
RTS ; Return
8782 .check_pattern_exhausted←1← 8765 BCS
JSR check_char_is_terminator ; Check if character is a filename terminator
8785 BNE bad_name_error ; Pattern check failed: Bad name
8787 .check_hash_wildcard←3← 876A BEQ← 8779 BCC← 8793 BPL
JSR check_char_is_terminator ; Check if character is a filename terminator
878A CMP #&23 ; Trailing '#' in pattern?
878C BEQ check_trailing_star ; Yes: match (name shorter than pattern)
878E CMP #&2a ; Trailing '*' in pattern?
8790 BEQ check_trailing_star ; Yes: match (wildcard eats rest)
8792 DEY ; Back up Y to try shorter match
8793 BPL check_hash_wildcard ; More positions to try
8795 CMP #&ff ; Compare &FF (force NE for no match)
8797 RTS ; Return Z clear (no match)

Check pattern and name both exhausted

After pattern ends, check whether the entry name has also ended. Returns Z set if the match succeeds.

8798 .check_both_exhausted←2← 8755 BCS← 875C BCC
CPY #&0a ; Pattern exhausted: check name too
879A BEQ return_10 ; Both at 10: exact match
879C JSR check_char_is_terminator ; Check if pattern char is terminator
879F BEQ return_10 ; Terminator: name matches
87A1 CMP #&2a ; Trailing '*': match
87A3 BEQ begin_star_match ; Is it '*'? Match rest
87A5 .check_trailing_star←2← 878C BEQ← 8790 BEQ
CMP #0 ; Compare 0 with 0 to set Z flag
87A7 RTS ; Return with Z flag result

Begin wildcard '*' matching

Skip past '*' in pattern and try matching the rest against each successive position in the entry name.

87A8 .begin_star_match←3← 876E BEQ← 87A3 BEQ← 87E3 BEQ
INY ; Skip past '*' in pattern
87A9 .try_star_position_loop←1← 87C4 BNE
LDA wksp_object_name,x ; Get object name char at X
87AC AND #&7f ; Strip bit 7
87AE CMP #&21 ; Control char: end of name
87B0 BCC check_name_ended ; End of name: check pattern trail
87B2 CPX #&0a ; X >= 10: end of name
87B4 BCS check_name_ended ; End of name: check pattern trail
87B6 TXA ; Save X (name position)
87B7 PHA ; Push on stack
87B8 TYA ; Save Y (pattern position)
87B9 PHA ; Push on stack
87BA JSR compare_filename ; Try matching from here (recursive)
87BD BEQ discard_saved_positions ; Z set: match succeeded
87BF PLA ; No match: restore Y
87C0 TAY ; Transfer to Y
87C1 PLA ; Restore X
87C2 TAX ; Transfer to X
87C3 INX ; Advance name position, try again
87C4 BNE try_star_position_loop ; Loop trying next position
87C6 .no_match_cleanup_loop←1← 87E5 BNE
CPX #0 ; Compare X with 0 to set Z flag
87C8 RTS ; Return with Z flag
87C9 .discard_saved_positions←1← 87BD BEQ
PLA ; Match: discard saved positions
87CA PLA ; Discard saved Y
fall through ↓

Return successful wildcard match

Set A=0 and carry to signal a successful match.

87CB .star_match_succeeded←4← 87D1 BCS← 87D7 BCC← 87DB BEQ← 87DF BEQ
LDA #0 ; A=0: set Z flag (match)
87CD SEC ; Set carry
87CE RTS ; Return Z set (match)

Check name ended during '*' match

After name is exhausted, check whether remaining pattern is only terminators. Returns Z set if match succeeds.

87CF .check_name_ended←2← 87B0 BCC← 87B4 BCS
CPY #&0a ; Name ended: check pattern trail
87D1 BCS star_match_succeeded ; Y >= 10: both exhausted, match
87D3 LDA (zp_text_ptr_lo),y ; Get pattern char
87D5 CMP #&21 ; Control char: pattern ended too
87D7 BCC star_match_succeeded ; Pattern ended: match
87D9 CMP #&2e ; Is it '.'?
87DB BEQ star_match_succeeded ; Dot: match (path separator)
87DD CMP #&22 ; Is it '"'?
87DF BEQ star_match_succeeded ; Quote: match (string end)
87E1 CMP #&2a ; Is it '*'?
87E3 BEQ begin_star_match ; Another '*': skip it and retry
87E5 BNE no_match_cleanup_loop ; Other char: no match (always)
87E7 .parse_pathname_entry←1← 88FF JSR
JSR skip_spaces ; Skip leading spaces
87EA JSR print_catalogue_header ; Point (&B6) to first dir entry
87ED JSR verify_dir_integrity ; Verify directory integrity
87F0 .begin_dir_entry_search←2← 8803 BCC← 8807 BNE
LDY #0 ; Y=0: start parsing pathname
87F2 LDA (zp_entry_ptr_lo),y ; Get first byte of entry
87F4 BEQ end_of_dir_entries ; Zero: end of entries
87F6 JSR check_filename_length ; Check name length and compare
87F9 BEQ return_11 ; Z set: exact match found
87FB BCC return_11 ; C clear: pattern < name, not found
87FD LDA zp_entry_ptr_lo ; Get entry pointer low
87FF ADC #&19 ; Add &19+C to advance past entry
8801 STA zp_entry_ptr_lo ; Store updated pointer
8803 BCC begin_dir_entry_search ; No page crossing: continue
8805 INC zp_entry_ptr_hi ; Increment page
8807 BNE begin_dir_entry_search ; Continue searching
8809 .end_of_dir_entries←1← 87F4 BEQ
CMP #&0f ; A=0 at end: compare with &0F
880B .return_11←2← 87F9 BEQ← 87FB BCC
RTS ; Return (Z clear = not found)

Disc operation templates for FSM and directory reads

Two overlapping disc operation control block templates that share common fields. The templates are copied to workspace &1014-&101F before issuing disc read commands.

disc_op_tpl_read_fsm (&880C, 10 bytes via l8816+offset): Read 2 sectors from sector 0 into &0E00 (FSM buffer). Used to reload the free space map from disc.

disc_op_tpl_read_dir (&8817, 11 bytes): Read 5 sectors from sector 2 into &1200 (directory buffer). Used to load a directory from disc.

The templates overlap at &8817-&881B, sharing the result byte (&01), host memory marker (&FFFF), and read command (&08). The zero byte at l8816 (&8816) provides padding when copying starts from &1014 instead of &1015.

880C .disc_op_tpl_read_fsm
EQUB &01 ; Result: &01 (default)
880D EQUB &00 ; Memory address low: &00
880E EQUB &0E ; Memory address high: &0E (-> &0E00 FSM buffer)
880F EQUB &FF ; Memory address byte 3: &FF (host memory)
8810 EQUB &FF ; Memory address byte 4: &FF (host memory)
8811 EQUB &08 ; Command: &08 (read sectors)
8812 EQUB &00 ; Sector high: &00
8813 EQUB &00 ; Sector mid: &00
8814 EQUB &00 ; Sector low: &00 (sector 0)
8815 EQUB &02 ; Sector count: &02 (2 sectors for FSM)
8816 .disc_op_tpl_padding←2← A7D6 LDA← A7F7 LDA
EQUB &00 ; Padding: &00 (for 12-byte copy from &1014)
8817 .disc_op_tpl_read_dir←4← 89B1 LDA← 89F4 LDA← 8F8E LDA← 94AA LDA
EQUB &01 ; Result: &01 (default)
8818 EQUB &00 ; Memory address low: &00
8819 EQUB &12 ; Memory address high: &12 (-> &1200 dir buffer)
881A EQUB &FF ; Memory address byte 3: &FF (host memory)
881B EQUB &FF ; Memory address byte 4: &FF (host memory)
881C EQUB &08 ; Command: &08 (read sectors)
881D EQUB &00 ; Sector high: &00
881E EQUB &00 ; Sector mid: &00
881F EQUB &02 ; Sector low: &02 (sector 2 = root dir)
8820 EQUB &05 ; Sector count: &05 (5 sectors per directory)
8821 EQUB &00 ; Control: &00

Parse drive number from ASCII character

Convert ASCII drive character ('0'-'7' or 'A'-'H') to a 3-bit drive ID in bits 5-7 of A. Limits to drives 0-3 if no hard drive present.

On EntryAASCII drive character ('0'-'7' or 'A'-'H')
On ExitAdrive ID (bits 5-7)
Xpreserved
Ypreserved
8822 .parse_drive_from_ascii←2← 886C JSR← A10A JSR
CMP #&30 ; Character >= '0'?
8824 BCC bad_drive_name ; Below '0': bad name
8826 CMP #&38 ; Character >= '8' (not digit)?
8828 BCC push_valid_drive ; Digit 0-7: valid drive
882A ORA #&20 ; Convert to lowercase
882C CMP #&61 ; Character >= 'a'?
882E BCC bad_drive_name ; Below 'a': bad name
8830 CMP #&69 ; Character >= 'i'?
8832 BCS bad_drive_name ; Above 'h': bad name
8834 SBC #0 ; Subtract to get drive number
8836 .push_valid_drive←1← 8828 BCC
PHA ; Save drive digit on stack
8837 LDA zp_adfs_flags ; Check for hard drive
8839 AND #&20 ; Bit 5: hard drive present?
883B BNE restore_drive_digit ; HD present: allow drives 0-7
883D PLA ; No HD: restore drive digit
883E AND #3 ; Mask to drives 0-3 only (floppy)
8840 PHA ; Re-push limited drive number
8841 .restore_drive_digit←1← 883B BNE
PLA ; Restore drive number
8842 AND #7 ; Mask to 3 bits (drives 0-7)
8844 LSR ; Shift into drive ID position
8845 ROR ; Rotate right
8846 ROR ; Rotate right
8847 ROR ; Rotate right (bits 5-7)
8848 RTS ; Return drive ID in A

Raise Bad name error for invalid drive

Jump to bad name error handler for an invalid drive specifier character.

8849 .bad_drive_name←4← 8824 BCC← 882E BCC← 8832 BCS← 884F BEQ
JMP bad_name_error ; Invalid: Bad name error

Parse filename from command line

Parse a filename from (&B4) including drive specifier, root, and parent directory references.

On ExitAcorrupted (Z set if found, (&B6) points to entry)
Xcorrupted
Ycorrupted
884C .parse_filename_from_cmdline←2← 8BB3 JSR← 8FDF JSR
JSR parse_and_setup_search ; Get filename from (&B4)
884F BEQ bad_drive_name ; Empty filename: bad name
8851 .full_pathname_parser←1← 947F JSR
JSR parse_and_setup_search ; Get first path character
8854 BEQ check_drive_initialised ; Empty: use current directory
8856 CMP #&3a ; Is it ':' (drive specifier)?
8858 BNE check_root_specifier ; No colon: check for $ or path
885A JSR advance_text_ptr ; Advance past ':'
885D LDX wksp_saved_drive ; Check if drive already saved
8860 INX ; Saved drive = &FF (not set)?
8861 BNE get_first_path_char ; Already set: keep it
8863 LDA wksp_current_drive ; Save current drive for restore
8866 STA wksp_saved_drive ; Store as saved drive
8869 .get_first_path_char←1← 8861 BNE
JSR check_char_is_terminator ; Get drive character
886C JSR parse_drive_from_ascii ; Parse drive number from ASCII
886F STA wksp_current_drive ; Store as new current drive
8872 .advance_past_colon←1← 88CD BEQ
JSR advance_text_ptr ; Advance past drive number
8875 .check_drive_initialised←1← 8854 BEQ
LDX wksp_current_drive ; Check drive is initialised
8878 INX ; Drive = &FF (uninitialised)?
8879 BNE set_fsm_loading_flag ; Not &FF: drive is valid
887B STX wksp_current_drive ; Set to drive 0 as default
887E .set_fsm_loading_flag←1← 8879 BNE
LDA zp_adfs_flags ; Set FSM-inconsistent flag (bit 4)
8880 ORA #&10 ; Bit 4: FSM being loaded
8882 STA zp_adfs_flags ; Store updated flags
8884 LDX #&0c ; Load FSM from disc (sectors 0-1)
8886 LDY #&88 ; Y=&88: FSM read control block page
8888 JSR exec_disc_command ; Read FSM from disc
888B LDA zp_adfs_flags ; Clear FSM-inconsistent flag
888D AND #&ef ; Mask off bit 4
888F STA zp_adfs_flags ; Store cleared flags
8891 LDA wksp_alt_sector_hi ; Check if alt workspace is set
8894 BPL load_root_directory ; Set: skip CSD copy
8896 LDY #2 ; Y=2: copy CSD sector to workspace
8898 .copy_csd_to_root_loop←1← 889F BPL
LDA wksp_csd_sector_lo,y ; Get CSD sector byte
889B STA wksp_csd_drive_sector,y ; Copy to CSD drive sector
889E DEY ; Next byte
889F BPL copy_csd_to_root_loop ; Loop for 3 bytes
88A1 .load_root_directory←1← 8894 BPL
LDY #&88 ; Load root directory (sector 2)
88A3 LDX #&17 ; X=&17: directory read block offset
88A5 JSR exec_disc_command ; Read directory from disc
88A8 LDA #2 ; Set root sector = 2
88AA STA wksp_csd_sector_lo ; Store root sector low
88AD LDA #0 ; A=0: clear mid and high bytes
88AF STA wksp_csd_sector_mid ; Clear sector mid byte
88B2 STA wksp_csd_sector_hi ; Clear sector high byte
88B5 JSR check_channels_on_drive ; Validate FSM checksums
88B8 LDY #0 ; Y=0: check next path character
88BA JSR check_char_is_terminator ; Get next character
88BD CMP #&2e ; Is it '.' (path separator)?
88BF BNE set_root_dir_entry ; No dot: this is the final component
88C1 JSR advance_text_ptr ; Skip past dot separator
88C4 .check_root_specifier←1← 8858 BNE
LDY #0 ; Y=0: check for $ or path component
88C6 JSR check_char_is_terminator ; Get character
88C9 AND #&fd ; Mask to check for $ (ignore bit 1)
88CB CMP #&24 ; Is it '$' (root directory)?
88CD BEQ advance_past_colon ; Yes: advance and load root
88CF JSR check_drive_and_reload_fsm ; Not root: load current directory
88D2 .check_special_dir_in_path←1← 89CD JMP
JSR check_special_dir_char ; Check for ^ or @ specifiers
88D5 BNE search_current_dir ; Not special: regular path component
88D7 INY ; Advance past ^ or @ character
88D8 STY wksp_copy_read_sector ; Store length marker
88DB JSR check_char_is_terminator ; Get next character
88DE CMP #&2e ; Is it '.' (more path follows)?
88E0 BNE return_12 ; No: this is the final component
88E2 JMP advance_text_past_component ; Jump to subdirectory descent
88E5 .set_root_dir_entry←1← 88BF BNE
LDA #&24 ; No dot after root: set up $ entry
88E7 STA wksp_object_name ; Store '$' as object name
88EA LDA #&0d ; CR padding
88EC STA wksp_object_name_1 ; Store CR after name
88EF LDA #&cc ; Point to dummy dir entry at &94CC
88F1 STA zp_entry_ptr_lo ; Store pointer low
88F3 LDA #&94 ; Pointer high = &94
88F5 STA zp_entry_ptr_hi ; Store pointer high
88F7 LDA #2 ; A=2: root sector number
88F9 STA wksp_search_flag ; Store as found sector
88FC LDA #0 ; A=0: success (Z set)
88FE RTS ; Return
88FF .search_current_dir←1← 88D5 BNE
JSR parse_pathname_entry ; Regular path: search current dir
8902 BEQ check_if_dir_entry ; Found? Proceed to check dir/file
8904 .return_12←1← 88E0 BNE
RTS ; Return (not found)

Save text pointer and determine object type

After a directory entry match, save the remaining text position and determine whether the entry is a file (type 1) or directory (type 2).

8905 .save_text_ptr_after_match←2← 8940 BCC← 8944 BEQ
LDA zp_text_ptr_lo ; Save current text pointer
8907 PHA ; Push low byte
8908 LDA zp_text_ptr_hi ; Get high byte
890A PHA ; Push high byte
890B TYA ; Transfer Y to A (matched length)
890C CLC ; Clear carry for addition
890D ADC zp_text_ptr_lo ; Add matched length to text pointer
890F STA zp_text_ptr_lo ; Store updated text pointer low
8911 LDA #0 ; A=0 for carry propagation
8913 ADC zp_text_ptr_hi ; Add carry to high byte
8915 STA zp_text_ptr_hi ; Store updated text pointer high
8917 JSR skip_spaces ; Skip spaces after path component
891A LDA zp_text_ptr_lo ; Save remaining text pointer
891C STA wksp_cmd_tail ; Store for later use
891F LDA zp_text_ptr_hi ; Get high byte
8921 STA wksp_cmd_tail_hi ; Store high byte
8924 PLA ; Restore original text pointer
8925 STA zp_text_ptr_hi ; Store high byte
8927 PLA ; Restore low byte
8928 STA zp_text_ptr_lo ; Store low byte
892A LDX #1 ; X=1: object type (file)
892C LDY #3 ; Y=3: check access byte
892E LDA (zp_entry_ptr_lo),y ; Get access/attribute byte
8930 BPL scan_for_component_end ; Bit 7 clear: not a directory
8932 INX ; Bit 7 set: X=2 (directory)
8933 .scan_for_component_end←1← 8930 BPL
STX wksp_search_flag ; Store object type
8936 LDA #0 ; A=0: success (Z set)
8938 RTS ; Return
8939 .check_if_dir_entry←1← 8902 BEQ
LDY #0 ; Y=0: scan for end of component
893B .scan_component_chars_loop←1← 894B BNE
JSR check_char_is_terminator ; Check next character
893E CMP #&21 ; Control char? End of component
8940 BCC save_text_ptr_after_match ; Yes: set up result
8942 CMP #&22 ; Double-quote? End of component
8944 BEQ save_text_ptr_after_match ; Yes: set up result
8946 CMP #&2e ; Dot? Path separator
8948 BEQ save_component_length ; Yes: descend into subdirectory
894A INY ; Next character
894B BNE scan_component_chars_loop ; Loop scanning
894D .save_component_length←1← 8948 BEQ
STY wksp_copy_read_sector ; Save component length
8950 .check_access_is_dir_loop←1← 8959 BEQ
LDY #3 ; Y=3: check if entry is directory
8952 LDA (zp_entry_ptr_lo),y ; Get access byte
8954 BMI descend_into_subdir ; Bit 7: is a directory
8956 JSR advance_dir_entry_ptr ; Not dir: advance to next entry
8959 BEQ check_access_is_dir_loop ; Found next entry: retry match
895B .next_entry_not_found←1← 896D BEQ
LDA #&ff ; A=&FF: return not found
895D RTS ; Return

Advance to next matching directory entry

Advance (&B6) by 26 bytes to the next directory entry, then check whether it matches the current search pattern.

On ExitAcorrupted (Z set if match, (&B6) points to entry)
Xcorrupted
Ycorrupted
895E .advance_dir_entry_ptr←8← 8956 JSR← 8972 BNE← 8BBA JSR← 948A JSR← 94F2 JSR← 99BE JSR← 9C4E JSR← A88C JSR
CLC ; Clear carry for addition
895F LDA zp_entry_ptr_lo ; Get entry pointer low
8961 ADC #&1a ; Add &1A (26 bytes per entry)
8963 STA zp_entry_ptr_lo ; Store updated pointer
8965 BCC compare_next_dir_entry ; No page crossing
8967 INC zp_entry_ptr_hi ; Increment page on overflow
8969 .compare_next_dir_entry←1← 8965 BCC
LDY #0 ; Y=0: check first byte
896B LDA (zp_entry_ptr_lo),y ; Get first byte of next entry
896D BEQ next_entry_not_found ; Zero: end of entries (not found)
896F JSR check_filename_length ; Compare against pattern
8972 BNE advance_dir_entry_ptr ; No match: try next entry
8974 RTS ; Match found: return
8975 .descend_into_subdir←1← 8954 BMI
LDY #9 ; Y=9: check last name byte
8977 LDA (zp_entry_ptr_lo),y ; Get name byte 9
8979 BPL advance_text_past_component ; Bit 7 clear: normal descent
897B AND #&7f ; Bit 7 set: clear it (bad rename?)
897D STA (zp_entry_ptr_lo),y ; Store cleaned name byte
897F JSR write_dir_and_validate ; Write directory back to disc
8982 .clean_dir_rename_bit←1← A500 JMP
JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
8985 EQUB &B0 ; Error &B0: Bad rename
8986 EQUS "Bad rename."
8991 .advance_text_past_component←2← 88E2 JMP← 8979 BPL
LDA wksp_copy_read_sector ; Get matched component length
8994 SEC ; Set carry (add 1 for separator)
8995 ADC zp_text_ptr_lo ; Add to text pointer
8997 STA zp_text_ptr_lo ; Store updated pointer low
8999 BCC check_alt_workspace_set ; No page crossing
899B INC zp_text_ptr_hi ; Increment page
899D .check_alt_workspace_set←1← 8999 BCC
LDA wksp_alt_sector_hi ; Check if alt workspace is set
89A0 CMP #&ff ; &FF: not set
89A2 BNE copy_disc_op_for_subdir ; Set: skip CSD copy
89A4 LDY #2 ; Y=2: copy CSD sector
89A6 .copy_csd_sector_after_descent←1← 89AD BPL
LDA wksp_csd_sector_lo,y ; Get CSD sector byte
89A9 STA wksp_csd_drive_sector,y ; Copy to CSD drive sector
89AC DEY ; Next byte
89AD BPL copy_csd_sector_after_descent ; Loop for 3 bytes
89AF .copy_disc_op_for_subdir←1← 89A2 BNE
LDX #&0a ; X=&0A: copy 11-byte disc op template
89B1 .copy_subdir_template_loop←1← 89B8 BPL
LDA disc_op_tpl_read_dir,x ; Get template byte from ROM
89B4 STA wksp_disc_op_result,x ; Store in disc op workspace
89B7 DEX ; Next byte
89B8 BPL copy_subdir_template_loop ; Loop for 11 bytes
89BA LDX #2 ; X=2: copy 3 sector address bytes
89BC LDY #&16 ; Y=&16: start sector in entry
89BE .copy_subdir_sector_loop←1← 89C8 BPL
LDA (zp_entry_ptr_lo),y ; Get sector byte from entry
89C0 STA wksp_disc_op_sector,x ; Store in disc op sector field
89C3 STA wksp_alt_csd_sector,y ; Also store in CSD info
89C6 INY ; Next entry byte
89C7 DEX ; Next sector byte
89C8 BPL copy_subdir_sector_loop ; Loop for 3 bytes
89CA JSR exec_disc_op_from_wksp ; Execute disc read command
89CD JMP check_special_dir_in_path ; Continue parsing path

Load object type and save workspace

Load object type from workspace and fall through to save_wksp_and_return.

On ExitAobject type (0=not found, 1=file, 2=directory)
Xcorrupted
Ycorrupted
89D0 .get_object_type_result←4← 8C5F JMP← 8CC6 JMP← 923B JMP← B360 JSR
LDA wksp_search_flag ; Get object type result
fall through ↓

Save workspace state and return result

Restore the original drive if changed, reload the FSM, restore alternative workspace if set, and save workspace with checksum. Return value passed via A on stack.

On EntryAresult value to preserve across save
On ExitAresult value from entry (preserved)
Xcorrupted
Ycorrupted
89D3 .save_wksp_and_return←34← 82D5 JSR← 8348 JSR← 8CC0 JMP← 9125 JMP← 914B JSR← 941C JMP← 94F7 JMP← 956D JMP← 9938 JMP← 99C6 JMP← 9A40 JMP← 9C1D JSR← 9C74 JSR← 9FF3 JMP← A3A6 JSR← A45D JMP← A488 JMP← A494 JMP← A4A8 JSR← A51B JSR← A579 JSR← A5E5 JMP← A63E JSR← A669 JSR← A682 JMP← A856 JSR← A891 JMP← A94E JSR← ADF2 JSR← B07A JSR← B2D9 JSR← B357 JSR← B3E7 JSR← B710 JSR
PHA ; Save on stack
89D4 LDA wksp_saved_drive ; Check if drive was changed
89D7 CMP #&ff ; Saved drive = &FF (not changed)?
89D9 BEQ check_alt_wksp_on_return ; Not changed: skip drive restore
89DB STA wksp_current_drive ; Restore original drive
89DE LDA #&ff ; A=&FF: clear saved drive marker
89E0 STA wksp_saved_drive ; Mark saved drive as unused
89E3 LDX #&0c ; X=&0C: FSM control block offset
89E5 LDY #&88 ; Y=&88: FSM control block page
89E7 JSR exec_disc_command ; Reload FSM for original drive
89EA .check_alt_wksp_on_return←1← 89D9 BEQ
LDA wksp_alt_sector_hi ; Check alt workspace pointer
89ED CMP #&ff ; &FF: not set
89EF BEQ save_workspace_checksum ; Not set: skip workspace restore
89F1 TAX ; Transfer to X
89F2 LDY #&0a ; Y=&0A: copy 11-byte template
89F4 .copy_alt_wksp_template_loop←1← 89FB BPL
LDA disc_op_tpl_read_dir,y ; Get template byte
89F7 STA wksp_disc_op_result,y ; Store in workspace
89FA DEY ; Next byte
89FB BPL copy_alt_wksp_template_loop ; Loop for 11 bytes
89FD STX wksp_csd_sector_hi ; Store alt sector high
8A00 STX wksp_disc_op_sector ; Store in disc op sector
8A03 LDA wksp_csd_drive_sector_mid ; Get alt sector mid
8A06 STA wksp_csd_sector_mid ; Store in CSD mid
8A09 STA wksp_disc_op_sector_mid ; Store in disc op mid
8A0C LDA wksp_csd_drive_sector ; Get CSD sector low
8A0F STA wksp_csd_sector_lo ; Store in CSD low
8A12 STA wksp_disc_op_sector_lo ; Store in disc op low
8A15 LDA #&ff ; A=&FF: clear alt workspace
8A17 STA wksp_alt_sector_hi ; Mark as unused
8A1A JSR exec_disc_op_from_wksp ; Read directory from disc
8A1D .save_workspace_checksum←1← 89EF BEQ
LDA zp_adfs_flags ; Save flags to workspace
8A1F STA wksp_flags_save ; Store in flags save area
8A22 JSR get_wksp_addr_ba ; Get workspace address into &BA
8A25 LDY #&fb ; Y=&FB: save 252 bytes of workspace
8A27 .save_wksp_page_loop←1← 8A2D BNE
LDA wksp_csd_name,y ; Get workspace byte
8A2A STA (zp_wksp_ptr_lo),y ; Store in saved workspace
8A2C DEY ; Next byte
8A2D BNE save_wksp_page_loop ; Loop until Y=0
8A2F LDA wksp_csd_name ; Get byte at Y=0 too
8A32 STA (zp_wksp_ptr_lo),y ; Store in saved workspace
8A34 JSR store_wksp_checksum_ba_y ; Update workspace checksum
8A37 LDX zp_osfile_ptr_lo ; Restore X from (&B8)
8A39 LDY zp_osfile_ptr_hi ; Restore Y from (&B9)
8A3B PLA ; Restore object type from stack
8A3C .return_13←1← 8A40 BEQ
RTS ; Return

Execute multi-sector disc command

Set up sector count and execute a disc read or write command. Rounds up partial counts for writes. Generates BRK on error.

On ExitA0 on success (Z set)
Xcorrupted
Ycorrupted
8A3D .multi_sector_disc_command←4← 8C59 JSR← 8F77 JSR← 962C JSR← B7D2 JSR
JSR check_disc_command_type ; Set up sector count and execute
8A40 BEQ return_13 ; Success: return Z set
8A42 JMP generate_error ; Generate a BRK error

Check command type and adjust sector count

For write commands with partial transfers, round up the sector count. For reads, skip the adjustment.

8A45 .check_disc_command_type←2← 8A3D JSR← 9D5C JSR
LDA wksp_disc_op_command ; Get disc op command
8A48 CMP #8 ; Command 8 (read)?
8A4A BEQ exec_disc_transfer_batched ; Yes: check sector count
8A4C LDA wksp_disc_op_transfer_len ; Get partial transfer count
8A4F BEQ exec_disc_transfer_batched ; Zero: no partial, skip adjust
8A51 LDA #0 ; Clear partial transfer count
8A53 STA wksp_disc_op_transfer_len ; Store zero
8A56 INC wksp_disc_op_xfer_len_1 ; Increment full sector count
8A59 BNE exec_disc_transfer_batched ; No wrap
8A5B INC wksp_disc_op_xfer_len_2 ; Wrap: increment mid byte
8A5E BNE exec_disc_transfer_batched ; No wrap
8A60 INC wksp_disc_op_xfer_len_3 ; Wrap: increment high byte
fall through ↓

Execute disc transfer in batches

For transfers exceeding 255 sectors, loop with full batches. For the final batch, use the remaining count.

8A63 .exec_disc_transfer_batched←4← 8A4A BEQ← 8A4F BEQ← 8A59 BNE← 8A5E BNE
LDX #&15 ; X=&15: disc op block offset
8A65 LDY #&10 ; Y=&10: disc op block page
8A67 LDA #&ff ; Set sector count to &FF (max)
8A69 STA wksp_disc_op_sector_count ; Store max sector count
8A6C .single_sector_read←2← 8AA8 BCS← 8AB5 BCC
LDA wksp_disc_op_xfer_len_3 ; Check if total > 255 sectors
8A6F ORA wksp_disc_op_xfer_len_2 ; OR with mid byte
8A72 BEQ partial_sector_complete ; Both zero: <= 255, use exact count
8A74 JSR command_exec_xy ; Execute disc command with control block at (X,Y)
8A77 BNE return_14 ; Non-zero: use max (&FF), loop
8A79 LDA #&ff ; Advance transfer address by &FF pages
8A7B CLC ; Clear carry for addition
8A7C ADC wksp_disc_op_mem_addr_1 ; Add &FF to transfer addr mid
8A7F STA wksp_disc_op_mem_addr_1 ; Store updated mid
8A82 BCC calc_partial_start_sector ; No carry
8A84 INC wksp_disc_op_mem_addr_2 ; Carry: increment high
8A87 BNE calc_partial_start_sector ; No wrap
8A89 INC wksp_disc_op_mem_addr_3 ; Wrap: increment highest
8A8C .calc_partial_start_sector←2← 8A82 BCC← 8A87 BNE
LDA #&ff ; Advance disc sector by &FF
8A8E CLC ; Clear carry
8A8F ADC wksp_disc_op_sector_lo ; Add &FF to sector low
8A92 STA wksp_disc_op_sector_lo ; Store updated sector low
8A95 BCC calc_partial_end_sector ; No carry
8A97 INC wksp_disc_op_sector_mid ; Carry: increment sector mid
8A9A BNE calc_partial_end_sector ; No wrap
8A9C INC wksp_disc_op_sector ; Wrap: increment sector high
8A9F .calc_partial_end_sector←2← 8A95 BCC← 8A9A BNE
LDA wksp_disc_op_xfer_len_1 ; Subtract &FF from remaining count
8AA2 SEC ; Set carry for subtraction
8AA3 SBC #&ff ; Subtract &FF
8AA5 STA wksp_disc_op_xfer_len_1 ; Store updated count low
8AA8 BCS single_sector_read ; No borrow: loop for more chunks
8AAA LDA wksp_disc_op_xfer_len_2 ; Borrow: decrement mid byte
8AAD BNE exec_partial_sector_op ; Non-zero: adjust high too
8AAF DEC wksp_disc_op_xfer_len_3 ; Decrement high byte
8AB2 .exec_partial_sector_op←1← 8AAD BNE
DEC wksp_disc_op_xfer_len_2 ; Decrement mid byte
8AB5 BCC single_sector_read ; Continue chunked read
8AB7 .partial_sector_complete←1← 8A72 BEQ
LDA wksp_disc_op_xfer_len_1 ; Get remaining sector count
8ABA BEQ read_256_via_hd ; Zero: check for partial sector
8ABC STA wksp_disc_op_sector_count ; Non-zero: use as final chunk size
8ABF JSR command_exec_xy ; Execute disc command with control block at (X,Y)
8AC2 BNE return_14 ; Non-zero: execute this chunk
8AC4 .read_256_via_hd←1← 8ABA BEQ
LDA wksp_disc_op_transfer_len ; Get partial transfer count
8AC7 BNE load_sector_check_result ; Non-zero: execute partial sector
8AC9 .return_14←3← 8A77 BNE← 8AC2 BNE← 8AFE BEQ
RTS ; All done: return
8ACA .load_sector_check_result←1← 8AC7 BNE
STA wksp_disc_op_sector_count ; Store partial count as sector count
8ACD LDA wksp_disc_op_xfer_len_1 ; Advance transfer address by partial
8AD0 CLC ; Clear carry
8AD1 ADC wksp_disc_op_sector_lo ; Add partial count to sector
8AD4 STA wksp_disc_op_sector_lo ; Store updated sector
8AD7 BCC calc_multi_sector_count ; No carry
8AD9 INC wksp_disc_op_sector_mid ; Carry: increment mid
8ADC BNE calc_multi_sector_count ; No wrap
8ADE INC wksp_disc_op_sector ; Wrap: increment high
8AE1 .calc_multi_sector_count←2← 8AD7 BCC← 8ADC BNE
LDA wksp_disc_op_xfer_len_1 ; Advance transfer address
8AE4 CLC ; Clear carry
8AE5 ADC wksp_disc_op_mem_addr_1 ; Add to transfer addr
8AE8 STA wksp_disc_op_mem_addr_1 ; Store updated addr
8AEB BCC copy_sector_to_transfer ; No carry
8AED INC wksp_disc_op_mem_addr_2 ; Carry: increment high
8AF0 BNE copy_sector_to_transfer ; No wrap
8AF2 INC wksp_disc_op_mem_addr_3 ; Wrap: increment highest
8AF5 .copy_sector_to_transfer←2← 8AEB BCC← 8AF0 BNE
JSR wait_ensuring ; Wait while files are being ensured
8AF8 JSR command_set_retries ; Set retry count for disc operation
8AFB .copy_sector_count_loop←1← 8B02 BPL
JSR set_transfer_length ; Execute disc read with retry
8AFE BEQ return_14 ; Success: return
8B00 DEC zp_retry_count ; Decrement retry counter
8B02 BPL copy_sector_count_loop ; More retries: try again
8B04 .set_transfer_length←1← 8AFB JSR
LDX #&15 ; X=&15: disc op block offset
8B06 LDY #&10 ; Y=&10: disc op block page
8B08 STX zp_ctrl_blk_lo ; Store in (&B0)
8B0A STY zp_ctrl_blk_hi ; Store page in (&B1)
8B0C LDA wksp_current_drive ; Get current drive
8B0F ORA wksp_disc_op_sector ; OR into sector high byte
8B12 STA wksp_disc_op_sector ; Store updated sector+drive
8B15 STA wksp_current_drive_hi ; Store as current drive info
8B18 LDA zp_adfs_flags ; Get ADFS flags
8B1A AND #&20 ; Check bit 5: hard drive present?
8B1C BNE hd_command_partial_sector ; Hard drive partial sector transfer
fall through ↓

Floppy disc partial sector transfer

Transfer a partial sector (less than 256 bytes) to or from a floppy disc. Used for operations that don't align to sector boundaries.

8B1E .floppy_partial_sector←1← 8B44 BMI
LDA wksp_disc_op_sector ; Get sector address high byte
8B21 ORA wksp_current_drive ; Combine with current drive
8B24 STA wksp_err_sector_hi ; Store in error sector workspace
8B27 LDA wksp_disc_op_sector_mid ; Get sector address mid byte
8B2A STA wksp_err_sector_mid ; Store in error sector workspace
8B2D LDA wksp_disc_op_sector_lo ; Get sector address low byte
8B30 STA wksp_err_sector ; Store in error sector workspace
8B33 JSR calc_buffer_page_from_offset ; Calculate buffer offset
8B36 STA wksp_buf_flag,x ; Store partial transfer count
8B39 TXA ; Channel offset to A for buffer calc
8B3A LSR ; Divide by 4 for buffer page index
8B3B LSR ; (continued)
8B3C ADC #&17 ; Add buffer base page (&17)
8B3E JMP exec_floppy_partial_sector_buf_ind ; Execute floppy partial sector op

Hard drive partial sector transfer

Transfer a partial sector via the SCSI hard drive interface.

8B41 .hd_command_partial_sector←1← 8B1C BNE
LDA wksp_current_drive_hi ; Check zp_flags for hard drive
8B44 BMI floppy_partial_sector ; Bit 5 clear: floppy, use floppy path
8B46 JSR scsi_start_command ; Select SCSI target
8B49 LDA wksp_disc_op_mem_addr ; Get transfer address low from blk
8B4C STA zp_mem_ptr_lo ; Store in (&B2)
8B4E LDA wksp_disc_op_mem_addr_1 ; Get transfer address mid
8B51 STA zp_mem_ptr_hi ; Store in (&B3)
8B53 LDA wksp_disc_op_mem_addr_2 ; Get transfer address high
8B56 CMP #&fe ; Address >= &FE00?
8B58 BCC check_partial_sector_needed ; Below: might need Tube claim
8B5A LDA wksp_disc_op_mem_addr_3 ; Get next address byte
8B5D CMP #&ff ; Is it &FF (host memory)?
8B5F BEQ setup_partial_sector_buffer ; Yes: skip Tube claim
8B61 .check_partial_sector_needed←1← 8B58 BCC
JSR claim_tube ; Claim Tube if present
8B64 .setup_partial_sector_buffer←1← 8B5F BEQ
LDA wksp_disc_op_sector_count ; Get partial transfer byte count
8B67 TAX ; Save count in X
8B68 LDA #1 ; Set sector count to 1
8B6A STA wksp_disc_op_sector_count ; Only read one sector
8B6D LDA #8 ; SCSI read command = 8
8B6F STA wksp_disc_op_command ; Store command byte
8B72 LDY #0 ; Y=0: start of 6-byte command
8B74 .copy_partial_sector_loop←1← 8B7D BNE
LDA wksp_disc_op_command,y ; Get SCSI command byte
8B77 JSR scsi_send_byte_a ; Send byte A on SCSI bus after REQ
8B7A INY ; Next command byte
8B7B CPY #6 ; Sent all 6 bytes?
8B7D BNE copy_partial_sector_loop ; No, send next
8B7F BIT zp_adfs_flags ; Tube in use?
8B81 BVC complete_partial_write ; No Tube: skip Tube transfer setup
8B83 TXA ; Save byte count from X to A
8B84 PHA ; Push byte count on stack
8B85 LDX #&27 ; X=&27: Tube workspace offset
8B87 LDY #&10 ; Y=&10: Tube workspace page
8B89 LDA #1 ; A=1: Tube read transfer type
8B8B JSR tube_entry ; Start Tube transfer
8B8E PLA ; Restore byte count
8B8F TAX ; Back to X
8B90 .complete_partial_write←1← 8B81 BVC
LDY #0 ; Y=0: data transfer byte index
8B92 JSR scsi_wait_for_req ; Wait for SCSI REQ signal
8B95 BMI execute_partial_disc_op ; Status phase: transfer complete
8B97 .copy_write_data_loop←1← 8BAE BNE
LDA fred_hard_drive_0 ; Read byte from SCSI data bus
8B9A CPX #0 ; Byte count exhausted?
8B9C BEQ partial_read_from_disc ; Yes: discard remaining bytes
8B9E BIT zp_adfs_flags ; Tube in use?
8BA0 BVC check_write_or_read ; No Tube: store in memory
8BA2 JSR tube_delay2 ; Tube timing delay
8BA5 STA tube_data_register_3 ; Write byte to Tube R3
8BA8 BVS partial_write_to_disc ; Always branch (V always set here)
8BAA .check_write_or_read←1← 8BA0 BVC
STA (zp_mem_ptr_lo),y ; Store byte in memory buffer
8BAC .partial_write_to_disc←1← 8BA8 BVS
DEX ; Decrement remaining byte count
8BAD .partial_read_from_disc←1← 8B9C BEQ
INY ; Next transfer position
8BAE BNE copy_write_data_loop ; Loop for 256 bytes (full sector)
8BB0 .execute_partial_disc_op←1← 8B95 BMI
JMP command_done ; Status phase: get SCSI result

Search for non-directory file

Parse a filename and search the current directory for a matching non-directory entry.

On ExitAcorrupted (Z set if found, (&B6) points to entry)
Xcorrupted
Ycorrupted
8BB3 .search_for_file←5← 8C05 JSR← A3A1 JSR← A3B4 JSR← A40E JSR← A82F JSR
JSR parse_filename_from_cmdline ; Search for file in directory
8BB6 BEQ complete_partial_op ; Found? Check if it's a directory
8BB8 BNE return_15 ; Not found: return Z clear
8BBA .copy_partial_read_loop←1← 8BC3 BMI
JSR advance_dir_entry_ptr ; Skip directory entries
8BBD BNE return_15 ; Not found after dirs: return Z clear
8BBF .complete_partial_op←1← 8BB6 BEQ
LDY #3 ; Y=3: check access byte
8BC1 LDA (zp_entry_ptr_lo),y ; Get access byte from entry
8BC3 BMI copy_partial_read_loop ; Bit 7: is a directory, skip it
8BC5 .check_partial_sectors_done←1← 8BEE BPL
LDA #0 ; A=0: return Z set (found)
8BC7 .return_15←3← 8BB8 BNE← 8BBD BNE← 8BE8 BNE
RTS ; Return

Generate Not found error

Check for special directory characters in path and generate either Bad name or Not found error.

8BC8 .not_found_error←6← 8C08 BNE← 8CF6 JMP← 94EC JMP← 9942 JMP← A514 JMP← A834 JMP
LDY #0 ; Y=0: get first path char
8BCA LDA (zp_text_ptr_lo),y ; Get first character
8BCC CMP #&5e ; Is it '^' (parent)?
8BCE BNE file_is_locked_error ; No, check '@'
8BD0 .check_locked_loop←1← 8BD5 BEQ
JMP bad_name_error ; Bad name error (^ or @ in context)
8BD3 .file_is_locked_error←1← 8BCE BNE
CMP #&40 ; Is it '@' (current dir)?
8BD5 BEQ check_locked_loop ; Yes: Bad name error
8BD7 .bad_parms_error←3← 8297 JMP← 948F JMP← A0C0 JMP
JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
8BDA EQUB &D6 ; Error &D6: Not found
8BDB EQUS "Not found."
8BE5 .find_file_and_validate←3← 907F JSR← 9101 JSR← A50F JSR
JSR find_first_matching_entry ; Search for file
8BE8 BNE return_15 ; Not found: return
8BEA LDY #4 ; Y=4: check E attribute byte
8BEC LDA (zp_entry_ptr_lo),y ; Get access/E byte
8BEE BPL check_partial_sectors_done ; Bit 7 clear: not E, return found
8BF0 .validate_found_entry←4← 8C0E BPL← A41B JMP← B256 JMP← B2F8 JMP
JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
8BF3 EQUB &BD ; Error &BD: Access violation
8BF4 EQUS "Access violation."
fall through ↓

OSFILE A=0: check for existing file before save

Entry point for OSFILE save (A=0), reached via RTS-trick dispatch from osfile_handler. Searches the current directory for an existing file with the same name, checking it is not a directory and has the correct access attributes.

On entry: (&B4) points to filename, (&B8) to OSFILE control block On exit: Falls through to osfile_save_handler if file is valid

8C05 .osfile_save_check_existing
JSR search_for_file ; Search for matching non-directory file
8C08 BNE not_found_error ; Not found: report Not found error
8C0A LDY #0 ; Y=0: check first entry name byte
8C0C LDA (zp_entry_ptr_lo),y ; Get first byte of found entry
8C0E BPL validate_found_entry ; Bit 7 clear: no read access, error
8C10 .create_new_dir_entry←1← A41E JSR
LDY #6 ; Y=6: check control block byte 6
8C12 LDA (zp_osfile_ptr_lo),y ; Get byte 6
8C14 BNE allocate_space_for_file ; Non-zero: use entry's load address
8C16 DEY ; Y=5: copy bytes 2-5 from block
8C17 .clear_osfile_block_loop←1← 8C1F BNE
LDA (zp_osfile_ptr_lo),y ; Get control block byte
8C19 STA wksp_disc_op_block,y ; Store in disc op workspace
8C1C DEY ; Next byte
8C1D CPY #1 ; Past byte 1?
8C1F BNE clear_osfile_block_loop ; No, continue copying
8C21 BEQ write_dir_entry ; Done: skip to sector setup
8C23 .allocate_space_for_file←1← 8C14 BNE
LDX #4 ; X=4: copy 4 bytes from dir entry
8C25 LDY #&0d ; Y=&0D: entry offset for load addr
8C27 .copy_alloc_sector_loop←1← 8C2E BNE
LDA (zp_entry_ptr_lo),y ; Get byte from directory entry
8C29 STA wksp_disc_op_result,x ; Store in disc op workspace
8C2C DEY ; Next entry byte
8C2D DEX ; Next workspace byte
8C2E BNE copy_alloc_sector_loop ; Loop for 4 bytes
8C30 .write_dir_entry←1← 8C21 BEQ
LDA #1 ; Disc op result = 1
8C32 STA wksp_disc_op_result ; Store result
8C35 LDA #8 ; SCSI read command = 8
8C37 STA wksp_disc_op_command ; Store command byte
8C3A LDA #0 ; Clear control byte
8C3C STA wksp_disc_op_control ; Store control
8C3F LDY #&16 ; Y=&16: entry offset for start sector
8C41 LDX #3 ; X=3: copy 3+1 sector bytes
8C43 .copy_name_byte_loop←1← 8C4A BNE
LDA (zp_entry_ptr_lo),y ; Get sector byte from entry
8C45 STA wksp_disc_op_command,x ; Store in disc op command block
8C48 INY ; Next entry byte
8C49 DEX ; Next command byte
8C4A BNE copy_name_byte_loop ; Loop for 3 bytes
8C4C LDY #&15 ; Y=&15: entry offset for length
8C4E LDX #4 ; X=4: copy length bytes
8C50 .set_access_bits_loop←1← 8C57 BNE
LDA (zp_entry_ptr_lo),y ; Get length byte from entry
8C52 STA wksp_disc_op_control,x ; Store in control field
8C55 DEY ; Next byte
8C56 DEX ; Next control byte
8C57 BNE set_access_bits_loop ; Loop for 4 bytes
8C59 JSR multi_sector_disc_command ; Calculate sector count from length
8C5C .store_length_and_sector←1← 8F83 JMP
JSR search_dir_for_file ; Validate checksum and flags
8C5F JMP get_object_type_result ; Save workspace and return

Search directory for matching file

Copy catalogue data from the entry at (zp_entry_ptr) and search the current directory for a matching filename.

8C62 .search_dir_for_file←2← 8C5C JSR← A89E JSR
JSR conditional_info_display ; Display info if *OPT1 verbose
8C65 .search_dir_with_wildcards←2← 8CC3 JSR← 9218 JSR
LDY #&15 ; Y=&15: start of entry data in dir
8C67 LDX #&0b ; X=&0B: copy 12 bytes to workspace
8C69 .scan_dir_entries_loop←1← 8C70 BPL
LDA (zp_entry_ptr_lo),y ; Get entry data byte
8C6B STA wksp_disc_op_result,x ; Store in disc op workspace
8C6E DEY ; Next entry byte (decreasing)
8C6F DEX ; Next workspace byte (decreasing)
8C70 BPL scan_dir_entries_loop ; Loop for 12 bytes
8C72 LDY #&0d ; Y=&0D: copy to OSFILE control block
8C74 LDX #&0b ; X=&0B: 12 bytes
8C76 .compare_entry_names_loop←1← 8C7D BPL
LDA wksp_disc_op_result,x ; Get byte from workspace
8C79 STA (zp_osfile_ptr_lo),y ; Store in OSFILE control block
8C7B DEY ; Next control block byte
8C7C DEX ; Next workspace byte
8C7D BPL compare_entry_names_loop ; Loop for 12 bytes
8C7F LDA #0 ; Clear access accumulator
8C81 STA wksp_csd_sector_temp ; Store zero in workspace
8C84 LDY #2 ; Y=2: process 3 name bytes (R,W,L)
8C86 .build_filename_loop←1← 8C8D BPL
LDA (zp_entry_ptr_lo),y ; Get name byte from entry
8C88 ASL ; Shift bit 7 (attribute) into carry
8C89 ROL wksp_csd_sector_temp ; Rotate into access accumulator
8C8C DEY ; Next name byte
8C8D BPL build_filename_loop ; Loop for 3 bytes
8C8F LDA wksp_csd_sector_temp ; Get accumulated access bits
8C92 ROR ; Rearrange bits to standard format
8C93 ROR ; Second rotation
8C94 ROR ; Third rotation
8C95 PHP ; Save intermediate flags
8C96 LSR ; Shift right
8C97 PLP ; Restore flags
8C98 ROR ; Rotate right with carry
8C99 STA wksp_csd_sector_temp ; Store partial result
8C9C LSR ; Shift down 4 more positions
8C9D LSR ; Second shift
8C9E LSR ; Third shift
8C9F LSR ; Fourth shift
8CA0 ORA wksp_csd_sector_temp ; OR with saved bits
8CA3 LDY #&0e ; Y=&0E: OSFILE access byte position
8CA5 STA (zp_osfile_ptr_lo),y ; Store access byte in control block
8CA7 RTS ; Return
8CA8 .osfile_save_handler
LDY #0 ; Y=0: get filename addr from block
8CAA LDA (zp_osfile_ptr_lo),y ; Get filename address low
8CAC STA zp_text_ptr_lo ; Store in (&B4)
8CAE INY ; Y=&01
8CAF LDA (zp_osfile_ptr_lo),y ; Get filename address high
8CB1 STA zp_text_ptr_hi ; Store in (&B5)
8CB3 JSR find_first_matching_entry ; Search for file in directory
8CB6 BNE delete_existing_before_save ; Found? Copy catalogue info
8CB8 LDY #4 ; Y=4: check E attribute
8CBA LDA (zp_entry_ptr_lo),y ; Get E attribute byte
8CBC BPL check_existing_for_save ; Bit 7 clear: not E, copy info
8CBE LDA #&ff ; E attribute: return A=&FF
8CC0 JMP save_wksp_and_return ; Save workspace and return

Check for existing file before save

Search directory using wildcards for an existing entry matching the save filename.

8CC3 .check_existing_for_save←2← 8CBC BPL← 90FE JMP
JSR search_dir_with_wildcards ; Copy catalogue info to block
8CC6 .delete_existing_before_save←1← 8CB6 BNE
JMP get_object_type_result ; Save workspace and return

Parse filename from OSFILE block and search

Extract filename from the OSFILE control block, parse the path, and search the current directory.

On ExitAcorrupted (Z set if found, (&B6) points to entry)
Xcorrupted
Ycorrupted
8CC9 .parse_osfile_and_search←3← 8CE2 JSR← 8CE9 JSR← 911E JSR
LDY #0 ; Y=0: get filename from block
8CCB LDA (zp_osfile_ptr_lo),y ; Get filename address low
8CCD STA zp_text_ptr_lo ; Store in (&B4)
8CCF INY ; Y=&01
8CD0 LDA (zp_osfile_ptr_lo),y ; Get filename address high
8CD2 STA zp_text_ptr_hi ; Store in (&B5)
8CD4 JSR set_up_gsinit_path ; Parse path and set up directory
8CD7 JSR find_first_matching_entry ; Search for file
8CDA BEQ return_16 ; Found: return Z set
8CDC JSR check_special_dir_char ; Check for ^ (parent) or @ (current) directory
8CDF BEQ mark_entry_dirty ; Z clear: check for create
8CE1 .return_16←1← 8CDA BEQ
RTS ; Return
8CE2 .build_osfile_control_block←1← A552 JSR
JSR parse_osfile_and_search ; Parse filename and search
8CE5 BEQ check_file_not_open ; Found: proceed to delete
8CE7 BNE check_4byte_addrs ; ALWAYS branch
8CE9 .copy_osfile_addrs←1← 8DF3 JSR
JSR parse_osfile_and_search ; Parse filename and search
8CEC BEQ write_entry_metadata ; Found: check if directory
8CEE .check_4byte_addrs←1← 8CE7 BNE
LDY #0 ; Y=0: check remaining path
8CF0 .copy_3byte_addrs_loop←1← 8D02 BNE
LDA (zp_text_ptr_lo),y ; Get next path character
8CF2 CMP #&2e ; Is it '.' (path separator)?
8CF4 BNE copy_4byte_addrs_loop ; Yes: check for ^ or @ error
8CF6 .mark_entry_dirty←1← 8CDF BEQ
JMP not_found_error ; Check for ^ or @ prefix error
8CF9 .copy_4byte_addrs_loop←1← 8CF4 BNE
CMP #&21 ; Is it printable (> '!')?
8CFB BCC update_entry_from_osfile ; No: end of filename
8CFD CMP #&22 ; Is it '"'?
8CFF BEQ update_entry_from_osfile ; Yes: end of filename
8D01 INY ; Next character
8D02 BNE copy_3byte_addrs_loop ; Loop scanning filename
8D04 .update_entry_from_osfile←2← 8CFB BCC← 8CFF BEQ
LDA #&11 ; A=&11: return code for file found
8D06 RTS ; Return
8D07 .write_entry_metadata←1← 8CEC BEQ
LDY #3 ; Y=3: check if it's a directory
8D09 LDA (zp_entry_ptr_lo),y ; Get access byte
8D0B BPL check_file_not_open ; Bit 7 clear: not dir, create file
8D0D JMP already_exists_error2 ; Directory: Already exists error

Check file is not locked or open

Check the entry at (&B6) for the locked attribute and generate a Locked error if set. Then check whether any files on the current drive are open.

8D10 .check_file_not_open←5← 8CE5 BEQ← 8D0B BPL← 9128 JSR← A589 JSR← B306 JSR
LDY #2 ; Y=2: check file access
8D12 LDA (zp_entry_ptr_lo),y ; Get access byte 2 (L attribute)
8D14 BPL check_open ; Check if file is open
8D16 JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
8D19 EQUB &C3 ; Error &C3: Locked
8D1A EQUS "Locked."
fall through ↓

Check if file is open

Check whether any files are currently open on a given drive. Used before operations that would be unsafe with open files.

8D21 .check_open←2← 8D14 BPL← B2EF JSR
LDX #9 ; X=9: check all 10 channels
8D23 .check_open_channel_loop←1← 8D6A BPL
LDA wksp_ch_flags,x ; Get channel flags
8D26 BEQ no_open_files_on_drive ; Channel not open? Skip
8D28 LDA wksp_ch_start_sec_h,x ; Get channel's drive number
8D2B AND #&e0 ; Isolate drive bits (top 3)
8D2D CMP wksp_current_drive ; Compare with current drive
8D30 BNE no_open_files_on_drive ; Different drive? Skip
8D32 LDA wksp_ch_dir_sec_ml,x ; Compare sector address byte
8D35 CMP wksp_csd_sector_lo ; With target sector
8D38 BNE no_open_files_on_drive ; No match? Skip
8D3A LDA wksp_ch_dir_sec_mh,x ; Compare sector mid byte
8D3D CMP wksp_csd_sector_mid ; With target sector mid
8D40 BNE no_open_files_on_drive ; No match? Skip
8D42 LDA wksp_ch_dir_sec_h,x ; Compare sector high byte
8D45 CMP wksp_csd_sector_hi ; With target sector high
8D48 BNE no_open_files_on_drive ; No match? Skip
8D4A LDY #&19 ; Y=&19: compare sequence number
8D4C LDA (zp_entry_ptr_lo),y ; Get entry sequence from dir
8D4E CMP wksp_ch_seq_num,x ; Compare with channel's sequence
8D51 BNE no_open_files_on_drive ; Mismatch: not the same file
8D53 .channel_on_same_drive←1← B24A JMP
JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
8D56 EQUB &C2 ; Error &C2: Already open
8D57 EQUS "Can't - File open."
fall through ↓

No open file conflict found

All channels checked with no conflicts. Continue with the file operation.

8D69 .no_open_files_on_drive←6← 8D26 BEQ← 8D30 BNE← 8D38 BNE← 8D40 BNE← 8D48 BNE← 8D51 BNE
DEX ; Next channel
8D6A BPL check_open_channel_loop ; Loop for all 10 channels
8D6C INX ; X=1: no conflict found
8D6D RTS ; Return (X=1 = no conflict)

Validate path and check for wildcards

Scan the filename at (&B4) checking for invalid characters and wildcards. Generates Bad name or Wild cards errors for invalid patterns.

8D6E .set_up_directory_search←2← 8712 JSR← 8DBD JSR
LDY #0 ; Y=0: scan filename
8D70 JSR check_char_is_terminator ; Check if character is a filename terminator
8D73 BNE begin_pathname_scan ; Non-terminator: check for wildcards
8D75 CMP #&2e ; Is it '.'?
8D77 BEQ bad_name_in_path ; Dot: wild cards error
8D79 RTS ; Return (no wildcards)
8D7A .begin_pathname_scan←1← 8D73 BNE
CMP #&3a ; Is it ':'?
8D7C BNE skip_dot_in_path ; No: check path components
8D7E INY ; Skip past ':D' drive specifier
8D7F .scan_name_bytes_loop←1← 8D91 BEQ
INY ; Skip past drive number
8D80 JSR check_char_is_terminator ; Check if character is a filename terminator
8D83 BNE bad_name_in_path ; Non-zero: wild cards error
8D85 CMP #&2e ; Is it '.'?
8D87 BNE return_17 ; No dot after drive: return
8D89 INY ; Skip past dot
8D8A JSR check_path_terminator ; Get next character
8D8D .skip_dot_in_path←1← 8D7C BNE
AND #&fd ; Strip to check for '$'
8D8F CMP #&24 ; Is it '$' (root)?
8D91 BEQ scan_name_bytes_loop ; Yes: continue past root specifier
8D93 .scan_name_alpha_loop←1← 8DA9 BNE
JSR check_path_terminator ; Get next path character
8D96 CMP #&5e ; Is it '^' (parent)?
8D98 BEQ check_bad_name_char ; Yes: skip past it
8D9A CMP #&40 ; Is it '@' (current)?
8D9C BNE c8dab ; No: check for wildcards in name
8D9E .check_bad_name_char←1← 8D98 BEQ
INY ; Skip past ^ or @ specifier
8D9F JSR check_char_is_terminator ; Check if character is a filename terminator
8DA2 BNE bad_name_in_path ; Non-terminator: wild cards error
8DA4 .check_special_chars_loop←1← 8DAE BEQ
CMP #&2e ; Is it '.'?
8DA6 BNE return_17 ; No dot: return
8DA8 INY ; Skip past dot
8DA9 BNE scan_name_alpha_loop ; Continue scanning
8DAB .c8dab←2← 8D9C BNE← 8DBB BNE
JSR check_char_is_terminator ; Check if character is a filename terminator
8DAE BEQ check_special_chars_loop ; Terminator: check for dot
8DB0 LDX #5 ; X=5: check against 6 special chars
8DB2 .valid_name_continue_loop←1← 8DB8 BPL
CMP tbl_forbidden_chars,x ; Compare with special char table
8DB5 BEQ bad_name_in_path ; Match: wild cards error
8DB7 DEX ; Next special char
8DB8 BPL valid_name_continue_loop ; Loop for 6 chars
8DBA INY ; Next filename character
8DBB BNE c8dab ; Continue scanning
8DBD .set_up_gsinit_path←3← 8CD4 JSR← A50C JSR← B2FE JSR
JSR set_up_directory_search ; Save text pointer low
8DC0 .gsinit_scan_loop←1← 8DD3 BNE
LDA (zp_text_ptr_lo),y ; Save text pointer high
8DC2 AND #&7f ; Push on stack
8DC4 CMP #&2a
8DC6 BEQ wild_cards_error ; Raise Wild cards error
8DC8 CMP #&23
8DCA BEQ wild_cards_error ; Raise Wild cards error
8DCC CMP #&2e ; Is it '.'?
8DCE BEQ return_17 ; Restore text pointer high
8DD0 DEY ; Decrement index
8DD1 CPY #&ff ; Restore text pointer low
8DD3 BNE gsinit_scan_loop
8DD5 .return_17←4← 8D87 BNE← 8DA6 BNE← 8DCE BEQ← 8DD9 BNE
RTS ; Return

Check next path character is terminator

Read the character at (&B4),Y and generate a Bad name error if it is not a filename terminator.

8DD6 .check_path_terminator←2← 8D8A JSR← 8D93 JSR
JSR check_char_is_terminator ; Get character at (&B4),Y
8DD9 BNE return_17
8DDB .bad_name_in_path←4← 8D77 BEQ← 8D83 BNE← 8DA2 BNE← 8DB5 BEQ
JMP bad_name_error ; Wild cards found: Bad name error

Raise Wild cards error

Reload FSM and directory then raise error &FD: Wild cards.

8DDE .wild_cards_error←2← 8DC6 BEQ← 8DCA BEQ
JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
8DE1 EQUB &FD ; Error &FD: Wild cards
8DE2 EQUS "Wild cards."
fall through ↓

Forbidden filename characters

Six characters that may not appear in ADFS filenames because they have special meaning in the pathname syntax. The path validator at set_up_directory_search loops through this table, rejecting any filename containing these characters.

8DED .tbl_forbidden_chars←1← 8DB2 CMP
EQUB &7F ; &7F: DEL (control character)
8DEE EQUB &5E ; '^': parent directory specifier
8DEF EQUB &40 ; '@': current directory specifier
8DF0 EQUB &3A ; ':': drive separator
8DF1 EQUB &24 ; '$': root directory specifier
8DF2 EQUB &26 ; '&': hex number prefix

Copy OSFILE addresses and search for empty entry

Copy the load and exec addresses from the OSFILE control block into the disc operation workspace, then search the current directory for an empty entry slot to use for a new file. Called when creating files via OSFILE save, *CDIR, *RENAME, and *COPY.

8DF3 .copy_addrs_and_find_empty_entry←4← 8F4C JSR← 9594 JSR← A649 JSR← A8DC JSR
JSR copy_osfile_addrs ; A=&FF: mark saved drive as unset
8DF6 .search_dir_for_new_entry←1← A556 JSR
BNE no_empty_entry_found ; Ensure directory integrity
8DF8 LDX #2
8DFA LDY #&12
8DFC LDA (zp_entry_ptr_lo),y
8DFE CMP #1
8E00 .scan_entry_bytes_loop←1← 8E09 BPL
INY ; Next entry byte
8E01 LDA #0 ; Get name and compare
8E03 ADC (zp_entry_ptr_lo),y
8E05 STA wksp_entry_size_base,y
8E08 DEX ; Clear carry for addition
8E09 BPL scan_entry_bytes_loop ; Add 26 bytes
8E0B LDY #&18 ; Store updated pointer low
8E0D LDX #2 ; No page crossing
8E0F .find_empty_entry_loop←1← 8E16 BPL
LDA (zp_entry_ptr_lo),y ; Increment page
8E11 STA wksp_object_sector,x ; Continue searching
8E14 DEY ; Back up one entry position
8E15 DEX ; Compare pointer with &16B1
8E16 BPL find_empty_entry_loop
8E18 RTS ; Return (slot found)
8E19 .no_empty_entry_found←1← 8DF6 BNE
LDA dir_last_entry_area ; Below limit: slot found
8E1C BEQ check_name_already_exists ; Exactly at limit: dir full
8E1E JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
8E21 EQUB &B3 ; Error &B3: Dir full
8E22 EQUS "Dir full."
8E2B .check_name_already_exists←1← 8E1C BEQ
LDA zp_text_ptr_lo ; Save text pointer low
8E2D STA wksp_tube_transfer_addr_1 ; Store in workspace
8E30 LDA zp_text_ptr_hi ; Save text pointer high
8E32 STA wksp_tube_xfer_addr_2 ; Store in workspace
8E35 LDA #&b1 ; Point to end of entries (&16B1)
8E37 STA zp_text_ptr_lo ; Store pointer low
8E39 LDA #&16 ; Page &16
8E3B STA zp_text_ptr_hi ; Store pointer high
8E3D LDY #&1a ; Y=&1A: offset for source entry
8E3F LDX #6 ; X=6: clear 7 bytes of new entry
8E41 LDA #0 ; A=0: zero fill
8E43 .compare_names_loop←1← 8E47 BNE
STA wksp_last_access_drive,x ; Clear workspace byte
8E46 DEX ; Next byte
8E47 BNE compare_names_loop ; Loop for 7 bytes
8E49 .copy_entry_data_loop←1← 8E61 JMP
LDA (zp_text_ptr_lo,x) ; Get source entry byte
8E4B STA (zp_text_ptr_lo),y ; Copy to destination (shift up)
8E4D LDA zp_text_ptr_lo ; Check if at target position
8E4F CMP zp_entry_ptr_lo ; Compare low byte
8E51 BNE write_entry_to_dir ; Not there yet
8E53 LDA zp_text_ptr_hi ; Compare high byte
8E55 CMP zp_entry_ptr_hi ; Match: target reached
8E57 BEQ mark_directory_modified ; Done shifting
8E59 .write_entry_to_dir←1← 8E51 BNE
LDA zp_text_ptr_lo ; Decrement source pointer
8E5B BNE mark_entry_created ; Low byte non-zero
8E5D DEC zp_text_ptr_hi ; Zero: decrement high byte
8E5F .mark_entry_created←1← 8E5B BNE
DEC zp_text_ptr_lo ; Decrement low byte
8E61 JMP copy_entry_data_loop ; Continue shifting loop
8E64 .mark_directory_modified←1← 8E57 BEQ
LDA wksp_tube_transfer_addr_1 ; Restore text pointer low
8E67 STA zp_text_ptr_lo ; Store back in (&B4)
8E69 LDA wksp_tube_xfer_addr_2 ; Restore text pointer high
8E6C STA zp_text_ptr_hi ; Store back in (&B5)
8E6E RTS ; Return

Allocate disc space and store in entry

Allocate disc space from the FSM for the requested file size, then store the allocated sector address in the directory entry at (&B6).

8E6F .allocate_disc_space_for_file←3← 8F4F JSR← A64C JSR← A8DF JSR
LDY #9 ; Get OSFILE block pointer low
8E71 .copy_alloc_request_loop←1← 8E88 BPL
LDA (zp_text_ptr_lo),y ; Store in (&B8)
8E73 AND #&7f ; Get OSFILE block pointer high
8E75 CMP #&21 ; Store in (&B9)
8E77 BCC store_allocated_sector ; Y=2: copy 3-byte start sector
8E79 CMP #&22 ; Get start sector byte from entry
8E7B BNE check_exact_alloc
8E7D .store_allocated_sector←1← 8E77 BCC
LDA #&0d ; A=CR: pad entry name
8E7F .check_exact_alloc←1← 8E7B BNE
CPY #2 ; Next byte (decreasing Y)
8E81 BCS reduce_alloc_to_available
8E83 ORA #&80 ; Set bit 7 for D attribute
8E85 .reduce_alloc_to_available←1← 8E81 BCS
STA (zp_entry_ptr_lo),y ; Compare with 1 (round up sectors)
8E87 DEY ; Next byte
8E88 BPL copy_alloc_request_loop ; Loop for all name bytes
8E8A RTS ; Return

Copy OSFILE template into directory entry

Copy filename, attributes, and sector information from the OSFILE workspace into the directory entry at (&B6).

8E8B .copy_entry_from_template←2← 8F52 JSR← A65D JSR
LDY #&11 ; Y=&11: copy filename and attributes
8E8D .copy_name_to_entry_loop←1← 8E93 BPL
LDA (zp_osfile_ptr_lo),y ; Get name byte from workspace
8E8F STA wksp_disc_op_result,y
8E92 DEY ; Next byte
8E93 BPL copy_name_to_entry_loop ; Loop for 10 bytes
8E95 LDY #&12 ; Increment dir sequence number
8E97 SEC ; Set carry for sector calculation
8E98 LDX #3 ; Store updated sequence in header
8E9A .copy_access_byte_loop←1← 8EA4 BPL
LDA wksp_entry_calc_base,y
8E9D SBC wksp_entry_field_base,y
8EA0 STA (zp_entry_ptr_lo),y ; Store sequence in entry
8EA2 INY ; Next byte
8EA3 DEX ; Decrement counter
8EA4 BPL copy_access_byte_loop ; Loop for required bytes
8EA6 LDY #&0a ; Y=&0A: copy access byte
8EA8 .store_entry_lengths_loop←1← 8EB0 BNE
LDA wksp_entry_field_base,y ; Get OSFILE data byte
8EAB STA (zp_entry_ptr_lo),y ; Store in directory entry
8EAD INY ; Next byte
8EAE CPY #&12 ; Past length field (Y=&12)?
8EB0 BNE store_entry_lengths_loop ; No: continue copying
8EB2 LDA zp_entry_ptr_lo ; Save (&B6) for entry shifting
8EB4 PHA ; Push on stack
8EB5 LDA zp_entry_ptr_hi ; Save (&B7)
8EB7 PHA ; Push on stack
8EB8 .store_entry_3byte_sector←1← 8EEA JMP
LDA #5 ; Point to first dir entry (&1205)
8EBA STA zp_entry_ptr_lo ; Store pointer low
8EBC LDA #&12 ; Page &12
8EBE STA zp_entry_ptr_hi ; Store pointer high
8EC0 .store_entry_4byte_sector←2← 8ED6 BCC← 8EDA BCS
LDY #0 ; Y=0: check entry
8EC2 LDA (zp_entry_ptr_lo),y ; Get first byte
8EC4 BEQ copy_osfile_to_entry_loop ; Zero: end of entries, done
8EC6 LDY #&19 ; Y=&19: check sequence number
8EC8 LDA (zp_entry_ptr_lo),y ; Get entry sequence
8ECA CMP dir_master_sequence ; Compare with dir master sequence
8ECD BEQ update_entry_access ; Match: needs incrementing
8ECF CLC ; Clear carry for entry advance
8ED0 LDA zp_entry_ptr_lo ; Get pointer low
8ED2 ADC #&1a ; Add 26 bytes per entry
8ED4 STA zp_entry_ptr_lo ; Store updated pointer
8ED6 BCC store_entry_4byte_sector ; No page crossing: continue
8ED8 INC zp_entry_ptr_hi ; Increment page
8EDA BCS store_entry_4byte_sector ; ALWAYS branch
8EDC .update_entry_access←1← 8ECD BEQ
LDA dir_master_sequence ; Get master sequence number
8EDF CLC ; Clear carry for BCD add
8EE0 SED ; Switch to BCD mode
8EE1 ADC #1 ; Increment sequence (BCD)
8EE3 CLD ; Back to binary mode
8EE4 STA dir_master_sequence ; Store updated sequence in footer
8EE7 STA dir_buffer ; Also store in header
8EEA JMP store_entry_3byte_sector ; Retry from beginning of entries
8EED .copy_osfile_to_entry_loop←1← 8EC4 BEQ
PLA ; Restore (&B7) from stack
8EEE STA zp_entry_ptr_hi ; Store back
8EF0 PLA ; Restore (&B6) from stack
8EF1 STA zp_entry_ptr_lo ; Store back
8EF3 LDY #&19 ; Y=&19: store new sequence in entry
8EF5 LDA dir_master_sequence ; Get current master sequence
8EF8 STA (zp_entry_ptr_lo),y ; Store in the new entry
8EFA LDA #1 ; Result = 1 (file created)
8EFC STA wksp_disc_op_result ; Store result code
8EFF LDX #4 ; X=4: copy 4 transfer length bytes
8F01 .copy_load_addr_loop←1← 8F08 BNE
LDA wksp_disc_op_sector_count,x ; Get transfer count byte
8F04 STA wksp_disc_op_result,x ; Copy to disc op result
8F07 DEX ; Next byte
8F08 BNE copy_load_addr_loop ; Loop for 4 bytes
8F0A LDA #&0a ; SCSI write command = &0A
8F0C STA wksp_disc_op_command ; Store command
8F0F LDA #0 ; Clear sector count (use transfer)
8F11 STA wksp_disc_op_sector_count ; Store zero sector count
8F14 LDA #0 ; A=0: clear control byte
8F16 STA wksp_disc_op_control ; Store zero control
8F19 LDY #&12 ; Y=&12: copy 4 length bytes to entry
8F1B .copy_exec_addr_to_entry_loop←1← 8F23 BNE
LDA (zp_entry_ptr_lo),y ; Get length byte from entry
8F1D STA wksp_entry_len_base,y ; Copy to workspace
8F20 INY ; Next byte
8F21 CPY #&16 ; Past length field (Y=&16)?
8F23 BNE copy_exec_addr_to_entry_loop ; No: continue
8F25 LDY #&12 ; Y=&12: calculate sector count
8F27 LDA (zp_entry_ptr_lo),y ; Get length low from entry
8F29 CMP #1 ; Compare with 1 (round up)
8F2B LDX #2 ; X=2: process 3 sector bytes
8F2D .check_if_updating_length←1← 8F36 BPL
LDA #0 ; A=0: zero for carry propagation
8F2F INY ; Next length byte
8F30 ADC (zp_entry_ptr_lo),y ; Add with carry from comparison
8F32 STA wksp_csd_drive_temp,y ; Store in sector workspace
8F35 DEX ; Next byte
8F36 BPL check_if_updating_length ; Loop for 3 bytes
8F38 BCC update_length_and_access ; No overflow: proceed
8F3A JMP disc_full_error ; Overflow: Disc full error
8F3D .update_length_and_access←1← 8F38 BCC
LDY #&16 ; Y=&16: mark entry sector as &FF
8F3F LDA #&ff ; A=&FF: temporary marker
8F41 STA (zp_entry_ptr_lo),y ; Store &FF in sector low
8F43 INY ; Y=&17
8F44 STA (zp_entry_ptr_lo),y ; Store &FF in sector mid
8F46 INY ; Y=&18
8F47 STA (zp_entry_ptr_lo),y ; Store &FF in sector high
8F49 JMP release_disc_space ; Release disc space back to free space map

Validate file is not locked then create entry

Check file is not locked or open, write the filename into the directory entry, allocate disc space, and copy the file length and sector address.

8F4C .validate_not_locked←3← 8F74 JSR← 8F7D JSR← B35A JSR
JSR copy_addrs_and_find_empty_entry ; Save (&B6) for restore
8F4F JSR allocate_disc_space_for_file ; Save (&B7)
8F52 .write_entry_sector_info←2← 95CA JSR← A8E2 JSR
JSR copy_entry_from_template ; Y=&0D: copy load/exec/length
8F55 JSR allocate_disc_space ; Allocate disc space from free space map
8F58 .copy_length_to_entry←1← A660 JSR
LDY #&18 ; Y=&18: get OSFILE data bytes
8F5A LDX #2 ; Get OSFILE block byte
8F5C .copy_3byte_length_loop←1← 8F63 BPL
LDA wksp_alloc_sector,x
8F5F STA (zp_entry_ptr_lo),y
8F61 DEY ; Next entry byte (decreasing)
8F62 DEX ; Next workspace byte
8F63 BPL copy_3byte_length_loop ; Next OSFILE byte (decreasing)
8F65 LDX #2 ; Loop for 12 bytes
8F67 LDY #6 ; Restore (&B7) from stack
8F69 .copy_sector_to_entry_loop←1← 8F71 BPL
LDA wksp_alloc_sector,x
8F6C STA wksp_disc_op_result,y
8F6F INY ; X=2: 3 sector bytes
8F70 DEX ; Decrement counter
8F71 BPL copy_sector_to_entry_loop ; Get new sector byte from workspace
8F73 RTS ; Return
8F74 .osfile_load_handler
JSR validate_not_locked ; Store in directory entry
8F77 JSR multi_sector_disc_command ; Execute multi-sector disc command
8F7A JMP search_for_osfile_target ; Next workspace byte (decreasing X)
8F7D .osfile_read_cat_info
JSR validate_not_locked ; Y=4: clear access byte 4
8F80 .search_for_osfile_target←2← 8F7A JMP← 962F JMP
JSR write_dir_and_validate ; Write directory and update
8F83 JMP store_length_and_sector ; Check and write entry

Write directory and FSM back to disc

Verify directory integrity, validate the free space map checksums, then write the current directory and FSM sectors back to disc.

8F86 .write_dir_and_validate←17← 897F JSR← 8F80 JSR← 90FB JSR← 9238 JSR← 96AC JSR← 97D4 JMP← 99C3 JSR← A007 JMP← A273 JMP← A5DF JSR← A5F8 JSR← A663 JSR← A6C4 JMP← A933 JSR← AF4F JSR← B35D JSR← B462 JSR
JSR verify_dir_integrity ; Verify directory integrity
8F89 JSR validate_fsm_entries ; Get (&B6) pointer
8F8C LDX #&0a ; Get (&B7) pointer
8F8E .read_osfile_cat_fields_loop←1← 8F95 BPL
LDA disc_op_tpl_read_dir,x ; Push on stack
8F91 STA wksp_disc_op_result,x ; Get template byte from ROM
8F94 DEX ; Store in disc op workspace
8F95 BPL read_osfile_cat_fields_loop ; Loop for template bytes
8F97 LDA #&0a ; Next byte
8F99 STA wksp_disc_op_command
8F9C LDA wksp_csd_sector_lo ; Y=&16: sector offset in info area
8F9F STA wksp_disc_op_sector_lo
8FA2 LDA wksp_csd_sector_mid
8FA5 STA wksp_disc_op_sector_mid
8FA8 LDA wksp_csd_sector_hi ; Next info byte
8FAB STA wksp_disc_op_sector ; Restore (&B7) from stack
8FAE JSR exec_disc_op_from_wksp ; Write directory to disc
8FB1 LDA wksp_current_drive ; Restore (&B6) from stack
8FB4 JSR convert_drive_to_slot ; Get FSM checksum byte
8FB7 LDA fsm_s1_disc_id_hi ; Is it zero (unmodified)?
8FBA STA wksp_disc_id_hi,x ; X=slot index (drive >> 4)
8FBD LDA system_via_t1c_l ; Clear FSM modification flag
8FC0 STA wksp_disc_id_lo,x ; Y=&FF: calculate FSM checksums
8FC3 STA fsm_s1_disc_id_lo
8FC6 JSR calc_fsm_checksums ; Next byte
8FC9 STX fsm_s0_checksum ; Store sector 0 checksum
8FCC STA fsm_s1_checksum ; A=0: reset for sector 1
8FCF LDX #&71 ; X=&71: validate FSM entry count
8FD1 LDY #&90 ; Add to checksum
8FD3 JSR exec_disc_command ; Execute disc command and check for error
8FD6 LDA zp_adfs_flags
8FD8 AND #&ef ; Clear FSM-inconsistent flag
8FDA STA zp_adfs_flags ; Return
8FDC LDA #0 ; A=0: success
8FDE RTS ; Return

Find first matching directory entry

Parse a filename from the command line and search the current directory for the first entry matching the parsed filename pattern.

On ExitAcorrupted (Z set if found, (&B6) points to entry)
Xcorrupted
Ycorrupted
8FDF .find_first_matching_entry←13← 8BE5 JSR← 8CB3 JSR← 8CD7 JSR← 94E7 JSR← 993D JSR← 9A2D JSR← 9C43 JSR← A586 JSR← A672 JSR← B20E JSR← B2E6 JSR← B301 JSR← B36D JSR
JSR parse_filename_from_cmdline ; Set up search with wildcards
8FE2 PHP ; Point to first dir entry
8FE3 PHA ; Save A on stack
8FE4 JSR validate_fsm_and_mark_dirty ; Validate FSM checksums and mark directory dirty
8FE7 PLA ; Restore A
8FE8 PLP ; Search for matching entry
8FE9 .return_18←3← 8FF8 BEQ← 900C BEQ← 9028 BCC
RTS ; Return

Validate FSM checksums and mark directory dirty

Validate the in-memory free space map by checking both sector checksums. Generates a Bad FS map error if the checksums do not match.

8FEA .validate_fsm_and_mark_dirty←5← 8FE4 JSR← 9FFA JSR← A255 JSR← A754 JSR← A872 JSR
JSR validate_fsm_entries
8FED JSR calc_fsm_checksums ; Mark directory as modified
8FF0 CMP fsm_s1_checksum ; Verify directory
8FF3 BNE bad_fs_map_error ; Point to first entry
8FF5 CPX fsm_s0_checksum ; X=FSM sector 0 checksum
8FF8 BEQ return_18
fall through ↓

Raise Bad FS map error

Generate a Bad FS map error (&A9) via generate_disc_error. Called when FSM checksum validation fails.

8FFA .bad_fs_map_error←7← 8FF3 BNE← 9017 BEQ← 901A BEQ← 9021 BNE← 903A BCS← 9045 BNE← 904A BMI
JSR generate_disc_error ; Bad FS map error
8FFD EQUB &A9 ; Error &A9: Bad FS map
8FFE EQUS "Bad FS map."
9009 .validate_fsm_entries←2← 8F89 JSR← 8FEA JSR
LDX fsm_s1_total_sectors_lo ; Get FSM end-of-list pointer
900C BEQ return_18 ; Empty: return OK
900E LDA #0 ; A=0: init check accumulator
9010 .check_fsm_entry_loop←1← 901D BNE
ORA fsm_s0_pre1,x ; OR entry address high byte
9013 ORA fsm_s0_checksum,x ; OR entry length high byte
9016 DEX ; Back up one
9017 BEQ bad_fs_map_error ; At entry 0: bad FS map
9019 DEX ; Back up more
901A BEQ bad_fs_map_error ; At entry 0: bad FS map
901C DEX ; Back up more
901D BNE check_fsm_entry_loop ; Loop for all entries
901F AND #&e0 ; Check drive bits in accumulator
9021 BNE bad_fs_map_error ; Non-zero: bad FS map
9023 LDX fsm_s1_total_sectors_lo ; Get end pointer again
9026 CPX #6 ; Need at least 2 entries (>= 6)
9028 BCC return_18 ; Not enough: return OK (empty disc)
902A LDX #3 ; X=3: check entry ordering
902C .check_fsm_ordering←1← 9059 BCC
LDY #2 ; Y=2: compare 3-byte addresses
902E CLC ; Clear carry for addition
902F .add_entry_size_loop←1← 9038 BPL
LDA fsm_s0_pre3,x ; Get prev entry address byte
9032 ADC fsm_s0_boot_option,x ; Add prev entry length byte
9035 PHA ; Push result on stack
9036 INX ; Next byte
9037 DEY ; Next comparison byte
9038 BPL add_entry_size_loop ; Loop for 3 bytes
903A BCS bad_fs_map_error ; Carry set: overlap, bad FS map
903C LDY #2 ; Y=2: compare prev+size with next
903E .compare_with_next_entry_loop←1← 9048 BPL
PLA ; Pop result byte
903F DEX ; Back up X
9040 CMP fsm_sector_0,x ; Compare with next entry address
9043 BCC discard_comparison_bytes ; Below: entries are ordered OK
9045 BNE bad_fs_map_error ; Above: bad ordering, bad FS map
9047 DEY ; Next comparison byte
9048 BPL compare_with_next_entry_loop ; Loop for 3 bytes
904A BMI bad_fs_map_error ; Raise Bad FS map error
904C .discard_comparison_bytes←2← 9043 BCC← 904F BPL
PLA ; Discard remaining stack bytes
904D DEX ; Back up X
904E DEY ; Decrement Y too
904F BPL discard_comparison_bytes ; More to discard
9051 PHA ; Push separator
9052 INX ; Advance to next entry pair
9053 INX ; Continue advancing
9054 INX ; Continue advancing
9055 INX ; Continue advancing
9056 CPX fsm_s1_total_sectors_lo ; Past end of list?
9059 BCC check_fsm_ordering ; No: check next pair
905B RTS ; All entries OK: return

Calculate FSM sector checksums

Compute 8-bit checksums of FSM sectors 0 and 1 by summing all 255 bytes of each sector.

On ExitAFSM sector 1 checksum
XFSM sector 0 checksum
Ycorrupted
905C .calc_fsm_checksums←2← 8FC6 JSR← 8FED JSR
CLC ; Clear carry for checksum
905D LDY #&ff ; Y=&FF: sum 255 bytes
905F TYA ; A=&ff
9060 .checksum_s0_loop←1← 9064 BNE
ADC fsm_s0_pre1,y ; Add FSM sector 0 byte
9063 DEY ; Next byte
9064 BNE checksum_s0_loop ; Loop for 255 bytes
9066 TAX ; Save sector 0 checksum in X
9067 DEY ; Y=&FF for sector 1
9068 TYA ; Transfer to A
9069 CLC ; Clear carry
906A .checksum_s1_loop←1← 906E BNE
ADC fsm_s0_checksum,y ; Add FSM sector 1 byte
906D DEY ; Next byte
906E BNE checksum_s1_loop ; Loop for 255 bytes
9070 RTS ; Return (X=chk0, A=chk1)

Unused write-FSM disc operation template

An unreferenced disc operation template for writing the FSM back to disc. The actual write-FSM code at write_dir_and_validate instead copies the read template (disc_op_tpl_read_dir) and patches the command byte from &08 (read) to &0A (write). This template may be a remnant from an earlier code revision.

9071 .disc_op_tpl_write_fsm_unused
EQUB &01 ; Result: &01 (default)
9072 EQUB &00 ; Memory address low: &00
9073 EQUB &0E ; Memory address high: &0E (-> &0E00 FSM buffer)
9074 EQUB &FF ; Memory address byte 3: &FF (host memory)
9075 EQUB &FF ; Memory address byte 4: &FF (host memory)
9076 EQUB &0A ; Command: &0A (write sectors)
9077 EQUB &00 ; Sector high: &00
9078 EQUB &00 ; Sector mid: &00
9079 EQUB &00 ; Sector low: &00 (sector 0)
907A EQUB &02 ; Sector count: &02 (2 sectors for FSM)
907B EQUB &00 ; Control: &00

OSFILE write catalogue info handler

Handle OSFILE A=1 (write all catalogue info), A=2 (write load address) and A=3 (write execution address). Finds the file, validates access, then updates the directory entry fields from the OSFILE parameter block.

907C .osfile_write_load_addr
STA wksp_disc_op_xfer_len_3 ; Store function code
907F JSR find_file_and_validate ; Find file, check access
9082 BEQ osfile_write_load_search ; Found?
9084 LDA #0 ; Not found: A=0 return
9086 RTS ; Return
9087 .osfile_write_load_search←1← 9082 BEQ
LDA wksp_disc_op_xfer_len_3 ; Get function code
908A CMP #3 ; A=3 (write exec addr)?
908C BEQ update_entry_after_write ; Yes: skip to exec addr
908E LDY #5 ; Y=5: get load addr from OSFILE blk
9090 LDX #3 ; X=3: copy 4 bytes
9092 .copy_load_to_entry_loop←1← 9099 BPL
LDA (zp_osfile_ptr_lo),y ; Get OSFILE block byte
9094 STA wksp_disc_op_result,x ; Store in workspace
9097 DEY ; Next OSFILE byte
9098 DEX ; Next workspace byte
9099 BPL copy_load_to_entry_loop ; Loop for 4 bytes
909B LDY #&0d ; Y=&0D: store in dir entry load addr
909D LDX #3 ; X=3: copy 4 bytes
909F .copy_exec_to_entry_loop←1← 90A6 BPL
LDA wksp_disc_op_result,x ; Get from workspace
90A2 STA (zp_entry_ptr_lo),y ; Store in directory entry
90A4 DEY ; Next entry byte
90A5 DEX ; Next workspace byte
90A6 BPL copy_exec_to_entry_loop ; Loop for 4 bytes
90A8 LDA wksp_disc_op_xfer_len_3 ; Get function code
90AB CMP #2 ; A=2 (write load addr only)?
90AD BEQ check_dir_access_bit ; Yes: skip exec addr, write dir
90AF .update_entry_after_write←1← 908C BEQ
LDY #9 ; Y=9: get exec addr from OSFILE blk
90B1 LDX #3 ; X=3: copy 4 bytes
90B3 .update_cat_info_loop←1← 90BA BPL
LDA (zp_osfile_ptr_lo),y ; Get OSFILE block byte
90B5 STA wksp_disc_op_result,x ; Store in workspace
90B8 DEY ; Next OSFILE byte
90B9 DEX ; Next workspace byte
90BA BPL update_cat_info_loop ; Loop for 4 bytes
90BC LDY #&11 ; Y=&11: store in dir entry exec addr
90BE LDX #3 ; X=3: copy 4 bytes
90C0 .copy_cat_info_to_entry_loop←1← 90C7 BPL
LDA wksp_disc_op_result,x ; Get from workspace
90C3 STA (zp_entry_ptr_lo),y ; Store in directory entry
90C5 DEY ; Next entry byte
90C6 DEX ; Next workspace byte
90C7 BPL copy_cat_info_to_entry_loop ; Loop for 4 bytes
90C9 LDX wksp_disc_op_xfer_len_3 ; Get function code again
90CC DEX ; X=0 means function was 1 (write all)
90CD BNE check_dir_access_bit ; A=1 (write all): continue to access
90CF .set_entry_access_from_osfile←1← 9104 BEQ
LDY #&0e ; Y=&0E: get access byte from OSFILE
90D1 LDA (zp_osfile_ptr_lo),y ; Get access byte from block
90D3 STA wksp_csd_sector_temp ; Store in workspace
90D6 LDY #3 ; Y=3: apply access to name bytes
90D8 LDA (zp_entry_ptr_lo),y ; Get name byte from entry
90DA BPL access_bit_clear ; Bit 7 set: directory, different fmt
90DC LSR wksp_csd_sector_temp ; Shift access right for R bit
90DF LSR wksp_csd_sector_temp ; Shift right for W bit
90E2 .apply_access_bits_loop←1← 90F9 BEQ
LSR wksp_csd_sector_temp ; Shift right for L bit
90E5 LDY #2 ; Y=2: apply to first 3 name bytes
90E7 BPL advance_access_bit ; ALWAYS branch
90E9 .access_bit_clear←1← 90DA BPL
LDY #0 ; Y=0: start with byte 0
90EB .advance_access_bit←2← 90E7 BPL← 90F7 BCC
LDA (zp_entry_ptr_lo),y ; Get name byte
90ED ASL ; Shift out bit 7 (old attribute)
90EE LSR wksp_csd_sector_temp ; Shift access bit into carry
90F1 ROR ; Shift carry into name bit 7
90F2 STA (zp_entry_ptr_lo),y ; Store updated name byte
90F4 INY ; Next byte
90F5 CPY #2 ; Past byte 2?
90F7 BCC advance_access_bit ; Below 2: continue
90F9 BEQ apply_access_bits_loop ; Exactly 2: handle L bit
90FB .check_dir_access_bit←2← 90AD BEQ← 90CD BNE
JSR write_dir_and_validate ; Write directory to disc
90FE JMP check_existing_for_save ; Return via catalogue info copy
9101 .osfile_delete_handler
JSR find_file_and_validate ; OSFILE A=4: write attributes only
9104 BEQ set_entry_access_from_osfile ; Found: write access byte
9106 LDA #0 ; Not found: A=0
9108 RTS ; Return

*REMOVE command handler

Remove a file from the current directory. Unlike *DELETE, *REMOVE does not report an error if the file is locked.

9109 .star_remove←1← A0BB JSR
JSR skip_spaces ; Skip leading spaces in filename
910C LDA zp_text_ptr_lo ; Save filename address in OSFILE blk
910E STA wksp_osfile_block ; Store filename addr in OSFILE block
9111 LDA zp_text_ptr_hi ; Get filename pointer high
9113 STA wksp_osfile_block_1 ; Store in OSFILE block+1
9116 LDA #&40 ; Point (&B8) to OSFILE control block
9118 STA zp_osfile_ptr_lo ; Store control block pointer low
911A LDA #&10 ; Control block page = &10
911C STA zp_osfile_ptr_hi ; Store control block pointer high
911E .search_and_delete_entry
JSR parse_osfile_and_search ; Search directory for the file
9121 BEQ check_and_delete_found ; Found? Proceed to delete
9123 LDA #0 ; Not found: A=0 (no error)
9125 JMP save_wksp_and_return ; Save workspace and return

Validate and delete a directory entry

Check file is not open, verify locked attribute, for directories confirm empty, then proceed with deletion.

9128 .check_and_delete_found←2← 9121 BEQ← 9A32 JSR
JSR check_file_not_open ; Check if file has open channels
912B LDY #3 ; Y=3: check access byte
912D LDA (zp_entry_ptr_lo),y ; Get access/attribute byte
912F BPL proceed_with_delete ; Bit 7 clear: regular file, skip
9131 LDY #3 ; Directory: check if empty
9133 .save_csd_for_dir_check_loop←1← 913A BPL
LDA wksp_csd_drive_sector,y ; Save CSD sector to temp workspace
9136 STA wksp_temp_sector,y ; Save CSD sector to temp workspace
9139 DEY ; Next byte
913A BPL save_csd_for_dir_check_loop ; Loop for 4 bytes
913C LDA #&ff ; Mark workspace as not saved
913E STA wksp_alt_sector_hi ; Mark alternative workspace as unset
9141 STA wksp_saved_drive ; Mark saved drive as unset
9144 JSR parse_path_and_load ; Load the subdirectory to check
9147 LDA dir_first_entry ; Is first entry empty (dir empty)?
914A PHP ; Save result (empty flag)
914B JSR save_wksp_and_return ; Restore CSD and directory
914E LDY #3 ; Y=3: restore 4 bytes of CSD sector
9150 .restore_csd_after_check_loop←1← 9157 BPL
LDA wksp_temp_sector,y ; Restore saved CSD sector
9153 STA wksp_csd_drive_sector,y ; Restore CSD sector byte
9156 DEY ; Next byte
9157 BPL restore_csd_after_check_loop ; Loop for 4 bytes
9159 PLP ; Restore empty flag
915A BEQ proceed_with_delete ; Directory was empty: proceed
915C JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
915F EQUB &B4 ; Error &B4: Dir not empty
9160 EQUS "Dir not empty."
916E .proceed_with_delete←2← 912F BPL← 915A BEQ
LDY #&12 ; Get file size from directory entry
9170 LDX #2 ; X=2: 3 bytes of length to process
9172 LDA (zp_entry_ptr_lo),y ; Y=&12: length bytes offset
9174 CMP #1 ; Calculate number of sectors
9176 .copy_locked_name_loop←1← 917F BPL
INY ; Next length byte
9177 LDA #0 ; Add carry from previous byte
9179 ADC (zp_entry_ptr_lo),y ; Add entry length byte
917B STA wksp_entry_size_base,y ; Store sector count in workspace
917E DEX ; Next length byte
917F BPL copy_locked_name_loop ; Loop for 3 bytes
9181 LDY #&18 ; Y=&18: get start sector
9183 LDX #2 ; X=2: copy 3 sector address bytes
9185 .check_locked_attr_loop←1← 918C BPL
LDA (zp_entry_ptr_lo),y ; Copy start sector to workspace
9187 STA wksp_object_sector,x ; Store sector address byte
918A DEY ; Next entry byte (decreasing)
918B DEX ; Next workspace byte (decreasing)
918C BPL check_locked_attr_loop ; Loop for 3 bytes
918E LDY #3 ; Y=3: check access byte for dir flag
9190 LDA (zp_entry_ptr_lo),y ; Check access byte for directory
9192 BPL write_dir_and_release ; Not a directory: skip to delete
9194 LDX wksp_saved_drive ; Get saved drive
9197 CPX #&ff ; Saved drive = &FF (not set)?
9199 BEQ file_is_locked ; Not set? Check CSD
919B CPX wksp_current_drive ; Same as current drive?
919E BNE release_entry_space ; No, skip CSD check
91A0 .file_is_locked←1← 9199 BEQ
LDX #2 ; X=2: compare 3 sector bytes
91A2 .copy_entry_name_to_wksp_loop←1← 91AB BPL
LDA wksp_object_sector,x ; Compare sector with CSD sector
91A5 CMP wksp_csd_drive_sector,x ; Compare with CSD sector byte
91A8 BNE release_entry_space ; Mismatch: not the CSD
91AA DEX ; Next byte in CSD comparison
91AB BPL copy_entry_name_to_wksp_loop ; Loop for 3 bytes
91AD JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
91B0 EQUB &96 ; Error &96: Cant delete CSD
91B1 EQUS "Can't delete CSD."
91C2 .release_entry_space←2← 919E BNE← 91A8 BNE
LDA wksp_current_drive ; Check if it's the library dir
91C5 CMP wksp_lib_sector_hi ; Compare drive with library drive
91C8 BNE update_dir_sequence ; Different: not the library
91CA LDX #2 ; Compare sector with lib sector
91CC .remove_entry_shift_loop←1← 91D5 BPL
LDA wksp_object_sector,x ; Compare sector with lib sector byte
91CF CMP wksp_lib_sector,x ; Compare with library sector byte
91D2 BNE update_dir_sequence ; Mismatch: not the library dir
91D4 DEX ; Next byte in library comparison
91D5 BPL remove_entry_shift_loop ; Loop for 3 bytes
91D7 JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
91DA EQUB &97 ; Error &97: Cant delete library
91DB EQUS "Can't delete Library."
91F0 .update_dir_sequence←2← 91C8 BNE← 91D2 BNE
LDA wksp_current_drive ; Check if it's the previous dir
91F3 CMP wksp_prev_dir_sector_hi ; Compare drive with prev dir drive
91F6 BNE write_dir_and_release ; Different: skip prev dir reset
91F8 LDX #2 ; Compare sector with prev dir sector
91FA .copy_entry_up_loop←1← 9203 BPL
LDA wksp_object_sector,x ; Get object sector byte
91FD CMP wksp_prev_dir_sector,x ; Compare with prev dir sector
9200 BNE write_dir_and_release ; Different: skip
9202 DEX ; Next byte in prev dir comparison
9203 BPL copy_entry_up_loop ; Loop for 3 bytes
9205 LDA #2 ; Reset previous dir to root (sector 2)
9207 STA wksp_prev_dir_sector ; Reset prev dir to root (sector 2)
920A LDA #0 ; A=0: clear high sector bytes
920C STA wksp_prev_dir_sector_lo ; Clear prev dir mid byte
920F STA wksp_prev_dir_sector_mid ; Clear prev dir high byte
9212 .write_dir_and_release←4← 9192 BPL← 91F6 BNE← 9200 BNE← A67F JSR
LDY #4 ; Remove entry from directory
9214 LDA (zp_entry_ptr_lo),y ; Y=4: check lock bit
9216 BMI check_csd_deleted ; Bit 7 set: directory, skip lock chk
9218 JSR search_dir_with_wildcards ; Check file is not locked
921B .check_csd_deleted←1← 9216 BMI
LDY #&1a ; Y=&1A: offset to next entry
921D LDX #0 ; X=0: for indirect store via (&B6,X)
921F .check_lib_deleted←2← 922D BNE← 9233 BNE
LDA (zp_entry_ptr_lo),y ; Copy next entry over this one
9221 STA (zp_entry_ptr_lo,x) ; Store in current position
9223 INC zp_entry_ptr_lo ; Advance pointer
9225 BNE check_prev_dir_deleted ; No page crossing
9227 INC zp_entry_ptr_hi ; Increment pointer high byte
9229 .check_prev_dir_deleted←1← 9225 BNE
LDA zp_entry_ptr_lo ; Check if past end of entries
922B CMP #&bb ; Low byte = &BB? (dir footer boundary)
922D BNE check_lib_deleted ; Low byte not at boundary, continue
922F LDA zp_entry_ptr_hi ; Check if past end of entries (&16xx)
9231 CMP #&16 ; High byte should be &16
9233 BNE check_lib_deleted ; Not past end: continue copying
9235 JSR release_disc_space ; Release the file's disc space
9238 JSR write_dir_and_validate ; Write modified directory to disc
923B JMP get_object_type_result ; Save workspace and return

OSFILE handler

Handle OSFILE calls for whole-file operations: load, save, read/write catalogue info, delete, create.

923E .osfile_handler
STX zp_osfile_ptr_lo ; Save control block address low
9240 STY zp_osfile_ptr_hi ; Save control block address high
9242 TAY ; Transfer function code to Y
9243 LDX #0 ; Clear current channel
9245 STX wksp_cur_channel ; Clear current channel
9248 ASL ; A = function * 2 (table index)
9249 TAX ; Transfer A*2 to X
924A INX ; X = A*2 + 1 (skip table base)
924B INX ; X = A*2 + 2 (dispatch table offset)
924C BMI return_19 ; Function < 0? Invalid
924E CPX #&12 ; Function >= 8? Invalid
9250 BCS return_19 ; Function >= 8: unsupported
9252 LDA osfile_dispatch_hi,x ; Push dispatch address high byte
9255 PHA ; Push dispatch address high
9256 LDA osfile_dispatch_lo,x ; Push dispatch address low byte
9259 PHA ; Push dispatch address low
925A TYA ; Restore function code to A
925B PHA ; Save function code on stack
925C LDY #0 ; Y=0: read filename pointer from block
925E LDA (zp_osfile_ptr_lo),y ; Filename address low byte
9260 STA zp_text_ptr_lo ; Store in (&B4)
9262 INY ; Y=&01
9263 LDA (zp_osfile_ptr_lo),y ; Filename address high byte
9265 STA zp_text_ptr_hi ; Store in (&B5)
9267 PLA ; Restore function code
9268 .return_19←2← 924C BMI← 9250 BCS
RTS ; RTS-dispatch to function handler

OSFILE dispatch table

RTS-trick dispatch table for OSFILE functions 0-7. Low bytes at &9269, high bytes at &926A, interleaved as pairs. Functions: 0=save, 1=write cat info, 2=write load addr, 3=write exec addr, 4=write attrs, 5=read cat info, 6=delete, 7=create.

9269 .osfile_dispatch_lo←1← 9256 LDA
EQUB <(osfile_save_check_existing-1) ; A=0 lo-1: OSFILE save
926A .osfile_dispatch_hi←1← 9252 LDA
EQUB >(osfile_save_check_existing-1) ; A=0 hi-1: OSFILE save
926B EQUW osfile_load_handler-1
926D EQUW osfile_write_load_addr-1
926F EQUW osfile_write_load_addr-1
9271 EQUW osfile_write_load_addr-1
9273 EQUW osfile_delete_handler-1
9275 EQUW osfile_save_handler-1
9277 EQUW search_and_delete_entry-1
9279 EQUW osfile_read_cat_info-1

Set up pointer to *HELP parameter format string

Point (zp_entry_ptr) to a pathname format string in ROM and prepare to print up to 12 characters.

On EntryAindex into tbl_help_param_ptrs
On ExitAcorrupted
Xzero
Ycorrupted
927B .setup_help_param_ptr←2← 9E35 JSR← 9E3B JSR
TAX ; Transfer index to X
927C LDA #&9f ; Set up (&B6) to point to pathname
927E STA zp_entry_ptr_hi ; Set pointer high byte
9280 LDA tbl_help_param_ptrs,x ; Get pathname format byte
9283 STA zp_entry_ptr_lo ; Store as pointer low byte
9285 LDX #&0c ; X=&0C: max 12 characters
9287 .print_padded_name←6← 92E0 JSR← 9337 JSR← 9366 JSR← 938C JSR← 93A3 JSR← 93BD JSR
LDY #0 ; Y=0: start of entry name
9289 .print_name_char_loop←1← 9296 BNE
LDA (zp_entry_ptr_lo),y ; Get character from entry
928B AND #&7f ; Strip bit 7 (access bit)
928D CMP #&20 ; Is it a printable character?
928F BCC pad_with_spaces ; No, pad rest with spaces
9291 JSR print_via_osasci ; Print character via OSASCI
9294 INY ; Next character
9295 DEX ; Decrement column counter
9296 BNE print_name_char_loop ; Loop for remaining columns
9298 RTS ; Return
9299 .pad_with_spaces←2← 928F BCC← 929D BNE
JSR print_space ; Print space padding
929C DEX ; Pad with spaces
929D BNE pad_with_spaces ; Loop for remaining columns
929F RTS ; Return

Print bit-7-terminated inline string

Pop the return address from the stack, print the inline string that follows the JSR instruction. Characters are printed via OSASCI until a byte with bit 7 set is found (the last character, printed with bit 7 stripped). Pushes the address past the string so RTS continues after it.

On ExitAcorrupted
Xpreserved
Ycorrupted
92A0 .print_inline_string←19← 933A JSR← 9345 JSR← 9369 JSR← 9379 JSR← 938F JSR← 93A6 JSR← 93C0 JSR← 99FD JSR← 9B78 JSR← 9DA7 JSR← 9DC9 JSR← 9E12 JSR← A021 JSR← A041 JSR← A04A JSR← A077 JSR← A0A0 JSR← A1D8 JSR← A247 JSR
PLA ; Pop return addr low (inline data)
92A1 STA zp_entry_ptr_lo ; Store as string pointer low
92A3 PLA ; Pop return addr high
92A4 STA zp_entry_ptr_hi ; Store as string pointer high
92A6 LDY #1 ; Y=1: start past JSR return addr
92A8 .print_char_loop←1← 92B0 BNE
LDA (zp_entry_ptr_lo),y ; Get next string character
92AA BMI last_char_reached ; Bit 7 set: last character
92AC JSR print_via_osasci ; Print character via OSASCI
92AF INY ; Next character
92B0 BNE print_char_loop ; Loop for more characters
92B2 .last_char_reached←1← 92AA BMI
AND #&7f ; Strip bit 7 from last char
92B4 JSR print_via_osasci ; Print last character
92B7 TYA ; Y = string length + 1
92B8 CLC ; Clear carry for address calc
92B9 ADC zp_entry_ptr_lo ; Add string length to pointer
92BB TAY ; Transfer low result to Y
92BC LDA #0 ; A=0: add carry only
92BE ADC zp_entry_ptr_hi ; Add carry to high byte
92C0 PHA ; Push updated return addr high
92C1 TYA ; Transfer low to A
92C2 PHA ; Push updated return addr low
92C3 RTS ; Return (past inline string)

Print character preserving registers

Write A via OSASCI while preserving A, X, and (&B6). Used during catalogue printing.

On EntryAcharacter to print via OSASCI
On ExitApreserved
Xpreserved
Ycorrupted
92C4 .print_via_osasci←3← 9291 JSR← 92AC JSR← 92B4 JSR
PHA ; Save character to print
92C5 TXA ; Save X
92C6 PHA ; Save X on stack
92C7 LDA zp_entry_ptr_lo ; Save (&B6) low
92C9 PHA ; Push on stack
92CA LDA zp_entry_ptr_hi ; Save (&B6) high
92CC PHA ; Push on stack
92CD TSX ; Get stack pointer
92CE LDA brk_error_block_4,x ; Get character from stack+4
92D1 JSR osasci ; Write character
92D4 PLA ; Restore (&B6) high
92D5 STA zp_entry_ptr_hi ; Store back
92D7 PLA ; Restore (&B6) low
92D8 STA zp_entry_ptr_lo ; Store back
92DA PLA ; Restore X
92DB TAX ; Transfer to X
92DC PLA ; Restore character (was printed)
92DD RTS ; Return

Print entry name and access string

Print the 10-character padded filename from (&B6) followed by the access attribute string (R, W, L, D).

92DE .print_entry_name_and_access←2← 93E2 JSR← 9501 JSR
LDX #&0a ; X=&0A: print up to 10 name chars
92E0 JSR print_padded_name ; Print name characters
92E3 JSR print_space ; Print space after name
92E6 LDY #4 ; Y=4: check 5 attribute bytes
92E8 LDX #3 ; X=3: print RWLD attribute chars
92EA .print_entry_char_loop←1← 92F7 BPL
LDA (zp_entry_ptr_lo),y ; Get entry byte (name byte Y)
92EC ROL ; Rotate bit 7 (attribute) into carry
92ED BCC print_access_space ; C=0: attribute not set
92EF LDA tbl_access_chars,y ; C=1: get attribute letter
92F2 JSR oswrch ; Write character
92F5 DEX ; Next attribute letter
92F6 .print_access_space←1← 92ED BCC
DEY ; Next entry byte (decreasing Y)
92F7 BPL print_entry_char_loop ; Loop for 5 bytes (E,D,L,W,R)
92F9 .print_access_chars_loop←1← 92FF JMP
DEX ; Decrement space counter
92FA BMI print_access_done ; All printed: add '/' separator
92FC JSR print_space ; Print space for unset attribute
92FF JMP print_access_chars_loop ; Continue attribute loop
9302 .print_access_done←1← 92FA BMI
LDA #&28 ; Print '(' before sequence number
9304 JSR oswrch ; Write character 40
9307 LDY #&19 ; Y=&19: offset to sequence number
9309 LDA (zp_entry_ptr_lo),y ; Get sequence number byte
930B JSR print_hex_byte ; Print as 2 hex digits
930E LDA #&29 ; Print ')' after sequence number
9310 JSR oswrch ; Write character 41
9313 JMP print_space ; Print space and return

Access attribute character table

Five-character table 'RWLDE' used to look up and display file access attributes. Indexed by attribute bit position.

9316 .tbl_access_chars←2← 92EF LDA← 99A7 CMP
EQUS "RWLDE"

Print a byte as two hex digits

Print the value in A as two hexadecimal ASCII digits via OSWRCH, high nibble first.

On EntryAbyte value to print as hex
931B .print_hex_byte←9← 930B JSR← 9342 JSR← 9376 JSR← 9521 JSR← A071 JSR← A087 JSR← A1C9 JSR← A1CF JSR← A1D5 JSR
PHA ; Save value
931C LSR ; Shift high nibble to low
931D LSR ; Second shift
931E LSR ; Third shift
931F LSR ; Fourth shift
9320 JSR print_hex_nibble ; Print high nibble as hex char
9323 PLA ; Restore value for low nibble
9324 .print_hex_nibble←1← 9320 JSR
JSR hex_digit ; Convert 4-bit value to ASCII hex digit
9327 JMP oswrch ; Write character

Verify directory and print catalogue header

Verify directory integrity then print directory title, sequence number, drive number, and name as the header for a catalogue listing.

932A .verify_dir_and_list←2← 93D4 JSR← 9436 JSR
JSR verify_dir_integrity ; Verify directory buffer integrity
932D LDA #&d9 ; Point to dir title at &16D9
932F STA zp_entry_ptr_lo ; Store low byte
9331 LDA #&16 ; Page &16
9333 STA zp_entry_ptr_hi ; Store high byte
9335 LDX #&13 ; X=&13: print 19 chars of title
9337 JSR print_padded_name ; Print title characters
933A JSR print_inline_string ; Print bit-7-terminated inline string
933D EQUS " "
933E TAY ; Transfer Y to A for display
933F LDA dir_master_sequence ; Get directory sequence number
9342 JSR print_hex_byte ; Print as 2 hex digits
9345 JSR print_inline_string ; Print bit-7-terminated inline string
9348 EQUS ").Drive"
934F TSX ; Get stack pointer
9350 LDA wksp_current_drive ; Get current drive number
9353 ASL ; Shift drive bits into position
9354 ROL ; Second shift
9355 ROL ; Third shift
9356 ROL ; Fourth shift
9357 ADC #&30 ; Convert to ASCII digit
9359 JSR oswrch ; Write character
935C LDA #&5f ; Point to CSD path string in ROM
935E STA zp_entry_ptr_lo ; Store pointer low
9360 LDA #&9a ; Page &9A
9362 STA zp_entry_ptr_hi ; Store pointer high
9364 LDX #&0d ; X=&0D: print CSD path
9366 JSR print_padded_name ; Print path characters
9369 JSR print_inline_string ; Print bit-7-terminated inline string
936C EQUS "Option"
9372 EQUB &A0 ; ' ' + bit 7: end of inline string
9373 LDA fsm_s1_boot_option ; Get boot option from FSM
9376 JSR print_hex_byte ; Print boot option as two hex digits
9379 JSR print_inline_string ; Print bit-7-terminated inline string
937C EQUS " "
937D TAY ; Transfer boot option to Y for lookup
937E LDX fsm_s1_boot_option ; Get boot option again for table index
9381 LDA l941f,x ; Look up option name string address
9384 STA zp_entry_ptr_lo ; Set entry ptr to option name string
9386 LDA #&94
9388 STA zp_entry_ptr_hi
938A LDX #4 ; X=4: print 4-char option name
938C JSR print_padded_name ; Print boot option name (Off/Load/Run/Exec)
938F JSR print_inline_string ; Print bit-7-terminated inline string
9392 EQUS ").Dir." ; ")" + CR + "Dir." + space: option close + dir label
9398 EQUB &A0 ; ' ' + bit 7: end of inline string
9399 LDA #0 ; A=&00: CSD name low (wksp_csd_name)
939B STA zp_entry_ptr_lo ; Store pointer low byte
939D LDA #&11 ; A=&11: CSD name high (&1100)
939F STA zp_entry_ptr_hi ; Store pointer high byte
93A1 LDX #&0a ; X=&0A: print 10-char directory name
93A3 JSR print_padded_name ; Print CSD directory name
93A6 JSR print_inline_string ; Print bit-7-terminated inline string
93A9 EQUS " Lib."
93B2 EQUB &A0 ; ' ' + bit 7: end of inline string
93B3 LDA #&0a ; A=&0A: library name low (wksp_lib_name)
93B5 STA zp_entry_ptr_lo ; Store pointer low byte
93B7 LDA #&11 ; A=&11: library name high (&110A)
93B9 STA zp_entry_ptr_hi ; Store pointer high byte
93BB LDX #&0a ; X=&0A: print 10-char library name
93BD JSR print_padded_name ; Print library directory name
93C0 JSR print_inline_string ; Print bit-7-terminated inline string
93C3 EQUS "." ; CR: end of library name line
93C4 EQUB &8D ; CR + bit 7: blank line after header
93C5 .print_catalogue_header←3← 87EA JSR← 97BE JSR← 98DA JSR
LDA #5 ; Point to first dir entry at &1205
93C7 STA zp_entry_ptr_lo ; Store pointer low = &05
93C9 LDA #&12 ; Page &12
93CB STA zp_entry_ptr_hi ; Store pointer high
93CD RTS ; Return
93CE .print_catalogue_entries
JSR skip_spaces ; Skip leading spaces in command argument
93D1 JSR parse_dir_argument ; Parse optional directory path argument
93D4 .print_cat_header_and_entries←1← A485 JSR
JSR verify_dir_and_list ; Load and validate directory
93D7 LDA #4 ; Columns per line = 4
93D9 STA wksp_csd_sector_temp ; Store column counter
93DC .print_cat_entry_loop←2← 93FF BCC← 9403 BCS
LDY #0 ; Y=0: check first byte of entry
93DE LDA (zp_entry_ptr_lo),y ; Get first byte
93E0 BEQ print_cat_pair_second ; Zero: end of entries
93E2 JSR print_entry_name_and_access ; Print entry name with access
93E5 DEC wksp_csd_sector_temp ; Decrement column counter
93E8 BNE advance_cat_entry ; Not zero: same line
93EA LDA #4 ; Reset column counter to 4
93EC STA wksp_csd_sector_temp ; Store column counter
93EF JSR osnewl ; Write newline (characters 10 and 13)
93F2 JMP print_cat_pair ; Jump to newline print
93F5 .advance_cat_entry←1← 93E8 BNE
JSR print_space ; Print space between entries
93F8 .print_cat_pair←1← 93F2 JMP
CLC ; Clear carry for pointer advance
93F9 LDA zp_entry_ptr_lo ; Get entry pointer low
93FB ADC #&1a ; Add &1A (26 bytes per entry)
93FD STA zp_entry_ptr_lo ; Store updated pointer
93FF BCC print_cat_entry_loop ; No page crossing: continue
9401 INC zp_entry_ptr_hi ; Increment page
9403 BCS print_cat_entry_loop ; ALWAYS branch
9405 .print_cat_pair_second←1← 93E0 BEQ
LDA wksp_csd_sector_temp ; Get column counter
9408 CMP #4 ; Full line (4 columns)?
940A BEQ print_cat_done ; Yes: no partial line to finish
940C LDA #osbyte_read_text_cursor_pos ; OSBYTE &86: read cursor position
940E JSR osbyte ; Read input cursor position (Sets X=POS and Y=VPOS)
9411 TXA ; X is the horizontal text position ('POS')
9412 BNE print_cat_newline ; X non-zero: cursor not at col 0
9414 LDA #&0b ; At col 0: VDU 11 (cursor up)
9416 JSR oswrch ; Write character 11
9419 .print_cat_newline←1← 9412 BNE
JSR osnewl ; Write newline (characters 10 and 13)
941C .print_cat_done←2← 940A BEQ← 943D BEQ
JMP save_wksp_and_return ; Save workspace and return
941F .l941f←1← 9381 LDA
EQUS "#'+/Off LoadRun Exec"

*EX command handler

Display a full catalogue of the current or specified directory, showing filename, attributes, load and execution addresses, length, and start sector for each entry.

9433 .star_ex
JSR parse_dir_argument ; Parse directory argument
9436 .load_dir_and_list_entries←1← A491 JSR
JSR verify_dir_and_list ; Load and validate the directory
9439 .print_next_entry_loop←2← 9449 BCC← 944D BCS
LDY #0 ; Y=0: check first byte of entry
943B LDA (zp_entry_ptr_lo),y ; Get first byte of entry name
943D BEQ print_cat_done ; Zero: end of entries, done
943F JSR print_entry_info ; Print this entry's full info
9442 CLC ; Clear carry for addition
9443 LDA zp_entry_ptr_lo ; Advance (&B6) by 26 to next entry
9445 ADC #&1a ; Add &1A (26 bytes per entry)
9447 STA zp_entry_ptr_lo ; Store low byte
9449 BCC print_next_entry_loop ; No page crossing, continue loop
944B INC zp_entry_ptr_hi ; Page crossed: increment high byte
944D BCS print_next_entry_loop ; Continue loop (always branches)
fall through ↓

Check for ^ (parent) or @ (current) directory

Check if the first character of the argument is ^ (parent directory) or @ (current directory). Sets (&B6) to point to the appropriate directory footer area.

On ExitAcorrupted (Z set if ^ or @ matched)
Xcorrupted
Ycorrupted
944F .check_special_dir_char←3← 88D2 JSR← 8CDC JSR← 9492 JSR
LDY #0 ; Y=0: get first argument char
9451 LDA (zp_text_ptr_lo),y ; Get first argument char
9453 AND #&7f ; Strip bit 7
9455 CMP #&5e ; Is it '^' (parent directory)?
9457 BNE check_at_sign ; No, check for '@'
9459 LDA #&c0 ; '^': point to dir parent sector
945B STA zp_entry_ptr_lo ; Set (&B6) low to &C0
945D LDA #&16 ; Set (&B6) high to &16 (dir footer)
945F STA zp_entry_ptr_hi ; Store high byte
9461 BNE set_matched_flag ; Set Z flag (matched)
9463 .check_at_sign←1← 9457 BNE
CMP #&40 ; Is it '@' (current directory)?
9465 BNE return_20 ; No, return Z clear (no match)
9467 LDA #&fe ; '@': point to workspace at &10FE
9469 STA zp_entry_ptr_lo ; Set (&B6) low to &FE
946B LDA #&10 ; Set (&B6) high to &10 (workspace)
946D STA zp_entry_ptr_hi ; Store high byte
946F .set_matched_flag←1← 9461 BNE
TYA ; Transfer Y=0 to A, setting Z flag
9470 .return_20←2← 9465 BNE← 947D BNE
RTS ; Return

Parse optional directory path argument

If a directory argument is given, parse the path and load the target directory. If no argument, use the current directory (checking it's initialised first).

Used by *EX, *CAT, *CDIR, and *DIR.

9471 .parse_dir_argument←2← 93D1 JSR← 9433 JSR
LDY #0 ; Y=0: check for argument
9473 LDA (zp_text_ptr_lo),y ; Get first char of argument
9475 CMP #&21 ; Is it a printable char?
9477 BCS parse_path_and_load ; Yes, parse the path
9479 LDX wksp_current_drive ; No arg: check drive is initialised
947C INX ; Drive = &FF (uninitialised)?
947D BNE return_20 ; Drive OK, return
fall through ↓

Parse path and load target directory

Parse a full pathname and load the target directory into the buffer. Handles drive specifiers, root, parent, and current directory references.

947F .parse_path_and_load←8← 9144 JSR← 9477 BCS← 953F JSR← 98C9 JSR← 991A JSR← A444 JSR← A6AB JSR← A86F JSR
JSR full_pathname_parser ; Parse path and load directory
9482 BNE check_special_dir ; Simple path or '^'/'@'?
9484 .search_for_dir_entry←1← 948D BEQ
LDY #3 ; Y=3: check entry access byte
9486 LDA (zp_entry_ptr_lo),y ; Get access/attribute byte
9488 BMI prepare_dir_read ; Bit 7 set: is a directory, found it
948A JSR advance_dir_entry_ptr ; Not a directory, search deeper
948D BEQ search_for_dir_entry ; Found directory: loop complete
948F .path_not_found←1← 9495 BNE
JMP bad_parms_error ; Not found: raise error
9492 .check_special_dir←1← 9482 BNE
JSR check_special_dir_char ; Check for '^' or '@' specifier
9495 BNE path_not_found ; Continue to next path component
9497 .prepare_dir_read←1← 9488 BMI
LDY wksp_alt_sector_hi ; Set up workspace for directory read
949A INY ; Increment: was &FF, now 0
949B BNE copy_disc_op_template ; Non-zero: skip CSD copy
949D LDY #2 ; Copy CSD sector to workspace
949F .copy_csd_sector_to_wksp←1← 94A6 BPL
LDA wksp_csd_sector_lo,y ; Get CSD sector byte
94A2 STA wksp_csd_drive_sector,y ; Copy to CSD workspace
94A5 DEY ; Next byte
94A6 BPL copy_csd_sector_to_wksp ; Loop for 3 bytes
94A8 .copy_disc_op_template←1← 949B BNE
LDX #&0a ; X=&0A: copy 11-byte disc op block
94AA .copy_template_loop←1← 94B1 BPL
LDA disc_op_tpl_read_dir,x ; Copy template control block
94AD STA wksp_disc_op_result,x ; Copy template byte to workspace
94B0 DEX ; Next byte
94B1 BPL copy_template_loop ; Loop for 11 bytes
94B3 LDX #2 ; X=2: copy 3 sector address bytes
94B5 LDY #&16 ; Y=&16: offset of start sector in entry
94B7 .copy_entry_sector_loop←1← 94C1 BPL
LDA (zp_entry_ptr_lo),y ; Get sector byte from directory entry
94B9 STA wksp_disc_op_sector,x ; Store in disc op control block
94BC STA wksp_alt_csd_sector,y ; Also store in workspace
94BF INY ; Next sector address byte
94C0 DEX ; Decrement counter
94C1 BPL copy_entry_sector_loop ; Loop for 3 bytes
94C3 LDA zp_entry_ptr_hi ; Check if this is an *INFO call
94C5 CMP #&94 ; zp_b7 = &94 means *INFO context
94C7 BEQ return_21 ; Yes, return without reading dir
94C9 JMP exec_disc_op_from_wksp ; Execute disc read to load directory

Dummy directory entry for root directory '$'

A synthetic 26-byte directory entry representing the root directory. Used when '$' is referenced directly to avoid loading the root directory just to read its metadata. The entry has name '$' (padded with CR), access R/L/D (read, locked, directory), load/exec &00000000, length &00000500 (5 sectors), start sector 2.

94CC .dummy_root_dir_entry
EQUB &A4 ; '$' + bit 7 (R access): filename char 0
94CD EQUB &0D ; CR: filename padding char 1
94CE EQUB &8D ; CR + bit 7 (L access): filename char 2
94CF EQUB &8D ; CR + bit 7 (D=directory): filename char 3
94D0 EQUB &0D, &0D, &0D, &0D, &0D, ; CR padding: filename chars &0D ; 4-9
94D6 EQUB &00, &00, &00, &00 ; Load address: &00000000
94DA EQUB &00, &00, &00, &00 ; Exec address: &00000000
94DE EQUB &00 ; Length low: &00
94DF EQUB &05 ; Length byte 1: &05 (5 sectors = &500 bytes)
94E0 EQUB &00, &00 ; Length bytes 2-3: &0000
94E2 EQUB &02 ; Start sector low: &02 (root directory)
94E3 EQUB &00, &00 ; Start sector mid/high: &0000
94E5 EQUB &00 ; Sequence number: &00
94E6 EQUB &00 ; Padding: &00

*INFO command handler

Display catalogue information for a single file, with the same format as *EX but for one file only. Supports wildcards.

94E7 .star_info←1← 99F4 JSR
JSR find_first_matching_entry ; Find first matching file
94EA BEQ print_info_loop ; Found? Print its info
94EC JMP not_found_error ; Not found: report error
94EF .print_info_loop←2← 94EA BEQ← 94F5 BEQ
JSR print_entry_info ; Print this entry's catalogue info
94F2 JSR advance_dir_entry_ptr ; Find next matching file
94F5 BEQ print_info_loop ; More matches? Continue loop
94F7 JMP save_wksp_and_return ; No more matches: save and return

Display file info if *OPT1 verbose

Check *OPT1 verbose flag. If set, print full catalogue info for the current directory entry.

94FA .conditional_info_display←2← 8C62 JSR← 99BB JSR
LDA zp_adfs_flags ; Check *OPT1 setting
94FC AND #4 ; Bit 2 set: verbose mode on
94FE BNE print_entry_info ; Yes, display the info
9500 .return_21←1← 94C7 BEQ
RTS ; Return

Print full catalogue info for one directory entry

Print a directory entry in *INFO/*EX format: filename access/ loadaddr execaddr length sector [D]

Entry at (&B6) is a 26-byte directory entry. Checks the E (execute-only) attribute and suppresses detail if set. Uses 3-byte addresses for small files, 4-byte for large.

9501 .print_entry_info←3← 943F JSR← 94EF JSR← 94FE BNE
JSR print_entry_name_and_access ; Print filename and access string
9504 JSR oswrch ; Print space after access string
9507 LDY #4 ; Y=4: check first access nibble byte
9509 LDA (zp_entry_ptr_lo),y ; Get access/attribute byte
950B BMI print_newline_return ; Bit 7 (E attribute): suppress info
950D DEY ; Y=3: get access byte for format
950E LDA (zp_entry_ptr_lo),y ; Get access byte
9510 ROL ; Shift bit 7 into C (directory flag)
9511 LDX #&0a ; X=&0A: start offset (3-byte addrs)
9513 LDY #&0d ; Y=&0D: end offset for 3-byte format
9515 BCC print_entry_field_loop ; C=0: 3-byte addresses
9517 LDX #&17 ; X=&17: start offset (4-byte addrs)
9519 LDY #&18 ; Y=&18: end offset for 4-byte format
951B .print_entry_field_loop←2← 9515 BCC← 953A BNE
CPX #&16 ; Skip sector field boundary?
951D BEQ check_field_boundary ; Yes, skip the sector field gap
951F LDA (zp_entry_ptr_lo),y ; Get byte from entry
9521 JSR print_hex_byte ; Print as 2 hex digits
9524 .check_field_boundary←1← 951D BEQ
TXA ; Check if at field boundary
9525 AND #3 ; Field boundary every 4 bytes (X&3=1)
9527 CMP #1 ; X mod 4 == 1? Field boundary
9529 BNE next_entry_byte ; Not at boundary, continue
952B JSR print_space ; Print two spaces between fields
952E JSR print_space ; Print second padding space
9531 TXA ; Skip ahead to next field
9532 CLC ; Clear carry for addition
9533 ADC #5 ; Advance Y by 5
9535 TAY ; Transfer new Y offset
9536 .next_entry_byte←1← 9529 BNE
DEY ; Next byte backwards
9537 INX ; Advance field index
9538 CPX #&1a ; Past end of entry (X=&1A)?
953A BNE print_entry_field_loop ; No, continue printing
953C .print_newline_return←1← 950B BMI
JMP osnewl ; Print newline at end of entry

*DIR command handler

Change the currently selected directory. With no argument, selects the root directory of the current drive.

953F .star_dir←1← A176 JSR
JSR parse_path_and_load ; Parse path and load target dir
9542 LDY #9 ; Y=9: copy 10-byte directory name
9544 .copy_dir_name_loop←1← 954B BPL
LDA dir_name,y ; Get name byte from dir buffer
9547 STA wksp_csd_name,y ; Store as CSD name
954A DEY ; Next byte in name copy
954B BPL copy_dir_name_loop ; Loop for all 10 bytes
954D LDA wksp_saved_drive ; Get saved drive number
9550 CMP #&ff ; Is it &FF (not set)?
9552 BNE store_csd_drive ; No, use saved drive
9554 LDA wksp_current_drive ; Use current drive instead
9557 .store_csd_drive←1← 9552 BNE
STA wksp_prev_dir_sector_hi ; Store as new CSD drive
955A LDY #2 ; Y=2: copy 3-byte sector address
955C .copy_csd_sector_loop←1← 9563 BPL
LDA wksp_csd_drive_sector,y ; Get CSD sector address byte
955F STA wksp_prev_dir_sector,y ; Save as previous dir sector (*BACK)
9562 DEY ; Next byte in sector copy
9563 BPL copy_csd_sector_loop ; Loop for 3 bytes
9565 LDA #&ff ; A=&FF: mark as unset
9567 STA wksp_alt_sector_hi ; Clear alternative workspace ptr
956A STA wksp_saved_drive ; Clear saved drive
956D JMP save_wksp_and_return ; Save workspace and return

*CDIR command handler

Create a new directory. Allocates 5 contiguous sectors on disc and initialises the directory structure with the Hugo identifier, title, and parent pointer.

9570 .star_cdir
LDA #&ff ; OSARGS &FF: ensure FS is selected
9572 LDY #0 ; Y=0: for OSARGS
9574 JSR osargs_handler ; OSARGS handler
9577 LDX #&0f ; X=&0F: copy 16-byte template block
9579 .check_dir_exists_loop←1← 9580 BPL
LDA osfile_tpl_cdir,x ; Copy OSFILE template to workspace
957C STA wksp_osfile_load_addr,x ; Copy template to workspace
957F DEX ; Next byte
9580 BPL check_dir_exists_loop ; Loop for 16 bytes
9582 LDA zp_text_ptr_lo ; Store filename pointer in OSFILE blk
9584 STA wksp_osfile_block ; Store filename in OSFILE block
9587 LDA zp_text_ptr_hi ; Get filename high byte
9589 STA wksp_osfile_block_1 ; Store in OSFILE block
958C LDA #&40 ; Point (&B8) to workspace OSFILE blk
958E STA zp_osfile_ptr_lo ; Store block pointer low
9590 LDA #&10 ; Block page = &10
9592 STA zp_osfile_ptr_hi ; Store block pointer high
9594 JSR copy_addrs_and_find_empty_entry ; Search for existing entry
9597 LDY #9 ; Y=9: check if entry has size > 0
9599 LDA wksp_object_size ; Check size bytes for non-zero
959C ORA wksp_object_size_mid ; OR size mid byte
959F ORA wksp_object_size_hi ; OR size high byte
95A2 BEQ cdir_name_validated ; Size is 0: entry slot is free
95A4 .already_exists_error2←2← 8D0D JMP← A5E8 JMP
JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
95A7 EQUB &C4 ; Error &C4: Already exists
95A8 EQUS "Already exists."
95B7 .cdir_name_validated←2← 95A2 BEQ← 95C8 BPL
LDA (zp_text_ptr_lo),y ; Copy filename to dir entry, max 10
95B9 AND #&7f ; Strip bit 7
95BB CMP #&22 ; Quote terminates name
95BD BEQ check_root_or_special ; Quote: pad with CR
95BF CMP #&21 ; Control char terminates name
95C1 BCS not_root_or_special ; Printable: use as-is
95C3 .check_root_or_special←1← 95BD BEQ
LDA #&0d ; Pad with CR
95C5 .not_root_or_special←1← 95C1 BCS
STA (zp_entry_ptr_lo),y ; Store character in entry
95C7 DEY ; Next name byte (decreasing)
95C8 BPL cdir_name_validated ; Loop for 10 bytes
95CA JSR write_entry_sector_info ; Allocate disc space for new dir
95CD LDY #3 ; Y=3: set directory attribute
95CF .copy_cdir_sector_loop←1← 95D8 BNE
LDA (zp_entry_ptr_lo),y ; Get entry byte
95D1 ORA #&80 ; Set bit 7 (D attribute on all)
95D3 STA (zp_entry_ptr_lo),y ; Store back
95D5 DEY ; Next byte down
95D6 CPY #1 ; Past byte 1? (byte 0 is special)
95D8 BNE copy_cdir_sector_loop ; No: continue setting attributes
95DA DEY ; Y=0: set D attribute on byte 0
95DB LDA (zp_entry_ptr_lo),y ; Get name byte 0
95DD ORA #&80 ; Set bit 7 (D attribute)
95DF STA (zp_entry_ptr_lo),y ; Store back
95E1 LDA #0 ; A=0: zero-fill all 5 dir pages
95E3 TAX ; X=&00
95E4 TAY ; Y=&00
95E5 .copy_dir_template_loop←1← 95F5 BNE
STA ra_buffer_2,x ; Zero page 2 (&1800)
95E8 STA ra_buffer_1,x ; Zero page 1 (&1700)
95EB STA ra_buffer_3,x ; Zero page 3 (&1900)
95EE STA ra_buffer_4,x ; Zero page 4 (&1A00)
95F1 STA ra_buffer_5,x ; Zero page 5 (&1B00)
95F4 INX ; Next byte
95F5 BNE copy_dir_template_loop ; Loop for 256 bytes per page
95F7 LDX #4 ; X=4: copy 5 bytes (seq+Hugo)
95F9 .init_dir_identity_loop←1← 9609 BPL
LDA str_hugo,x ; Get Hugo identifier byte from ROM
95FC STA ra_buffer_1,x ; Store in dir header (&1700)
95FF STA dir2_master_sequence,x ; Store in dir footer (&1BFA)
9602 LDA wksp_csd_sector_lo,x ; Get parent dir sector byte
9605 STA dir2_parent_sector,x ; Store in footer parent pointer
9608 DEX ; Next byte
9609 BPL init_dir_identity_loop ; Loop for 5 bytes
960B LDX #0 ; X=0: copy name as title and name
960D .zero_dir_entries_loop←1← 9625 BNE
LDA (zp_text_ptr_lo),y ; Get name character from argument
960F AND #&7f ; Strip bit 7
9611 CMP #&22 ; Is it double-quote?
9613 BEQ write_new_dir_to_disc ; Yes: pad with CR
9615 CMP #&21 ; Is it printable (> '!')?
9617 BCS set_dir_parent_sector ; Yes: use character as-is
9619 .write_new_dir_to_disc←1← 9613 BEQ
LDA #&0d ; Non-printable: use CR padding
961B .set_dir_parent_sector←1← 9617 BCS
STA dir2_title,x ; Store in directory title
961E STA dir2_name,x ; Store in directory name
9621 INY ; Next argument character
9622 INX ; Next position in title/name
9623 CPX #&0a ; Copied all 10 characters?
9625 BNE zero_dir_entries_loop ; No: continue copying
9627 LDA #&0d ; A=CR: terminate title
9629 STA dir2_title,x ; Store CR after last title char
962C JSR multi_sector_disc_command ; Calculate sectors and write dir
962F JMP search_for_osfile_target ; Write directory and update FSM

OSFILE control block template for *CDIR

16-byte template copied to the OSFILE control block at &1042-&1051 when creating a new directory. Sets the data region to &1700-&1BFF (the 5-page random access buffer area used as scratch space to build the new directory before writing to disc). The &FFFF prefix marks host memory (not Tube).

9632 .osfile_tpl_cdir←1← 9579 LDA
EQUB &00, &00, ; Load address: &00000000 (not used) &00, &00
9636 EQUB &00, &00, ; Exec address: &00000000 (not used) &00, &00
963A EQUB &00 ; Data start low: &00
963B EQUB &17 ; Data start high: &17 (-> &1700 ra_buffer_1)
963C EQUB &FF ; Data start byte 3: &FF (host memory)
963D EQUB &FF ; Data start byte 4: &FF (host memory)
963E EQUB &00 ; Data end low: &00
963F EQUB &1C ; Data end high: &1C (-> &1C00, 5 pages)
9640 EQUB &FF ; Data end byte 3: &FF (host memory)
9641 EQUB &FF ; Data end byte 4: &FF (host memory)
9642 .copy_sectors_between_dirs←1← 98A8 JSR
LDA wksp_saved_drive ; Check if saved drive matches
9645 CMP wksp_current_drive ; Compare with current drive
9648 BEQ read_source_sector ; Same: check CSD sector match
964A CMP #&ff ; Saved = &FF (not set)?
964C BNE advance_sector_ptrs ; Different drive: skip CSD check
964E .read_source_sector←1← 9648 BEQ
LDY #2 ; Y=2: compare 3 sector bytes
9650 .copy_sector_data_loop←1← 9659 BPL
LDA wksp_copy_read_sector,y ; Get old sector address byte
9653 CMP wksp_csd_drive_sector,y ; Compare with CSD sector
9656 BNE advance_sector_ptrs ; Mismatch: not CSD
9658 DEY ; Next byte
9659 BPL copy_sector_data_loop ; Loop for 3 bytes
965B LDY #2 ; CSD matches: update to new sector
965D .write_dest_sector←1← 9664 BPL
LDA wksp_copy_src_sector,y ; Get new sector byte
9660 STA wksp_csd_drive_sector,y ; Store as CSD sector
9663 DEY ; Next byte
9664 BPL write_dest_sector ; Loop for 3 bytes
9666 .advance_sector_ptrs←2← 964C BNE← 9656 BNE
LDA wksp_lib_sector_hi ; Check library directory
9669 CMP wksp_current_drive ; Compare lib drive with current
966C BNE advance_source_sector ; Different drive: skip lib
966E LDY #2 ; Y=2: compare 3 sector bytes
9670 .copy_remaining_loop←1← 9679 BPL
LDA wksp_copy_read_sector,y ; Get old sector address byte
9673 CMP wksp_lib_sector,y ; Compare with lib sector
9676 BNE advance_source_sector ; Mismatch: not library
9678 DEY ; Next byte
9679 BPL copy_remaining_loop ; Loop for 3 bytes
967B LDY #2 ; Lib matches: update to new sector
967D .advance_dest_sector←1← 9684 BPL
LDA wksp_copy_src_sector,y ; Get new sector byte
9680 STA wksp_lib_sector,y ; Store as lib sector
9683 DEY ; Next byte
9684 BPL advance_dest_sector ; Loop for 3 bytes
9686 .advance_source_sector←2← 966C BNE← 9676 BNE
LDA wksp_prev_dir_sector_hi ; Check previous directory
9689 CMP wksp_current_drive ; Compare prev dir drive
968C BNE execute_sector_copy ; Different drive: skip
968E LDY #2 ; Y=2: compare 3 sector bytes
9690 .copy_dir_name_to_entry←1← 9699 BPL
LDA wksp_copy_read_sector,y ; Get old sector byte
9693 CMP wksp_prev_dir_sector,y ; Compare with prev dir sector
9696 BNE execute_sector_copy ; Mismatch: not prev dir
9698 DEY ; Next byte
9699 BPL copy_dir_name_to_entry ; Loop for 3 bytes
969B LDY #2 ; Prev dir matches: update
969D .set_entry_dir_attribute←1← 96A4 BPL
LDA wksp_copy_src_sector,y ; Get new sector byte
96A0 STA wksp_prev_dir_sector,y ; Store as prev dir sector
96A3 DEY ; Next byte
96A4 BPL set_entry_dir_attribute ; Loop for 3 bytes
96A6 .execute_sector_copy←4← 968C BNE← 9696 BNE← A92C JSR← AF84 JSR
LDA zp_adfs_flags ; Check bit 3 (copy in progress?)
96A8 AND #8 ; Bit 3: copy operation flag
96AA BNE check_tube_for_copy ; Set: skip directory write
96AC JSR write_dir_and_validate ; Write directory to disc
96AF JSR flush_all_channels ; Flush OSARGS workspace
96B2 .check_tube_for_copy←1← 96AA BNE
LDA wksp_copy_write_sector_2 ; Check if sectors remain to copy
96B5 ORA wksp_copy_write_sector_1 ; OR with mid byte
96B8 ORA wksp_copy_write_sector ; OR with high byte
96BB BNE read_source_to_buffer ; Non-zero: more to copy
96BD RTS ; All done: return
96BE .read_source_to_buffer←1← 96BB BNE
LDA wksp_copy_write_sector_2 ; Get sector count high
96C1 ORA wksp_copy_write_sector_1 ; OR with mid byte
96C4 BNE write_buffer_to_dest ; Non-zero: more than buffer fits
96C6 LDA wksp_copy_write_sector ; Get sector count low
96C9 CMP wksp_compact_length ; Compare with buffer size
96CC BCC advance_copy_sector ; Fits in buffer: use exact count
96CE .write_buffer_to_dest←1← 96C4 BNE
LDA wksp_compact_length ; Too many: use buffer size
96D1 .advance_copy_sector←1← 96CC BCC
STA wksp_disc_op_sector_count ; Store sector count for this chunk
96D4 LDA wksp_compact_start_page ; Set transfer addr to buffer start
96D7 STA wksp_disc_op_mem_addr_1 ; Store transfer addr mid
96DA LDX #0 ; X=0: clear other addr bytes
96DC STX wksp_disc_op_mem_addr ; Clear transfer addr low
96DF DEX ; X=&ff
96E0 STX wksp_disc_op_mem_addr_2 ; Clear high bytes
96E3 STX wksp_disc_op_mem_addr_3 ; Clear highest byte
96E6 .copy_sectors_remaining←1← 977A JMP
SEC ; Set carry for subtraction
96E7 LDA wksp_copy_write_sector ; Subtract copied amount from total
96EA SBC wksp_compact_length ; Subtract buffer size
96ED STA wksp_copy_write_sector ; Store reduced count low
96F0 LDA wksp_copy_write_sector_1 ; Get count mid
96F3 SBC #0 ; Subtract borrow
96F5 STA wksp_copy_write_sector_1 ; Store reduced mid
96F8 LDA wksp_copy_write_sector_2 ; Get count high
96FB SBC #0 ; Subtract borrow
96FD STA wksp_copy_write_sector_2 ; Store reduced high
9700 BCS set_transfer_address ; No underflow: proceed
9702 LDA wksp_copy_write_sector ; Underflow: adjust sector count
9705 ADC wksp_compact_length ; Add buffer size back
9708 STA wksp_disc_op_sector_count ; Store as final chunk size
970B .set_transfer_address←1← 9700 BCS
LDA #8 ; Read command = 8
970D STA wksp_disc_op_command ; Store in disc op
9710 LDA wksp_copy_read_sector ; Get source sector low
9713 STA wksp_disc_op_sector_lo ; Store in disc op sector
9716 LDA wksp_copy_read_sector_1 ; Get source sector mid
9719 STA wksp_disc_op_sector_mid ; Store in disc op
971C LDA wksp_copy_read_sector_2 ; Get source sector high + drive
971F STA wksp_disc_op_sector ; Store in disc op
9722 JSR exec_disc_op_from_wksp ; Read from source
9725 LDA #&0a ; Write command = &0A
9727 STA wksp_disc_op_command ; Store in disc op
972A LDA wksp_copy_src_sector ; Get dest sector low
972D STA wksp_disc_op_sector_lo ; Store in disc op sector
9730 LDA wksp_copy_src_sector_1 ; Get dest sector mid
9733 STA wksp_disc_op_sector_mid ; Store in disc op
9736 LDA wksp_copy_src_sector_2 ; Get dest sector high + drive
9739 STA wksp_disc_op_sector ; Store in disc op
973C JSR exec_disc_op_from_wksp ; Write to destination
973F LDA wksp_copy_write_sector ; Check if more sectors to copy
9742 ORA wksp_copy_write_sector_1 ; OR with mid byte
9745 ORA wksp_copy_write_sector_2 ; OR with high byte
9748 BEQ validate_disc_size ; Zero: all copied
974A LDA wksp_disc_op_sector_count ; Check if full buffer was used
974D CMP wksp_compact_length ; Compare with buffer size
9750 BNE validate_disc_size ; Partial: done
9752 CLC ; Advance source sector
9753 LDA wksp_copy_read_sector ; Get source low
9756 ADC wksp_compact_length ; Add buffer pages copied
9759 STA wksp_copy_read_sector ; Store updated source low
975C BCC check_format_parameters ; No carry
975E INC wksp_copy_read_sector_1 ; Carry: inc source mid
9761 BNE check_format_parameters ; No wrap
9763 INC wksp_copy_read_sector_2 ; Wrap: inc source high
9766 .check_format_parameters←2← 975C BCC← 9761 BNE
CLC ; Advance dest sector
9767 LDA wksp_copy_src_sector ; Get dest low
976A ADC wksp_compact_length ; Add buffer pages
976D STA wksp_copy_src_sector ; Store updated dest low
9770 BCC validate_sector_count ; No carry
9772 INC wksp_copy_src_sector_1 ; Carry: inc dest mid
9775 BNE validate_sector_count ; No wrap
9777 INC wksp_copy_src_sector_2 ; Wrap: inc dest high
977A .validate_sector_count←2← 9770 BCC← 9775 BNE
JMP copy_sectors_remaining ; Loop for more chunks
977D .validate_disc_size←2← 9748 BEQ← 9750 BNE
LDA zp_adfs_flags ; Check copy operation flag
977F AND #8 ; Bit 3: copy in progress?
9781 BEQ begin_format_operation ; Not set: reload directory
9783 RTS ; Set: return directly
9784 .begin_format_operation←1← 9781 BEQ
LDA #&12 ; Set transfer addr to &12 page
9786 STA wksp_disc_op_mem_addr_1 ; Store addr mid
9789 LDA #8 ; Read command = 8
978B STA wksp_disc_op_command ; Store command
978E LDA wksp_csd_sector_lo ; Get dir sector low
9791 STA wksp_disc_op_sector_lo ; Store in disc op sector low
9794 LDA wksp_csd_sector_mid ; Get dir sector mid
9797 STA wksp_disc_op_sector_mid ; Store in disc op mid
979A LDA wksp_csd_sector_hi ; Get dir sector high
979D STA wksp_disc_op_sector ; Store in disc op high
97A0 LDA #5 ; Read 5 sectors (full directory)
97A2 STA wksp_disc_op_sector_count ; Store sector count
97A5 JMP exec_disc_command ; Execute disc read

Initialise directory structure for format

Set up source and destination sector addresses for directory initialisation during a disc format operation.

97A8 .format_init_dir←2← 98AB JMP← 98D7 JSR
LDA #0 ; A=0: clear search state
97AA STA wksp_copy_dest_sector ; Clear dest sector low
97AD STA wksp_copy_dest_sector_1 ; Clear dest sector mid
97B0 STA wksp_copy_dest_sector_2 ; Clear dest sector high
97B3 .format_init_fsm←2← 9835 JMP← 9869 JMP
LDA #&ff ; A=&FF: init source sector to &FFFFFF
97B5 STA wksp_copy_read_sector ; Set source sector low
97B8 STA wksp_copy_read_sector_1 ; Set source sector mid
97BB STA wksp_copy_read_sector_2 ; Set source sector high
97BE JSR print_catalogue_header ; Point to first directory entry
97C1 .init_fsm_zeros_loop←2← 9813 BCC← 9817 BCS
LDY #0 ; Y=0: check entry first byte
97C3 LDA (zp_entry_ptr_lo),y ; Get first byte
97C5 BNE init_fsm_total_sectors ; Non-zero: valid entry
97C7 LDA wksp_copy_read_sector ; End of entries: check if any found
97CA AND wksp_copy_read_sector_1 ; AND all source sector bytes
97CD AND wksp_copy_read_sector_2 ; All &FF?
97D0 CMP #&ff ; Compare with &FF
97D2 BNE init_root_dir_name ; Not &FF: found an entry
97D4 JMP write_dir_and_validate ; All &FF: no entries, write dir
97D7 .init_fsm_total_sectors←1← 97C5 BNE
LDY #&16 ; Y=&16: get entry start sector
97D9 LDX #2 ; X=2: compare 3 sector bytes
97DB SEC ; Set carry for subtraction
97DC .init_fsm_sector_loop←1← 97E3 BPL
LDA wksp_osgbpb_end_ptr,y ; Get workspace sector byte
97DF SBC (zp_entry_ptr_lo),y ; Subtract entry sector byte
97E1 INY ; Next byte
97E2 DEX ; Next workspace byte
97E3 BPL init_fsm_sector_loop ; Loop for 3 bytes
97E5 BCS init_root_dir_entries ; Workspace >= entry: skip
97E7 LDY #&16 ; Y=&16: compare with other workspace
97E9 LDX #2 ; X=2: 3 bytes
97EB SEC ; Set carry
97EC .write_fsm_to_disc_loop←1← 97F3 BPL
LDA wksp_copy_osfile_exec,y ; Get other workspace byte
97EF SBC (zp_entry_ptr_lo),y ; Subtract entry sector byte
97F1 INY ; Next byte
97F2 DEX ; Next workspace byte
97F3 BPL write_fsm_to_disc_loop ; Loop for 3 bytes
97F5 BCC init_root_dir_entries ; Other < entry: update best entry
97F7 LDY #&16 ; Y=&16: copy entry sector to best
97F9 LDX #2 ; X=2: 3 bytes
97FB .create_root_dir←1← 9802 BPL
LDA (zp_entry_ptr_lo),y ; Get entry sector byte
97FD STA wksp_copy_osfile_exec,y ; Store as best entry sector
9800 INY ; Next byte
9801 DEX ; Next workspace byte
9802 BPL create_root_dir ; Loop for 3 bytes
9804 LDA zp_entry_ptr_lo ; Save entry pointer
9806 STA zp_text_ptr_lo ; Store as best entry pointer low
9808 LDA zp_entry_ptr_hi ; Get pointer high
980A STA zp_text_ptr_hi ; Store as best entry pointer high
980C .init_root_dir_entries←2← 97E5 BCS← 97F5 BCC
LDA zp_entry_ptr_lo ; Advance to next dir entry
980E CLC ; Clear carry for addition
980F ADC #&1a ; Add 26 bytes per entry
9811 STA zp_entry_ptr_lo ; Store updated pointer
9813 BCC init_fsm_zeros_loop ; No page crossing: continue search
9815 INC zp_entry_ptr_hi ; Increment page
9817 BCS init_fsm_zeros_loop ; ALWAYS branch
9819 .init_root_dir_name←1← 97D2 BNE
LDA zp_text_ptr_lo ; Restore best entry pointer
981B STA zp_entry_ptr_lo ; Store in (&B6)
981D LDA zp_text_ptr_hi ; Get high byte
981F STA zp_entry_ptr_hi ; Store in (&B7)
9821 LDY #2 ; Y=2: copy 3 source sector bytes
9823 .fill_root_name_loop←1← 982A BPL
LDA wksp_copy_read_sector,y ; Get source sector byte
9826 STA wksp_copy_dest_sector,y ; Store as dest for allocation
9829 DEY ; Next byte
982A BPL fill_root_name_loop ; Loop for 3 bytes
982C LDX #0 ; X=0: start scanning FSM
982E STX zp_mem_ptr_lo ; Store scan position
9830 .set_root_identity_loop←1← 984A BNE
CPX fsm_s1_total_sectors_lo ; Past end of FSM?
9833 BCC write_root_dir_to_disc ; No: check this entry
9835 JMP format_init_fsm ; Past end: reinit search
9838 .write_root_dir_to_disc←1← 9833 BCC
INX ; Advance X by 3
9839 INX ; Continue advancing
983A INX ; 3rd byte
983B STX zp_mem_ptr_lo ; Save position
983D LDY #2 ; Y=2: compare sector bytes
983F .write_root_sectors_loop←1← 984F BPL
DEX ; Back up one
9840 LDA fsm_sector_0,x ; Get FSM address byte
9843 CMP wksp_copy_read_sector,y ; Compare with source sector
9846 BCS set_root_as_csd ; FSM >= source: possible match
9848 LDX zp_mem_ptr_lo ; Restore X, try next
984A BNE set_root_identity_loop ; Loop (X != 0)
984C .set_root_as_csd←1← 9846 BCS
BNE copy_root_sector_loop ; Exact match? Check all bytes
984E DEY ; Next byte (decreasing)
984F BPL write_root_sectors_loop ; Loop for 3 bytes
9851 .copy_root_sector_loop←1← 984C BNE
LDX zp_mem_ptr_lo ; Restore entry position
9853 CPX #6 ; Need at least 2 entries (>= 6)
9855 BCC set_format_drive ; Not enough entries: reinit
9857 LDY #0 ; Check if entry is adjacent
9859 CLC ; Clear carry for addition
985A PHP ; Save carry
985B .init_workspace_for_root←1← 9870 BNE
PLP ; Restore carry
985C LDA fsm_s0_pre6,x ; Get previous entry end address
985F ADC fsm_s0_reserved,x ; Add previous entry length
9862 PHP ; Save carry
9863 CMP wksp_copy_read_sector,y ; Compare with source sector
9866 BEQ format_next_track_loop ; Match: entries are adjacent
9868 PLP ; Restore carry, not adjacent
9869 .set_format_drive←1← 9855 BCC
JMP format_init_fsm ; Not adjacent: reinit search
986C .format_next_track_loop←1← 9866 BEQ
INX ; Next byte
986D INY ; Next source byte
986E CPY #3 ; All 3 bytes?
9870 BNE init_workspace_for_root ; No: continue comparing
9872 PLP ; Restore carry
9873 LDX #2 ; X=2: copy sector address
9875 LDY #&12 ; Y=&12: entry length offset
9877 LDA (zp_entry_ptr_lo),y ; Get entry length byte
9879 CMP #1 ; Compare with 1 (min sector)
987B .format_write_sectors_loop←1← 9890 BPL
INY ; Next length byte
987C LDA (zp_entry_ptr_lo),y ; Get next byte
987E ADC #0 ; Add carry from compare
9880 STA wksp_filename_save_hi,y ; Store sector count
9883 STA wksp_csd_drive_temp,y ; Store in alt workspace
9886 STA wksp_entry_size_base,y ; Store in disc op
9889 LDA wksp_copy_read_sector,x ; Get source sector byte
988C STA wksp_object_sector,x ; Store in object sector
988F DEX ; Next byte
9890 BPL format_write_sectors_loop ; Loop for 3 bytes
9892 JSR release_disc_space ; Release disc space back to free space map
9895 JSR allocate_disc_space ; Allocate space from FSM
9898 LDX #2 ; X=2: copy new sector address
989A LDY #&18 ; Y=&18: start sector in entry
989C .verify_formatted_sectors←1← 98A6 BPL
LDA wksp_alloc_sector,x ; Get new sector byte
989F STA (zp_entry_ptr_lo),y ; Store in directory entry
98A1 STA wksp_copy_src_sector,x ; Store as dest sector
98A4 DEY ; Next entry byte (decreasing)
98A5 DEX ; Next workspace byte
98A6 BPL verify_formatted_sectors ; Loop for 3 bytes
98A8 JSR copy_sectors_between_dirs ; Update CSD/lib/prev dir pointers
98AB JMP format_init_dir ; Continue compaction search
98AE .calculate_total_sectors←1← A350 JSR
LDA #0 ; A=0: init recursion stack pointer
98B0 STA zp_name_ptr_lo ; Store in workspace
98B2 STA wksp_osfile_attr_1 ; Clear root sector low
98B5 STA wksp_osfile_attr_2 ; Clear root sector mid
98B8 LDA #2 ; Root sector = 2
98BA STA wksp_osfile_attr ; Store root sector low
98BD LDA #&1b ; Set up path ':0.$' for root
98BF STA zp_name_ptr_hi ; Store in workspace
98C1 LDA #&3c ; Path string address low
98C3 STA zp_text_ptr_lo ; Store in (&B4)
98C5 LDA #&99 ; Path string page &99
98C7 STA zp_text_ptr_hi ; Store in (&B5)
98C9 .prepare_cdir_directory←1← 990C BMI
JSR parse_path_and_load ; Load root directory
98CC LDY #2 ; Y=2: copy parent sector
98CE .init_cdir_entries_loop←1← 98D5 BPL
LDA wksp_osfile_attr,y ; Get sector byte from workspace
98D1 STA dir_parent_sector,y ; Store as dir parent pointer
98D4 DEY ; Next byte
98D5 BPL init_cdir_entries_loop ; Loop for 3 bytes
98D7 JSR format_init_dir ; Init search state for this dir
98DA JSR print_catalogue_header ; Point to first entry
98DD .setup_cdir_dir_entry←2← 9932 BCC← 9936 BCS
LDY #0 ; Y=0: check entry
98DF LDA (zp_entry_ptr_lo),y ; Get first byte
98E1 BEQ set_cdir_parent_sector ; Zero: end of entries in this dir
98E3 LDY #3 ; Y=3: check access byte
98E5 LDA (zp_entry_ptr_lo),y ; Get access byte
98E7 BPL write_cdir_directory ; Bit 7 clear: regular file
98E9 LDA zp_name_ptr_lo ; Directory: check stack depth
98EB CMP #&fe ; Compare with &FE (max depth)
98ED BEQ set_cdir_parent_sector ; At max depth: skip this subdir
98EF LDY #0 ; Push subdir entry address on stack
98F1 LDA zp_entry_ptr_lo ; Get entry pointer low
98F3 STA zp_text_ptr_lo ; Store in (&B4)
98F5 STA (zp_name_ptr_lo),y ; Store on recursion stack
98F7 INC zp_name_ptr_lo ; Advance stack pointer
98F9 LDA zp_entry_ptr_hi ; Get entry pointer high
98FB STA zp_text_ptr_hi ; Store in (&B5)
98FD STA (zp_name_ptr_lo),y ; Store on recursion stack
98FF INC zp_name_ptr_lo ; Advance stack pointer
9901 LDX #2 ; X=2: save parent dir sector
9903 .copy_name_to_cdir_loop←1← 990A BPL
LDA wksp_csd_sector_lo,x ; Get parent sector byte
9906 STA wksp_osfile_attr,x ; Store in workspace
9909 DEX ; Next byte
990A BPL copy_name_to_cdir_loop ; Loop for 3 bytes
990C BMI prepare_cdir_directory ; ALWAYS branch
990E .set_cdir_parent_sector←2← 98E1 BEQ← 98ED BEQ
LDA zp_name_ptr_lo ; Check recursion stack
9910 BEQ finalise_cdir ; Stack empty: compaction done
9912 LDA #&3b ; Set up path for parent return
9914 STA zp_text_ptr_lo ; Store path address low
9916 LDA #&99 ; Path page &99
9918 STA zp_text_ptr_hi ; Store path address high
991A JSR parse_path_and_load ; Load parent directory
991D LDY #0 ; Y=0: pop entry address from stack
991F DEC zp_name_ptr_lo ; Decrement stack pointer
9921 LDA (zp_name_ptr_lo),y ; Get entry pointer high
9923 STA zp_entry_ptr_hi ; Restore (&B7)
9925 DEC zp_name_ptr_lo ; Decrement stack pointer
9927 LDA (zp_name_ptr_lo),y ; Get entry pointer low
9929 STA zp_entry_ptr_lo ; Restore (&B6)
992B .write_cdir_directory←1← 98E7 BPL
CLC ; Advance to next entry
992C LDA zp_entry_ptr_lo ; Get entry pointer low
992E ADC #&1a ; Add 26 bytes per entry
9930 STA zp_entry_ptr_lo ; Store updated pointer
9932 BCC setup_cdir_dir_entry ; No page crossing: continue scan
9934 INC zp_entry_ptr_hi ; Increment page
9936 BCS setup_cdir_dir_entry ; ALWAYS branch
9938 .finalise_cdir←1← 9910 BEQ
JMP save_wksp_and_return ; Save workspace and return
993B EQUS "^." ; Unused "^" + CR: dead remnant

*ACCESS command handler

Change the access attributes of a file. Attributes are specified as a combination of L (locked), W (write), R (read), D (directory), and E (execute).

993D .star_access
JSR find_first_matching_entry ; Find first matching file
9940 BEQ set_file_attributes ; Found? Set attributes
9942 JMP not_found_error ; Not found: report error

Clear R, W, L attribute bits in entry

Strip bit 7 from the first three name bytes of the directory entry at (zp_entry_ptr).

9945 .clear_rwl_attributes←2← 9951 JSR← 9995 JSR
LDY #2 ; Y=2: clear R,W,L attribute bits
9947 .clear_attr_bits_loop←1← 994E BPL
LDA (zp_entry_ptr_lo),y ; Get name byte
9949 AND #&7f ; Strip bit 7 (clear attribute)
994B STA (zp_entry_ptr_lo),y ; Store back
994D DEY ; Next name byte
994E BPL clear_attr_bits_loop ; Loop for 3 bytes
9950 RTS ; Return (attributes cleared)

Set file attributes from access string

Clear existing R, W, L attributes then parse the access string to set appropriate flags including E and D.

9951 .set_file_attributes←2← 9940 BEQ← 99C1 BEQ
JSR clear_rwl_attributes ; Clear existing R,W,L attributes
9954 LDY #4 ; Y=4: check E attribute byte
9956 LDA (zp_entry_ptr_lo),y ; Get byte 4
9958 BMI save_e_attribute_state ; Bit 7 set: E attribute, skip
995A DEY ; Y=3: get D attribute byte
995B LDA (zp_entry_ptr_lo),y ; Get byte 3
995D AND #&80 ; Keep only bit 7 (D flag)
995F LDY #0 ; Y=0: get first name byte
9961 ORA (zp_entry_ptr_lo),y ; OR D flag into name byte 0
9963 STA (zp_entry_ptr_lo),y ; Store back
9965 .save_e_attribute_state←1← 9958 BMI
STA wksp_csd_sector_temp ; Save for E attribute check
9968 LDY #0 ; Y=0: scan for attribute string
996A .skip_filename_loop←1← 9977 BNE
LDA (zp_text_ptr_lo),y ; Skip filename characters
996C CMP #&20 ; Compare with space
996E BCC display_and_find_next ; Control char: end of command
9970 BEQ skip_spaces_before_attrs ; Space: skip to attributes
9972 CMP #&22 ; Double-quote?
9974 BEQ skip_spaces_before_attrs ; Yes: skip to attributes
9976 INY ; Next filename character
9977 BNE skip_filename_loop ; Loop scanning filename
9979 .skip_spaces_before_attrs←3← 9970 BEQ← 9974 BEQ← 9986 BNE
LDA (zp_text_ptr_lo),y ; Skip spaces between name and attrs
997B CMP #&20 ; Is it a space?
997D BCC display_and_find_next ; Control char: no attributes given
997F BEQ skip_space_or_quote ; Space: keep skipping
9981 CMP #&22 ; Is it a double-quote?
9983 BNE parse_attr_char ; No, start parsing attribute chars
9985 .skip_space_or_quote←1← 997F BEQ
INY ; Skip quote character
9986 BNE skip_spaces_before_attrs ; Continue skipping spaces
9988 .parse_attr_char←2← 9983 BNE← 99B9 BNE
LDA (zp_text_ptr_lo),y ; Parse attribute character
998A AND #&df ; Convert to uppercase
998C BIT wksp_csd_sector_temp ; Check if E attribute already set
998F BMI check_rwl_char ; E set: only L attribute allowed
9991 CMP #&45 ; Is it 'E'?
9993 BNE check_rwl_char ; No, check R/W/L
9995 JSR clear_rwl_attributes ; E: clear R,W,L first
9998 LDY #4 ; Y=4: set bit 7 of byte 4
999A LDA (zp_entry_ptr_lo),y ; Get entry byte at attribute pos
999C ORA #&80 ; Set E attribute
999E STA (zp_entry_ptr_lo),y ; Store with E bit set
99A0 STA wksp_csd_sector_temp ; Save E flag for later checks
99A3 BMI next_attr_char ; ALWAYS branch
99A5 .check_rwl_char←2← 998F BMI← 9993 BNE
LDX #2 ; X=2: check against "RWL" table
99A7 .match_rwl_loop←1← 99B2 BPL
CMP tbl_access_chars,x ; Compare with R/W/L character
99AA BEQ set_rwl_attribute_bit ; Match: set this attribute
99AC BIT wksp_csd_sector_temp ; E already set? Only L allowed
99AF BMI check_attr_terminator ; E already set: only L allowed
99B1 DEX ; Try next R/W/L character
99B2 BPL match_rwl_loop ; Loop through R, W, L
99B4 .check_attr_terminator←1← 99AF BMI
CMP #&21 ; Unknown char: check if printable
99B6 BCC display_and_find_next ; Control char: end of attributes
99B8 .next_attr_char←2← 99A3 BMI← 99D5 BNE
INY ; Next attribute character
99B9 BNE parse_attr_char ; Continue parsing
99BB .display_and_find_next←3← 996E BCC← 997D BCC← 99B6 BCC
JSR conditional_info_display ; Display info if *OPT1 verbose
99BE JSR advance_dir_entry_ptr ; Find next matching file
99C1 BEQ set_file_attributes ; More matches? Continue
99C3 JSR write_dir_and_validate ; Write directory back to disc
99C6 JMP save_wksp_and_return ; Save workspace and return
99C9 .set_rwl_attribute_bit←1← 99AA BEQ
TYA ; Set attribute: save text pointer
99CA PHA ; Save Y (text position) on stack
99CB TXA ; X = index into R/W/L (0,1,2)
99CC TAY ; Use as Y index into entry
99CD LDA (zp_entry_ptr_lo),y ; Get name byte at that position
99CF ORA #&80 ; Set bit 7 (attribute flag)
99D1 STA (zp_entry_ptr_lo),y ; Store back
99D3 PLA ; Restore text pointer
99D4 TAY ; Restore Y
99D5 BNE next_attr_char ; Continue parsing attributes
99D7 .print_aborted_error←1← 9A1B BNE
JSR osnewl ; Write newline (characters 10 and 13)
99DA JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
99DD EQUB &92 ; Error &92: Aborted
99DE EQUS "Aborted."
fall through ↓

*DESTROY command handler

Delete multiple files matching a wildcard specification. Prompts for confirmation before deleting.

99E6 .star_destroy
LDA zp_text_ptr_lo ; Save filename pointer low
99E8 PHA ; Push low byte
99E9 LDA zp_text_ptr_hi ; Save filename pointer high
99EB PHA ; Push high byte
99EC LDA #&40 ; Set up workspace for *INFO call
99EE STA zp_osfile_ptr_lo ; Store in control block pointer low
99F0 LDA #&10 ; Control block page = &10
99F2 STA zp_osfile_ptr_hi ; Store in control block pointer high
99F4 JSR star_info ; List matching files via *INFO
99F7 PLA ; Restore filename pointer high
99F8 STA zp_text_ptr_hi ; Store in (&B5)
99FA PLA ; Restore filename pointer low
99FB STA zp_text_ptr_lo ; Store in (&B4)
99FD JSR print_inline_string ; Print "Destroy ? "
9A00 EQUS "Destroy ?"
9A09 EQUB &A0 ; ' ' + bit 7: end of inline string
9A0A LDX #3 ; X=3: expect 4 chars (CR,Y,E,S)
9A0C .confirm_destroy_loop←1← 9A1E BPL
JSR osrdch ; Read character from keyboard
9A0F CMP #&20 ; Is it a printable char?
9A11 BCC check_confirm_response ; No, don't echo control chars
9A13 JSR osasci ; Echo the typed character
9A16 .check_confirm_response←1← 9A11 BCC
AND #&df ; Convert to uppercase
9A18 CMP str_yes,x ; Compare with "YES\r" (reversed)
9A1B BNE print_aborted_error ; Mismatch: abort with Aborted error
9A1D DEX ; Next expected character
9A1E BPL confirm_destroy_loop ; Loop for all 4 chars
9A20 JSR osnewl ; Print newline after YES
9A23 INX ; Clear channel for error messages
9A24 STX wksp_cur_channel ; Store in current channel workspace
9A27 .delete_matching_files_loop←1← 9A3B JMP
LDA zp_text_ptr_lo ; Deletion loop: save filename low
9A29 PHA ; Push low byte
9A2A LDA zp_text_ptr_hi ; Save filename pointer high
9A2C PHA ; Push high byte
9A2D JSR find_first_matching_entry ; Find next matching file
9A30 BNE all_files_deleted ; Not found: all deleted, finish
9A32 JSR check_and_delete_found ; Delete this file
9A35 PLA ; Restore filename pointer high
9A36 STA zp_text_ptr_hi ; Store in (&B5)
9A38 PLA ; Restore filename pointer low
9A39 STA zp_text_ptr_lo ; Store in (&B4)
9A3B JMP delete_matching_files_loop ; Loop to delete next match
9A3E .all_files_deleted←1← 9A30 BNE
PLA ; Discard saved filename from stack
9A3F PLA ; Discard second saved byte
9A40 JMP save_wksp_and_return ; Save workspace and return

Jump through FSCV indirect vector

Jump indirectly through the filing system control vector.

9A43 .jmp_indirect_fscv←1← 9B89 JSR
JMP (fscv) ; Jump through filing system control

Default workspace initialisation template

29-byte template copied to workspace page &1100 during hard break initialisation (service call 2). Bytes beyond &1C are zeroed. Sets both CSD and library to the root directory '$' on drive 0, sector 2.

+00 wksp_csd_name (10 bytes): '$' + 9 spaces +0A wksp_lib_name (10 bytes): '$' + 9 spaces +14 wksp_csd_sector (3 bytes): sector 2 (root directory) +17 wksp_current_drive: drive 0 +18 wksp_lib_sector (3 bytes): sector 2 (root directory) +1B wksp_lib_drive: drive 0 +1C wksp_prev_dir_sector low: sector 2

9A46 .default_workspace_data←1← 9AFF LDA
EQUS "$ " ; '$' + 9 spaces: default CSD name
9A50 .default_lib_name
EQUS "$ " ; '$' + 9 spaces: default library name
9A5A .default_csd_sector
EQUB &02 ; CSD sector low: 2 (root directory)
9A5B EQUB &00 ; CSD sector mid: 0
9A5C EQUB &00 ; CSD sector high: 0
9A5D EQUB &00 ; Current drive: 0
9A5E .default_lib_sector
EQUB &02 ; Library sector low: 2 (root directory)
9A5F EQUB &00 ; Library sector mid: 0
9A60 EQUB &00 ; Library sector high: 0
9A61 EQUB &00 ; Library drive: 0
9A62 .default_prev_dir_sector
EQUB &02 ; Previous dir sector low: 2 (root dir)

Detect hard drive hardware

Check whether a SCSI hard drive is present by attempting to read the SCSI status register.

On ExitAcorrupted (Z set if hard drive present)
Xzero
Ypreserved
9A63 .hd_init_detect←3← 9AD7 JSR← 9B4B JSR← 9BFB JSR
LDA #&5a ; Write &5A to SCSI data register
9A65 JSR scsi_write_read_test ; Check if value survived
9A68 BNE return_22 ; No match: SCSI hardware not present
9A6A LDA #&a5 ; Write complement &A5
9A6C .scsi_write_read_test←1← 9A65 JSR
STA fred_hard_drive_0 ; Write test value to SCSI data port
9A6F LDX #0 ; X=0: clear IRQ enable register
9A71 STX fred_hard_drive_3 ; Disable SCSI interrupts
9A74 CMP fred_hard_drive_0 ; Read back: does value match?
9A77 .return_22←2← 9A68 BNE← 9C9C LDX
RTS ; Return

Boot option OSCLI address table and command strings

Three-byte lookup table of OSCLI string low addresses, indexed by boot option number (1-3). The high byte is always &9A. The auto-boot code reads fsm_s1_boot_option and uses it as an index into this table to select the OSCLI command.

  Option 1 (Load): &7B -> "L.$.!BOOT" at &9A7B
  Option 2 (Run):  &7D -> "$.!BOOT" at &9A7D (*RUN)
  Option 3 (Exec): &85 -> "E.$.!BOOT" at &9A85

Option 2 cleverly points into the middle of the "L.$.!BOOT" string to get just "$.!BOOT", which OSCLI interprets as *RUN $.!BOOT.

9A78 .boot_option_addr_table
EQUB <(str_l_boot) ; Option 1: *LOAD $.!BOOT
9A79 EQUB <(str_run_boot) ; Option 2: *RUN $.!BOOT
9A7A EQUB <(str_e_boot) ; Option 3: *EXEC $.!BOOT
9A7B .str_l_boot
EQUS "L.$.!BOOT." ; "L.$.!BOOT" + CR: load boot file
9A85 .str_e_boot
EQUS "E.$.!BOOT." ; "E.$.!BOOT" + CR: exec boot file E.$.!BOOT (exec boot file)

Service call dispatch table

RTS-trick dispatch table for MOS service calls 0-9. Low bytes at &9A8F, high bytes at &9A99, 10 entries.

9A8F .service_dispatch_lo←1← 9AC7 LDA
EQUB <(service_handler_0-1)
9A90 EQUB <(service_handler_1-1)
9A91 EQUB <(service_handler_2-1)
9A92 EQUB <(service_handler_3-1)
9A93 EQUB <(service_handler_4-1)
9A94 EQUB <(svc5_irq-1)
9A95 EQUB <(service_handler_0-1)
9A96 EQUB <(service_handler_0-1)
9A97 EQUB <(service_handler_8-1)
9A98 EQUB <(service_handler_9-1)
9A99 .service_dispatch_hi←1← 9AC3 LDA
EQUB >(service_handler_0-1)
9A9A EQUB >(service_handler_1-1)
9A9B EQUB >(service_handler_2-1)
9A9C EQUB >(service_handler_3-1)
9A9D EQUB >(service_handler_4-1)
9A9E EQUB >(svc5_irq-1)
9A9F EQUB >(service_handler_0-1)
9AA0 EQUB >(service_handler_0-1)
9AA1 EQUB >(service_handler_8-1)
9AA2 EQUB >(service_handler_9-1)

ROM service call handler

Main entry point for MOS service calls. Dispatches to individual handlers based on the service call number in A.

9AA3 .service_handler←1← 8003 JMP
.service_call_handler←1← 8003 JMP
PHA ; Save service call number
9AA4 CMP #1 ; Service 1: absolute workspace claim?
9AA6 BNE check_workspace_claimed ; Not service 1, continue
9AA8 LDA rom_wksp_table,x ; Read our ROM status byte
9AAB AND #&bf ; Clear bit 6 (ADFS workspace claimed)
9AAD STA rom_wksp_table,x ; Store updated status
9AB0 .check_workspace_claimed←1← 9AA6 BNE
LDA rom_wksp_table,x ; Read ROM status byte
9AB3 CMP #&40 ; Bit 6 set (workspace claimed)?
9AB5 BCC dispatch_service_call ; No, continue with dispatch
9AB7 PLA ; Yes, discard call and return
9AB8 .service_handler_0←1← 9AC0 BCS
RTS ; Return (service not claimed)
9AB9 .dispatch_service_call←1← 9AB5 BCC
PLA ; Restore service call number
9ABA CMP #&12 ; Service &12: select filing system?
9ABC BEQ select_adfs_filing_system ; Yes, handle FS selection
9ABE CMP #&0a ; Service >= &0A?
9AC0 BCS service_handler_0 ; Yes, not for us, return
9AC2 TAX ; Transfer to X for table index
9AC3 LDA service_dispatch_hi,x ; Get dispatch address high byte
9AC6 PHA ; Push dispatch high byte
9AC7 LDA service_dispatch_lo,x ; Get dispatch address low byte
9ACA PHA ; Push dispatch low byte
9ACB TXA ; Restore service number to A
9ACC LDX romsel_copy ; Get our ROM number
9ACE RTS ; RTS-dispatch to service handler

Service 1: absolute workspace claim

Initialise ADFS on a ROM filing system init service call. Checks for floppy and hard drive hardware. If either is present, claims the ROM workspace slot and raises PAGE to make room for ADFS workspace.

9ACF .service_handler_1←1← 9AD3 BPL
JSR floppy_check_present ; Check if floppy hardware present
9AD2 INX ; Increment result counter
9AD3 BPL service_handler_1 ; Service 1: absolute workspace claim
9AD5 BCC adfs_hardware_found ; No floppy, check hard drive
9AD7 JSR hd_init_detect ; Check if hard drive present
9ADA BEQ adfs_hardware_found ; Not present, skip ADFS init
9ADC LDA #&40 ; Mark ROM as having ADFS workspace
9ADE LDX romsel_copy ; Get our ROM number
9AE0 STA rom_wksp_table,x ; Store flag in ROM status table
9AE3 LDA #1 ; Return A=1: service handled
9AE5 RTS ; Return A=1 (claim 1 page)

Claim workspace for ADFS

Return A=1 to claim one workspace page and set Y=&1C to raise PAGE to &1D00 for ADFS workspace.

9AE6 .adfs_hardware_found←2← 9AD5 BCC← 9ADA BEQ
LDA #1 ; Return A=1: claim 1 page
9AE8 LDX romsel_copy ; Get our ROM number
9AEA CPY #&1c ; Y < &1C (PAGE already high enough)?
9AEC BCS return_23 ; Yes, don't change PAGE
9AEE LDY #&1c ; Y=&1C: ADFS PAGE value high byte
9AF0 .return_23←1← 9AEC BCS
RTS ; Return

Service 2: private workspace claim

Claim private workspace pages. On hard break, initialises the workspace with default values (CSD name, directory sector pointers, checksum). On soft break, preserves existing workspace. Sets up the filing system vectors and checks for Tube presence.

9AF1 .service_handler_2
TYA ; Save workspace page in ROM table
9AF2 STA rom_wksp_table,x ; Store workspace page in ROM table
9AF5 PHA ; Save Y on stack
9AF6 LDA last_break_type ; Check break type
9AF9 BEQ verify_workspace_checksum ; Soft break, skip workspace init
9AFB JSR get_wksp_addr_ba ; Get workspace base address
9AFE TAY ; Transfer Y to A
9AFF .copy_default_workspace_loop←1← 9B0B BNE
LDA default_workspace_data,y ; Get default workspace byte
9B02 CPY #&1d ; Past initialisation data (Y>=&1D)?
9B04 BCC check_workspace_initialised ; No, use default value from table
9B06 LDA #0 ; A=0: zero for unused workspace
9B08 .check_workspace_initialised←1← 9B04 BCC
STA (zp_wksp_ptr_lo),y ; Store byte in workspace
9B0A INY ; Next byte
9B0B BNE copy_default_workspace_loop ; Loop for all 256 workspace bytes
9B0D JSR store_wksp_checksum_ba_y ; Store workspace checksum
9B10 .verify_workspace_checksum←1← 9AF9 BEQ
JSR check_wksp_checksum ; Verify workspace checksum
9B13 INY ; Y=next byte in workspace
9B14 LDA (zp_wksp_ptr_lo),y ; Read stored workspace byte
9B16 CMP #&ff ; Is it &FF (uninitialised)?
9B18 BNE claim_filing_system ; No, workspace valid from soft break
9B1A ROR zp_adfs_flags ; Clear Tube-present flag (bit 7)
9B1C CLC ; Clear carry for rotate
9B1D ROL zp_adfs_flags ; Restore bit 0, Tube flag cleared
9B1F JSR fsc6_new_filing_system ; FSC 6: new filing system selected
9B22 .claim_filing_system←1← 9B18 BNE
LDX #buffer_keyboard ; X=0: keyboard buffer number
9B24 LDA #osbyte_flush_buffer ; OSBYTE &15: flush buffer
9B26 JSR osbyte ; Flush the keyboard buffer (X=0)
9B29 LDA #osbyte_insert_buffer ; OSBYTE &8A: insert into buffer
9B2B LDY #&ca ; Y=&CA: character to insert
9B2D JSR osbyte ; Insert value 202 into buffer X; carry is clear if successful
9B30 PLA ; Restore Y (original service param)
9B31 TAY ; Restore Y
9B32 LDX romsel_copy ; Get our ROM number
9B34 INY ; Increment Y (next workspace page)
9B35 LDA #2 ; A=2: return service 2 handled
9B37 .return_24←1← 9B3A BNE
RTS ; Return
9B38 .select_adfs_filing_system←1← 9ABC BEQ
CPY #8 ; Service &12: select filing system?
9B3A BNE return_24 ; No, return
9B3C TYA ; Y=8: ADFS filing system number
9B3D PHA ; Save on stack twice for later
9B3E PHA ; Push again (2 copies on stack)
9B3F BNE boot_run_option ; Always branch to FS init code
fall through ↓

Service 3: auto-boot

Handle auto-boot on power-on or Ctrl+Break. Scans the keyboard for Shift+Break (floppy boot) or A+Break (hard drive boot). Selects ADFS as the filing system and executes the boot file if configured.

9B41 .service_handler_3
TYA ; Save Y (boot flag)
9B42 PHA ; Push Y on stack
9B43 LDA #osbyte_scan_keyboard_from_16 ; OSBYTE &7A: keyboard scan
9B45 JSR osbyte ; Keyboard scan starting from key 16
9B48 INX ; Key pressed? (X=-1 means no)
9B49 BNE check_boot_option ; Yes, key pressed - check which
9B4B JSR hd_init_detect ; No key: try hard drive boot
9B4E BEQ boot_shift_pressed ; Hard drive found?
9B50 LDA last_break_type ; Check break type
9B53 BEQ boot_shift_pressed ; Power-on break? Skip to boot
9B55 LDX #&44 ; X=&44: floppy drive 4 default
9B57 .check_boot_option←1← 9B49 BNE
DEX ; Adjust key code
9B58 CPX #&79 ; Shift (key 122-1)?
9B5A BEQ boot_shift_pressed ; Shift+Break: boot from floppy
9B5C CPX #&41 ; A (key 66-1)?
9B5E BEQ boot_shift_pressed ; A+Break: boot from hard drive
9B60 CPX #&43 ; Ctrl+Break?
9B62 BEQ check_boot_key ; Yes, handle Ctrl+Break boot
9B64 PLA ; Unrecognised key: pass on service
9B65 TAY ; Restore Y
9B66 LDX romsel_copy ; Get our ROM number
9B68 LDA #3 ; A=3: service not claimed
9B6A RTS ; Return
9B6B .check_boot_key←1← 9B62 BEQ
PLA ; Ctrl+Break: discard saved Y
9B6C TXA ; Push key code instead
9B6D PHA ; Push key code on stack
9B6E .boot_shift_pressed←4← 9B4E BEQ← 9B53 BEQ← 9B5A BEQ← 9B5E BEQ
CLI ; Enable interrupts for OSBYTE
9B6F TXA ; Transfer key code to A
9B70 PHA ; Push key code for later
9B71 LDY #0 ; Y=key
9B73 LDA #osbyte_write_keys_pressed ; OSBYTE &78: clear keys pressed
9B75 JSR osbyte ; Write current keys pressed (X and Y)
9B78 JSR print_inline_string ; Print bit-7-terminated inline string
9B7B EQUS "Acorn ADFS."
9B86 .sub_c9b86
STA ext_vec_fsc_lo
9B89 JSR jmp_indirect_fscv ; Jump through FSCV indirect vector
9B8C LDA #osbyte_issue_service_request ; OSBYTE &8F: issue service 10
9B8E LDX #&0a ; X=&0A: service 10 (claim workspace)
9B90 LDY #&ff ; Y=&FF
9B92 JSR osbyte ; Issue paged ROM service call, Reason X=10 - Claim absolute workspace
9B95 LDA #&10 ; Default retry count = &10
9B97 STA wksp ; Store in workspace base
9B9A LDY #&0d ; Y=&0D: copy 14 bytes of vectors
9B9C .copy_boot_command_loop←1← 9BA3 BPL
LDA tbl_fs_vectors,y ; Get vector table byte from ROM
9B9F STA filev,y ; Store in MOS vector table
9BA2 DEY ; Next byte
9BA3 BPL copy_boot_command_loop ; Loop for 14 bytes
9BA5 LDA #&a8 ; OSBYTE &A8: read ROM pointer table
9BA7 JSR osbyte_y_ff_x_00 ; Read current value
9BAA STX zp_text_ptr_lo ; Store extended vector base low
9BAC STY zp_text_ptr_hi ; Store extended vector base high
9BAE LDY #&2f ; Y=&2F: offset into ext vector table
9BB0 LDX #&14 ; X=&14: 21 bytes of ext vectors
9BB2 .copy_csd_name_loop←1← 9BBF BPL
LDA tbl_extended_vectors,x ; Get ext vector byte from ROM
9BB5 CMP #&ff ; Is it &FF (use our ROM number)?
9BB7 BNE set_default_csd ; No, use value as-is
9BB9 LDA romsel_copy ; Replace &FF with our ROM number
9BBB .set_default_csd←1← 9BB7 BNE
STA (zp_text_ptr_lo),y ; Store in extended vector table
9BBD DEY ; Next vector byte
9BBE DEX ; Next ROM table byte
9BBF BPL copy_csd_name_loop ; Loop for 21 bytes
9BC1 LDA #osbyte_issue_service_request ; OSBYTE &8F: issue service 15
9BC3 LDX #&0f ; X=&0F: service 15 (vectors claimed)
9BC5 LDY #&ff ; Y=&FF
9BC7 JSR osbyte ; Issue paged ROM service call, Reason X=15 - Vectors claimed
9BCA JSR mark_partial_transfer ; Initialise floppy state
9BCD JSR check_wksp_checksum ; Verify workspace checksum
9BD0 LDX #0 ; X=0: clear workspace entries
9BD2 STX wksp_buf_flag_1 ; Clear workspace byte &08
9BD5 STX wksp_buf_flag_2 ; Clear workspace byte &0C
9BD8 STX wksp_osword_block ; Clear OSWORD block
9BDB STX wksp_disc_op_block ; Clear workspace byte &14
9BDE INX ; X=&01
9BDF STX wksp_buf_flag ; Set workspace byte &04 to 1
9BE2 LDY #&fb ; Y=&FB: copy 252 bytes from saved ws
9BE4 .restore_boot_workspace_loop←1← 9BEA BNE
LDA (zp_wksp_ptr_lo),y ; Get byte from saved workspace
9BE6 STA wksp_csd_name,y ; Copy to CSD name area
9BE9 DEY ; Next byte
9BEA BNE restore_boot_workspace_loop ; Loop until Y=0
9BEC LDA (zp_wksp_ptr_lo),y ; Copy byte at Y=0 too
9BEE STA wksp_csd_name,y ; Store in CSD name byte 0
9BF1 LDA wksp_flags_save ; Get saved flags from workspace
9BF4 AND #4 ; Keep only *OPT1 bit
9BF6 STA zp_adfs_flags ; Set as current ADFS flags
9BF8 JSR load_dir_for_drive ; Store channel checksum
9BFB JSR hd_init_detect ; Detect hard drive hardware
9BFE BNE boot_load_from_disc ; HD not found: skip HD flag
9C00 LDA zp_adfs_flags ; Get current flags
9C02 ORA #&20 ; Set bit 5: hard drive present
9C04 STA zp_adfs_flags ; Store updated flags
9C06 .boot_load_from_disc←1← 9BFE BNE
DEY ; Y=-1 (will be &FF after DEY)
9C07 TYA ; Transfer to A
9C08 STA (zp_wksp_ptr_lo),y ; Store &FF in workspace (marking done)
9C0A PLA ; Retrieve key code from stack
9C0B CMP #&43 ; Was it Ctrl+Break (key C = &43)?
9C0D BNE boot_set_page ; No, do normal boot sequence
9C0F JSR invalidate_fsm_and_dir ; Mark FSM and directory as invalid
9C12 .boot_set_page←1← 9C0D BNE
LDY #3 ; Y=3: copy CSD sector to workspace
9C14 .copy_workspace_to_save_loop←1← 9C1B BPL
LDA wksp_csd_sector_lo,y ; Get CSD sector byte
9C17 STA wksp_csd_drive_sector,y ; Copy to CSD drive sector
9C1A DEY ; Next byte
9C1B BPL copy_workspace_to_save_loop ; Loop for 4 bytes
9C1D JSR save_wksp_and_return ; Save workspace state
9C20 LDX wksp_current_drive ; Check current drive is valid
9C23 INX ; Drive = &FF (uninitialised)?
9C24 BEQ set_fsm_load_flag ; Yes, skip to Tube detection
9C26 JSR check_disc_changed ; Ensure files are closed
9C29 LDA wksp_lib_sector ; Check if library is at default
9C2C CMP #2 ; Sector = 2 (root)?
9C2E BNE init_channel_complete ; No, library is already set
9C30 LDA wksp_lib_sector_lo ; Check other sector bytes
9C33 ORA wksp_lib_sector_mid ; OR with mid byte
9C36 ORA wksp_lib_sector_hi ; OR with high byte
9C39 BNE init_channel_complete ; Non-zero: lib sector is set
9C3B LDA #&ab ; Set up path ':0.LIB*'
9C3D STA zp_text_ptr_lo ; Store path address low
9C3F LDA #&9c ; Path in this ROM page
9C41 STA zp_text_ptr_hi ; Store path address high
9C43 JSR find_first_matching_entry ; Search for LIB directory
9C46 BNE init_channel_complete ; Not found: leave lib as default
9C48 .copy_drive_info_loop←1← 9C53 BEQ
LDY #3 ; Y=3: check access byte
9C4A LDA (zp_entry_ptr_lo),y ; Get access byte
9C4C BMI set_workspace_drive ; Bit 7: is it a directory?
9C4E JSR advance_dir_entry_ptr ; Not a dir: try next match
9C51 BNE init_channel_complete ; No more matches: leave default
9C53 BEQ copy_drive_info_loop ; ALWAYS branch
9C55 .set_workspace_drive←1← 9C4C BMI
LDX #2 ; X=2: copy 3 sector address bytes
9C57 LDY #&18 ; Y=&18: start sector in entry
9C59 .init_channel_flags_loop←1← 9C60 BPL
LDA (zp_entry_ptr_lo),y ; Get sector byte
9C5B STA wksp_lib_sector,x ; Store as library sector
9C5E DEY ; Next entry byte (decreasing Y)
9C5F DEX ; Next workspace byte (decreasing X)
9C60 BPL init_channel_flags_loop ; Loop for 3 bytes
9C62 LDA wksp_current_drive ; Get current drive number
9C65 STA wksp_lib_sector_hi ; Store as library drive
9C68 LDY #9 ; Y=9: copy 10-byte directory name
9C6A .init_per_channel_loop←1← 9C72 BPL
LDA (zp_entry_ptr_lo),y ; Get name byte from entry
9C6C AND #&7f ; Strip bit 7 (access flag)
9C6E STA wksp_lib_name,y ; Store as library name
9C71 DEY ; Next byte
9C72 BPL init_per_channel_loop ; Loop for 10 bytes
9C74 .init_channel_complete←4← 9C2E BNE← 9C39 BNE← 9C46 BNE← 9C51 BNE
JSR save_wksp_and_return ; Save workspace state
9C77 .set_fsm_load_flag←1← 9C24 BEQ
LDA #&ea ; OSBYTE &EA: read Tube presence
9C79 JSR osbyte_y_ff_x_00 ; Read current value
9C7C LDA zp_adfs_flags ; Get current ADFS flags
9C7E AND #&7f ; Clear bit 7 (Tube flag)
9C80 INX ; X+1: was X &FF (Tube present)?
9C81 BNE load_fsm_for_boot ; Non-zero: no Tube
9C83 ORA #&80 ; Tube present: set bit 7
9C85 .load_fsm_for_boot←1← 9C81 BNE
STA zp_adfs_flags ; Store updated flags
9C87 PLA ; Retrieve key/boot code from stack
9C88 PHA ; Push back for later
9C89 BNE set_default_dir_for_boot ; Non-zero: skip auto-boot
9C8B LDX wksp_current_drive ; Check drive is valid
9C8E INX ; Drive = &FF?
9C8F BNE clear_fsm_flag_after_load ; No, check boot option
9C91 STX wksp_drive_number ; X=0: store as drive for mount
9C94 JSR mount_drive_setup ; Mount drive 0
9C97 .clear_fsm_flag_after_load←1← 9C8F BNE
LDY fsm_s1_boot_option ; Get boot option from FSM
9C9A BEQ set_default_dir_for_boot ; Option 0: no auto-boot
9C9C LDX return_22,y ; Get boot command addr from table
9C9F LDY #&9a ; Y=&9A: command string page
9CA1 JSR oscli ; Execute boot command via OSCLI
9CA4 .set_default_dir_for_boot←2← 9C89 BNE← 9C9A BEQ
LDX romsel_copy ; Restore ROM number
9CA6 PLA ; Restore Y
9CA7 TAY ; Transfer to Y
9CA8 LDA #0 ; A=0: service claimed
9CAA RTS ; Return
9CAB EQUS ":0.LIB*." ; ":0.LIB*" + CR: default library path

Filing system vector addresses

Seven 2-byte vector addresses copied to the MOS vector table at &0212-&021F when ADFS is selected. All point into the extended vector jump block at &FFxx, which dispatches through tbl_extended_vectors to reach the actual ADFS handler routines.

  FILEV  &FF1B  OSFILE handler
  ARGSV  &FF1E  OSARGS handler
  BGETV  &FF21  OSBGET handler
  BPUTV  &FF24  OSBPUT handler
  GBPBV  &FF27  OSGBPB handler
  FINDV  &FF2A  OSFIND handler
  FSCV   &FF2D  Filing system control handler
9CB3 .tbl_fs_vectors←1← 9B9C LDA
EQUW &FF1B ; FILEV: &FF1B (OSFILE)
9CB5 EQUW &FF1E ; ARGSV: &FF1E (OSARGS)
9CB7 EQUW &FF21 ; BGETV: &FF21 (OSBGET)
9CB9 EQUW &FF24 ; BPUTV: &FF24 (OSBPUT)
9CBB EQUW &FF27 ; GBPBV: &FF27 (OSGBPB)
9CBD EQUW &FF2A ; FINDV: &FF2A (OSFIND)
9CBF EQUW &FF2D ; FSCV: &FF2D (FSC)

Extended vector table

Seven 3-byte extended vector entries for the filing system API. Each entry is: handler address low, handler address high, ROM number (&FF, patched to actual ROM number when installed). Copied to the MOS extended vector area when ADFS is selected as the current filing system.

  FILEV  &923E  osfile_handler
  ARGSV  &A955  osargs_handler
  BGETV  &AD63  osbget_handler
  BPUTV  &B08F  osbput_handler
  GBPBV  &B57F  osgbpb_handler
  FINDV  &B1B6  osfind_handler
  FSCV   &9E50  fscv_handler
9CC1 .tbl_extended_vectors←1← 9BB2 LDA
EQUW &923E ; FILEV: osfile_handler (&923E)
9CC3 EQUB &FF ; ROM: &FF (patched at runtime)
9CC4 EQUW &A955 ; ARGSV: osargs_handler (&A955)
9CC6 EQUB &FF ; ROM: &FF
9CC7 EQUW &AD63 ; BGETV: osbget_handler (&AD63)
9CC9 EQUB &FF ; ROM: &FF
9CCA EQUW &B08F ; BPUTV: osbput_handler (&B08F)
9CCC EQUB &FF ; ROM: &FF
9CCD EQUW &B57F ; GBPBV: osgbpb_handler (&B57F)
9CCF EQUB &FF ; ROM: &FF
9CD0 EQUW &B1B6 ; FINDV: osfind_handler (&B1B6)
9CD2 EQUB &FF ; ROM: &FF
9CD3 EQUW &9E50 ; FSCV: fscv_handler (&9E50)
9CD5 EQUB &FF ; ROM: &FF

Filing system name string

The string 'adfs' (reversed for stack-based comparison) used to identify the filing system during service call handling.

9CD6 .str_filing_system_name←2← 9CF7 CMP← 9DF9 CMP
EQUS "sfda"

Service 4: unrecognised star command

Handle unrecognised star commands passed to filing system ROMs. Matches commands against the ADFS command table and dispatches to the appropriate handler.

9CDA .service_handler_4
TYA ; Save Y (text offset)
9CDB PHA ; Save Y for later restore
9CDC LDA #&ff ; Push &FF (no prefix flag)
9CDE PHA ; Push default prefix flag (&FF)
9CDF LDA (os_text_ptr),y ; Get first command character
9CE1 ORA #&20 ; Convert to lowercase
9CE3 CMP #&66 ; Is it 'f' (FADFS prefix)?
9CE5 BNE check_adfs_prefix ; No, check for ADFS prefix
9CE7 PLA ; Replace &FF with 'C' (FSC code)
9CE8 LDA #&43 ; Replace with 'C' (FSC code)
9CEA PHA ; Push FSC code
9CEB INY ; Skip past 'F' prefix
9CEC .check_adfs_prefix←1← 9CE5 BNE
LDX #3 ; X=3: match 4 chars of 'ADFS'
9CEE .match_command_loop←1← 9CFD BPL
LDA (os_text_ptr),y ; Get next command character
9CF0 INY ; Advance text pointer
9CF1 CMP #&2e ; Is it a dot (abbreviation)?
9CF3 BEQ service4_not_matched ; Yes, match succeeded
9CF5 ORA #&20 ; Convert to lowercase for compare
9CF7 CMP str_filing_system_name,x ; Compare with "adfs" (backwards)
9CFA BNE service4_decline ; No match, not for us
9CFC DEX ; Next char in 'ADFS'
9CFD BPL match_command_loop ; Loop for 4 characters
9CFF .service4_not_matched←2← 9CF3 BEQ← 9D04 BEQ
LDA (os_text_ptr),y ; Skip spaces after 'ADFS'
9D01 INY ; Advance past matched space
9D02 CMP #&20 ; Space?
9D04 BEQ service4_not_matched ; Yes, skip more spaces
9D06 BCS service4_decline ; Printable: more text follows, fail
9D08 PLA ; Get prefix flag
9D09 TAX ; Transfer prefix flag to X
9D0A PLA ; Get saved text offset
9D0B TXA ; Transfer back to A
9D0C PHA ; Push for later restore
9D0D PHA ; Push again
9D0E JMP boot_run_option ; Select ADFS and execute command

Decline service 4 and pass on

Clean up stack and return A=4 to pass the unrecognised command to the next ROM in the service chain.

9D11 .service4_decline←2← 9CFA BNE← 9D06 BCS
PLA ; Not for us: clean up stack
9D12 PLA ; Clean up stack (discard flag)
9D13 TAY ; Restore Y
9D14 LDA #4 ; A=4: pass on to next ROM
9D16 LDX romsel_copy ; Get our ROM number
9D18 RTS ; Return (not our command)

Service 8: unrecognised OSWORD

Handle unrecognised OSWORD calls. ADFS claims OSWORD &72 for direct disc access.

9D19 .service_handler_8
TYA ; Save Y (OSWORD number is at &EF)
9D1A PHA ; Save Y on stack
9D1B LDA #0 ; A=0 for OSARGS read filing system
9D1D TAY ; Y=&00
9D1E JSR osargs ; Get current filing system number
; A is the filing system number:
; A=0, no filing system selected
; A=1, 1200 baud CFS
; A=2, 300 baud CFS
; A=3, ROM filing system
; A=4, Disc filing system
; A=5, Network filing system
; A=6, Teletext filing system
; A=7, IEEE filing system
; A=8, ADFS
; A=9, Host filing system
; A=10, Videodisc filing system
9D21 CMP #8 ; Is it ADFS (filing system 8)?
9D23 BNE store_result_byte ; No, pass on to next ROM
9D25 LDA zp_osbyte_last_a ; Get OSWORD number from &EF
9D27 CMP #&72 ; Is it OSWORD &72 (disc access)?
9D29 BNE check_transfer_complete ; No, check other OSWORD numbers
9D2B LDA zp_osbyte_last_x ; Get control block address from &F0
9D2D STA zp_wksp_ptr_lo ; Store in (&BA) for access
9D2F LDA zp_osbyte_last_y ; Get control block high byte
9D31 STA zp_wksp_ptr_hi ; Store in (&BB)
9D33 LDY #&0f ; Y=&0F: copy 16 bytes of ctrl block
9D35 .match_osword_block_loop←1← 9D3B BPL
LDA (zp_wksp_ptr_lo),y ; Copy control block to workspace
9D37 STA wksp_disc_op_result,y ; Copy control block to workspace
9D3A DEY ; Next byte
9D3B BPL match_osword_block_loop ; Loop for 16 bytes
9D3D LDA wksp_disc_op_command ; Get disc operation command byte
9D40 AND #&fd ; Mask out direction bit
9D42 CMP #8 ; Command 8 = verify?
9D44 BEQ store_osword_result ; Yes, handle verify specially
9D46 .copy_disc_op_params_loop←1← 9D5A BNE
LDX #&15 ; Set up disc op control block
9D48 LDY #&10 ; Y=&10: workspace control block
9D4A INC wksp_current_drive ; Temporarily set drive to &FF+1=0
9D4D BEQ execute_osword_disc_op ; Was it already 0 (unset)?
9D4F DEC wksp_current_drive ; No, restore original drive
9D52 .execute_osword_disc_op←1← 9D4D BEQ
JSR command_exec_xy ; Execute the disc command
9D55 BPL copy_result_sector_loop ; Success?
9D57 .store_osword_result←1← 9D44 BEQ
LDA wksp_disc_op_sector_count ; Check sector count for verify
9D5A BNE copy_disc_op_params_loop ; More sectors to verify
9D5C JSR check_disc_command_type ; Process verify result
9D5F .copy_result_sector_loop←1← 9D55 BPL
LDY #0 ; Y=0: store result at block+0
9D61 STA (zp_wksp_ptr_lo),y ; Write result back to control block
9D63 .set_result_error_code←4← 9D7F BMI← 9D91 JMP← 9DA5 BMI← ABA2 JMP
LDX romsel_copy ; Restore ROM number
9D65 PLA ; Restore Y
9D66 TAY ; Restore Y
9D67 LDA #0 ; A=0: service call claimed
9D69 RTS ; Return (service claimed)
9D6A .store_result_byte←2← 9D23 BNE← 9D96 BNE
LDX romsel_copy ; Not our filing system
9D6C PLA ; Restore Y
9D6D TAY ; Transfer to Y
9D6E LDA #8 ; A=8: pass on to next ROM
9D70 RTS ; Return (not claimed)
9D71 .check_transfer_complete←1← 9D29 BNE
CMP #&73 ; OSWORD &73 (read last error)?
9D73 BNE adjust_partial_transfer ; No, check next
9D75 LDY #4 ; Y=4: copy 5 bytes of error info
9D77 .copy_transfer_count_loop←1← 9D7D BPL
LDA wksp_err_sector,y ; Copy error sector+code to block
9D7A STA (zp_osbyte_last_x),y ; Store error byte in control block
9D7C DEY ; Next byte
9D7D BPL copy_transfer_count_loop ; Loop for 5 error bytes
9D7F BMI set_result_error_code ; Return as claimed
9D81 .adjust_partial_transfer←1← 9D73 BNE
CMP #&70 ; OSWORD &70 (read dir state)?
9D83 BNE store_adjusted_count ; No, check next
9D85 LDA dir_master_sequence ; Get directory master sequence
9D88 LDY #0 ; Y=0: store at block+0
9D8A STA (zp_osbyte_last_x),y ; Write sequence number to block
9D8C LDA zp_adfs_flags ; Get ADFS flags
9D8E INY ; Y=&01
9D8F STA (zp_osbyte_last_x),y ; Write flags to block+1
9D91 JMP set_result_error_code ; Return as claimed
9D94 .store_adjusted_count←1← 9D83 BNE
CMP #&71 ; OSWORD &71 (read free space)?
9D96 BNE store_result_byte ; No, not for us
9D98 JSR calc_total_free_space ; Calculate free space on disc
9D9B LDY #3 ; Y=3: copy 4 bytes of result
9D9D .copy_adjusted_bytes_loop←1← 9DA3 BPL
LDA wksp_disc_op_result,y ; Copy free space to control block
9DA0 STA (zp_osbyte_last_x),y ; Store free space byte
9DA2 DEY ; Next byte
9DA3 BPL copy_adjusted_bytes_loop ; Loop for 4 bytes
9DA5 BMI set_result_error_code ; Return as claimed
fall through ↓

Print *HELP version header line

Print a newline followed by the ROM version string for *HELP output. Uses print_inline_string.

9DA7 .help_print_header←2← 9DC6 JSR← 9E08 JSR
JSR print_inline_string ; Print bit-7-terminated inline string
9DAA EQUS ".Advanced DFS 1.30"
9DBC EQUB &8D ; CR + bit 7: end of version string
9DBD RTS ; Return to caller

Service 9: *HELP

Handle *HELP requests. Prints ADFS version information when *HELP ADFS is entered.

9DBE .service_handler_9
TYA ; Save Y (text pointer offset)
9DBF PHA ; Save text pointer on stack
9DC0 LDA (os_text_ptr),y ; Get first char of *HELP argument
9DC2 CMP #&20 ; Is it a printable char?
9DC4 BCS end_of_command_name ; Yes, try matching 'ADFS'
9DC6 JSR help_print_header ; No argument: print version banner
9DC9 JSR print_inline_string ; Print ' ADFS'
9DCC EQUS " ADFS"
9DD2 .check_help_adfs_keyword
STA la868 ; Store flag for help type
9DD5 LDX romsel_copy ; Get our ROM number
9DD7 LDA #9 ; A=9: return service 9 (not claimed)
9DD9 .return_25←1← 9DDF BCS
RTS ; Return

Print *HELP ADFS command list

Print the ADFS command list for *HELP output, formatting each command name with padding.

9DDA .print_help_command_list←2← 9DE5 JSR← 9DEA JSR
INY ; Check next char of HELP argument
9DDB LDA (os_text_ptr),y ; Get next char from help text
9DDD CMP #&20 ; Is it printable?
9DDF BCS return_25 ; Yes, return (more text follows)
9DE1 PLA ; End of argument: pop return address
9DE2 PLA ; Pop 2 return addresses
9DE3 BCC l9dd3 ; Return to service dispatcher
9DE5 .print_next_command←3← 9DE8 BNE← 9DFC BNE← 9E06 BCS
JSR print_help_command_list ; Skip non-space chars in argument
9DE8 BNE print_next_command ; Loop skipping non-space chars
9DEA .output_command_name_loop←1← 9DED BEQ
JSR print_help_command_list ; Skip space chars after word
9DED BEQ output_command_name_loop ; Loop skipping space chars
9DEF .end_of_command_name←1← 9DC4 BCS
LDX #3 ; X=3: compare 4 chars of 'ADFS'
9DF1 .pad_command_name_loop←1← 9E00 BPL
LDA (os_text_ptr),y ; Get char from argument
9DF3 CMP #&2e ; Is it a dot (abbreviation)?
9DF5 BEQ check_more_commands ; Yes, match succeeded
9DF7 ORA #&20 ; Convert to lowercase for compare
9DF9 CMP str_filing_system_name,x ; Compare with "adfs" backwards
9DFC BNE print_next_command ; No match, skip this word
9DFE INY ; Next char in argument
9DFF DEX ; Next char in 'ADFS'
9E00 BPL pad_command_name_loop ; Loop for 4 chars
9E02 LDA (os_text_ptr),y ; Check char after 'ADFS' match
9E04 CMP #&21 ; More alpha chars? Not exact match
9E06 BCS print_next_command ; Not a match, skip word
9E08 .check_more_commands←1← 9DF5 BEQ
JSR help_print_header ; Print version info
9E0B LDX #0 ; X=0: start of command table
9E0D .print_help_data_commands←1← 9E46 BNE
LDA tbl_commands,x ; Get command table byte
9E10 BMI l9dd3 ; Bit 7 set: end of table
9E12 JSR print_inline_string ; Print " " indent before command name
9E15 EQUS " "
9E16 EQUB &A0 ; ' ' + bit 7: end of inline string
9E17 LDY #9 ; Y=9: max 10 chars per command name
9E19 .print_data_cmd_name_loop←1← 9E23 BPL
LDA tbl_commands,x ; Get char from command table
9E1C BMI end_of_data_command ; Bit 7 set: end of command name
9E1E JSR osasci ; Print command name character
9E21 INX ; Next table byte
9E22 DEY ; Decrement char counter
9E23 BPL print_data_cmd_name_loop ; Loop for up to 10 chars
9E25 .end_of_data_command←2← 9E1C BMI← 9E29 BPL
JSR print_space ; Print space for padding
9E28 DEY ; Decrement padding counter
9E29 BPL end_of_data_command ; Loop until 10 columns filled
9E2B TXA ; Save table index
9E2C PHA ; Save table index on stack
9E2D LDA l9ee5,x ; Get address byte from table+2
9E30 PHA ; Save for low nibble
9E31 LSR ; Shift high nibble down
9E32 LSR ; Shift high nibble to low
9E33 LSR ; 4 right shifts total
9E34 LSR ; 4th shift
9E35 JSR setup_help_param_ptr ; Print as hex digit
9E38 PLA ; Restore address byte
9E39 AND #&0f ; Isolate low nibble
9E3B JSR setup_help_param_ptr ; Print as hex digit
9E3E JSR osnewl ; Print newline
9E41 PLA ; Restore table index
9E42 TAX ; Restore table index to X
9E43 INX ; Skip past 3-byte entry data
9E44 INX ; Skip past 1st dispatch byte
9E45 INX ; Skip past 2nd dispatch byte
9E46 BNE print_help_data_commands ; Loop for all commands
fall through ↓

*HELP parameter format string pointer table

Eight low-byte pointers into the &9Fxx page, indexing the parameter format strings displayed after each command name in the *HELP ADFS output. Each command's third table byte packs two nibble indices: the high nibble selects the first parameter string, the low nibble selects the second. For example, *ACCESS has byte &16 meaning index 1 then index 6, producing "ACCESS <List Spec> (L)(W)(R)(E)" in the listing.

  0: (none)         4: (<Drive>)
  1: <List Spec>    5: <SP> <LP>
  2: <Ob Spec>      6: (L)(W)(R)(E)
  3: <*Ob Spec*>    7: <Title>
9E48 .tbl_help_param_ptrs←1← 9280 LDA
EQUB <(help_param_none) ; (no parameter)
9E49 EQUB <(help_param_list_spec) ; "<List Spec>"
9E4A EQUB <(help_param_ob_spec) ; "<Ob Spec>"
9E4B EQUB <(help_param_wild_ob_spec) ; "<*Ob Spec*>"
9E4C EQUB <(help_param_drive) ; "(<Drive>)"
9E4D EQUB <(help_param_sp_lp) ; "<SP> <LP>"
9E4E EQUB <(help_param_access) ; "(L)(W)(R)(E)"
9E4F EQUB <(help_param_title) ; "<Title>"

Filing system control vector handler

Handle filing system control calls via FSCV. Dispatches star commands, *RUN, *CAT, etc.

9E50 .fscv_handler
STX zp_text_ptr_lo ; Save text pointer in (&B4)
9E52 STY zp_text_ptr_hi ; Store text pointer high
9E54 TAX ; Transfer FSC code to X
9E55 BMI return_26 ; FSC >= &80? Not for us
9E57 CMP #9 ; FSC >= 9? Not for us
9E59 BCS return_26 ; FSC >= 9: not for us
9E5B LDA #0 ; Clear current channel
9E5D STA wksp_cur_channel ; Clear current channel
9E60 LDA fscv_dispatch_hi,x ; Get dispatch address high byte
9E63 PHA ; Push dispatch high byte
9E64 LDA fscv_dispatch_lo,x ; Get dispatch address low byte
9E67 PHA ; Push dispatch low byte
9E68 LDX zp_text_ptr_lo ; Restore X (text pointer low)
9E6A LDY zp_text_ptr_hi ; Restore Y (text pointer high)
9E6C .return_26←2← 9E55 BMI← 9E59 BCS
RTS ; RTS-dispatch to handler

FSCV dispatch table

RTS-trick dispatch table for filing system control calls 0-8. Low bytes at &9E6D, high bytes at &9E76, 9 entries. FSC 0=*OPT, 1=check EOF, 2=*/, 3=*command, 4=*RUN, 5=*CAT, 6=new FS, 7=handle range, 8=*command (OS 1.20).

9E6D .fscv_dispatch_lo←1← 9E64 LDA
EQUB <(fsc0_star_opt-1)
9E6E EQUB <(check_eof_for_handle-1)
9E6F EQUB <(star_run-1)
9E70 EQUB <(star_cmd-1)
9E71 EQUB <(star_run-1)
9E72 EQUB <(print_catalogue_entries-1)
9E73 EQUB <(fsc6_new_filing_system-1)
9E74 EQUB <(fsc7_read_handle_range-1)
9E75 EQUB <(check_compaction_recommended-1)
9E76 .fscv_dispatch_hi←1← 9E60 LDA
EQUB >(fsc0_star_opt-1)
9E77 EQUB >(check_eof_for_handle-1)
9E78 EQUB >(star_run-1)
9E79 EQUB >(star_cmd-1)
9E7A EQUB >(star_run-1)
9E7B EQUB >(print_catalogue_entries-1)
9E7C EQUB >(fsc6_new_filing_system-1)
9E7D EQUB >(fsc7_read_handle_range-1)
9E7E EQUB >(check_compaction_recommended-1)

Parse and dispatch star command

Match the command string at (&B4) against the command table at tbl_commands. The table encodes command names with their dispatch addresses. Supports abbreviation with dot.

Uses RTS-trick dispatch to the matched command handler.

9E7F .star_cmd
JSR wait_ensuring ; Wait if files being ensured
9E82 LDA #&a2 ; Set up workspace pointer
9E84 STA zp_osfile_ptr_lo ; Store workspace pointer low
9E86 LDA #&10 ; Workspace page = &10
9E88 STA zp_osfile_ptr_hi ; Store workspace pointer high
9E8A JSR skip_spaces ; Skip leading spaces in command
9E8D LDX #&fd ; X=&FD: start before first table entry
9E8F .next_command_entry←2← 9EAF BNE← 9EC1 BCC
INX ; Advance X past previous entry's data
9E90 INX ; Next table entry
9E91 LDY #&ff ; Y=&FF: start before first char
9E93 .match_command_char←2← 9E9C BEQ← 9EA2 BEQ
INX ; Next table byte and command char
9E94 INY ; Next argument character
9E95 LDA tbl_commands,x ; Get byte from command table
9E98 BMI end_of_table_name ; Bit 7 set: end of command name
9E9A CMP (zp_text_ptr_lo),y ; Compare with input character
9E9C BEQ match_command_char ; Match, continue
9E9E ORA #&20 ; Try case-insensitive (OR &20)
9EA0 CMP (zp_text_ptr_lo),y ; Compare again
9EA2 BEQ match_command_char ; Match, continue
9EA4 DEX ; Back up table pointer
9EA5 .skip_to_end_of_name←1← 9EA9 BPL
INX ; Skip to next table entry
9EA6 LDA tbl_commands,x ; Read table byte
9EA9 BPL skip_to_end_of_name ; Loop until bit 7 set (end marker)
9EAB LDA (zp_text_ptr_lo),y ; Check if input has abbreviation dot
9EAD CMP #&2e ; Is it a dot?
9EAF BNE next_command_entry ; No, try next command
9EB1 INY ; Skip past the dot
9EB2 BNE advance_past_command ; Always branch (Y != 0)
9EB4 .end_of_table_name←1← 9E98 BMI
TYA ; Y=0: no chars matched at all?
9EB5 BEQ dispatch_command ; Yes, unknown command
9EB7 LDA (zp_text_ptr_lo),y ; Check if next input char is alpha
9EB9 AND #&5f ; Mask to uppercase
9EBB CMP #&41 ; Below 'A'? Not alpha, command done
9EBD BCC advance_past_command ; Not alpha: command name complete
9EBF CMP #&5b ; Above 'Z'? Not alpha, command done
9EC1 BCC next_command_entry ; Alpha: partial match, try next cmd
9EC3 .advance_past_command←2← 9EB2 BNE← 9EBD BCC
TYA ; Advance text pointer past matched chars
9EC4 CLC ; Clear carry for pointer advance
9EC5 ADC zp_text_ptr_lo ; Add matched length to pointer
9EC7 STA zp_text_ptr_lo ; Store updated pointer low
9EC9 BCC skip_spaces_before_args ; No page crossing
9ECB INC zp_text_ptr_hi ; Increment pointer high page
9ECD .skip_spaces_before_args←1← 9EC9 BCC
JSR skip_spaces ; Skip spaces after command
9ED0 LDA zp_text_ptr_lo ; Save text pointer for command handler
9ED2 STA wksp_cmd_tail ; Save text pointer low for handler
9ED5 LDA zp_text_ptr_hi ; Get text pointer high
9ED7 STA wksp_cmd_tail_hi ; Save for handler
9EDA .dispatch_command←1← 9EB5 BEQ
LDA tbl_commands,x ; Get dispatch address from table
9EDD PHA ; Push high byte
9EDE LDA l9ee4,x ; Get dispatch low from table+1
9EE1 PHA ; Push low byte
9EE2 RTS ; RTS-dispatch to command handler

Star command name and dispatch table

Table of ADFS star command names with dispatch addresses. Each entry is: command name bytes (bit 7 set on last), dispatch address high byte, dispatch address low byte, parameter count nibbles.

Dispatch uses the RTS trick: the high and low bytes are pushed onto the stack, then RTS pops and adds 1 to form the target address. The stored address is therefore the handler address minus one.

9EE3 .tbl_commands←5← 9E0D LDA← 9E19 LDA← 9E95 LDA← 9EA6 LDA← 9EDA LDA
EQUS "ACCESS" ; "ACCESS" command name
9EE9 EQUB >(star_access-1) ; Dispatch hi-1 -> star_access
9EEA EQUB <(star_access-1) ; Dispatch lo-1 -> star_access
9EEB EQUB &16 ; Params &16: <List Spec> (L)(W)(R)(E)
9EEC EQUS "BACK" ; "BACK" command name
9EF0 EQUB >(star_back-1) ; Dispatch hi-1 -> star_back
9EF1 EQUB <(star_back-1) ; Dispatch lo-1 -> star_back
9EF2 EQUB &00 ; Params &00: (none)
9EF3 EQUS "BYE" ; "BYE" command name
9EF6 EQUB >(star_bye-1) ; Dispatch hi-1 -> star_bye
9EF7 EQUB <(star_bye-1) ; Dispatch lo-1 -> star_bye
9EF8 EQUB &00 ; Params &00: (none)
9EF9 EQUS "CDIR" ; "CDIR" command name
9EFD EQUB >(star_cdir-1) ; Dispatch hi-1 -> star_cdir
9EFE EQUB <(star_cdir-1) ; Dispatch lo-1 -> star_cdir
9EFF EQUB &20 ; Params &20: <Ob Spec>
9F00 EQUS "CLOSE" ; "CLOSE" command name
9F05 EQUB >(star_close-1) ; Dispatch hi-1 -> star_close
9F06 EQUB <(star_close-1) ; Dispatch lo-1 -> star_close
9F07 EQUB &00 ; Params &00: (none)
9F08 EQUS "COMPACT" ; "COMPACT" command name
9F0F EQUB >(star_compact-1) ; Dispatch hi-1 -> star_compact
9F10 EQUB <(star_compact-1) ; Dispatch lo-1 -> star_compact
9F11 EQUB &50 ; Params &50: <SP> <LP>
9F12 EQUS "COPY" ; "COPY" command name
9F16 EQUB >(star_copy-1) ; Dispatch hi-1 -> star_copy
9F17 EQUB <(star_copy-1) ; Dispatch lo-1 -> star_copy
9F18 EQUB &13 ; Params &13: <List Spec> <*Ob Spec*>
9F19 EQUS "DELETE" ; "DELETE" command name
9F1F EQUB >(star_delete-1) ; Dispatch hi-1 -> star_delete
9F20 EQUB <(star_delete-1) ; Dispatch lo-1 -> star_delete
9F21 EQUB &20 ; Params &20: <Ob Spec>
9F22 EQUS "DESTROY" ; "DESTROY" command name
9F29 EQUB >(star_destroy-1) ; Dispatch hi-1 -> star_destroy
9F2A EQUB <(star_destroy-1) ; Dispatch lo-1 -> star_destroy
9F2B EQUB &10 ; Params &10: <List Spec>
9F2C EQUS "DIR" ; "DIR" command name
9F2F EQUB >(star_dir-1) ; Dispatch hi-1 -> star_dir
9F30 EQUB <(star_dir-1) ; Dispatch lo-1 -> star_dir
9F31 EQUB &20 ; Params &20: <Ob Spec>
9F32 EQUS "DISMOUNT" ; "DISMOUNT" command name
9F3A EQUB >(star_dismount-1) ; Dispatch hi-1 -> star_dismount
9F3B EQUB <(star_dismount-1) ; Dispatch lo-1 -> star_dismount
9F3C EQUB &40 ; Params &40: (<Drive>)
9F3D EQUS "EX" ; "EX" command name
9F3F EQUB >(star_ex-1) ; Dispatch hi-1 -> star_ex
9F40 EQUB <(star_ex-1) ; Dispatch lo-1 -> star_ex
9F41 EQUB &30 ; Params &30: <*Ob Spec*>
9F42 EQUS "FREE" ; "FREE" command name
9F46 EQUB >(star_free-1) ; Dispatch hi-1 -> star_free
9F47 EQUB <(star_free-1) ; Dispatch lo-1 -> star_free
9F48 EQUB &00 ; Params &00: (none)
9F49 EQUS "INFO" ; "INFO" command name
9F4D EQUB >(star_info-1) ; Dispatch hi-1 -> star_info
9F4E EQUB <(star_info-1) ; Dispatch lo-1 -> star_info
9F4F EQUB &10 ; Params &10: <List Spec>
9F50 EQUS "LCAT" ; "LCAT" command name
9F54 EQUB >(star_lcat-1) ; Dispatch hi-1 -> star_lcat
9F55 EQUB <(star_lcat-1) ; Dispatch lo-1 -> star_lcat
9F56 EQUB &00 ; Params &00: (none)
9F57 EQUS "LEX" ; "LEX" command name
9F5A EQUB >(star_lex-1) ; Dispatch hi-1 -> star_lex
9F5B EQUB <(star_lex-1) ; Dispatch lo-1 -> star_lex
9F5C EQUB &00 ; Params &00: (none)
9F5D EQUS "LIB" ; "LIB" command name
9F60 EQUB >(star_lib-1) ; Dispatch hi-1 -> star_lib
9F61 EQUB <(star_lib-1) ; Dispatch lo-1 -> star_lib
9F62 EQUB &30 ; Params &30: <*Ob Spec*>
9F63 EQUS "MAP" ; "MAP" command name
9F66 EQUB >(star_map-1) ; Dispatch hi-1 -> star_map
9F67 EQUB <(star_map-1) ; Dispatch lo-1 -> star_map
9F68 EQUB &00 ; Params &00: (none)
9F69 EQUS "MOUNT" ; "MOUNT" command name
9F6E EQUB >(star_mount-1) ; Dispatch hi-1 -> star_mount
9F6F EQUB <(star_mount-1) ; Dispatch lo-1 -> star_mount
9F70 EQUB &40 ; Params &40: (<Drive>)
9F71 EQUS "REMOVE" ; "REMOVE" command name
9F77 EQUB >(star_remove-1) ; Dispatch hi-1 -> star_remove
9F78 EQUB <(star_remove-1) ; Dispatch lo-1 -> star_remove
9F79 EQUB &20 ; Params &20: <Ob Spec>
9F7A EQUS "RENAME" ; "RENAME" command name
9F80 EQUB >(star_rename-1) ; Dispatch hi-1 -> star_rename
9F81 EQUB <(star_rename-1) ; Dispatch lo-1 -> star_rename
9F82 EQUB &22 ; Params &22: <Ob Spec> <Ob Spec>
9F83 EQUS "TITLE" ; "TITLE" command name
9F88 EQUB >(star_title-1) ; Dispatch hi-1 -> star_title
9F89 EQUB <(star_title-1) ; Dispatch lo-1 -> star_title
9F8A EQUB &70 ; Params &70: <Title>
9F8B EQUB HI(star_run-1) ; End: dispatch hi-1 -> star_run
9F8C EQUB LO(star_run-1) ; End: dispatch lo-1 -> star_run

*HELP parameter format strings

Seven NUL-terminated strings displayed after command names in the *HELP ADFS listing. Indexed via tbl_help_param_ptrs using nibble pairs from each command's parameter byte. Index 0 points to the NUL at &9FD7 (end of the last string), producing no output for commands with no parameters.

  1: "<List Spec>"     Wildcard file specification
  2: "<Ob Spec>"       Single object specification
  3: "<*Ob Spec*>"     Optional wildcard specification
  4: "(<Drive>)"       Optional drive number
  5: "<SP> <LP>"       Start page and length page
  6: "(L)(W)(R)(E)"    Access attribute flags
  7: "<Title>"         Directory title string
9F8D .help_param_list_spec
EQUS "<List Spec>." ; Index 1: file list specification
9F99 .help_param_ob_spec
EQUS "<Ob Spec>." ; Index 2: object specification
9FA3 .help_param_wild_ob_spec
EQUS "<*Ob Spec*>." ; Index 3: wildcard object specification
9FAF .help_param_drive
EQUS "(<Drive>)." ; Index 4: optional drive number
9FB9 .help_param_sp_lp
EQUS "<SP> <LP>." ; Index 5: *COMPACT start/length pages
9FC3 .help_param_access
EQUS "(L)(W)(R)(E)." ; Index 6: access attribute flags
9FD0 .help_param_title
EQUS "<Title>." ; Index 7: directory title string

FSC 7: return ADFS file handle range

Return the range of file handles used by ADFS. The MOS calls FSC 7 to determine which handles belong to the current filing system. ADFS uses handles &30-&39 (ASCII '0'-'9', 10 channels).

On ExitAcorrupted
X&30 (lowest handle, ASCII '0')
Y&39 (highest handle, ASCII '9')
9FD8 .fsc7_read_handle_range
LDX #&30 ; X=&30 ('0'): lowest ADFS file handle X=&30 ('0'): lowest ADFS file handle
9FDA LDY #&39 ; Y=&39 ('9'): highest ADFS file handle Y=&39 ('9'): highest ADFS file handle
9FDC RTS ; Return X=&30, Y=&39 to MOS Return to FSC dispatcher

FSC 0: *OPT command handler

Handle the *OPT command. *OPT 1,N controls verbose mode (bit 2 of zp_adfs_flags). *OPT 4,N sets the disc boot option in the free space map.

On EntryXfirst *OPT parameter (option number)
Ysecond *OPT parameter (value)
9FDD .fsc0_star_opt
LDX zp_text_ptr_lo ; Get *OPT first parameter
9FDF BEQ clear_opt1_verbose ; Param=0: *OPT 0 (clear OPT1)
9FE1 DEX ; Param-1: check for *OPT 1
9FE2 BNE check_opt4_boot ; Not *OPT 1: check *OPT 4
9FE4 TYA ; Param=1: check second parameter
9FE5 BEQ clear_opt1_verbose ; Second param=0: clear OPT1
9FE7 LDA zp_adfs_flags ; Get ADFS flags
9FE9 ORA #4 ; Set bit 2 (*OPT1 verbose on)
9FEB BNE store_opt_flags ; ALWAYS branch
9FED .clear_opt1_verbose←2← 9FDF BEQ← 9FE5 BEQ
LDA zp_adfs_flags ; Clear bit 2 (OPT1 verbose off)
9FEF AND #&fb ; Clear bit 2 (*OPT1 verbose off)
9FF1 .store_opt_flags←1← 9FEB BNE
STA zp_adfs_flags ; Store updated flags
9FF3 JMP save_wksp_and_return ; Save workspace and return
9FF6 .check_opt4_boot←1← 9FE2 BNE
CPX #3 ; Check for *OPT 4 (boot option)
9FF8 BNE bad_opt_error ; Not *OPT 4: bad opt error
9FFA JSR validate_fsm_and_mark_dirty ; Mark directory as modified
9FFD JSR check_drive_and_reload_fsm ; Ensure dir loaded and writable
A000 LDA zp_text_ptr_hi ; Get boot option value (second param)
A002 AND #3 ; Mask to 2 bits (options 0-3)
A004 STA fsm_s1_boot_option ; Store in FSM boot option byte
A007 JMP write_dir_and_validate ; Write FSM back to disc
A00A .bad_opt_error←1← 9FF8 BNE
JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
A00D EQUB &CB ; Error &CB: Bad opt
A00E EQUS "Bad opt."
fall through ↓

Print a space character

Print a single space (&20) via OSWRCH.

A016 .print_space←8← 9299 JSR← 92E3 JSR← 92FC JSR← 9313 JMP← 93F5 JSR← 952B JSR← 952E JSR← 9E25 JSR
LDA #&20 ; A=&20: space character
A018 JMP oswrch ; Write character 32

*FREE command handler

Display the free space remaining on the current or specified drive, in bytes and as a number of sectors.

A01B .star_free
JSR calc_total_free_space ; Calculate total free space
A01E JSR print_space_value ; Print free space with header
A021 JSR print_inline_string ; Print "Free" + CR
A024 EQUS "Free"
A028 EQUB &8D ; CR + bit 7: end of inline string
A029 JSR calc_total_free_space ; Calculate total free space again
A02C LDY #1 ; Y=1: start from FSM byte 1
A02E LDX #2 ; X=2: subtract 3 bytes
A030 SEC ; Set carry for subtraction
A031 .print_used_space←1← A03C BPL
LDA fsm_s0_disc_id_lo,y ; Total sectors (from FSM sector 0)
A034 SBC wksp_disc_op_result,y ; Subtract free space
A037 STA wksp_disc_op_result,y ; Store result (used space)
A03A INY ; Next FSM byte
A03B DEX ; Decrement byte counter
A03C BPL print_used_space ; Loop for 3 bytes
A03E JSR print_space_value ; Print used space with header
A041 JSR print_inline_string ; Print "Used" + CR
A044 EQUS "Used"
A048 EQUB &8D ; CR + bit 7: end of inline string
A049 .return_27←4← A064 BEQ← A097 BNE← A09E BCC← A0BE BNE
RTS ; Return

*MAP command handler

Display the free space map of the current or specified drive, showing the address and length of each free space region.

A04A .star_map
JSR print_inline_string ; Print "Address : Length" + CR header
A04D EQUS "Address : Length"
A05E EQUB &8D ; CR + bit 7: end of inline string
A05F LDX #0 ; X=0: start of FSM entries
A061 .print_map_header←1← A092 BNE
CPX fsm_s1_total_sectors_lo ; Past end of free space list?
A064 BEQ return_27 ; Yes, done
A066 INX ; Advance X to entry+3
A067 INX ; Advance X: 2nd byte of 3-byte entry
A068 INX ; Advance X: 3rd byte of 3-byte entry
A069 STX zp_gspb_ptr_lo ; Save FSM index for next iteration
A06B LDY #2 ; Y=2: print 3 address bytes
A06D .print_fsm_entries_loop←1← A075 BPL
DEX ; Back up to previous byte
A06E LDA fsm_sector_0,x ; Get address byte from FSM sector 0
A071 JSR print_hex_byte ; Print as 2 hex digits
A074 DEY ; Next byte
A075 BPL print_fsm_entries_loop ; Loop for 3 bytes (high to low)
A077 JSR print_inline_string ; Print " : " separator
A07A EQUS " : "
A07E EQUB &A0 ; ' ' + bit 7: end of inline string
A07F LDX zp_gspb_ptr_lo ; Restore FSM index
A081 LDY #2 ; Y=2: print 3 length bytes
A083 .print_entry_hex_loop←1← A08B BPL
DEX ; Back up to previous byte
A084 LDA fsm_sector_1,x ; Get length byte from FSM sector 1
A087 JSR print_hex_byte ; Print as 2 hex digits
A08A DEY ; Next byte
A08B BPL print_entry_hex_loop ; Loop for 3 bytes (high to low)
A08D JSR osnewl ; Print newline after each entry
A090 LDX zp_gspb_ptr_lo ; Restore FSM index for next entry
A092 BNE print_map_header ; Loop if more entries
fall through ↓

Check if disc compaction is recommended

After *MAP output, check if the FSM has become fragmented enough to recommend compaction. Prints a recommendation message if the free space list pointer exceeds &E1.

A094 .check_compaction_recommended
LDX wksp_compaction_reported ; Check if already reported
A097 BNE return_27 ; Already done, skip
A099 LDX fsm_s1_total_sectors_lo ; Get FSM end-of-list pointer
A09C CPX #&e1 ; Pointer >= &E1 (many fragments)?
A09E BCC return_27 ; No, space not fragmented enough
A0A0 JSR print_inline_string ; Print "Compaction recommended" + CR
A0A3 EQUS "Compaction recommended"
A0B9 EQUB &8D ; CR + bit 7: end of inline string
A0BA RTS ; Return to caller

*DELETE command handler

Delete a file from the current directory. Reports an error if the file is locked.

A0BB .star_delete
JSR star_remove ; Try to remove the file
A0BE BNE return_27 ; Not found? Just return
A0C0 JMP bad_parms_error ; Found: delete from directory

*BYE command handler

Close all open files and dismount all drives. Equivalent to *CLOSE followed by *DISMOUNT for all drives.

A0C3 .star_bye
LDA wksp_current_drive ; Save current drive for restore
A0C6 PHA ; Save current drive on stack
A0C7 TAX ; Transfer to X to check for &FF
A0C8 INX ; Drive &FF = uninitialised?
A0C9 BEQ close_all_drives_start ; Yes, skip close
A0CB JSR star_close ; Close all open files
A0CE .close_all_drives_start←1← A0C9 BEQ
LDA #&60 ; Start with drive 3 (ID = &60)
A0D0 STA wksp_current_drive ; Set as current drive
A0D3 .close_each_drive_loop←1← A0E3 BCS
LDX #&ea ; X=&EA: scsi_cmd_park control block low
A0D5 LDY #&a0 ; Y=&A0: scsi_cmd_park control block high
A0D7 JSR command_exec_xy ; Park heads on this drive
A0DA LDA wksp_current_drive ; Get current drive ID
A0DD SEC ; Set carry for subtraction
A0DE SBC #&20 ; Next drive (subtract &20)
A0E0 STA wksp_current_drive ; Store updated drive ID
A0E3 BCS close_each_drive_loop ; Loop while drive ID >= 0
A0E5 PLA ; Restore original drive
A0E6 STA wksp_current_drive ; Store back as current drive
A0E9 RTS ; Return

SCSI park heads disc operation control block

Disc operation control block used by *BYE to park the hard drive heads on shutdown. Referenced indirectly as X=&EA, Y=&A0 from the close_each_drive_loop. Issues SCSI command &1B (Start/Stop Unit) with count=0 (stop/park). The companion block at scsi_cmd_unpark (&A19F) has count=1 (start/unpark) and is used by *MOUNT.

A0EA .scsi_cmd_park
EQUB &00 ; Result: &00
A0EB EQUB &00 ; Memory address low: &00
A0EC EQUB &17 ; Memory address high: &17 (buffer page)
A0ED EQUB &FF ; Memory address byte 3: &FF (host memory)
A0EE EQUB &FF ; Memory address byte 4: &FF (host memory)
A0EF EQUB &1B ; Command: &1B (SCSI Start/Stop Unit)
A0F0 EQUB &00 ; Sector high: &00
A0F1 EQUB &00 ; Sector mid: &00
A0F2 EQUB &00 ; Sector low: &00
A0F3 EQUB &00 ; Sector count: &00 (stop/park heads)
A0F4 EQUB &00 ; Control: &00

Parse optional drive number argument

Parse an optional drive number from the command line for commands like *DISMOUNT, *MOUNT, *FREE, *MAP. If no argument given, uses the current drive.

A0F5 .parse_drive_argument←2← A111 JSR← A15E JSR
JSR skip_spaces ; Skip leading spaces
A0F8 LDY wksp_current_drive ; Get current drive
A0FB INY ; Drive uninitialised (&FF)?
A0FC BEQ store_default_drive ; Yes, use 0 instead
A0FE DEY ; Decrement Y (was INY+1)
A0FF .store_default_drive←1← A0FC BEQ
STY wksp_drive_number ; Store default drive number
A102 LDY #0 ; Y=0: check for argument
A104 LDA (zp_text_ptr_lo),y ; Get first argument char
A106 CMP #&20 ; Is it a printable char?
A108 BCC return_28 ; No argument: use default drive
A10A JSR parse_drive_from_ascii ; Parse drive number from argument
A10D STA wksp_drive_number ; Store parsed drive number
A110 .return_28←1← A108 BCC
RTS ; Return

*DISMOUNT command handler

Close all open files on the specified drive and mark the drive as not mounted.

A111 .star_dismount
JSR parse_drive_argument ; Parse drive number argument
A114 LDX #9 ; X=9: check all 10 channels
A116 .close_drive_channels_loop←1← A130 BPL
LDA wksp_ch_flags,x ; Get channel flags
A119 BEQ check_csd_on_drive ; Channel not open? Skip
A11B LDA wksp_ch_start_sec_h,x ; Get channel's drive number
A11E AND #&e0 ; Isolate drive bits
A120 CMP wksp_drive_number ; Compare with target drive
A123 BNE check_csd_on_drive ; Different drive? Skip
A125 CLC ; Clear carry for addition
A126 TXA ; Channel index to A
A127 ADC #&30 ; Add &30 to get file handle
A129 TAY ; Transfer to Y for OSFIND
A12A LDA #0 ; A=0: close file
A12C JSR osfind_handler ; Close this file
A12F .check_csd_on_drive←2← A119 BEQ← A123 BNE
DEX ; Next channel
A130 BPL close_drive_channels_loop ; Loop for all 10 channels
A132 LDA wksp_current_drive ; Is dismounted drive the CSD drive?
A135 CMP wksp_drive_number ; Compare with target drive
A138 BNE mount_read_root_dir ; Different drive: CSD unaffected
A13A LDA #&ff ; Mark current drive as uninitialised
A13C STA wksp_current_drive ; Set CSD drive to &FF (unset)
A13F STA wksp_csd_sector_hi ; Invalidate drive status
A142 LDX #0 ; X=0: reset CSD name to "Unset"
A144 JSR copy_default_dir_name ; Copy default name to CSD workspace
A147 BMI mount_read_root_dir ; Always branch to exit code
fall through ↓

Copy default directory name to workspace

Copy the reversed string 'Unset' (with quotes and CR padding) to the CSD or library name workspace at &1100+X. Used when dismounting or initialising to set the directory name to the default 'Unset' value.

On EntryXworkspace offset (0 for CSD, 10 for library)
A149 .copy_default_dir_name←4← 8483 JSR← 8486 JSR← A144 JSR← A19B JSR
LDY #9 ; Y=9: copy 10 bytes
A14B .copy_default_name_loop←1← A153 BPL
LDA la154,y ; Get byte from reversed name table
A14E STA wksp_csd_name,x ; Store in CSD/lib name workspace
A151 INX ; Next workspace byte
A152 DEY ; Next table byte (backwards)
A153 .sub_ca153
BPL copy_default_name_loop ; Loop for 10 bytes
A155 RTS ; Return
A156 EQUB &0D ; CR (read backwards as name terminator)
A157 EQUS ""tesnU"" ; Reversed: '"Unset"' default dir name

*MOUNT command handler

Mount a drive by loading its free space map and root directory into memory.

A15E .star_mount
JSR parse_drive_argument ; Parse drive number argument
A161 .mount_drive_setup←1← 9C94 JSR
LDA wksp_drive_number ; Get drive number to mount
A164 STA wksp_current_drive ; Set as current drive
A167 LDX #&9f ; Point to unpark command block
A169 LDY #&a1 ; Y=&A1: control block page
A16B JSR command_exec_xy ; Send unpark command to drive
A16E LDA #&aa ; Point to root directory path
A170 STA zp_text_ptr_lo ; Point to root dir path '$'
A172 LDA #&a2 ; Path string is in this page
A174 STA zp_text_ptr_hi ; Store path high byte
A176 JSR star_dir ; Set root as CSD via *DIR
A179 .mount_read_root_dir←2← A138 BNE← A147 BMI
LDA wksp_prev_dir_sector_hi ; Check if previous dir is on drive
A17C CMP wksp_drive_number ; Compare with target drive
A17F BNE mount_set_boot_option ; Different drive, leave previous
A181 LDA #&ff ; Invalidate previous directory
A183 STA wksp_prev_dir_sector_mid ; Invalidate prev dir high byte
A186 STA wksp_prev_dir_sector_hi ; Invalidate prev dir drive byte
A189 .mount_set_boot_option←1← A17F BNE
LDA wksp_lib_sector_hi ; Check if library is on this drive
A18C CMP wksp_drive_number ; Compare with target drive
A18F BNE return_29 ; Different drive, leave library
A191 LDA #&ff ; Invalidate library sector
A193 STA wksp_lib_sector_mid ; Invalidate lib sector high
A196 STA wksp_lib_sector_hi ; Invalidate lib drive byte
A199 LDX #&0a ; X=&0A: reset lib name to "Unset"
A19B JSR copy_default_dir_name ; Copy default name to library
A19E .return_29←1← A18F BNE
RTS ; Return

SCSI unpark heads disc operation control block

Disc operation control block used by *MOUNT to unpark (spin up) the hard drive heads. Referenced indirectly as X=&9F, Y=&A1 from star_mount. Issues SCSI command &1B (Start/Stop Unit) with count=1 (start/unpark). The companion block at scsi_cmd_park (&A0EA) has count=0 (stop/park) and is used by *BYE.

A19F .scsi_cmd_unpark
EQUB &00 ; Result: &00
A1A0 EQUB &00 ; Memory address low: &00
A1A1 EQUB &17 ; Memory address high: &17 (buffer page)
A1A2 EQUB &FF ; Memory address byte 3: &FF (host memory)
A1A3 EQUB &FF ; Memory address byte 4: &FF (host memory)
A1A4 EQUB &1B ; Command: &1B (SCSI Start/Stop Unit)
A1A5 EQUB &00 ; Sector high: &00
A1A6 EQUB &00 ; Sector mid: &00
A1A7 EQUB &00 ; Sector low: &00
A1A8 EQUB &01 ; Sector count: &01 (start/unpark heads)
A1A9 EQUB &00 ; Control: &00

Calculate total free space on disc

Sum all free space entries in the FSM to get the total free space. Prepares workspace for display by *FREE.

On exit: 3-byte sum in wksp_disc_op_result (little-endian)

A1AA .calc_total_free_space←3← 9D98 JSR← A01B JSR← A029 JSR
LDA #0 ; A=0: clear accumulators
A1AC LDX #3 ; X=3: clear 4 bytes
A1AE .clear_accumulators_loop←1← A1B5 BPL
STA wksp_disc_op_result,x ; Clear disc op result bytes
A1B1 STA wksp_tube_transfer_addr_1,x ; Clear Tube transfer bytes
A1B4 DEX ; Next byte
A1B5 BPL clear_accumulators_loop ; Loop for 4 bytes
A1B7 JSR sum_free_space ; Sum the free space entries
A1BA LDX #2 ; X=2: copy 3 bytes of result
A1BC .copy_result_loop←1← A1C3 BPL
LDA wksp_access_accum,x ; Get result byte
A1BF STA wksp_disc_op_mem_addr,x ; Store in disc op workspace
A1C2 DEX ; Next result byte
A1C3 BPL copy_result_loop ; Loop for 3 bytes
A1C5 RTS ; Return

Print space value in hex and decimal

Print a 3-byte sector count from the disc op workspace as hex bytes, then convert to decimal bytes and print as ' Sectors = NNN,NNN,NNN Bytes'. Used by *FREE to display free and used space.

The hex part prints the 3-byte value at &1016-&1018. The decimal part uses the double-dabble algorithm (also called shift-and-add-3) to convert the 4-byte binary value at &1015-&1018 into 10 BCD digits stored at &1040-&1049. Each iteration shifts the binary value left one bit and rotates the carry into the BCD digits, subtracting 10 from any digit that reaches 10 or above (carrying into the next digit). After 31 iterations (32 bits minus the sign bit), the BCD digits are printed with leading-zero suppression and comma separators at positions 3 and 6 (thousands and millions).

A1C6 .print_space_value←2← A01E JSR← A03E JSR
LDA wksp_disc_op_mem_addr_2 ; Print high byte as hex
A1C9 JSR print_hex_byte ; Print mid byte as hex
A1CC LDA wksp_disc_op_mem_addr_1 ; Print mid byte as hex
A1CF JSR print_hex_byte ; Print low byte as hex
A1D2 LDA wksp_disc_op_mem_addr ; Print low byte as hex
A1D5 JSR print_hex_byte ; Print result byte as hex
A1D8 JSR print_inline_string ; Print " Sectors ="
A1DB EQUS " Sectors ="
A1E5 EQUB &A0 ; ' ' + bit 7: end of inline string
A1E6 LDX #&1f ; X=&1F: 31 bit shifts (32-bit value)
A1E8 STX wksp_last_access_drive ; Store bit counter in workspace
A1EB LDA #0 ; A=0: clear all BCD digit accumulators
A1ED LDX #9 ; X=9: clear 10 BCD digits (0-9)
A1EF .clear_bcd_digits_loop←1← A1F3 BPL
STA wksp_osfile_block,x ; Clear BCD digit at &1040+X
A1F2 DEX ; Next digit
A1F3 BPL clear_bcd_digits_loop ; Loop for all 10 digits
A1F5 .shift_binary_bit←1← A219 BPL
ASL wksp_disc_op_result ; Shift binary value left: byte 0
A1F8 ROL wksp_disc_op_mem_addr ; Rotate carry into byte 1
A1FB ROL wksp_disc_op_mem_addr_1 ; Rotate carry into byte 2
A1FE ROL wksp_disc_op_mem_addr_2 ; Rotate carry into byte 3
A201 LDX #0 ; X=0: start from least significant digit
A203 LDY #9 ; Y=9: process 10 BCD digits
A205 .dabble_digit_loop←1← A214 BPL
LDA wksp_osfile_block,x ; Get BCD digit
A208 ROL ; Rotate shifted bit into digit
A209 CMP #&0a ; Digit >= 10?
A20B BCC store_bcd_digit ; No: digit is valid (0-9)
A20D SBC #&0a ; Yes: subtract 10 (carry propagates)
A20F .store_bcd_digit←1← A20B BCC
STA wksp_osfile_block,x ; Store corrected BCD digit
A212 INX ; Next digit (toward most significant)
A213 DEY ; Decrement digit counter
A214 BPL dabble_digit_loop ; Loop for all 10 digits
A216 DEC wksp_last_access_drive ; Decrement bit counter
A219 BPL shift_binary_bit ; Loop for all 31 bits
A21B LDY #&20 ; Y=' ': separator starts as space
A21D LDX #8 ; X=8: start from most significant digit
A21F .print_digit_loop←1← A245 BPL
BNE check_leading_zero ; X!=0: not at units position yet
A221 LDY #&2c ; X=0: switch separator to comma
A223 .check_leading_zero←1← A21F BNE
LDA wksp_osfile_block,x ; Get BCD digit value
A226 BNE print_nonzero_digit ; Non-zero: print this digit
A228 CPY #&2c ; Zero: has a non-zero digit been seen?
A22A BEQ print_nonzero_digit ; Yes (separator=comma): print zero
A22C LDA #&20 ; No: suppress leading zero with space
A22E BNE output_digit_char ; Skip to output
A230 .print_nonzero_digit←2← A226 BNE← A22A BEQ
LDY #&2c ; Mark that we've seen a non-zero digit
A232 CLC ; Clear carry for addition
A233 ADC #&30 ; Convert BCD digit to ASCII ('0'-'9')
A235 .output_digit_char←1← A22E BNE
JSR oswrch ; Print digit or space
A238 CPX #6 ; At position 6 (millions boundary)?
A23A BEQ print_comma_separator ; Yes: print comma separator
A23C CPX #3 ; At position 3 (thousands boundary)?
A23E BNE next_digit ; No: skip separator
A240 .print_comma_separator←1← A23A BEQ
TYA ; Print separator (space or comma)
A241 JSR oswrch ; Output separator character
A244 .next_digit←1← A23E BNE
DEX ; Next digit (toward least significant)
A245 BPL print_digit_loop ; Loop for 9 digits (8 down to 0)
A247 JSR print_inline_string ; Print bit-7-terminated inline string
A24A EQUS " Bytes"
A250 EQUB &A0 ; ' ' + bit 7: end of inline string
A251 RTS ; Return to caller

*TITLE command handler

Change the title of the currently selected directory. The title may be up to 19 characters long.

A252 .star_title
JSR check_drive_and_reload_fsm ; Ensure dir is loaded and writable
A255 JSR validate_fsm_and_mark_dirty ; Mark directory as modified
A258 JSR skip_spaces ; Skip leading spaces in argument
A25B LDY #0 ; Y=0: index into title string
A25D .copy_title_loop←1← A271 BNE
LDA (zp_text_ptr_lo),y ; Get next character
A25F AND #&7f ; Strip bit 7
A261 CMP #&22 ; Double-quote terminates title
A263 BEQ pad_title_with_cr ; Yes, pad with CR
A265 CMP #&20 ; Control char terminates title
A267 BCS store_title_char ; Printable, store it
A269 .pad_title_with_cr←1← A263 BEQ
LDA #&0d ; Use CR as padding character
A26B .store_title_char←1← A267 BCS
STA dir_title,y ; Store in directory title field
A26E INY ; Next character
A26F CPY #&13 ; Title is 19 characters max
A271 BNE copy_title_loop ; Loop for all 19 characters
A273 JMP write_dir_and_validate ; Write directory back to disc

*COMPACT command handler

Compact the free space on a drive by moving files to consolidate fragmented free space into a single contiguous region.

A276 .star_compact
JSR skip_spaces ; Skip leading spaces
A279 LDY #0 ; Y=0: check for argument
A27B LDA (zp_text_ptr_lo),y ; Get first char
A27D CMP #&21 ; Printable char? Parse SP and LP
A27F BCS parse_compact_start_page ; Yes, parse hex SP LP arguments
A281 LDA #osbyte_read_himem ; OSBYTE &84: read top of user memory
A283 JSR osbyte ; Read top of user memory (HIMEM)
A286 TXA ; X = HIMEM low byte
A287 BNE bad_compact_error ; Non-zero low byte: bad compact
A289 TYA ; Y = HIMEM high byte
A28A BMI bad_compact_error ; Bit 7 set (>= &80): bad compact
A28C STA wksp_compact_start_page ; Store HIMEM page as start page
A28F LDA #&80 ; Calculate length: &80 - start
A291 SEC ; Set carry for subtraction
A292 SBC wksp_compact_start_page ; Subtract start page from HIMEM
A295 STA wksp_compact_length ; Store buffer length in pages
A298 JMP begin_compaction ; Jump to compaction main loop

Raise Bad compact error

Reload FSM and directory then raise error &94: Bad compact.

A29B .bad_compact_error←11← A287 BNE← A28A BMI← A2BD BNE← A2E6 BCS← A2EF BCC← A301 BCC← A305 BCS← A313 BMI← A31F JMP← A331 JMP← A341 JMP
JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
A29E EQUB &94 ; Error &94: Bad compact
A29F EQUS "Bad compact."
A2AB .parse_compact_start_page←1← A27F BCS
STA wksp_disc_op_result ; Store first hex digit
A2AE INY ; Next argument character
A2AF LDA (zp_text_ptr_lo),y ; Get second hex digit
A2B1 STA wksp_disc_op_mem_addr ; Store as second digit
A2B4 INY ; Next character
A2B5 LDA (zp_text_ptr_lo),y ; Get separator/terminator
A2B7 CMP #&20 ; Is it a space?
A2B9 BEQ skip_separator_spaces ; Yes, skip to length parameter
A2BB CMP #&2c ; Is it a comma?
A2BD BNE bad_compact_error ; No separator: bad compact error
A2BF .skip_separator_spaces←2← A2B9 BEQ← A2C4 BEQ
INY ; Skip spaces/commas
A2C0 LDA (zp_text_ptr_lo),y ; Get length first digit
A2C2 CMP #&20 ; Is it a space?
A2C4 BEQ skip_separator_spaces ; Yes, skip more spaces
A2C6 STA wksp_disc_op_mem_addr_1 ; Store length first digit
A2C9 INY ; Next character
A2CA LDA (zp_text_ptr_lo),y ; Get length second digit
A2CC STA wksp_disc_op_mem_addr_2 ; Store length second digit
A2CF CMP #&21 ; Is second digit printable?
A2D1 BCS skip_trailing_spaces ; Yes, both digits present
A2D3 LDA wksp_disc_op_mem_addr_1 ; Only one digit: treat as low nibble
A2D6 STA wksp_disc_op_mem_addr_2 ; Move to high position
A2D9 LDA #&30 ; Set low nibble to '0'
A2DB STA wksp_disc_op_mem_addr_1 ; Store '0' as low nibble
A2DE DEY ; Back up one char position
A2DF .skip_trailing_spaces←2← A2D1 BCS← A2E4 BEQ
INY ; Skip past length argument
A2E0 LDA (zp_text_ptr_lo),y ; Get next character
A2E2 CMP #&20 ; Is it a space?
A2E4 BEQ skip_trailing_spaces ; Yes, skip spaces
A2E6 BCS bad_compact_error ; Printable after length: bad compact
A2E8 LDX #3 ; X=3: convert 4 hex digits to 2 bytes
A2EA .convert_hex_digits_loop←1← A30D BPL
LDA wksp_disc_op_result,x ; Get hex digit
A2ED CMP #&30 ; Is it '0'-'9'?
A2EF BCC bad_compact_error ; Below '0': bad compact
A2F1 CMP #&3a ; Above '9'?
A2F3 BCS check_hex_af ; No, it's '0'-'9': convert
A2F5 SEC ; Set carry for subtraction
A2F6 SBC #&30 ; Convert ASCII digit to value
A2F8 STA wksp_disc_op_result,x ; Store value
A2FB BPL store_converted_byte ; Always branch (non-negative)
A2FD .check_hex_af←1← A2F3 BCS
AND #&5f ; Convert to uppercase
A2FF CMP #&41 ; Below 'A'?
A301 BCC bad_compact_error ; Yes: bad compact
A303 CMP #&47 ; Above 'F'?
A305 BCS bad_compact_error ; Yes: bad compact
A307 SBC #&36 ; Convert 'A'-'F' to 10-15
A309 STA wksp_disc_op_result,x ; Store value
A30C .store_converted_byte←1← A2FB BPL
DEX ; Next digit
A30D BPL convert_hex_digits_loop ; Loop for 4 digits
A30F INX ; X=0: combine first pair
A310 JSR combine_hex_digit_pair ; Combine two hex digits into byte
A313 BMI bad_compact_error ; Negative result: bad compact
A315 STA wksp_compact_start_page ; Store as start page
A318 LDX #2 ; X=2: combine second pair
A31A JSR combine_hex_digit_pair ; Combine two hex digits into byte
A31D BPL convert_two_digits ; Positive result: valid
A31F .check_hex_digit_valid←1← A322 BEQ
JMP bad_compact_error ; Zero length: bad compact
A322 .convert_two_digits←1← A31D BPL
BEQ check_hex_digit_valid ; Also zero: bad compact
A324 STA wksp_compact_length ; Store as buffer length in pages
A327 LDX romsel_copy ; Get our ROM number
A329 LDA rom_wksp_table,x ; Get workspace page from ROM table
A32C CMP wksp_compact_start_page ; Start page below workspace?
A32F BCC combine_hex_nibbles ; Yes: buffer doesn't overlap
A331 JMP bad_compact_error ; No: bad compact (overlaps workspace)
A334 .combine_hex_nibbles←1← A32F BCC
CLC ; Clear carry for addition
A335 LDA wksp_compact_start_page ; Start page + length
A338 ADC wksp_compact_length ; Add buffer length
A33B BPL begin_compaction ; Result > &7F: check for exactly &80
A33D CMP #&80 ; Is it exactly &80?
A33F BEQ begin_compaction ; Yes: OK (up to screen memory)
A341 JMP bad_compact_error ; Above &80: bad compact
A344 .begin_compaction←3← A298 JMP← A33B BPL← A33F BEQ
JSR star_close ; *CLOSE command handler
A347 JSR wait_ensuring ; Wait while files are being ensured
A34A LDA zp_adfs_flags ; Set bit 3 of ADFS flags
A34C ORA #8 ; Indicate compaction in progress
A34E STA zp_adfs_flags ; Store updated flags
A350 JSR calculate_total_sectors ; Execute compaction algorithm
A353 LDA zp_adfs_flags ; Clear bit 3 when done
A355 AND #&f7 ; Mask off bit 3
A357 STA zp_adfs_flags ; Store cleared flags
A359 RTS ; Return

Combine two hex nibbles into a byte

Take high nibble from workspace, shift left 4, and OR with low nibble to produce a combined byte.

On EntryXoffset into wksp_disc_op_result (0 or 2)
On ExitAcombined byte value
Xpreserved
Ypreserved
A35A .combine_hex_digit_pair←2← A310 JSR← A31A JSR
LDA wksp_disc_op_result,x ; Get hex digit pair high nibble
A35D ASL ; Shift left 4 positions
A35E ASL ; Second shift
A35F ASL ; Third shift
A360 ASL ; Fourth shift
A361 ORA wksp_disc_op_mem_addr,x ; OR in low nibble
A364 RTS ; Return combined byte

Parse second filename from command line

Skip past the first filename, save the text pointer, then parse the second filename for commands like *RENAME and *COPY. Raises Bad command if extra arguments follow.

A365 .parse_second_filename←4← A544 JSR← A5A1 JSR← A62E JSR← A864 JSR
JSR skip_filename ; Skip past filename in command string
A368 LDA zp_text_ptr_hi ; Save text pointer high
A36A PHA ; Push on stack
A36B LDA zp_text_ptr_lo ; Save text pointer low
A36D PHA ; Push on stack
A36E JSR skip_filename ; Skip past filename in command string
A371 LDY #0 ; Y=0: check for argument
A373 LDA (zp_text_ptr_lo),y ; Get first char
A375 CMP #&20 ; Is it printable?
A377 BCS bad_command_error ; No: end of command
A379 PLA ; Restore text pointer low
A37A STA zp_text_ptr_lo ; Store in (&B4)
A37C STA wksp_osfile_block ; Also in OSFILE block
A37F PLA ; Restore text pointer high
A380 STA zp_text_ptr_hi ; Store in (&B5)
A382 STA wksp_osfile_block_1 ; Also in OSFILE block+1
A385 RTS ; Return
A386 .restore_csd_and_error←1← A3B7 BNE
JSR restore_csd ; Restore CSD sector from saved copy
A389 .bad_command_error←1← A377 BCS
JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
A38C EQUB &FE ; Error &FE: Bad command
A38D EQUS "Bad command."
fall through ↓

*RUN command handler

Load and execute a file. Sets the execution address from the file's catalogue entry.

A399 .star_run
LDA zp_text_ptr_lo ; Save filename pointer for retry
A39B STA zp_name_ptr_lo ; Store in save area low
A39D LDA zp_text_ptr_hi ; Get filename high byte
A39F STA zp_name_ptr_hi ; Store in save area high
A3A1 JSR search_for_file ; Try to find file in CSD
A3A4 BEQ search_lib_for_command ; Found in CSD, proceed to load
A3A6 JSR save_wksp_and_return ; Not found: save workspace state
A3A9 LDA zp_name_ptr_lo ; Restore filename pointer
A3AB STA zp_text_ptr_lo ; Restore filename low
A3AD LDA zp_name_ptr_hi ; Get saved high byte
A3AF STA zp_text_ptr_hi ; Restore filename high
A3B1 JSR switch_to_library ; Switch CSD to library directory
A3B4 JSR search_for_file ; Try to find file in library
A3B7 BNE restore_csd_and_error ; Not in library either: Not found
A3B9 JSR restore_csd ; Restore CSD after library search
A3BC .search_lib_for_command←1← A3A4 BEQ
LDA zp_text_ptr_lo ; Save filename address for OSFILE
A3BE STA wksp_copy_read_sector ; Store filename addr for OSFILE
A3C1 LDA zp_text_ptr_hi ; Get filename pointer high
A3C3 STA wksp_copy_read_sector_1 ; Store in OSFILE block
A3C6 LDY #&0e ; Y=&0E: check exec address bytes
A3C8 LDA (zp_entry_ptr_lo),y ; Get exec addr byte 0
A3CA LDX #2 ; X=2: AND with bytes 1 and 2
A3CC .copy_run_params_loop←1← A3D0 BPL
INY ; AND exec addr bytes together
A3CD AND (zp_entry_ptr_lo),y ; AND exec addr bytes together
A3CF DEX ; Next byte
A3D0 BPL copy_run_params_loop ; Loop for 3 bytes
A3D2 CMP #&ff ; All &FF? Exec addr = &FFFFFFFF
A3D4 BNE execute_loaded_file ; No, check load address
A3D6 LDX zp_entry_ptr_lo ; Exec = &FFFFFFFF: open with OSFIND
A3D8 LDY zp_entry_ptr_hi ; Get directory entry pointer high
A3DA LDA #&40 ; A=&40: open for reading
A3DC JSR osfind_handler ; Open the file
A3DF STA wksp_exec_handle ; Save handle for *EXEC
A3E2 LDX #<(str_e_boot) ; Point to "E.$.!BOOT" string
A3E4 LDY #>(str_e_boot) ; Y: high byte of E.$.!BOOT string
A3E6 JMP oscli ; Execute via OSCLI
A3E9 .execute_loaded_file←1← A3D4 BNE
LDY #&0b ; Y=&0B: check load addr bytes
A3EB LDA (zp_entry_ptr_lo),y ; Get load addr byte 1
A3ED INY ; Y=&0c
A3EE AND (zp_entry_ptr_lo),y ; AND with byte 2
A3F0 INY ; Y=&0d
A3F1 AND (zp_entry_ptr_lo),y ; AND with byte 3
A3F3 CMP #&ff ; All &FF? Load addr = &FFFFFFFF
A3F5 BNE run_tube_transfer ; No, proceed with load and execute
A3F7 JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
A3FA EQUB &93 ; Error &93: Wont
A3FB EQUS "Won't."
A401 .run_tube_transfer←1← A3F5 BNE
LDA #&a5 ; Set up OSFILE block for load
A403 STA wksp_copy_src_sector ; Store exec addr for later JMP
A406 LDX #&a2 ; X=&A2: OSFILE block offset
A408 LDY #&10 ; Y=&10: OSFILE block page
A40A STX zp_osfile_ptr_lo ; Store block pointer low
A40C STY zp_osfile_ptr_hi ; Store block pointer high
A40E JSR search_for_file ; Load the file
A411 LDY #4 ; Y=4: check if Tube/IO address
A413 LDA (zp_entry_ptr_lo),y ; Get exec addr high byte
A415 LDY #0 ; Y=0: check low byte of exec addr
A417 ORA (zp_entry_ptr_lo),y ; OR with lowest byte
A419 BMI run_set_exec_addr ; Bit 7 set: I/O or Tube address
A41B JMP validate_found_entry ; Host address: jump directly
A41E .run_set_exec_addr←1← A419 BMI
JSR create_new_dir_entry ; Set up Tube transfer
A421 LDA wksp_copy_dest_sector ; Check exec addr for &FFxx (Tube)
A424 CMP #&ff ; High byte = &FF (Tube address)?
A426 BNE run_jump_to_file ; No, check further
A428 LDA wksp_copy_src_sector_2 ; Get next exec addr byte
A42B CMP #&fe ; Is it >= &FE (I/O space)?
A42D BCC run_jump_to_file ; No, normal Tube address
A42F .copy_exec_addr_loop←1← A436 BPL
LDA #1 ; A=1: language entry point
A431 JMP (wksp_copy_src_sector) ; Jump to execution address
A434 .run_jump_to_file←2← A426 BNE← A42D BCC
BIT zp_adfs_flags ; Check if Tube present
A436 BPL copy_exec_addr_loop ; No Tube: execute directly
A438 JSR claim_tube_retry ; Tube: set up Tube transfer
A43B LDX #&a8 ; Point to exec addr block
A43D LDY #&10 ; Y=&10: Tube workspace page
A43F LDA #4 ; A=4: Tube transfer type
A441 JMP tube_entry ; Start Tube execution

*LIB command handler

Change the current library directory. The library is searched for commands not found in the current directory.

A444 .star_lib
JSR parse_path_and_load ; Parse path and load target dir
A447 LDY #9 ; Y=9: copy 10-byte directory name
A449 .copy_lib_name_loop←1← A450 BPL
LDA dir_name,y ; Get name byte from dir buffer
A44C STA wksp_lib_name,y ; Store as library name
A44F DEY ; Next byte in name copy
A450 BPL copy_lib_name_loop ; Loop for 10 bytes
A452 LDY #3 ; Y=3: copy 4-byte sector+drive
A454 .copy_lib_sector_loop←1← A45B BPL
LDA wksp_csd_sector_lo,y ; Get sector address byte
A457 STA wksp_lib_sector,y ; Store as library sector
A45A DEY ; Next byte in sector copy
A45B BPL copy_lib_sector_loop ; Loop for 4 bytes
A45D .save_workspace_and_return←1← A471 BMI
JMP save_wksp_and_return ; Save workspace and return

Switch CSD to library directory

Save the current CSD sector address, then replace it with the library directory sector address. Used before *LCAT and *LEX to temporarily operate on the library.

A460 .switch_to_library←3← A3B1 JSR← A47F JSR← A48B JSR
LDY #3 ; Y=3: copy 4 bytes
A462 .swap_csd_to_lib_loop←1← A46F BPL
LDA wksp_csd_sector_lo,y ; Save current CSD sector
A465 STA wksp_temp_sector,y ; To temporary workspace
A468 LDA wksp_lib_sector,y ; Get library sector
A46B STA wksp_csd_drive_sector,y ; Set as CSD sector
A46E DEY ; Next byte
A46F BPL swap_csd_to_lib_loop ; Always branch (loop back)
A471 BMI save_workspace_and_return ; Load the library directory
fall through ↓

Restore CSD sector from saved copy

Restore the CSD sector address from the temporary save in wksp_1030. Used after *LCAT/*LEX to switch back.

A473 .restore_csd←4← A386 JSR← A3B9 JSR← A482 JSR← A48E JSR
LDY #3 ; Y=3: copy 4 bytes
A475 .restore_csd_sector_loop←1← A47C BPL
LDA wksp_temp_sector,y ; Get saved CSD sector
A478 STA wksp_csd_drive_sector,y ; Restore to CSD workspace
A47B DEY ; Next byte
A47C BPL restore_csd_sector_loop ; Loop for 4 bytes
A47E RTS ; Return

*LCAT command handler

Display a catalogue of the current library directory.

A47F .star_lcat
JSR switch_to_library ; Switch CSD to library
A482 JSR restore_csd ; Restore CSD after catalogue
A485 JSR print_cat_header_and_entries ; Print catalogue (*CAT format)
A488 JMP save_wksp_and_return ; Save workspace and return

*LEX command handler

Display a full catalogue of the current library directory, with the same format as *EX.

A48B .star_lex
JSR switch_to_library ; Switch CSD to library
A48E JSR restore_csd ; Restore CSD after display
A491 JSR load_dir_and_list_entries ; Print full catalogue (*EX format)
A494 JMP save_wksp_and_return ; Save workspace and return

*BACK command handler

Switch the current directory to the previously selected directory and vice versa.

A497 .star_back
LDY #3 ; Y=3: swap 4 bytes of sector+drive
A499 .swap_dir_sectors_loop←1← A4A6 BPL
LDA wksp_prev_dir_sector,y ; Get previous dir sector byte
A49C STA wksp_csd_drive_sector,y ; Store as CSD sector
A49F LDA wksp_csd_sector_lo,y ; Get current CSD sector byte
A4A2 STA wksp_prev_dir_sector,y ; Store as previous dir sector
A4A5 DEY ; Next byte
A4A6 BPL swap_dir_sectors_loop ; Loop for 4 bytes (sector+drive)
A4A8 JSR save_wksp_and_return ; Reload directory from new sector
A4AB LDY #9 ; Y=9: copy 10-byte directory name
A4AD .copy_prev_dir_name_loop←1← A4B4 BPL
LDA dir_name,y ; Get dir name from buffer
A4B0 STA wksp_csd_name,y ; Store as CSD name
A4B3 DEY ; Next byte
A4B4 BPL copy_prev_dir_name_loop ; Loop for 10 bytes
A4B6 RTS ; Return

Skip past filename in command string

Advance (&B4) past the next filename component in the command string, handling dots as path separators.

A4B7 .skip_filename←2← A365 JSR← A36E JSR
LDY #0 ; Y=0: start scanning
A4B9 .scan_filename_loop←1← A4BF BNE
JSR check_char_is_terminator ; Check if char is a terminator
A4BC BEQ check_dot_separator ; Yes, check if it's a dot
A4BE .advance_past_char←1← A4C3 BEQ
INY ; Advance past non-terminator
A4BF BNE scan_filename_loop ; Loop scanning characters
A4C1 .check_dot_separator←1← A4BC BEQ
CMP #&2e ; Is terminator a dot?
A4C3 BEQ advance_past_char ; Yes, skip dot and continue
A4C5 TYA ; Y = number of chars scanned
A4C6 CLC ; Clear carry for addition
A4C7 ADC zp_text_ptr_lo ; Add to (&B4) to advance pointer
A4C9 STA zp_text_ptr_lo ; Store updated pointer low
A4CB BCC skip_spaces ; Skip leading spaces in command argument
A4CD INC zp_text_ptr_hi ; Increment pointer high on overflow
fall through ↓

Skip leading spaces in command argument

Advance (&B4) past leading spaces. Also handles double-quoted strings (skips to closing quote).

On exit: (&B4) points past the skipped characters

A4CF .skip_spaces←11← 870F JSR← 87E7 JSR← 8917 JSR← 9109 JSR← 93CE JSR← 9E8A JSR← 9ECD JSR← A0F5 JSR← A258 JSR← A276 JSR← A4CB BCC
LDY #0 ; Y=0: start scanning
A4D1 CLC ; C=0: not inside quotes
A4D2 PHP ; Save quote tracking flag
A4D3 .scan_spaces_loop←1← A4E8 BNE
LDA (zp_text_ptr_lo),y ; Get character from command line
A4D5 CMP #&20 ; Compare with space
A4D7 BCC end_of_spaces ; Control char: end of argument
A4D9 BEQ advance_and_continue ; Space: skip it
A4DB CMP #&22 ; Double-quote?
A4DD BNE end_of_spaces ; No, end of argument
A4DF PLP ; Restore C (quote tracking flag)
A4E0 BCC enter_quoted_string ; C=0 first quote: start quoted str
A4E2 JMP bad_name_error ; C=1 second quote: bad name error
A4E5 .enter_quoted_string←1← A4E0 BCC
SEC ; C=1: inside quoted string now
A4E6 PHP ; Save updated quote flag
A4E7 .advance_and_continue←1← A4D9 BEQ
INY ; Next character
A4E8 BNE scan_spaces_loop ; Continue scanning
A4EA .end_of_spaces←2← A4D7 BCC← A4DD BNE
TYA ; Y = number of chars to skip
A4EB PLP ; Restore quote flag
A4EC CLC ; Clear carry for addition
A4ED ADC zp_text_ptr_lo ; Add to (&B4) to advance pointer
A4EF STA zp_text_ptr_lo ; Store updated pointer low
A4F1 BCC return_30 ; No overflow, return
A4F3 INC zp_text_ptr_hi ; Increment pointer high on overflow
A4F5 .return_30←2← A4F1 BCC← A4FE BNE
RTS ; Return

Check for drive specifier colon

Check if the next character at (&B4) is a colon, indicating a drive number follows.

On exit: Z set if no colon found If colon found, jumps to parse drive number

A4F6 .check_drive_colon←2← A509 JSR← A547 JSR
LDY #0 ; Y=0
A4F8 LDA (zp_text_ptr_lo),y ; Get next character
A4FA AND #&7f ; Strip bit 7
A4FC CMP #&3a ; Is it ':'?
A4FE BNE return_30 ; No, return
A500 .parse_drive_specifier←2← A532 BEQ← A53B BEQ
JMP clean_dir_rename_bit ; Yes, parse drive number

*RENAME command handler

Rename a file or move it between directories on the same drive. The source and destination must be on the same drive.

A503 .star_rename
LDA zp_text_ptr_lo ; Save first argument pointer
A505 PHA ; Save first arg pointer low
A506 LDA zp_text_ptr_hi ; Get first arg pointer high
A508 PHA ; Save on stack
A509 JSR check_drive_colon ; Check for drive specifier
A50C JSR set_up_gsinit_path ; Parse and validate destination path
A50F JSR find_file_and_validate ; Search for source file
A512 BEQ source_is_found ; Found?
A514 JMP not_found_error ; Not found: report error
A517 .source_is_found←1← A512 BEQ
LDY #3 ; Y=3: check if source is directory
A519 LDA (zp_entry_ptr_lo),y ; Get source entry access byte
A51B JSR save_wksp_and_return ; Save workspace state
A51E BPL parse_destination_name ; Not a directory: skip self-ref check
A520 PLA ; Restore first argument pointer
A521 TAX ; Transfer to X for save
A522 PLA ; Restore first arg low from stack
A523 STA zp_text_ptr_lo ; Store in (&B4)
A525 PHA ; Re-save on stack
A526 TXA ; Get saved high byte from X
A527 STA zp_text_ptr_hi ; Store in (&B5)
A529 PHA ; Save on stack
A52A LDY #0 ; Y=0: check path for $ root ref
A52C LDA (zp_text_ptr_lo),y ; Check for '$' (root specifier)
A52E AND #&7d ; Mask to ignore L and D bits
A530 CMP #&24 ; Is it '$'?
A532 BEQ parse_drive_specifier ; Root: Bad rename error
A534 .scan_dest_for_parent_ref←1← A53E BNE
JSR check_char_is_terminator ; Scan for '^' in destination path
A537 BEQ check_dot_in_dest ; Terminator found, check type
A539 CMP #&5e ; Is it '^' (parent)?
A53B BEQ parse_drive_specifier ; Parent ref in dest: Bad rename error
A53D .advance_dest_scan←1← A542 BEQ
INY ; Next character in scan
A53E BNE scan_dest_for_parent_ref ; Loop scanning destination path
A540 .check_dot_in_dest←1← A537 BEQ
CMP #&2e ; Check for '.' separator
A542 BEQ advance_dest_scan ; Dot separator: continue past it
A544 .parse_destination_name←1← A51E BPL
JSR parse_second_filename ; Parse second arg (destination)
A547 JSR check_drive_colon ; Check for drive specifier colon
A54A LDA #&40 ; Set up OSFILE block pointer
A54C STA zp_osfile_ptr_lo ; Store low byte
A54E LDA #&10 ; Block page = &10
A550 STA zp_osfile_ptr_hi ; Store high byte
A552 JSR build_osfile_control_block ; Search for dest filename
A555 PHP ; Save search result flags
A556 JSR search_dir_for_new_entry ; Check directory state
A559 PLP ; Restore flags
A55A BNE check_alt_workspace ; Dest not found: good for rename
A55C LDA zp_entry_ptr_lo ; Dest exists: save entry pointer
A55E LDY #3 ; Y=3: copy sector+entry info
A560 .save_dest_dir_info_loop←1← A567 BPL
STA wksp_object_sector,y ; Store in object sector workspace
A563 LDA wksp_csd_sector,y ; Get CSD sector byte
A566 DEY ; Next byte
A567 BPL save_dest_dir_info_loop ; Loop for 4 bytes
A569 .check_alt_workspace←1← A55A BNE
LDA wksp_alt_sector_hi ; Check if alt workspace is set
A56C BPL reload_and_parse_source ; Set: skip CSD restore
A56E LDY #2 ; Y=2: copy CSD sector from backup
A570 .restore_csd_sector_loop2←1← A577 BPL
LDA wksp_csd_sector_lo,y ; Get saved CSD sector byte
A573 STA wksp_csd_drive_sector,y ; Restore to CSD workspace
A576 DEY ; Next byte
A577 BPL restore_csd_sector_loop2 ; Loop for 3 bytes
A579 .reload_and_parse_source←1← A56C BPL
JSR save_wksp_and_return ; Save workspace and reload dir
A57C PLA ; Restore second arg pointer
A57D STA zp_text_ptr_hi ; Store in (&B5)
A57F TAX ; Save in X
A580 PLA ; Restore first arg pointer
A581 STA zp_text_ptr_lo ; Store in (&B4)
A583 PHA ; Re-save for later
A584 TXA ; Get high byte from X
A585 PHA ; Re-save
A586 JSR find_first_matching_entry ; Search source in original dir
A589 JSR check_file_not_open ; Check if file is open
A58C LDY #3 ; Y=3: compare directories
A58E LDA zp_entry_ptr_lo ; Get source entry pointer
A590 .compare_src_dest_dir_loop←1← A599 BPL
CMP wksp_object_sector,y ; Compare with dest dir sector
A593 BNE cross_dir_rename ; Different: cross-dir rename
A595 LDA wksp_csd_sector,y ; Get CSD sector byte
A598 DEY ; Next byte
A599 BPL compare_src_dest_dir_loop ; Loop for 4 bytes
A59B PLA ; Same dir: restore dest name ptr
A59C STA zp_text_ptr_hi ; Store in (&B5)
A59E PLA ; Restore first arg low
A59F STA zp_text_ptr_lo ; Store in (&B4)
A5A1 JSR parse_second_filename ; Parse last component of dest path
A5A4 .find_last_path_component←2← A5BA BCC← A5BE BNE
LDY #0 ; Y=0: scan for end of path component
A5A6 .scan_component_chars←1← A5B3 BNE
LDA (zp_text_ptr_lo),y ; Get next character
A5A8 CMP #&2e ; Is it '.' separator?
A5AA BEQ advance_past_component ; Yes: advance past component
A5AC AND #&7d ; Strip to printable range
A5AE CMP #&21 ; Control char: end of name
A5B0 BCC copy_new_name_to_entry ; End of destination name found
A5B2 INY ; Next character
A5B3 BNE scan_component_chars ; Loop scanning
A5B5 .advance_past_component←1← A5AA BEQ
TYA ; Advance pointer past component
A5B6 ADC zp_text_ptr_lo ; Add Y to pointer
A5B8 STA zp_text_ptr_lo ; Store updated pointer
A5BA BCC find_last_path_component ; No carry: scan next component
A5BC INC zp_text_ptr_hi ; Increment high byte on overflow
A5BE BNE find_last_path_component ; Always branch back to scan
A5C0 .copy_new_name_to_entry←1← A5B0 BCC
LDY #9 ; Y=9: copy 10-byte new name
A5C2 .merge_name_attributes_loop←1← A5DD BPL
LDA (zp_entry_ptr_lo),y ; Get old name byte (with attributes)
A5C4 AND #&80 ; Keep only bit 7 (attribute flag)
A5C6 STA wksp_csd_sector_temp ; Save attribute bit
A5C9 LDA (zp_text_ptr_lo),y ; Get new name character
A5CB AND #&7f ; Strip bit 7
A5CD CMP #&22 ; Is it '"'?
A5CF BEQ pad_with_cr ; Yes: pad with CR
A5D1 CMP #&21 ; Is it printable?
A5D3 BCS store_merged_name_byte ; Yes: use as-is
A5D5 .pad_with_cr←1← A5CF BEQ
LDA #&0d ; Non-printable: use CR padding
A5D7 .store_merged_name_byte←1← A5D3 BCS
ORA wksp_csd_sector_temp ; Merge attribute bit with new char
A5DA STA (zp_entry_ptr_lo),y ; Store renamed byte in entry
A5DC DEY ; Next byte
A5DD BPL merge_name_attributes_loop ; Loop for 10 bytes
A5DF JSR write_dir_and_validate ; Write directory back to disc
A5E2 JSR update_moved_dir_parent ; Update directory checksums
A5E5 JMP save_wksp_and_return ; Save workspace and return
A5E8 .already_exists_error←1← A5EE BNE
JMP already_exists_error2 ; Already exists: error
A5EB .cross_dir_rename←1← A593 BNE
LDA wksp_object_size ; Check if dest has zero size
A5EE BNE already_exists_error ; Non-zero: Already exists error
A5F0 LDY #9 ; Y=9: mark old entry as deleted
A5F2 LDA (zp_entry_ptr_lo),y ; Get last name byte
A5F4 ORA #&80 ; Set bit 7 (mark as directory?)
A5F6 STA (zp_entry_ptr_lo),y ; Store back
A5F8 JSR write_dir_and_validate ; Write source directory
A5FB LDY #&0a ; Y=&0A: copy entry data to workspace
A5FD LDX #7 ; X=7: 8 bytes of entry metadata
A5FF .copy_entry_metadata_loop←1← A606 BPL
LDA (zp_entry_ptr_lo),y ; Get entry data byte
A601 STA wksp_object_size_mid,y ; Store in workspace for dest entry
A604 INY ; Next byte
A605 DEX ; Decrement counter
A606 BPL copy_entry_metadata_loop ; Loop for 8 bytes
A608 LDA #0 ; Clear OSFILE block fields
A60A STA wksp_osfile_start_addr ; Clear load address
A60D STA wksp_osfile_start_addr_1 ; Clear exec address
A610 STA wksp_osfile_start_addr_2 ; Clear length
A613 STA wksp_osfile_start_addr_3 ; Clear attributes
A616 LDX #3 ; X=3: copy 3+1 start sector bytes
A618 .copy_entry_sector_loop2←1← A61F BPL
LDA (zp_entry_ptr_lo),y ; Get sector byte from entry
A61A STA wksp_saved_count_1,y ; Store in workspace
A61D INY ; Next byte
A61E DEX ; Decrement counter
A61F BPL copy_entry_sector_loop2 ; Loop for 4 bytes
A621 LDY #0 ; Y=0: build access byte from entry
A623 .build_access_byte_loop←1← A62C BNE
LDA (zp_entry_ptr_lo),y ; Get name byte
A625 ROL ; Shift bit 7 into carry
A626 ROL wksp_access_accum ; Rotate into access accumulator
A629 INY ; Next name byte
A62A CPY #4 ; Done 4 bytes?
A62C BNE build_access_byte_loop ; No, continue building access
A62E JSR parse_second_filename ; Parse dest path and switch dir
A631 LDY #&18 ; Y=&18: start sector in entry
A633 LDX #2 ; X=2: copy 3 sector bytes
A635 .copy_start_sector_loop←1← A63C BPL
LDA (zp_entry_ptr_lo),y ; Get start sector byte
A637 STA wksp_alloc_sector,x ; Store in workspace
A63A DEY ; Next byte (decreasing)
A63B DEX ; Next workspace byte
A63C BPL copy_start_sector_loop ; Loop for 3 bytes
A63E JSR save_wksp_and_return ; Save workspace state
A641 LDA #&40 ; Set up OSFILE block for create
A643 STA zp_osfile_ptr_lo ; Store block pointer low
A645 LDA #&10 ; Block page = &10
A647 STA zp_osfile_ptr_hi ; Store block pointer high
A649 JSR copy_addrs_and_find_empty_entry ; Create entry in dest directory
A64C JSR allocate_disc_space_for_file ; Allocate disc space
A64F LDY #3 ; Y=3: copy attributes back to entry
A651 .restore_attributes_loop←1← A65B BPL
LDA (zp_entry_ptr_lo),y ; Get new entry access byte
A653 ASL ; Shift attribute bit to position
A654 ROR wksp_access_accum ; Rotate into access accumulator
A657 ROR ; Shift back
A658 STA (zp_entry_ptr_lo),y ; Store in entry name byte
A65A DEY ; Next byte
A65B BPL restore_attributes_loop ; Loop for 4 bytes
A65D JSR copy_entry_from_template ; Write entry metadata
A660 JSR copy_length_to_entry ; Update entry size
A663 JSR write_dir_and_validate ; Write dest directory to disc
A666 JSR update_moved_dir_parent ; Update moved dir's parent pointer
A669 JSR save_wksp_and_return ; Save workspace state
A66C PLA ; Restore source name pointer
A66D STA zp_text_ptr_hi ; Store high byte
A66F PLA ; Restore low byte
A670 STA zp_text_ptr_lo ; Store low byte
A672 JSR find_first_matching_entry ; Find source entry again
A675 LDX #5 ; X=5: clear 6 bytes of sector info
A677 LDA #0 ; A=0: zero fill
A679 .clear_sector_workspace_loop←1← A67D BPL
STA wksp_object_sector,x ; Clear sector/size workspace
A67C DEX ; Next byte
A67D BPL clear_sector_workspace_loop ; Loop for 6 bytes
A67F JSR write_dir_and_release ; Remove entry from source directory
A682 JMP save_wksp_and_return ; Save workspace and return
A685 .update_moved_dir_parent←2← A5E2 JSR← A666 JSR
LDY #3 ; Y=3: check if entry is directory
A687 LDA (zp_entry_ptr_lo),y ; Get access byte
A689 BMI update_parent_sector ; Bit 7: is a directory
A68B RTS ; Not a dir: nothing to update
A68C .update_parent_sector←1← A689 BMI
LDY #2 ; Y=2: copy 3 dir sector bytes
A68E .copy_parent_sector_loop←1← A695 BPL
LDA wksp_csd_sector_lo,y ; Get CSD sector byte
A691 STA wksp_new_parent_sector,y ; Store as new parent sector
A694 DEY ; Next byte
A695 BPL copy_parent_sector_loop ; Loop for 3 bytes
A697 LDY #9 ; Y=9: copy 10-byte directory name
A699 .copy_dir_name_from_entry←1← A6A1 BPL
LDA (zp_entry_ptr_lo),y ; Get name byte from entry
A69B AND #&7f ; Strip bit 7 (attribute)
A69D STA wksp_dest_name,y ; Store as directory name
A6A0 DEY ; Next byte
A6A1 BPL copy_dir_name_from_entry ; Loop for 10 bytes
A6A3 LDA #&74 ; Point to workspace name buffer
A6A5 STA zp_text_ptr_lo ; Low byte = &74
A6A7 LDA #&10 ; Page = &10
A6A9 STA zp_text_ptr_hi ; High byte
A6AB JSR parse_path_and_load ; Load the moved directory
A6AE LDY #9 ; Y=9: copy name to dir header
A6B0 .write_dir_name_loop←1← A6B7 BPL
LDA wksp_dest_name,y ; Get name from workspace
A6B3 STA dir_name,y ; Store in directory name field
A6B6 DEY ; Next byte
A6B7 BPL write_dir_name_loop ; Loop for 10 bytes
A6B9 LDY #2 ; Y=2: copy parent sector pointer
A6BB .write_parent_sector_loop←1← A6C2 BPL
LDA wksp_new_parent_sector,y ; Get new parent sector byte
A6BE STA dir_parent_sector,y ; Store in directory parent field
A6C1 DEY ; Next byte
A6C2 BPL write_parent_sector_loop ; Loop for 3 bytes
A6C4 JMP write_dir_and_validate ; Write updated directory to disc

Ensure current directory is loaded

Check that the current directory buffer contains valid data. If not, reload it from disc.

A6C7 .check_dir_loaded←2← 8090 JSR← A6DE JSR
LDX wksp_current_drive ; Get current drive number
A6CA INX ; Increment: &FF becomes 0
A6CB BNE return_31 ; Non-zero = drive is set, OK
A6CD JSR generate_error_no_suffix ; Drive is &FF: no directory loaded
A6D0 EQUB &A9 ; Error &A9: Bad FS map
A6D1 EQUS "No directory."
fall through ↓

Verify directory buffer integrity

Check that the directory buffer contains a valid directory by verifying the Hugo identity string and master sequence number are consistent at both ends of the directory. Raises Broken directory error if verification fails.

A6DE .verify_dir_integrity←3← 87ED JSR← 8F86 JSR← 932A JSR
JSR check_dir_loaded ; Check drive is loaded
A6E1 LDX #0 ; X=0: compare index
A6E3 LDA dir_master_sequence ; Get master sequence from footer
A6E6 .compare_hugo_loop←1← A6F6 BNE
CMP dir_buffer,x ; Compare with header sequence+ID
A6E9 BNE broken_directory_error ; Mismatch: broken directory
A6EB CMP dir_master_sequence,x ; Compare footer sequence+ID
A6EE BNE broken_directory_error ; Mismatch: broken directory
A6F0 INX ; Next byte
A6F1 LDA str_hugo,x ; Check against "Hugo" string
A6F4 CPX #5 ; Checked all 5 bytes (seq+Hugo)?
A6F6 BNE compare_hugo_loop ; No, continue checking
A6F8 .return_31←1← A6CB BNE
RTS ; Return

Raise Broken directory error

Generate disc error with state recovery, then raise error &A8: Broken directory.

A6F9 .broken_directory_error←2← A6E9 BNE← A6EE BNE
JSR generate_disc_error ; Dir broken: save drive and error
A6FC EQUB &A8 ; Error &A8: Broken directory
A6FD EQUS "Broken directory."
fall through ↓

Get workspace address into &BA

Load a workspace address into zero page locations &BA-&BB.

A70E .get_wksp_addr_ba←4← 8A22 JSR← 9AFB JSR← A71A JSR← A93C JSR
LDX romsel_copy ; Get our ROM number
A710 LDA rom_wksp_table,x ; Read workspace page from ROM table
A713 STA zp_wksp_ptr_hi ; Store as high byte of (&BA)
A715 LDA #0 ; Low byte = 0 (page-aligned)
A717 STA zp_wksp_ptr_lo ; Store low byte
A719 RTS ; Return

Calculate workspace checksum

Calculate a checksum over the workspace area for integrity checking.

A71A .calc_wksp_checksum←2← A72B JSR← A731 JSR
JSR get_wksp_addr_ba ; Get workspace page address
A71D LDY #&fd ; Y=&FD: start from byte 253
A71F TYA ; A=&FD: initial accumulator
A720 CLC ; Clear carry for addition
A721 .sum_workspace_loop←1← A724 BNE
ADC (zp_wksp_ptr_lo),y ; Add workspace byte to checksum
A723 DEY ; Next byte down
A724 BNE sum_workspace_loop ; Loop until Y wraps to 0
A726 ADC (zp_wksp_ptr_lo),y ; Add byte 0
A728 LDY #&fe ; Y=&FE: index of checksum byte
A72A RTS ; Return

Calculate and store workspace checksum

Calculate workspace checksum and store at (zp_wksp_ptr)+&FE.

A72B .store_wksp_checksum_ba_y←2← 8A34 JSR← 9B0D JSR
JSR calc_wksp_checksum ; Calculate checksum
A72E STA (zp_wksp_ptr_lo),y ; Store at (&BA)+&FE
A730 .return_32←1← A736 BEQ
RTS ; Return

Verify workspace checksum

Check the workspace checksum matches the stored value. Raises a Bad checksum error if verification fails.

A731 .check_wksp_checksum←2← 9B10 JSR← 9BCD JSR
JSR calc_wksp_checksum ; Calculate actual checksum
A734 CMP (zp_wksp_ptr_lo),y ; Compare with stored checksum
A736 BEQ return_32 ; Match: workspace is valid
A738 .bad_checksum_error←7← A752 BNE← A761 BCS← A765 BNE← A76D BCC← A775 BNE← ACE6 JMP← AE32 JMP
LDA #&0f ; Checksum mismatch or corruption
A73A STA wksp_error_suppress ; Set error flag
A73D JSR generate_error_no_suffix ; Generate error without drive/sector suffix
A740 EQUB &AA ; Error &AA: Bad checksum
A741 EQUS "Bad sum."
fall through ↓

Save all registers and workspace

Save registers, validate workspace checksum, check FSM integrity, and store workspace with updated checksum.

A749 .save_workspace_state←8← A95F JSR← A995 JSR← AD42 JSR← AD76 JSR← B0E4 JSR← B12F JSR← B1B6 JSR← B57F JSR
PHP ; Save all registers
A74A PHA ; Save A
A74B TYA ; Transfer Y to A
A74C PHA ; Save Y
A74D TXA ; Transfer X to A
A74E PHA ; Save X
A74F LDA wksp_error_suppress ; Check error flag
A752 BNE bad_checksum_error ; Non-zero: workspace corrupt, error
A754 JSR validate_fsm_and_mark_dirty ; Mark directory as modified
A757 CLC ; Clear carry for scan
A758 LDX #&10 ; X=&10: scan open channel table
A75A .save_wksp_byte_loop←1← A76B BPL
LDA wksp_buf_flag,x ; Get channel state entry
A75D AND #&21 ; Check bits 0 and 5 (dirty flags)
A75F BEQ save_wksp_and_checksum ; Both clear: channel clean
A761 BCS bad_checksum_error ; Carry set + dirty: corrupt
A763 CMP #1 ; Only bit 0 set: check value
A765 BNE bad_checksum_error ; Not exactly 1: corrupt
A767 .save_wksp_and_checksum←1← A75F BEQ
DEX ; Step back 4 bytes
A768 DEX ; Continue stepping
A769 DEX ; Continue stepping
A76A DEX ; Continue stepping
A76B BPL save_wksp_byte_loop ; Loop for all entries
A76D BCC bad_checksum_error ; No dirty entries + C=0: corrupt
A76F JSR restore_wksp_from_save ; Calculate channel checksum
A772 CMP wksp_workspace_checksum ; Compare with stored checksum
A775 BNE bad_checksum_error ; Mismatch: corrupt
A777 PHA ; Push 2 dummy bytes for stack frame
A778 PHA ; Second push
A779 LDY #5 ; Y=5: shift 6 bytes on stack
A77B TSX ; Get current stack pointer
A77C .restore_workspace_state←1← A784 BPL
LDA brk_error_block_3,x ; Get byte from stack+3
A77F STA brk_error_block_1,x ; Move down to stack+1
A782 INX ; Next byte
A783 DEY ; Decrement counter
A784 BPL restore_workspace_state ; Loop for 6 bytes
A786 LDA #&a1 ; Insert return addr low = &A1
A788 STA brk_error_block_1,x ; Store at stack+1
A78B LDA #&a7 ; Insert return addr high = &A7
A78D STA brk_error_block_2,x ; Store at stack+2 (return to &A7A2)
A790 PLA ; Restore X from dummy push
A791 TAX ; Transfer to X
A792 PLA ; Restore Y from dummy push
A793 TAY ; Transfer to Y
A794 PLA ; Restore A
A795 PLP ; Restore flags
A796 RTS ; Return (via inserted &A7A2 addr)
A797 .restore_wksp_from_save←2← A76F JSR← A7A8 JSR
LDX #&78 ; X=&78: sum 120 bytes of channel data
A799 TXA ; A=&78
A79A CLC ; Clear carry for summation
A79B .restore_wksp_byte_loop←1← A79F BNE
ADC wksp_ch_alloc_pad,x ; Add channel table byte
A79E DEX ; Next byte
A79F BNE restore_wksp_byte_loop ; Loop for 120 bytes
A7A1 RTS ; Return checksum in A

Restore workspace and load directory

Restore workspace from saved copy, then load the current directory from disc for the active drive.

A7A2 .load_dir_for_drive←2← 83F7 JSR← 9BF8 JSR
PHP ; Save all registers
A7A3 PHA ; Save A
A7A4 TYA ; Transfer Y to A
A7A5 PHA ; Save Y
A7A6 TXA ; Transfer X to A
A7A7 PHA ; Save X
A7A8 JSR restore_wksp_from_save ; Calculate channel checksum
A7AB STA wksp_workspace_checksum ; Store checksum in workspace
A7AE LDA #0 ; A=0: clear flags
A7B0 STA wksp_compaction_reported ; Clear compaction-reported flag
A7B3 STA wksp_error_suppress ; Clear error flag
A7B6 STA wksp_cur_channel ; Clear current channel
A7B9 PLA ; Restore X
A7BA TAX ; Transfer to X
A7BB PLA ; Restore Y
A7BC TAY ; Transfer to Y
A7BD PLA ; Restore A
A7BE PLP ; Restore flags
A7BF RTS ; Return

Set up disc read for directory load

Copy a disc operation template to the workspace and set up the sector address for reading a directory from disc.

A7C0 .setup_disc_read_for_dir←2← A880 JSR← A936 JSR
LDA wksp_filename_save ; Get saved filename pointer low
A7C3 STA zp_text_ptr_lo ; Store in (&B4)
A7C5 LDA wksp_filename_save_hi ; Get saved filename pointer high
A7C8 STA zp_text_ptr_hi ; Store in (&B5)
A7CA LDA wksp_entry_save_hi ; Get saved dir entry high
A7CD STA zp_entry_ptr_hi ; Store in (&B7)
A7CF LDA wksp_entry_save ; Get saved dir entry low
A7D2 STA zp_entry_ptr_lo ; Store in (&B6)
A7D4 LDX #&0b ; X=&0B: copy 12-byte disc op template
A7D6 .copy_disc_op_template_loop←1← A7DD BNE
LDA disc_op_tpl_padding,x ; Get template byte
A7D9 STA wksp_disc_op_block,x ; Copy to workspace
A7DC DEX ; Next byte
A7DD BNE copy_disc_op_template_loop ; Loop for 12 bytes
A7DF LDY #3 ; Y=3: copy 4-byte sector address
A7E1 .copy_dir_sector_loop←1← A7F0 BPL
LDA wksp_saved_dir_sector,y ; Get source sector byte
A7E4 STA wksp_csd_sector_lo,y ; Store in CSD sector
A7E7 CPX #0 ; X=0?
A7E9 BEQ read_dir_from_disc ; Yes, skip disc op sector store
A7EB STA wksp_disc_op_command,x ; Store in disc op sector field
A7EE .read_dir_from_disc←1← A7E9 BEQ
INX ; Next X
A7EF DEY ; Next Y (decreasing)
A7F0 BPL copy_dir_sector_loop ; Loop for 4 bytes
A7F2 JMP exec_disc_op_from_wksp ; Execute disc read command
A7F5 .setup_fsm_read←1← A8D9 JSR
LDX #&0b ; X=&0B: copy 12-byte disc op template
A7F7 .copy_fsm_template_loop←1← A7FE BNE
LDA disc_op_tpl_padding,x ; Get template byte
A7FA STA wksp_disc_op_block,x ; Copy to workspace
A7FD DEX ; Next byte
A7FE BNE copy_fsm_template_loop ; Loop for 12 bytes
A800 LDY #3 ; Y=3: copy 4-byte sector address
A802 .copy_fsm_sector_loop←1← A811 BPL
LDA wksp_new_parent_sector,y ; Get dest sector byte
A805 STA wksp_csd_sector_lo,y ; Store in CSD sector
A808 CPX #0 ; X=0?
A80A BEQ read_fsm_from_disc ; Yes, skip disc op store
A80C STA wksp_disc_op_command,x ; Store in disc op sector field
A80F .read_fsm_from_disc←1← A80A BEQ
INX ; Next X
A810 DEY ; Next Y (decreasing)
A811 BPL copy_fsm_sector_loop ; Loop for 4 bytes
A813 JSR exec_disc_op_from_wksp ; Execute disc read command
fall through ↓

Load free space map from disc

Read sectors 0 and 1 from the current drive into the free space map workspace at &0E00-&0FFF. Validates the checksum.

A816 .load_fsm
LDX #&0c ; X=&0C: control block offset
A818 LDY #&88 ; Y=&88: control block page
A81A JMP exec_disc_command ; Execute disc read command

*COPY command handler

Copy a file. The source and destination may be on different drives.

A81D .star_copy
LDA #&7f ; Set up control block pointers
A81F STA zp_osfile_ptr_lo ; Store control block pointer low
A821 LDA #&10 ; Control block page = &10
A823 STA zp_osfile_ptr_hi ; Store control block pointer high
A825 LDA #&74 ; Store source name offset
A827 STA wksp_copy_name_ptr ; Store source name offset
A82A LDA #&10 ; Source name page = &10
A82C STA wksp_copy_name_ptr_hi ; Store source name page
A82F JSR search_for_file ; Find source file
A832 BEQ source_file_found ; Found?
A834 JMP not_found_error ; Not found: report error
A837 .source_file_found←1← A832 BEQ
LDA zp_entry_ptr_lo ; Save directory entry pointer
A839 STA wksp_entry_save ; Save dir entry pointer low
A83C LDA zp_entry_ptr_hi ; Get dir entry pointer high
A83E STA wksp_entry_save_hi ; Save dir entry pointer high
A841 LDA zp_text_ptr_lo ; Save filename pointer
A843 STA wksp_filename_save ; Save filename pointer low
A846 LDA zp_text_ptr_hi ; Get filename pointer high
A848 STA wksp_filename_save_hi ; Save filename pointer high
A84B LDY #3 ; Y=3: save current directory sector
A84D .save_source_dir_sector_loop←1← A854 BPL
LDA wksp_csd_sector_lo,y ; Copy CSD sector to workspace
A850 STA wksp_saved_dir_sector,y ; Save CSD sector byte
A853 DEY ; Next byte
A854 BPL save_source_dir_sector_loop ; Loop for 4 bytes
A856 JSR save_wksp_and_return ; Save workspace state
A859 LDY #3 ; Y=3: copy current dir sector
A85B .copy_csd_for_dest_loop←1← A862 BPL
LDA wksp_csd_sector_lo,y ; Get CSD sector byte
A85E STA wksp_csd_drive_sector,y ; Set as target dir for copy
A861 DEY ; Next byte
A862 BPL copy_csd_for_dest_loop ; Loop for 4 bytes
A864 JSR parse_second_filename ; Parse destination path
A867 .check_dest_terminator
JSR check_char_is_terminator ; Check if character is a filename terminator
A86A BNE load_dest_directory ; Found destination dir?
A86C JMP bad_name_error ; Bad name: invalid destination
A86F .load_dest_directory←1← A86A BNE
JSR parse_path_and_load ; Load destination directory
A872 JSR validate_fsm_and_mark_dirty ; Mark destination dir as modified
A875 LDY #3 ; Y=3: save dest dir sector
A877 .save_dest_dir_sector_loop←1← A87E BPL
LDA wksp_csd_sector_lo,y ; Get dest dir sector byte
A87A STA wksp_new_parent_sector,y ; Store in workspace
A87D DEY ; Next byte
A87E BPL save_dest_dir_sector_loop ; Loop for 4 bytes
A880 JSR setup_disc_read_for_dir ; Set up disc read for source
A883 .scan_source_entries_loop←1← A88F BEQ
LDY #4 ; Y=4: check entry access byte
A885 LDA (zp_entry_ptr_lo),y ; Get access byte from entry
A887 DEY ; Y=&03
A888 ORA (zp_entry_ptr_lo),y ; OR with first name byte
A88A BPL copy_file_entry ; Bit 7 clear: regular file, copy it
A88C .skip_dir_entry_or_done←1← A939 JMP
JSR advance_dir_entry_ptr ; Directory: find next entry
A88F BEQ scan_source_entries_loop ; More entries: loop
A891 JMP save_wksp_and_return ; No more entries: done
A894 .copy_file_entry←1← A88A BPL
LDA zp_entry_ptr_lo ; Save source entry pointer
A896 STA wksp_entry_save ; Store entry pointer low
A899 LDA zp_entry_ptr_hi ; Get entry pointer high
A89B STA wksp_entry_save_hi ; Store entry pointer high
A89E JSR search_dir_for_file ; Check if file already exists at dest
A8A1 LDY #&16 ; Y=&16: get source start sector
A8A3 LDA (zp_entry_ptr_lo),y ; Get sector low byte
A8A5 STA wksp_copy_read_sector ; Store in load address workspace
A8A8 INY ; Y=&17
A8A9 LDA (zp_entry_ptr_lo),y ; Get sector mid byte
A8AB STA wksp_copy_read_sector_1 ; Store in load address workspace
A8AE INY ; Y=&18
A8AF LDA (zp_entry_ptr_lo),y ; Get sector high byte
A8B1 ORA wksp_current_drive ; OR with drive number
A8B4 STA wksp_copy_read_sector_2 ; Store in load address workspace
A8B7 LDX #0 ; X=0: clear length bytes
A8B9 LDY #3 ; Y=3: copy 4-byte OSFILE params
A8BB .copy_osfile_params_loop←1← A8C6 BPL
LDA wksp_copy_osfile_params,y ; Get source OSFILE param
A8BE STA wksp_copy_dest_params,y ; Copy to dest OSFILE block
A8C1 TXA ; Transfer X (=0) for clearing
A8C2 STA wksp_copy_osfile_params,y ; Clear source param
A8C5 DEY ; Next byte
A8C6 BPL copy_osfile_params_loop ; Loop for 4 bytes
A8C8 LDY #9 ; Y=9: copy 10-byte filename
A8CA .copy_source_name_loop←1← A8D2 BPL
LDA (zp_entry_ptr_lo),y ; Get filename byte from source
A8CC AND #&7f ; Strip bit 7 (access flag)
A8CE STA wksp_dest_name,y ; Store in dest name workspace
A8D1 DEY ; Next byte
A8D2 BPL copy_source_name_loop ; Loop for 10 bytes
A8D4 LDA #&0d ; A=CR: terminate filename
A8D6 STA wksp_dest_filename_end ; Store terminator
A8D9 JSR setup_fsm_read ; Set up disc read for source file
A8DC JSR copy_addrs_and_find_empty_entry ; Check if file is open
A8DF JSR allocate_disc_space_for_file ; Allocate space for dest file
A8E2 JSR write_entry_sector_info ; Write dest directory entry
A8E5 LDY #2 ; Y=2: copy sector addresses
A8E7 .copy_sector_addresses_loop←1← A8F4 BPL
LDA wksp_alloc_sector,y ; Get source sector byte
A8EA STA wksp_copy_src_sector,y ; Store as read sector
A8ED LDA wksp_alloc_size,y ; Get dest sector byte
A8F0 STA wksp_copy_write_sector,y ; Store as write sector
A8F3 DEY ; Next byte
A8F4 BPL copy_sector_addresses_loop ; Loop for 3 bytes
A8F6 LDA #osbyte_read_oshwm ; OSBYTE &83: read OSHWM
A8F8 JSR osbyte ; Read top of operating system RAM address (OSHWM)
A8FB STY wksp_compact_start_page ; X and Y contain the address of OSHWM (low, high)
A8FE LDA #osbyte_read_himem ; OSBYTE &84: read HIMEM
A900 JSR osbyte ; Read top of user memory (HIMEM)
A903 TYA ; X and Y contain the address of HIMEM (low, high)
A904 SEC ; Calculate buffer size: HIMEM-OSHWM
A905 SBC wksp_compact_start_page ; Subtract OSHWM page
A908 STA wksp_compact_length ; Store buffer size in pages
A90B LDA zp_adfs_flags ; Set bit 3 of ADFS flags
A90D ORA #8 ; Indicate copy operation in progress
A90F STA zp_adfs_flags ; Store updated flags
A911 LDA wksp_drive_number ; Get source drive number
A914 ORA wksp_copy_read_sector_2 ; OR into read sector high byte
A917 STA wksp_copy_read_sector_2 ; Store source drive+sector
A91A LDA wksp_dest_drive ; Get dest drive number
A91D ORA wksp_copy_src_sector_2 ; OR into write sector high byte
A920 STA wksp_copy_src_sector_2 ; Store dest drive+sector
A923 LDA wksp_current_drive ; Save current drive
A926 PHA ; Push on stack
A927 LDA #0 ; Set drive to 0 temporarily
A929 STA wksp_current_drive ; Store temporary drive
A92C JSR execute_sector_copy ; Execute sector-by-sector copy
A92F PLA ; Restore original drive
A930 STA wksp_current_drive ; Set as current drive
A933 JSR write_dir_and_validate ; Write modified directory
A936 JSR setup_disc_read_for_dir ; Set up for next source file
A939 JMP skip_dir_entry_or_done ; Loop to copy next file

FSC 6: new filing system selected

Handle the FSC 6 call which notifies ADFS that a new filing system is being selected. Ensures all files are closed and workspace is saved.

A93C .fsc6_new_filing_system←1← 9B1F JSR
JSR get_wksp_addr_ba ; Get workspace page address
A93F LDY #&ff ; Y=&FF: store at byte 255
A941 STA (zp_wksp_ptr_lo),y ; Mark workspace as needing save
A943 LDX wksp_current_drive ; Check if drive is initialised
A946 INX ; Drive = &FF (uninitialised)?
A947 BEQ return_33 ; Yes, nothing more to do
A949 LDA #osbyte_close_spool_exec ; OSBYTE &77: close spool/exec files
A94B JSR osbyte ; Close any *SPOOL and *EXEC files
A94E JSR save_wksp_and_return ; Save workspace state to disc
A951 LDY #&ff ; Y=&FF: will become 0 after INY
A953 TYA ; A=&FF: flag for OSARGS
A954 INY ; Y=0: falls through to osargs_handler
fall through ↓

OSARGS handler

Handle OSARGS calls for reading and writing file arguments (PTR, EXT, allocation) and filing system information.

A955 .osargs_handler←1← 9574 JSR
CPY #0 ; Y=0? General OSARGS query
A957 BNE osargs_file_specific ; Y!=0: file-specific handler
A959 TAY ; Transfer A to Y (function code)
A95A BNE osargs_general_query ; A=0? Return FS number
A95C LDA #8 ; A=8: ADFS filing system number
A95E .return_33←1← A947 BEQ
RTS ; Return (FS number in A)
A95F .osargs_general_query←1← A95A BNE
JSR save_workspace_state ; Save registers for later restore
A962 STX zp_save_x ; Save X (zero page pointer)
A964 DEY ; Y=0 means function was 1
A965 BNE flush_all_channels ; A!=1: check further functions
A967 LDA wksp_cmd_tail ; A=1: return command tail low byte
A96A STA zp_user_ptr_0,x ; Store in zero page at X+0
A96C LDA wksp_cmd_tail_hi ; Command tail high byte
A96F STA zp_user_ptr_1,x ; Store in zero page at X+1
A971 DEY ; Y=&FF
A972 STY zp_user_ptr_2,x ; Clear X+2 (high bytes)
A974 STY zp_user_ptr_3,x ; Clear X+3
A976 .return_success←2← A992 JMP← AAC3 JMP
LDX zp_save_x ; Restore X
A978 LDA #0 ; A=0: success
A97A TAY ; Y=&00
A97B RTS ; Return (success)

Flush all open channel buffers

Iterate all channel entries, flushing dirty buffers to disc and clearing state flags. Used by OSARGS A=&FF.

A97C .flush_all_channels←2← 96AF JSR← A965 BNE
LDX #&10 ; X=&10: scan open channels
A97E .flush_channels_loop←1← A98A BPL
JSR flush_dirty_channel_buffer ; Flush channel buffer if dirty
A981 LDA #0 ; Clear workspace entry
A983 STA wksp_buf_flag,x ; Clear channel dirty flag
A986 DEX ; Step back 4 bytes (entry size)
A987 DEX ; Step back 4 bytes (next channel)
A988 DEX ; Continue stepping
A989 DEX ; Continue stepping
A98A BPL flush_channels_loop ; Loop for all entries
A98C INC wksp_buf_flag ; Increment flush counter
A98F JSR wait_ensuring ; Wait while files are being ensured
A992 JMP return_success ; Return success
A995 .osargs_file_specific←1← A957 BNE
JSR save_workspace_state ; Save regs for file-specific OSARGS
A998 .set_channel_and_dispatch←1← B5E1 JSR
STX zp_save_x ; Save X (ZP pointer)
A99A PHA ; Save function code on stack
A99B JSR check_set_channel_y ; Validate and set channel number from Y
A99E JSR sync_ext_to_ptr ; Flush channel buffer
A9A1 PLA ; Restore function code
A9A2 LDY zp_channel_offset ; Get channel index
A9A4 TAX ; A still non-zero?
A9A5 BNE check_write_ptr ; A=2: skip (A-1!=0 means not A=2)
A9A7 LDX zp_save_x ; A=2: read PTR to user zero page
A9A9 LDA wksp_ch_ptr_l,y ; Get PTR low byte from channel table
A9AC STA zp_user_ptr_0,x ; Store at user's X+0
A9AE LDA wksp_ch_ptr_ml,y ; Get PTR mid-low byte
A9B1 STA zp_user_ptr_1,x ; Store PTR mid-low at X+1
A9B3 LDA wksp_ch_ptr_mh,y ; Get PTR mid-high byte
A9B6 STA zp_user_ptr_2,x ; Store PTR mid-high at X+2
A9B8 LDA wksp_ch_ptr_h,y ; Get PTR high byte
A9BB STA zp_user_ptr_3,x ; Store PTR high at X+3
A9BD .return_after_flag_update←3← AA00 JMP← AA32 JMP← AA5F JMP
JSR update_channel_flags_for_ptr ; Ensure channel state is consistent
A9C0 LDA #0 ; A=0: success return
A9C2 LDX zp_save_x ; Restore X
A9C4 LDY zp_save_y ; Restore Y
A9C6 RTS ; Return
A9C7 .check_write_ptr←1← A9A5 BNE
DEX ; Decrement: A=3 (write PTR)?
A9C8 BNE check_write_ext ; No, check A=4
A9CA LDA wksp_ch_flags,y ; A=3: check file is open for write
A9CD BPL not_open_for_update ; Bit 7 clear: read-only, error
A9CF .copy_new_ptr_from_user←1← AAA3 JMP
LDX zp_save_x ; A=3: copy new PTR from user's ZP
A9D1 LDA zp_user_ptr_0,x ; Get new PTR low byte
A9D3 STA wksp_new_ptr_lo ; Store new PTR low in workspace
A9D6 LDA zp_user_ptr_1,x ; Get new PTR mid-low from user ZP
A9D8 STA wksp_new_ptr_mid ; Store in workspace
A9DB LDA zp_user_ptr_2,x ; Get new PTR mid-high
A9DD STA wksp_new_ptr_mid_hi ; Store in workspace
A9E0 LDA zp_user_ptr_3,x ; Get new PTR high
A9E2 STA wksp_new_ptr_hi ; Store in workspace
A9E5 JSR check_ptr_within_allocation ; Validate and apply new PTR
A9E8 LDX zp_save_x ; Store new PTR in channel table
A9EA LDY zp_channel_offset ; Get channel index
A9EC LDA zp_user_ptr_0,x ; Get validated PTR low from user ZP
A9EE STA wksp_ch_ptr_l,y ; Set PTR low byte
A9F1 LDA zp_user_ptr_1,x ; Get PTR mid-low
A9F3 STA wksp_ch_ptr_ml,y ; Set PTR mid-low byte
A9F6 LDA zp_user_ptr_2,x ; Get PTR mid-high
A9F8 STA wksp_ch_ptr_mh,y ; Set PTR mid-high byte
A9FB LDA zp_user_ptr_3,x ; Get PTR high
A9FD STA wksp_ch_ptr_h,y ; Set PTR high byte
AA00 JMP return_after_flag_update ; Jump to success return
AA03 .not_open_for_update←1← A9CD BPL
LDX zp_save_x ; A=3: check new PTR <= EXT
AA05 LDY zp_channel_offset ; Get channel index for EXT compare
AA07 SEC ; Subtract new PTR from EXT
AA08 LDA wksp_ch_ext_l,y ; Get EXT low byte
AA0B SBC zp_user_ptr_0,x ; Subtract new PTR low
AA0D LDA wksp_ch_ext_ml,y ; Get EXT mid-low
AA10 SBC zp_user_ptr_1,x ; Subtract new PTR mid-low
AA12 LDA wksp_ch_ext_mh,y ; Get EXT mid-high
AA15 SBC zp_user_ptr_2,x ; Subtract new PTR mid-high
AA17 LDA wksp_ch_ext_h,y ; Get EXT high
AA1A SBC zp_user_ptr_3,x ; Subtract new PTR high
AA1C BCC check_read_allocation ; New PTR > EXT: error
AA1E LDA zp_user_ptr_0,x ; New PTR <= EXT: set PTR
AA20 STA wksp_ch_ptr_l,y ; Set new PTR low byte
AA23 LDA zp_user_ptr_1,x ; Get mid-low from user ZP
AA25 STA wksp_ch_ptr_ml,y ; Set PTR mid-low
AA28 LDA zp_user_ptr_2,x ; Get mid-high from user ZP
AA2A STA wksp_ch_ptr_mh,y ; Set PTR mid-high
AA2D LDA zp_user_ptr_3,x ; Get high from user ZP
AA2F STA wksp_ch_ptr_h,y ; Set PTR high
AA32 JMP return_after_flag_update ; Jump to success return
AA35 .check_read_allocation←1← AA1C BCC
JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
AA38 EQUB &B7 ; Error &B7: Outside file
AA39 EQUS "Outside file."
AA46 .check_write_ext←1← A9C8 BNE
DEX ; Decrement: A=4 (read EXT)?
AA47 BNE read_ext_value ; No, check A=5
AA49 LDX zp_save_x ; A=4: read EXT to user zero page
AA4B LDA wksp_ch_ext_l,y ; Get EXT low byte
AA4E STA zp_user_ptr_0,x ; Store EXT low at user X+0
AA50 LDA wksp_ch_ext_ml,y ; Get EXT mid-low
AA53 STA zp_user_ptr_1,x ; Store at user X+1
AA55 LDA wksp_ch_ext_mh,y ; Get EXT mid-high
AA58 STA zp_user_ptr_2,x ; Store at user X+2
AA5A LDA wksp_ch_ext_h,y ; Get EXT high
AA5D STA zp_user_ptr_3,x ; Store at user X+3
AA5F .read_allocation_size←1← AAA1 BCS
JMP return_after_flag_update ; Jump to success return
AA62 .read_ext_value←1← AA47 BNE
DEX ; Decrement: A=5 (write EXT)?
AA63 BNE validate_and_set_ptr ; No, handle ensure
AA65 LDX zp_save_x ; A=5: check file is open for write
AA67 LDA wksp_ch_flags,y ; Get channel flags
AA6A BMI write_new_ext ; Bit 7 set: writable, proceed
AA6C JMP not_open_for_update_error ; Not writable: error
AA6F .write_new_ext←1← AA6A BMI
LDA zp_user_ptr_0,x ; Copy new EXT from user's ZP
AA71 STA wksp_new_ptr_lo ; Store new EXT low in workspace
AA74 LDA zp_user_ptr_1,x ; Get new EXT mid-low
AA76 STA wksp_new_ptr_mid ; Store in workspace
AA79 LDA zp_user_ptr_2,x ; Get new EXT mid-high
AA7B STA wksp_new_ptr_mid_hi ; Store in workspace
AA7E LDA zp_user_ptr_3,x ; Get new EXT high
AA80 STA wksp_new_ptr_hi ; Store in workspace
AA83 JSR check_ptr_within_allocation ; Validate and apply new EXT
AA86 LDX zp_save_x ; Restore X
AA88 LDY zp_channel_offset ; Get channel index
AA8A LDA zp_user_ptr_0,x ; Get validated EXT low
AA8C STA wksp_ch_ext_l,y ; Set channel EXT low
AA8F LDA zp_user_ptr_1,x ; Get EXT mid-low
AA91 STA wksp_ch_ext_ml,y ; Set channel EXT mid-low
AA94 LDA zp_user_ptr_2,x ; Get EXT mid-high
AA96 STA wksp_ch_ext_mh,y ; Set channel EXT mid-high
AA99 LDA zp_user_ptr_3,x ; Get EXT high
AA9B STA wksp_ch_ext_h,y ; Set channel EXT high
AA9E JSR compare_ext_to_ptr ; Compare file EXT to PTR
AAA1 BCS read_allocation_size ; EXT >= current: just update table
AAA3 JMP copy_new_ptr_from_user ; EXT < current: also update PTR

Flush buffers and set file pointer

Scan the ensure table for entries matching the current channel and flush any dirty buffers before updating PTR.

AAA6 .validate_and_set_ptr←3← AA63 BNE← B3E4 JSR← B7CF JSR
LDX #&10 ; X=&10: scan ensure table
AAA8 .copy_ptr_to_channel_loop←1← AAC1 BPL
LDA wksp_buf_flag,x ; Get ensure table entry
AAAB LSR ; Shift right to get channel index
AAAC AND #&0f ; Mask to 4-bit channel number
AAAE CMP zp_channel_offset ; This channel's entry?
AAB0 BNE set_ptr_complete ; No, skip to next entry
AAB2 JSR flush_dirty_channel_buffer ; Flush this entry's buffer
AAB5 LDA wksp_buf_flag,x ; Get ensure table entry again
AAB8 AND #1 ; Keep only bit 0 (dirty flag)
AABA STA wksp_buf_flag,x ; Clear other bits
AABD .set_ptr_complete←1← AAB0 BNE
DEX ; Step back 4 bytes
AABE DEX ; Continue stepping
AABF DEX ; Continue stepping
AAC0 DEX ; Continue stepping
AAC1 BPL copy_ptr_to_channel_loop ; Loop for all ensure entries
AAC3 JMP return_success ; Return success

Hard drive single sector for BGET/BPUT

Read or write a single sector via the SCSI interface for byte-level file access (BGET/BPUT channel operations).

On EntryASCSI command byte (&08=read, &0A=write)
Xchannel buffer table offset
AAC6 .hd_command_bget_bput_sector←2← AB4F JSR← ACB4 JSR
PHA ; Wait if files being ensured
AAC7 JSR wait_ensuring ; Wait while files are being ensured
AACA JSR scsi_start_command2 ; Start SCSI command phase (Y in cmd)
AACD PLA ; Restore command byte from stack
AACE JSR scsi_send_cmd_byte ; Send one byte during SCSI command phase
AAD1 LDA wksp_buf_sec_hi,x ; Get drive+LUN from channel block
AAD4 STA wksp_current_drive_hi ; Save as current drive info
AAD7 JSR scsi_send_cmd_byte ; Send one byte during SCSI command phase
AADA LDA wksp_buf_sec_mid,x ; Get sector address high
AADD JSR scsi_send_cmd_byte ; Send one byte during SCSI command phase
AAE0 LDA wksp_buf_sec_lo,x ; Get sector address mid
AAE3 JSR scsi_send_cmd_byte ; Send one byte during SCSI command phase
AAE6 LDA #1 ; Sector count = 1
AAE8 JSR scsi_send_cmd_byte ; Send one byte during SCSI command phase
AAEB LDA #0 ; Control byte = 0
AAED JMP scsi_send_cmd_byte ; Send last command byte and return
AAF0 .calc_channel_buffer_page←1← AC5F JMP
JSR calc_buffer_page_from_offset ; Calculate buffer page from channel
AAF3 .flush_dirty_channel_buffer←3← A97E JSR← AAB2 JSR← B035 JSR
JSR ensure_channel_buffer ; Ensure channel buffer is allocated
AAF6 LDA wksp_buf_flag,x ; Get channel state byte
AAF9 CMP #&c0 ; State >= &C0 (dirty write)?
AAFB BCC return_34 ; No: buffer clean, return
AAFD TXA ; Transfer X to A
AAFE LSR ; Divide by 4 for channel number
AAFF LSR ; Second shift
AB00 ADC #&17 ; Add &17 for buffer page base
AB02 STA zp_buf_src_hi ; Store as buffer high byte
AB04 LDA #0 ; Buffer low byte = 0
AB06 STA zp_buf_src_lo ; Store buffer low byte
AB08 LDA wksp_buf_flag,x ; Get channel state
AB0B AND #&bf ; Clear bit 6 (dirty flag)
AB0D STA wksp_buf_flag,x ; Store cleared state
AB10 AND #&1e ; Isolate channel number bits
AB12 ROR ; Shift right to get channel index
AB13 ORA #&30 ; OR with &30 to get file handle
AB15 STA wksp_err_handle ; Store file handle for errors
AB18 LDA wksp_buf_sec_lo,x ; Get sector address low
AB1B STA wksp_err_sector ; Store in error sector workspace
AB1E LDA wksp_buf_sec_mid,x ; Get sector address mid
AB21 STA wksp_err_sector_mid ; Store in error sector mid
AB24 LDA wksp_buf_sec_hi,x ; Get drive+sector high
AB27 STA wksp_err_sector_hi ; Store in error sector high
AB2A JSR set_drive_from_channel ; Flush channel if dirty
AB2D JSR command_set_retries ; Set retry count for disc operation
AB30 STX zp_name_ptr_hi ; Save channel index
AB32 LDA zp_adfs_flags ; Check for hard drive
AB34 AND #&20 ; Bit 5: hard drive present?
AB36 BEQ write_dirty_sector_to_disc ; No HD: use floppy
AB38 LDA wksp_buf_sec_hi,x ; Get drive number from channel
AB3B BPL hd_bput_write_sector ; Bit 7 clear: use SCSI hard drive
AB3D .write_dirty_sector_to_disc←2← AB36 BEQ← AB46 BPL
LDX zp_name_ptr_hi ; Restore channel index for floppy
AB3F JSR exec_floppy_write_bput_sector_ind ; Execute floppy write sector
AB42 BEQ write_buffer_to_scsi_loop ; Success? Done
AB44 DEC zp_retry_count ; Decrement retry counter
AB46 BPL write_dirty_sector_to_disc ; More retries: try again
AB48 JMP generate_error ; Generate a BRK error
AB4B .hd_bput_write_sector←2← AB3B BPL← AB5E BPL
LDX zp_name_ptr_hi ; Restore channel index for SCSI
AB4D LDA #&0a ; A=&0A: SCSI write command
AB4F JSR hd_command_bget_bput_sector ; Hard drive single sector for BGET/BPUT
AB52 LDY #0 ; Y=0: data transfer index
AB54 JSR scsi_wait_for_req ; Wait for SCSI REQ signal
AB57 BPL scsi_write_page ; Status OK: continue
AB59 JSR command_done ; Complete SCSI command and read status
AB5C DEC zp_retry_count ; Decrement retry counter
AB5E BPL hd_bput_write_sector ; More retries: try write again
AB60 JMP generate_error ; Generate a BRK error

Write 256 bytes to SCSI bus

Transfer a page from (zp_buf_src) to the SCSI data register, then set the ensuring flag.

AB63 .scsi_write_page←2← AB57 BPL← AB69 BNE
LDA (zp_buf_src_lo),y ; Get byte from buffer
AB65 STA fred_hard_drive_0 ; Write to SCSI data bus
AB68 INY ; Next byte
AB69 BNE scsi_write_page ; Loop for 256 bytes
AB6B LDA #1 ; Set ensuring flag
AB6D ORA zp_adfs_flags ; OR into ADFS flags
AB6F STA zp_adfs_flags ; Store updated flags
AB71 DEY ; Y=&FF: disable SCSI IRQ
AB72 STY fred_hard_drive_3 ; Write to SCSI IRQ enable register
AB75 .write_buffer_to_scsi_loop←1← AB42 BEQ
LDX zp_name_ptr_hi ; Restore channel index
AB77 .return_34←1← AAFB BCC
RTS ; Return (success)
AB78 .svc5_irq
LDA zp_adfs_flags ; Get ADFS flags
AB7A AND #&21 ; Check ensuring + HD bits
AB7C CMP #&21 ; Both set?
AB7E BNE advance_write_page ; No: not our interrupt
AB80 JSR scsi_get_status ; Read SCSI status
AB83 CMP #&f2 ; Status = &F2 (completion)?
AB85 BEQ write_complete ; Yes: handle SCSI completion
AB87 .advance_write_page←1← AB7E BNE
LDA #5 ; Not ours: A=5 (not claimed)
AB89 RTS ; Return
AB8A .write_complete←1← AB85 BEQ
TYA ; Save Y
AB8B PHA ; Push on stack
AB8C LDA #0 ; A=0: clear SCSI IRQ
AB8E STA fred_hard_drive_3 ; Write to SCSI IRQ enable
AB91 ROR zp_adfs_flags ; Clear ensuring flag (bit 0)
AB93 CLC ; Clear carry
AB94 ROL zp_adfs_flags ; Restore bit 0 cleared
AB96 LDA fred_hard_drive_0 ; Read SCSI status byte
AB99 JSR scsi_wait_for_req ; Wait for message phase
AB9C ORA fred_hard_drive_0 ; OR with final status byte
AB9F STA wksp_scsi_status ; Store combined status
ABA2 JMP set_result_error_code ; Return to service dispatcher
ABA5 .ensure_channel_buffer←1← AAF3 JSR
LDA wksp_scsi_status ; Check for pending data lost error
ABA8 BEQ return_35 ; Zero: no error, return
ABAA LDA #0 ; Clear pending error
ABAC STA wksp_scsi_status ; Clear error status
ABAF LDX wksp_err_handle ; Get file handle for error message
ABB2 JSR generate_error_suffix_x ; Generate error with suffix control in X
ABB5 EQUB &CA ; Error &CA: Data lost
ABB6 EQUS "Data lost, channel."
ABC9 .calc_buffer_address←2← AC05 JSR← AC89 JSR
TXA ; Save X (channel index)
ABCA STX wksp_ch_buf_sector_1 ; Store in workspace
ABCD LSR ; Divide by 4 for channel number
ABCE LSR ; Second shift
ABCF ADC #&17 ; Add &17 for buffer page base
ABD1 STA zp_buf_dest_hi ; Store as buffer high byte
ABD3 LDA #0 ; Buffer low = 0 (page-aligned)
ABD5 STA zp_buf_dest_lo ; Store buffer low
ABD7 .return_35←1← ABA8 BEQ
RTS ; Return

Find or allocate a buffer for a sector

Scan channel buffer table for a buffer matching the target sector. If not found, evict the oldest buffer for reuse.

On EntryAbuffer mode (&40=read, &C0=write)
On ExitAcorrupted
Xbuffer table offset for slot
Ycorrupted
ABD8 .find_buffer_for_sector←6← ADAD JSR← AFAF JSR← B01F JSR← B10E JSR← B6EE JSR← B811 JSR
LDX #&10 ; X=&10: start of channel table
ABDA STX wksp_osgbpb_end_ptr ; Store as initial best match
ABDD TAY ; Transfer A to Y (mode flag)
ABDE .scan_channel_buffers←1← AC68 JMP
LDA wksp_buf_flag,x ; Get channel state entry
ABE1 AND #1 ; Check bit 0 (dirty flag)
ABE3 BEQ buffer_sector_match ; Not dirty: skip
ABE5 STX wksp_osgbpb_end_ptr ; Update best dirty channel index
ABE8 .buffer_sector_match←1← ABE3 BEQ
LDA wksp_buf_flag,x ; Get channel state
ABEB BPL read_single_hd_sector ; Bit 7 clear: channel not active
ABED LDA wksp_buf_sec_lo,x ; Get channel sector low
ABF0 CMP wksp_osgbpb_sector_lo ; Compare with target sector low
ABF3 BNE read_single_hd_sector ; No match: try next channel
ABF5 LDA wksp_buf_sec_mid,x ; Get channel sector mid
ABF8 CMP wksp_osgbpb_sector_mid ; Compare with target sector mid
ABFB BNE read_single_hd_sector ; No match: try next channel
ABFD LDA wksp_buf_sec_hi,x ; Get channel drive+sector high
AC00 CMP wksp_osgbpb_sector_hi ; Compare with target high
AC03 BNE read_single_hd_sector ; No match: try next channel
AC05 JSR calc_buffer_address ; Match: set up buffer address
AC08 .allocate_new_buffer_slot←1← ACD4 JMP
TYA ; Transfer mode flag to A
AC09 LSR ; Shift right for direction bit
AC0A AND #&40 ; Isolate bit 6 (read/write)
AC0C ORA wksp_buf_flag,x ; Merge with channel state
AC0F ROR ; Rotate state bits
AC10 AND #&e0 ; Keep top 3 bits
AC12 ORA zp_channel_offset ; OR in channel index
AC14 PHP ; Save flags
AC15 CLC ; Clear carry for rotate
AC16 ROL ; Shift left to final position
AC17 STA wksp_buf_flag,x ; Store updated channel state
AC1A PLP ; Restore flags
AC1B BCC evict_oldest_buffer ; C=0: read operation, skip write
AC1D LDY #&10 ; Y=&10: scan for empty ensure slot
AC1F .find_free_slot_loop←1← AC2F BPL
LDA wksp_buf_flag,y ; Get ensure table entry
AC22 BNE use_free_slot ; Non-zero: slot in use
AC24 LDA #1 ; A=1: mark slot as dirty
AC26 STA wksp_buf_flag,y ; Store in ensure table
AC29 BNE load_sector_to_buffer ; ALWAYS branch
AC2B .use_free_slot←1← AC22 BNE
DEY ; Step back 4 bytes
AC2C DEY ; Continue stepping
AC2D DEY ; Continue stepping
AC2E DEY ; Continue stepping
AC2F BPL find_free_slot_loop ; Loop for all ensure entries
AC31 JSR convert_handle_to_offset ; Flush oldest dirty buffer
AC34 ROR wksp_buf_flag,x ; Clear bit 0 of channel state
AC37 SEC ; Set carry
AC38 ROL wksp_buf_flag,x ; Set bit 0 (mark as dirty)
AC3B .evict_oldest_buffer←1← AC1B BCC
INX ; Advance X past current entry
AC3C INX ; Continue advancing
AC3D INX ; Continue advancing
AC3E INX ; Continue advancing
AC3F CPX #&11 ; Past end of table (&11)?
AC41 BCC evict_check_dirty ; No: continue
AC43 LDX #0 ; Wrap to start: X=0
AC45 .evict_check_dirty←1← AC41 BCC
LDA wksp_buf_flag,x ; Get channel state at new position
AC48 LSR ; Shift right to check state
AC49 BEQ load_sector_to_buffer ; Empty slot: use it
AC4B BCC load_sector_to_buffer ; C=0: clean buffer, reuse it
AC4D CLC ; Clear carry for rotate back
AC4E ROL ; Restore state bits
AC4F STA wksp_buf_flag,x ; Store updated state
AC52 JSR convert_handle_to_offset ; Flush this buffer to disc
AC55 JSR convert_handle_to_offset ; Flush again (ensure completion)
AC58 ROR wksp_buf_flag,x ; Clear dirty bit
AC5B SEC ; Set carry
AC5C ROL wksp_buf_flag,x ; Set dirty bit (will be written)
AC5F .load_sector_to_buffer←3← AC29 BNE← AC49 BEQ← AC4B BCC
JMP calc_channel_buffer_page ; Jump to buffer fill

Read a single sector via SCSI

Issue a single-sector read command and transfer 256 bytes from the SCSI data register into the buffer.

AC62 .read_single_hd_sector←4← ABEB BPL← ABF3 BNE← ABFB BNE← AC03 BNE
DEX ; Step back 4 bytes to prev entry
AC63 DEX ; Continue stepping
AC64 DEX ; Continue stepping
AC65 DEX ; Continue stepping
AC66 BMI wait_read_data_phase ; Past start: no match found
AC68 JMP scan_channel_buffers ; Continue scanning from top
AC6B .wait_read_data_phase←1← AC66 BMI
LDX wksp_osgbpb_end_ptr ; Get best dirty channel index
AC6E LDA wksp_osgbpb_sector_lo ; Get target sector low
AC71 STA wksp_buf_sec_lo,x ; Store as channel sector low
AC74 STA wksp_err_sector ; Also store in error workspace
AC77 LDA wksp_osgbpb_sector_mid ; Get target sector mid
AC7A STA wksp_buf_sec_mid,x ; Store as channel sector mid
AC7D STA wksp_err_sector_mid ; Store in error workspace
AC80 LDA wksp_osgbpb_sector_hi ; Get target drive+sector high
AC83 STA wksp_buf_sec_hi,x ; Store as channel drive+sector
AC86 STA wksp_err_sector_hi ; Store in error workspace
AC89 JSR calc_buffer_address ; Calculate buffer page for channel
AC8C LDA wksp_osgbpb_sector_hi ; Get drive+sector high for read
AC8F JSR set_drive_from_channel ; Set up disc read control block
AC92 STY zp_ctrl_blk_hi ; Save Y (buffer high)
AC94 STX zp_ctrl_blk_lo ; Save X (buffer low)
AC96 JSR command_set_retries ; Set retry count for disc operation
AC99 .read_scsi_to_buffer_loop←1← ACAD BPL
LDX zp_ctrl_blk_lo ; Restore buffer pointer
AC9B LDA zp_adfs_flags ; Check for hard drive
AC9D AND #&20 ; Bit 5: hard drive present?
AC9F BEQ advance_read_page ; No: use floppy
ACA1 LDA wksp_buf_sec_hi,x ; Get drive from channel
ACA4 BPL hd_bget_read_sector ; Bit 7 clear: use SCSI
ACA6 .advance_read_page←1← AC9F BEQ
JSR exec_floppy_read_bput_sector_ind ; Floppy: read sector to buffer
ACA9 BEQ check_read_error ; Success? Done
ACAB .read_hd_256_complete←1← ACC9 BNE
DEC zp_retry_count ; Decrement retry counter
ACAD BPL read_scsi_to_buffer_loop ; More retries: try again
ACAF JMP generate_error ; Generate a BRK error
ACB2 .hd_bget_read_sector←1← ACA4 BPL
LDA #8 ; SCSI: read command = 8
ACB4 JSR hd_command_bget_bput_sector ; Hard drive single sector for BGET/BPUT
ACB7 JSR scsi_wait_for_req ; Wait for SCSI REQ signal
ACBA BMI store_read_result ; Status phase: read complete
ACBC LDY #0 ; Y=0: read data byte index
ACBE .read_complete_check←1← ACC4 BNE
LDA fred_hard_drive_0 ; Read byte from SCSI data bus
ACC1 STA (zp_buf_dest_lo),y ; Store in buffer
ACC3 INY ; Next byte
ACC4 BNE read_complete_check ; Loop for 256 bytes
ACC6 .store_read_result←1← ACBA BMI
JSR command_done ; Complete SCSI command and read status
ACC9 BNE read_hd_256_complete ; Error: retry
ACCB .check_read_error←1← ACA9 BEQ
LDX zp_ctrl_blk_lo ; Restore buffer pointer X
ACCD LDY zp_ctrl_blk_hi ; Restore buffer pointer Y
ACCF LDA #&81 ; A=&81: buffer valid + dirty
ACD1 STA wksp_buf_flag,x ; Store as channel state
ACD4 JMP allocate_new_buffer_slot ; Jump to set up buffer access

Calculate buffer page from channel offset

Divide the channel offset by 4 and add the buffer base page (&17) to compute the buffer memory page.

ACD7 .calc_buffer_page_from_offset←2← 8B33 JSR← AAF0 JSR
LDX #&10 ; X=&10: start of channel table
ACD9 .step_channel_offset_loop←1← ACE4 BPL
LDA wksp_buf_flag,x ; Get channel state
ACDC AND #1 ; Bit 0: dirty flag
ACDE BNE return_36 ; Not dirty: done scanning
ACE0 DEX ; Step back 4 bytes
ACE1 DEX ; Continue stepping
ACE2 DEX ; Continue stepping
ACE3 DEX ; Continue stepping
ACE4 BPL step_channel_offset_loop ; Loop for all entries
ACE6 JMP bad_checksum_error ; No dirty buffers: workspace error

Step through ensure table entries

Step backward through the ensure table checking for entries associated with the current channel.

ACE9 .step_ensure_offset_loop←3← AD05 BCS← AD0B BCC← AD13 BEQ
JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
ACEC EQUB &DE ; Error &DE: Channel
ACED EQUS "Channel."
ACF5 .convert_handle_to_offset←3← AC31 JSR← AC52 JSR← AC55 JSR
DEX ; Step back 4 bytes
ACF6 DEX ; Continue stepping
ACF7 DEX ; Continue stepping
ACF8 DEX ; Continue stepping
ACF9 BPL return_37 ; Still in range: return
ACFB LDX #&10 ; Wrap: X=&10 (back to end)
ACFD .return_37←1← ACF9 BPL
RTS ; Return

Validate and set channel number from Y

Check that Y contains a valid file handle and set the channel offset workspace variable.

On EntryYfile handle (&30-&39)
On ExitAchannel flags from wksp_ch_flags
Xchannel offset
Ypreserved
ACFE .check_set_channel_y←6← A99B JSR← AD3C JSR← AD65 JSR← B092 JSR← B3B3 JSR← B5AC JSR
STY zp_save_y ; Save file handle
AD00 STY wksp_cur_channel ; Store as current channel for errors
AD03 CPY #&3a ; Handle >= &3A?
AD05 BCS step_ensure_offset_loop ; Yes, invalid handle
AD07 TYA ; Transfer handle to A
AD08 SEC ; Set carry for subtraction
AD09 SBC #&30 ; Subtract &30 to get channel index
AD0B BCC step_ensure_offset_loop ; Handle < &30? Invalid
AD0D STA zp_channel_offset ; Store channel index offset
AD0F TAX ; Transfer to X for table lookup
AD10 LDA wksp_ch_flags,x ; Read channel flags
AD13 BEQ step_ensure_offset_loop ; Zero = channel not open
AD15 .return_36←1← ACDE BNE
RTS ; Return

Compare file EXT to PTR

Compare the file extent (EXT) with the current pointer (PTR) for the channel in the workspace.

On ExitAlast compared EXT byte (C clear if at EOF)
Xchannel offset
Ypreserved
AD16 .compare_ext_to_ptr←4← AA9E JSR← AD48 JSR← AD6F JSR← B644 JSR
LDX zp_channel_offset ; Get channel index
AD18 LDA wksp_ch_ext_h,x ; Compare EXT high byte
AD1B CMP wksp_ch_ptr_h,x ; With PTR high byte
AD1E BNE return_38 ; Different: not at EOF (C set)
AD20 LDA wksp_ch_ext_mh,x ; Compare EXT mid-high byte
AD23 CMP wksp_ch_ptr_mh,x ; With PTR mid-high byte
AD26 BNE return_38 ; Different: not at EOF
AD28 LDA wksp_ch_ext_ml,x ; Compare EXT mid-low byte
AD2B CMP wksp_ch_ptr_ml,x ; With PTR mid-low byte
AD2E BNE return_38 ; Different: not at EOF
AD30 LDA wksp_ch_ext_l,x ; Compare EXT low byte
AD33 CMP wksp_ch_ptr_l,x ; With PTR low byte
AD36 BNE return_38 ; Different: not at EOF
AD38 CLC ; All equal: C=0, at EOF
AD39 .return_38←4← AD1E BNE← AD26 BNE← AD2E BNE← AD36 BNE
RTS ; Return (EXT == PTR: C clear)
AD3A .check_eof_for_handle
LDY zp_text_ptr_lo ; Get file handle from (&B4)
AD3C JSR check_set_channel_y ; Validate and set channel number from Y
AD3F ROR ; Rotate flags bit 0 into carry
AD40 BCS return_eof_status ; Carry set: skip flush
AD42 JSR save_workspace_state ; Ensure workspace is valid
AD45 JSR sync_ext_to_ptr ; Flush channel buffer if dirty
AD48 JSR compare_ext_to_ptr ; Compare file EXT to PTR
AD4B .return_eof_status←1← AD40 BCS
LDX #0 ; X=0: PTR == EXT result
AD4D BCS return_eof_result ; Carry set: PTR == EXT
AD4F DEX ; X=&ff
AD50 .return_eof_result←1← AD4D BCS
LDY zp_text_ptr_hi ; Restore Y from (&B5)
AD52 RTS ; Return

Raise EOF error

Clear EOF and buffer flags then raise error &DF: EOF.

AD53 .eof_error←2← AD6D BNE← AD74 BNE
LDA wksp_ch_flags,x ; Clear EOF and buffer dirty flags
AD56 AND #&c8 ; Keep bits 7,6,3 (writeable,open)
AD58 STA wksp_ch_flags,x ; Store updated flags
AD5B JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
AD5E EQUB &DF ; Error &DF: EOF
AD5F EQUS "EOF."
fall through ↓

OSBGET handler

Handle OSBGET calls to read a single byte from an open file.

AD63 .osbget_handler
STX zp_save_x ; Save X register
AD65 JSR check_set_channel_y ; Validate file handle in Y
AD68 ROR ; Rotate channel flags bit 0 to C
AD69 BCS calc_bget_sector_addr ; Bit 0 set: file is readable
AD6B AND #4 ; Check bit 2 (at EOF flag)
AD6D BNE eof_error ; At EOF: raise EOF error
AD6F JSR compare_ext_to_ptr ; Compare EXT with PTR
AD72 BCS calc_bget_sector_addr ; EXT != PTR: not at EOF, read byte
AD74 BNE eof_error ; EXT == PTR and EOF: raise error
AD76 JSR save_workspace_state ; Save registers for restore
AD79 LDX zp_channel_offset ; Get channel index
AD7B LDA wksp_ch_flags,x ; Get channel flags
AD7E AND #&c0 ; Keep open+writable bits only
AD80 ORA #8 ; Set EOF-read flag (bit 3)
AD82 STA wksp_ch_flags,x ; Store updated channel flags
AD85 LDY zp_save_y ; Restore Y
AD87 LDX zp_save_x ; Restore X
AD89 SEC ; Set carry: C=1 means EOF
AD8A LDA #&fe ; A=&FE: EOF return value
AD8C RTS ; Return (EOF)

Calculate sector address for BGET

Compute disc sector from channel base + PTR, load the sector into the buffer, and set up the byte offset.

AD8D .calc_bget_sector_addr←2← AD69 BCS← AD72 BCS
LDX zp_channel_offset ; Get channel index for buffer calc
AD8F CLC ; Clear carry for address calculation
AD90 LDA wksp_ch_start_sec_ml,x ; Get channel buffer offset low
AD93 ADC wksp_ch_ptr_ml,x ; Add PTR mid-low for buffer addr
AD96 STA wksp_osgbpb_sector_lo ; Store sector address low
AD99 LDA wksp_ch_start_sec_mh,x ; Get channel buffer offset mid
AD9C ADC wksp_ch_ptr_mh,x ; Add PTR mid-high
AD9F STA wksp_osgbpb_sector_mid ; Store sector address mid
ADA2 LDA wksp_ch_start_sec_h,x ; Get channel buffer base page
ADA5 ADC wksp_ch_ptr_h,x ; Add PTR high
ADA8 STA wksp_osgbpb_sector_hi ; Store sector address high
ADAB LDA #&40 ; A=&40: read buffer mode
ADAD JSR find_buffer_for_sector ; Load sector into channel buffer
ADB0 LDX zp_channel_offset ; Get channel index
ADB2 LDY wksp_ch_ptr_l,x ; Get PTR low byte as buffer offset
ADB5 LDA #0 ; A=0: clear modification flag
ADB7 STA wksp_bput_modified ; Store zero mod flag
ADBA JSR increment_ptr_after_write ; Advance PTR and update flags
ADBD LDA (zp_buf_dest_lo),y ; Read byte from buffer at PTR offset
ADBF LDY zp_save_y ; Restore Y
ADC1 LDX zp_save_x ; Restore X
ADC3 CLC ; Clear carry: C=0 means success
ADC4 RTS ; Return (byte in A)

Switch to channel's drive for I/O

Save CSD sector and current drive, then switch to the drive associated with the current channel.

ADC5 .switch_to_channel_drive←2← AED7 JSR← B3F1 JSR
LDY #2 ; Y=2: save 3 bytes of CSD sector
ADC7 .save_csd_sector_loop←1← ADCE BPL
LDA wksp_csd_sector_lo,y ; Get CSD sector byte
ADCA STA wksp_temp_sector,y ; Store in temp workspace
ADCD DEY ; Next byte
ADCE BPL save_csd_sector_loop ; Loop for 3 bytes
ADD0 LDA wksp_current_drive ; Save current drive
ADD3 STA wksp_last_access_drive ; Store as last access drive
ADD6 LDX zp_channel_offset ; Get channel index
ADD8 LDA wksp_ch_start_sec_h,x ; Get channel's drive number
ADDB AND #&e0 ; Isolate drive bits (top 3)
ADDD STA wksp_saved_drive ; Save as current working drive
ADE0 LDA wksp_ch_dir_sec_ml,x ; Get channel's sector low
ADE3 STA wksp_csd_drive_sector ; Store in CSD sector low
ADE6 LDA wksp_ch_dir_sec_mh,x ; Get channel's sector mid
ADE9 STA wksp_csd_drive_sector_mid ; Store in workspace mid
ADEC LDA wksp_ch_dir_sec_h,x ; Get channel's sector high
ADEF STA wksp_alt_sector_hi ; Store in workspace high
ADF2 JSR save_wksp_and_return ; Save workspace state
ADF5 LDY #2 ; Y=2: restore CSD sector
ADF7 .restore_csd_after_switch_loop←1← ADFE BPL
LDA wksp_temp_sector,y ; Get saved CSD sector byte
ADFA STA wksp_csd_drive_sector,y ; Restore to CSD workspace
ADFD DEY ; Next byte
ADFE BPL restore_csd_after_switch_loop ; Loop for 3 bytes
AE00 LDA wksp_last_access_drive ; Get last access drive
AE03 STA wksp_saved_drive ; Set as saved drive for restore
AE06 JSR read_clock_and_compare ; Ensure files on drive are closed
AE09 LDX zp_channel_offset ; Get channel index
AE0B LDA wksp_ch_start_sec_ml,x ; Get channel's allocation low
AE0E STA wksp_object_sector ; Store in object sector low
AE11 LDA wksp_ch_start_sec_mh,x ; Get allocation mid
AE14 STA wksp_object_sector_mid ; Store in workspace
AE17 LDA wksp_ch_start_sec_h,x ; Get allocation high + drive
AE1A AND #&1f ; Mask to sector bits only
AE1C STA wksp_object_sector_hi ; Store sector high
AE1F LDA #5 ; Set (&B8) to dir entry at &1205
AE21 STA zp_osfile_ptr_lo ; Store low byte
AE23 LDA #&12 ; Page &12
AE25 STA zp_osfile_ptr_hi ; Store high byte
AE27 LDX zp_channel_offset ; Get channel index
AE29 .search_dir_for_channel←2← AE53 BCC← AE57 BCS
LDY #0 ; Y=0: check first dir entry byte
AE2B LDA (zp_osfile_ptr_lo),y ; Get first byte
AE2D BNE compare_entry_sequence ; Non-zero: valid entry
AE2F STA wksp_ch_flags,x ; Zero: channel invalid, clear flags
AE32 JMP bad_checksum_error ; Bad checksum error
AE35 .compare_entry_sequence←1← AE2D BNE
LDY #&19 ; Y=&19: check entry sequence number
AE37 LDA (zp_osfile_ptr_lo),y ; Get sequence number from entry
AE39 CMP wksp_ch_seq_num,x ; Compare with channel's saved seq
AE3C BNE advance_to_next_dir_entry ; Mismatch: different entry
AE3E DEY ; Y=&18
AE3F .compare_entry_sector_loop←1← AE49 BCS
LDA (zp_osfile_ptr_lo),y ; Check next entry field
AE41 CMP wksp_disc_op_sector_count,y ; Compare sector field with channel
AE44 BNE advance_to_next_dir_entry ; Mismatch: try next entry
AE46 DEY ; Next byte (decreasing Y)
AE47 CPY #&16 ; Past start of sector field (&16)?
AE49 BCS compare_entry_sector_loop ; Still in range: continue comparing
AE4B RTS ; All fields match: return

Advance directory scan pointer

Add 26 bytes to the directory entry pointer to move to the next entry, handling page crossing.

AE4C .advance_to_next_dir_entry←2← AE3C BNE← AE44 BNE
LDA zp_osfile_ptr_lo ; Advance to next dir entry (+&1A)
AE4E CLC ; Clear carry
AE4F ADC #&1a ; Add 26 bytes per entry
AE51 STA zp_osfile_ptr_lo ; Store updated pointer
AE53 BCC search_dir_for_channel ; No page crossing: continue search
AE55 INC zp_osfile_ptr_hi ; Increment page
AE57 BCS search_dir_for_channel ; ALWAYS branch
AE59 .check_ptr_within_allocation←3← A9E5 JSR← AA83 JSR← B0EB JSR
LDA #0 ; A=0: clear allocation flag
AE5B STA wksp_osgbpb_mode ; Clear extension flag
AE5E .extend_file_if_needed←1← B5FF JSR
LDA wksp_saved_drive ; Get saved drive
AE61 STA wksp_saved_drive_2 ; Store for restore later
AE64 LDX #2 ; X=2: save 3 bytes of CSD
AE66 .save_csd_for_extend_loop←1← AE6D BPL
LDA wksp_csd_drive_sector,x ; Get CSD sector byte
AE69 STA wksp_osgbpb_wksp_bc,x ; Store in temp workspace
AE6C DEX ; Next byte
AE6D BPL save_csd_for_extend_loop ; Loop for 3 bytes
AE6F LDA #&ff ; A=&FF: mark workspace as modified
AE71 STA wksp_alt_sector_hi ; Clear alt workspace pointer
AE74 STA wksp_saved_drive ; Clear saved drive
AE77 LDX zp_channel_offset ; Get channel index
AE79 LDA wksp_ch_alloc_h,x ; Compare allocation with new PTR
AE7C CMP wksp_new_ptr_hi ; High byte matches?
AE7F BNE check_alloc_vs_ptr ; No: need to extend
AE81 LDA wksp_ch_alloc_mh,x ; Compare mid-high
AE84 CMP wksp_new_ptr_mid_hi ; Match?
AE87 BNE check_alloc_vs_ptr ; No: need to extend
AE89 LDA wksp_ch_alloc_ml,x ; Compare mid-low
AE8C CMP wksp_new_ptr_mid ; Match?
AE8F BNE check_alloc_vs_ptr ; No: need to extend
AE91 LDA wksp_ch_alloc_l,x ; Compare low byte
AE94 CMP wksp_new_ptr_lo ; Match?
AE97 .check_alloc_vs_ptr←3← AE7F BNE← AE87 BNE← AE8F BNE
BCC extend_file_allocation ; Alloc < PTR: need to extend
AE99 LDA wksp_ch_ext_h,x ; Compare EXT with new PTR
AE9C CMP wksp_new_ptr_hi ; High byte matches?
AE9F BNE update_ext_to_ptr ; No: EXT needs update
AEA1 LDA wksp_ch_ext_mh,x ; Compare mid-high
AEA4 CMP wksp_new_ptr_mid_hi ; Match?
AEA7 BNE update_ext_to_ptr ; No: EXT needs update
AEA9 LDA wksp_ch_ext_ml,x ; Compare mid-low
AEAC CMP wksp_new_ptr_mid ; Match?
AEAF BNE update_ext_to_ptr ; No: EXT needs update
AEB1 LDA wksp_ch_ext_l,x ; Compare low byte
AEB4 CMP wksp_new_ptr_lo ; Match?
AEB7 BNE update_ext_to_ptr ; No: EXT needs update
AEB9 .handle_eof_write←1← AEBC BCS
JMP restore_drive_after_extend ; PTR == EXT: handle EOF write

Handle PTR exceeding EXT

If PTR has exceeded the file allocation, begin file extension. Otherwise jump to EOF write handler.

AEBC .update_ext_to_ptr←4← AE9F BNE← AEA7 BNE← AEAF BNE← AEB7 BNE
BCS handle_eof_write ; EXT > PTR: still within file
AEBE JMP skip_zero_fill ; PTR > alloc: need to extend file
AEC1 .extend_file_allocation←1← AE97 BCC
SEC ; Calculate new allocation size
AEC2 LDA #0 ; A=0: compute pages needed
AEC4 ADC wksp_new_ptr_mid_hi ; Add PTR mid-low + 1 page
AEC7 STA wksp_new_ptr_4 ; Store new allocation mid
AECA LDA #0 ; A=0: propagate carry
AECC ADC wksp_new_ptr_hi ; Add PTR high + carry
AECF STA wksp_osgbpb_wksp_9f ; Store new allocation high
AED2 BCC switch_drive_for_extend ; No overflow: proceed
AED4 JMP disc_full_error ; Overflow: Disc full error
AED7 .switch_drive_for_extend←1← AED2 BCC
JSR switch_to_channel_drive ; Switch to file's drive
AEDA LDA wksp_ch_alloc_l,x ; Get current allocation low
AEDD CMP #1 ; Compare with 1 (minimum)
AEDF LDA wksp_ch_alloc_ml,x ; Get allocation mid-low
AEE2 ADC #0 ; Add carry from compare
AEE4 STA wksp_object_size ; Store as required size low
AEE7 LDA wksp_ch_alloc_mh,x ; Get allocation mid-high
AEEA ADC #0 ; Add carry
AEEC STA wksp_object_size_mid ; Store as required size mid
AEEF LDA wksp_ch_alloc_h,x ; Get allocation high
AEF2 ADC #0 ; Add carry
AEF4 STA wksp_object_size_hi ; Store as required size high
AEF7 LDA #0 ; Clear sector info
AEF9 STA wksp_alloc_size ; Clear low
AEFC LDA wksp_new_ptr_4 ; Get new allocation mid
AEFF STA wksp_alloc_size_mid ; Store as extension mid
AF02 LDA wksp_osgbpb_wksp_9f ; Get new allocation high
AF05 STA wksp_alloc_size_hi ; Store as extension high
AF08 JSR release_disc_space ; Release disc space back to free space map
AF0B JSR allocate_disc_space ; Allocate disc space from FSM
AF0E LDY #&12 ; Y=&12: update dir entry length
AF10 LDA #0 ; A=0: clear length low byte
AF12 LDX zp_channel_offset ; Get channel index
AF14 STA (zp_osfile_ptr_lo),y ; Store zero in entry length low
AF16 STA wksp_ch_alloc_l,x ; Update channel alloc low
AF19 INY ; Y=&13
AF1A STA (zp_osfile_ptr_lo),y ; Store in dir entry
AF1C STA wksp_ch_alloc_ml,x ; Update channel alloc mid-low
AF1F LDA wksp_new_ptr_4 ; Get new alloc mid
AF22 INY ; Y=&14
AF23 STA (zp_osfile_ptr_lo),y ; Store in dir entry
AF25 STA wksp_ch_alloc_mh,x ; Update channel alloc mid-high
AF28 LDA wksp_osgbpb_wksp_9f ; Get new alloc high
AF2B INY ; Y=&15
AF2C STA (zp_osfile_ptr_lo),y ; Store in dir entry
AF2E STA wksp_ch_alloc_h,x ; Update channel alloc high
AF31 LDA wksp_alloc_sector ; Get new start sector low
AF34 INY ; Y=&16
AF35 STA (zp_osfile_ptr_lo),y ; Store in dir entry start sector
AF37 STA wksp_ch_start_sec_ml,x ; Update channel start sector low
AF3A LDA wksp_saved_count ; Get new start sector mid
AF3D INY ; Y=&17
AF3E STA (zp_osfile_ptr_lo),y ; Store in dir entry
AF40 STA wksp_ch_start_sec_mh,x ; Update channel start sector mid
AF43 LDA wksp_saved_count_1 ; Get new start sector high
AF46 INY ; Y=&18
AF47 STA (zp_osfile_ptr_lo),y ; Store in dir entry
AF49 ORA wksp_current_drive ; OR with drive number for channel
AF4C STA wksp_ch_start_sec_h,x ; Update channel start sector+drive
AF4F JSR write_dir_and_validate ; Write directory back to disc
AF52 LDA zp_adfs_flags ; Clear bit 3 of ADFS flags
AF54 AND #&f7 ; Mask off bit 3
AF56 STA zp_adfs_flags ; Store cleared flags
AF58 LDA #&12 ; Set up buffer: page &12
AF5A STA wksp_compact_start_page ; Store buffer start page
AF5D LDA #9 ; Buffer length: 9 pages (&1200)
AF5F STA wksp_compact_length ; Store buffer length
AF62 LDX #0 ; X=0: check if file was relocated
AF64 LDY #2 ; Y=2: compare old and new sectors
AF66 .copy_old_sector_info_loop←1← AF7F BPL
LDA wksp_object_sector,y ; Get old start sector byte
AF69 STA wksp_copy_read_sector,y ; Store for copy source
AF6C CMP wksp_alloc_sector,y ; Compare with new start sector
AF6F BEQ check_relocation_needed ; Same: no relocation needed
AF71 INX ; Different: flag relocation
AF72 LDA wksp_alloc_sector,y ; Get new start sector byte
AF75 .check_relocation_needed←1← AF6F BEQ
STA wksp_copy_src_sector,y ; Store for copy destination
AF78 LDA wksp_object_size,y ; Get required size byte
AF7B STA wksp_copy_write_sector,y ; Store for copy length
AF7E DEY ; Next byte
AF7F BPL copy_old_sector_info_loop ; Loop for 3 bytes
AF81 TXA ; X non-zero: relocation occurred
AF82 BEQ skip_zero_fill ; Zero: no relocation, skip copy
AF84 JSR execute_sector_copy ; Copy data from old to new location
AF87 .skip_zero_fill←2← AEBE JMP← AF82 BEQ
LDA wksp_osgbpb_mode ; Check extension flag
AF8A BEQ calc_zero_fill_start ; Non-zero: skip zeroing
AF8C JMP update_ext_from_new_ptr ; Jump to update EXT
AF8F .calc_zero_fill_start←1← AF8A BEQ
LDX zp_channel_offset ; Get channel index
AF91 CLC ; Clear carry for address calculation
AF92 LDA wksp_ch_ext_ml,x ; Get EXT mid-low
AF95 ADC wksp_ch_start_sec_ml,x ; Add channel offset low
AF98 STA wksp_osgbpb_sector_lo ; Store zero-fill start sector low
AF9B LDA wksp_ch_ext_mh,x ; Get EXT mid-high
AF9E ADC wksp_ch_start_sec_mh,x ; Add channel offset mid
AFA1 STA wksp_osgbpb_sector_mid ; Store zero-fill start mid
AFA4 LDA wksp_ch_ext_h,x ; Get EXT high
AFA7 ADC wksp_ch_start_sec_h,x ; Add channel base + drive
AFAA STA wksp_osgbpb_sector_hi ; Store zero-fill start high
AFAD LDA #&c0 ; A=&C0: write buffer mode
AFAF JSR find_buffer_for_sector ; Set up buffer for writing zeros
AFB2 LDX zp_channel_offset ; Get channel index
AFB4 LDY wksp_ch_ext_l,x ; Get EXT low as buffer start
AFB7 LDA #0 ; A=0: zero fill
AFB9 .zero_fill_sector_loop←1← AFBC BNE
STA (zp_buf_dest_lo),y ; Write zero to buffer
AFBB INY ; Next byte
AFBC BNE zero_fill_sector_loop ; Loop for rest of sector
AFBE LDA wksp_new_ptr_mid ; Get new PTR mid-low
AFC1 CLC ; Clear carry
AFC2 ADC wksp_ch_start_sec_ml,x ; Add channel base
AFC5 STA wksp_object_sector ; Store target sector low
AFC8 LDA wksp_new_ptr_mid_hi ; Get new PTR mid-high
AFCB ADC wksp_ch_start_sec_mh,x ; Add channel offset
AFCE STA wksp_object_sector_mid ; Store target sector mid
AFD1 LDA wksp_new_ptr_hi ; Get new PTR high
AFD4 ADC wksp_ch_start_sec_h,x ; Add channel base + drive
AFD7 STA wksp_object_sector_hi ; Store target sector high
AFDA LDA wksp_new_ptr_lo ; Get PTR low byte
AFDD BNE decrement_fill_sector ; Non-zero: not sector-aligned
AFDF LDA wksp_object_sector ; Check sector low
AFE2 BNE check_sector_mid ; Non-zero: adjust sector
AFE4 LDA wksp_object_sector_mid ; Check sector mid
AFE7 BNE check_sector_low ; Non-zero: adjust mid
AFE9 DEC wksp_object_sector_hi ; Decrement sector high
AFEC .check_sector_low←1← AFE7 BNE
DEC wksp_object_sector_mid ; Decrement sector mid
AFEF .check_sector_mid←1← AFE2 BNE
DEC wksp_object_sector ; Decrement sector low
AFF2 .decrement_fill_sector←1← AFDD BNE
LDA wksp_object_sector ; Compare with buffer sector
AFF5 CMP wksp_osgbpb_sector_lo ; Match low byte?
AFF8 BNE write_zero_sector ; No: need to write more zeros
AFFA LDA wksp_object_sector_mid ; Match mid byte?
AFFD CMP wksp_osgbpb_sector_mid ; Check mid
B000 BNE write_zero_sector ; No: need more
B002 LDA wksp_object_sector_hi ; Match high byte?
B005 CMP wksp_osgbpb_sector_hi ; Check high
B008 BNE write_zero_sector ; No: need more
B00A JMP update_ext_from_new_ptr ; All match: done zeroing
B00D .write_zero_sector←3← AFF8 BNE← B000 BNE← B008 BNE
JSR wait_ensuring ; Wait while files are being ensured
B010 INC wksp_osgbpb_sector_lo ; Advance buffer sector: inc low
B013 BNE advance_fill_sector ; No wrap
B015 INC wksp_osgbpb_sector_mid ; Wrap: inc mid
B018 BNE advance_fill_sector ; No wrap
B01A INC wksp_osgbpb_sector_hi ; Wrap: inc high
B01D .advance_fill_sector←2← B013 BNE← B018 BNE
LDA #&40 ; A=&40: read buffer mode
B01F JSR find_buffer_for_sector ; Load next sector into buffer
B022 LDY #0 ; Y=0: zero fill entire sector
B024 TYA ; A=&00
B025 .zero_entire_sector_loop←1← B028 BNE
STA (zp_buf_dest_lo),y ; Write zero to buffer
B027 INY ; Next byte
B028 BNE zero_entire_sector_loop ; Loop for 256 bytes
B02A .mark_buffer_dirty←3← B053 BNE← B058 BNE← B05D JMP
LDX wksp_ch_buf_sector_1 ; Get channel buffer table index
B02D LDA #&c0 ; A=&C0: mark buffer as dirty
B02F ORA wksp_buf_flag,x ; OR with channel state
B032 STA wksp_buf_flag,x ; Store dirty state
B035 JSR flush_dirty_channel_buffer ; Flush dirty buffer to disc
B038 LDA wksp_object_sector ; Compare current sector with target
B03B CMP wksp_buf_sec_lo,x ; Compare low bytes
B03E BNE advance_channel_sector ; No match: advance sector
B040 LDA wksp_object_sector_mid ; Compare mid bytes
B043 CMP wksp_buf_sec_mid,x ; Compare
B046 BNE advance_channel_sector ; No match: advance
B048 LDA wksp_object_sector_hi ; Compare high bytes
B04B CMP wksp_buf_sec_hi,x ; Compare
B04E BEQ update_ext_from_new_ptr ; Match: done writing zeros
B050 .advance_channel_sector←2← B03E BNE← B046 BNE
INC wksp_buf_sec_lo,x ; Advance channel sector: inc low
B053 BNE mark_buffer_dirty ; No wrap
B055 INC wksp_buf_sec_mid,x ; Wrap: inc mid
B058 BNE mark_buffer_dirty ; No wrap
B05A INC wksp_buf_sec_hi,x ; Wrap: inc high
B05D JMP mark_buffer_dirty ; Continue zeroing loop

Update EXT from new PTR value

Copy 4-byte PTR from workspace to the channel's EXT, then save workspace and restore drive state.

B060 .update_ext_from_new_ptr←3← AF8C JMP← B00A JMP← B04E BEQ
LDX zp_channel_offset ; Get channel index
B062 LDA wksp_new_ptr_lo ; Get new PTR low
B065 STA wksp_ch_ext_l,x ; Store as new EXT low
B068 LDA wksp_new_ptr_mid ; Get new PTR mid-low
B06B STA wksp_ch_ext_ml,x ; Store as new EXT mid-low
B06E LDA wksp_new_ptr_mid_hi ; Get new PTR mid-high
B071 STA wksp_ch_ext_mh,x ; Store as new EXT mid-high
B074 LDA wksp_new_ptr_hi ; Get new PTR high
B077 STA wksp_ch_ext_h,x ; Store as new EXT high
B07A JSR save_wksp_and_return ; Save workspace
B07D .restore_drive_after_extend←1← AEB9 JMP
LDA wksp_saved_drive_2 ; Restore saved drive from temp
B080 STA wksp_saved_drive ; Set as saved drive
B083 LDX #2 ; X=2: restore 3 bytes of CSD
B085 .restore_csd_after_extend_loop←1← B08C BPL
LDA wksp_osgbpb_wksp_bc,x ; Get saved CSD byte
B088 STA wksp_csd_drive_sector,x ; Restore to CSD workspace
B08B DEX ; Next byte
B08C BPL restore_csd_after_extend_loop ; Loop for 3 bytes
B08E RTS ; Return

OSBPUT handler

Handle OSBPUT calls to write a single byte to an open file.

B08F .osbput_handler
STX zp_save_x ; Save X register
B091 PHA ; Save byte to write on stack
B092 JSR check_set_channel_y ; Validate file handle in Y
B095 LDY #0 ; Clear modification flag
B097 STY wksp_bput_modified ; Clear modification flag
B09A TAY ; Transfer channel flags to Y
B09B BMI check_buffer_state ; Bit 7 set: file is writable
B09D .not_open_for_update_error←2← AA6C JMP← B5C5 JMP
JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
B0A0 EQUB &C1 ; Error &C1: Not open for update
B0A1 EQUS "Not open for update."
B0B5 .check_buffer_state←1← B09B BMI
LDA wksp_ch_flags,x ; Get channel flags
B0B8 AND #7 ; Isolate buffer state bits (0-2)
B0BA CMP #6 ; State >= 6: buffer dirty, ready
B0BC BCS calc_buffer_sector_addr ; Buffer state >= 6: ready
B0BE CMP #3 ; State = 3: buffer clean, skip load
B0C0 BEQ calc_buffer_sector_addr ; Buffer state = 3: skip load
B0C2 LDA wksp_ch_ptr_l,x ; Compute PTR+1 to check if extending
B0C5 SEC ; Set carry for PTR+1 calculation
B0C6 ADC #0 ; Add 1 (carry) to PTR low
B0C8 STA wksp_new_ptr_lo ; Store next PTR low in workspace
B0CB LDA wksp_ch_ptr_ml,x ; Get PTR mid-low
B0CE ADC #0 ; Add carry
B0D0 STA wksp_new_ptr_mid ; Store next PTR mid-low
B0D3 LDA wksp_ch_ptr_mh,x ; Get PTR mid-high
B0D6 ADC #0 ; Add carry
B0D8 STA wksp_new_ptr_mid_hi ; Store next PTR mid-high
B0DB LDA wksp_ch_ptr_h,x ; Get PTR high
B0DE ADC #0 ; Add carry
B0E0 STA wksp_new_ptr_hi ; Store next PTR high
B0E3 PLA ; Restore byte to write
B0E4 JSR save_workspace_state ; Save registers for restore
B0E7 PHA ; Re-push byte to write
B0E8 DEC wksp_bput_modified ; Set modification flag
B0EB JSR check_ptr_within_allocation ; Validate PTR and load sector
B0EE LDX zp_channel_offset ; Get channel index
B0F0 .calc_buffer_sector_addr←2← B0BC BCS← B0C0 BEQ
CLC ; Clear carry for address calc
B0F1 LDA wksp_ch_start_sec_ml,x ; Get channel start sector low
B0F4 ADC wksp_ch_ptr_ml,x ; Add PTR to get current disc sector
B0F7 STA wksp_osgbpb_sector_lo ; Store disc sector address low
B0FA LDA wksp_ch_start_sec_mh,x ; Get channel start sector mid
B0FD ADC wksp_ch_ptr_mh,x ; Add PTR mid-high with carry
B100 STA wksp_osgbpb_sector_mid ; Store disc sector address mid
B103 LDA wksp_ch_start_sec_h,x ; Get channel start sector+drive
B106 ADC wksp_ch_ptr_h,x ; Add PTR high with carry
B109 STA wksp_osgbpb_sector_hi ; Store disc sector address high
B10C LDA #&c0 ; A=&C0: buffer write mode
B10E JSR find_buffer_for_sector ; Load sector into buffer
B111 LDX zp_channel_offset ; Get channel index
B113 LDY wksp_ch_ptr_l,x ; Get PTR low as buffer offset
B116 PLA ; Restore byte to write
B117 STA (zp_buf_dest_lo),y ; Write byte into buffer at PTR
B119 PHA ; Save byte again
B11A JSR increment_ptr_after_write ; Advance PTR and update flags
B11D PLA ; Restore written byte
B11E LDY zp_save_y ; Restore Y
B120 LDX zp_save_x ; Restore X
B122 .return_39←1← B128 BNE
RTS ; Return

Increment PTR after byte write

Increment the channel's 4-byte PTR. On page boundaries, save workspace and propagate carry through mid/high bytes.

B123 .increment_ptr_after_write←2← ADBA JSR← B11A JSR
LDX zp_channel_offset ; Get channel index
B125 INC wksp_ch_ptr_l,x ; Increment PTR low byte
B128 BNE return_39 ; No wrap: done
B12A BIT wksp_bput_modified ; Check modification flag
B12D BMI increment_ptr_mid_bytes ; Not modified: skip workspace save
B12F JSR save_workspace_state ; Save workspace state
B132 .increment_ptr_mid_bytes←1← B12D BMI
INC wksp_ch_ptr_ml,x ; Increment PTR mid-low
B135 BNE update_channel_flags_for_ptr ; No wrap: update flags
B137 INC wksp_ch_ptr_mh,x ; Increment PTR mid-high
B13A BNE update_channel_flags_for_ptr ; No wrap: update flags
B13C INC wksp_ch_ptr_h,x ; Increment PTR high
B13F .update_channel_flags_for_ptr←5← A9BD JSR← B135 BNE← B13A BNE← B2D5 JSR← B713 JSR
JSR sync_ext_to_ptr ; Update channel flags for new PTR
B142 PHA ; Save current flags on stack
B143 SEC ; Set carry for subtraction
B144 LDA wksp_ch_ptr_ml,x ; Compare PTR with EXT: mid-low
B147 SBC wksp_ch_ext_ml,x ; Subtract EXT mid-low
B14A LDA wksp_ch_ptr_mh,x ; PTR mid-high
B14D SBC wksp_ch_ext_mh,x ; Subtract EXT mid-high
B150 LDA wksp_ch_ptr_h,x ; PTR high
B153 SBC wksp_ch_ext_h,x ; Subtract EXT high
B156 BCC set_buffer_dirty_and_flush ; PTR < EXT: not at EOF
B158 LDA wksp_ch_ptr_l,x ; PTR >= EXT: compare low bytes
B15B CMP wksp_ch_ext_l,x ; Compare PTR low with EXT low
B15E BNE check_ext_vs_allocation ; Not equal: PTR past EXT
B160 PLA ; Equal: set EOF flag (bit 2)
B161 ORA #4 ; Set bit 2 in flags
B163 PHA ; Re-push updated flags
B164 .check_ext_vs_allocation←1← B15E BNE
SEC ; Check if buffer needs flushing
B165 LDA wksp_ch_ext_ml,x ; Compare EXT mid-low with allocation
B168 SBC wksp_ch_alloc_ml,x ; Subtract allocation mid-low
B16B LDA wksp_ch_ext_mh,x ; EXT mid-high
B16E SBC wksp_ch_alloc_mh,x ; Subtract allocation mid-high
B171 LDA wksp_ch_ext_h,x ; EXT high
B174 SBC wksp_ch_alloc_h,x ; Subtract allocation high
B177 BCC set_buffer_flush_flag ; EXT < allocation: buffer has room
B179 PLA ; Restore flags
B17A BNE apply_writable_mask ; Non-zero flags: keep them
B17C .set_buffer_flush_flag←1← B177 BCC
PLA ; Restore flags
B17D ORA #2 ; Set bit 1 (buffer needs flushing)
B17F BNE apply_writable_mask ; ALWAYS branch
B181 .set_buffer_dirty_and_flush←1← B156 BCC
PLA ; Restore flags
B182 ORA #3 ; Set bits 0+1 (dirty + flush)
B184 .apply_writable_mask←2← B17A BNE← B17F BNE
BMI store_channel_flags ; Bit 7 set: writable channel
B186 AND #&f9 ; Clear bits 0-2 (read-only mode)
B188 .store_channel_flags←2← B184 BMI← B1B1 BNE
STA wksp_ch_flags,x ; Store updated channel flags
B18B RTS ; Return

Synchronise EXT to PTR if at EOF

If the EOF flag is set, copy PTR to EXT. Then recalculate channel flags from the writable and open bits.

B18C .sync_ext_to_ptr←5← A99E JSR← AD45 JSR← B13F JSR← B3B6 JSR← B5B0 JSR
LDX zp_channel_offset ; Get channel index
B18E LDA wksp_ch_flags,x ; Get current channel flags
B191 PHA ; Save on stack
B192 AND #4 ; Check EOF flag (bit 2)
B194 BEQ recalc_flags_from_base ; Not at EOF: skip EXT update
B196 LDA wksp_ch_ptr_l,x ; At EOF: set EXT = PTR
B199 STA wksp_ch_ext_l,x ; Copy PTR low to EXT low
B19C LDA wksp_ch_ptr_ml,x ; Copy PTR mid-low to EXT mid-low
B19F STA wksp_ch_ext_ml,x ; Store in EXT
B1A2 LDA wksp_ch_ptr_mh,x ; Copy PTR mid-high to EXT mid-high
B1A5 STA wksp_ch_ext_mh,x ; Store in EXT
B1A8 LDA wksp_ch_ptr_h,x ; Copy PTR high to EXT high
B1AB STA wksp_ch_ext_h,x ; Store in EXT
B1AE .recalc_flags_from_base←1← B194 BEQ
PLA ; Restore flags from stack
B1AF AND #&c0 ; Keep only writable+open bits
B1B1 BNE store_channel_flags ; Non-zero: store flags
fall through ↓

*CLOSE command handler

Close all open files on all drives.

B1B3 .star_close←2← A0CB JSR← A344 JSR
LDA #0 ; A=0: OSFIND close function
B1B5 TAY ; Y=0: close all files
fall through ↓

OSFIND handler

Handle OSFIND calls to open and close files for byte access.

B1B6 .osfind_handler←2← A12C JSR← A3DC JSR
JSR save_workspace_state ; Save registers for later restore
B1B9 STX wksp_osfile_block ; Save X in OSFILE block as filename
B1BC STX zp_text_ptr_lo ; Filename pointer low = X
B1BE STX zp_osfind_x ; Save Y for close channel
B1C0 STY zp_osfind_y ; Y also to OSFILE block + filename hi
B1C2 STY wksp_osfile_block_1 ; Store filename high in OSFILE blk
B1C5 STY zp_text_ptr_hi ; Filename pointer high = Y
B1C7 AND #&c0 ; Isolate open mode (bits 6-7)
B1C9 LDY #0 ; Y=0: clear current channel
B1CB STY wksp_cur_channel ; Clear current channel for errors
B1CE TAY ; Transfer mode to Y
B1CF BNE find_empty_channel_slot ; A!=0: open file
B1D1 JMP close_file_handler ; A=0: close file(s)
B1D4 .find_empty_channel_slot←1← B1CF BNE
LDA wksp_exec_handle ; Check for stored EXEC handle
B1D7 BEQ store_exec_handle ; No stored handle: normal open
B1D9 LDY #0 ; Clear stored EXEC handle
B1DB STY wksp_exec_handle ; Clear stored EXEC handle
B1DE LDY zp_text_ptr_hi ; Return with stored handle in Y
B1E0 RTS ; Return with stored handle
B1E1 .store_exec_handle←1← B1D7 BEQ
LDX #9 ; X=9: scan channels for empty slot
B1E3 .scan_channels_loop←1← B1E9 BPL
LDA wksp_ch_flags,x ; Get channel flags
B1E6 BEQ open_for_read_channel ; Flags=0: channel is free
B1E8 DEX ; Try next channel
B1E9 BPL scan_channels_loop ; Loop for all 10 channels
B1EB JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
B1EE EQUB &C0 ; Error &C0: Too many open
B1EF EQUS "Too many open files."
B203 .open_for_read_channel←1← B1E6 BEQ
STX zp_channel_offset ; Store channel index in zp_cf
B205 STY wksp_ch_buf_sector ; Save open mode (Y) in workspace
B208 TYA ; Transfer mode to A
B209 BPL search_for_input_file ; Bit 7 set: open for output/random
B20B JMP check_random_access_mode ; Output/random: jump to write path
B20E .search_for_input_file←1← B209 BPL
JSR find_first_matching_entry ; Open for input: search for file
B211 BEQ check_read_conflicts ; Found?
B213 LDA #0 ; Not found: A=0 (no handle)
B215 JMP save_and_return_handle ; Return with A=0
B218 .check_read_conflicts←2← B211 BEQ← B2FB JMP
LDX #9 ; X=9: check all channels for conflict
B21A .check_open_conflict_loop←1← B24E BPL
LDA wksp_ch_flags,x ; Get channel flags
B21D BPL next_conflict_check ; Bit 7 clear: channel not active
B21F LDA wksp_ch_start_sec_h,x ; Get channel drive number
B222 AND #&e0 ; Isolate drive bits
B224 CMP wksp_current_drive ; Same drive as file being opened?
B227 BNE next_conflict_check ; Different drive: no conflict
B229 LDA wksp_ch_dir_sec_ml,x ; Compare sector address bytes
B22C CMP wksp_csd_sector_lo ; Compare with file's sector low
B22F BNE next_conflict_check ; No match: no conflict
B231 LDA wksp_ch_dir_sec_mh,x ; Compare sector mid
B234 CMP wksp_csd_sector_mid ; Match?
B237 BNE next_conflict_check ; No match: no conflict
B239 LDA wksp_ch_dir_sec_h,x ; Compare sector high
B23C CMP wksp_csd_sector_hi ; Match?
B23F BNE next_conflict_check ; No match: no conflict
B241 LDY #&19 ; Y=&19: compare sequence number
B243 LDA (zp_entry_ptr_lo),y ; Get entry's sequence number
B245 CMP wksp_ch_seq_num,x ; Compare with channel's sequence
B248 BNE next_conflict_check ; Mismatch: not the same file
B24A JMP channel_on_same_drive ; Match: Already open error

Continue open-channel conflict scan

Advance to next channel and continue scanning for files that conflict with the file being opened.

B24D .next_conflict_check←6← B21D BPL← B227 BNE← B22F BNE← B237 BNE← B23F BNE← B248 BNE
DEX ; Next channel
B24E BPL check_open_conflict_loop ; Loop for all 10 channels
B250 LDY #0 ; Y=0: check entry first byte
B252 LDA (zp_entry_ptr_lo),y ; Get first name byte
B254 BMI copy_ext_from_entry ; Bit 7 set: has attributes, open it
B256 JMP validate_found_entry ; No attributes: access violation
B259 .copy_ext_from_entry←1← B254 BMI
LDY #&12 ; Y=&12: entry length (4 bytes)
B25B LDX zp_channel_offset ; Get channel index
B25D LDA (zp_entry_ptr_lo),y ; Get length low from entry
B25F STA wksp_ch_ext_l,x ; Store as channel EXT low
B262 INY ; Y=&13: length mid-low
B263 LDA (zp_entry_ptr_lo),y ; Get length mid-low
B265 STA wksp_ch_ext_ml,x ; Store as channel EXT mid-low
B268 INY ; Y=&14: length mid-high
B269 LDA (zp_entry_ptr_lo),y ; Get length mid-high
B26B STA wksp_ch_ext_mh,x ; Store as channel EXT mid-high
B26E INY ; Y=&15: length high
B26F LDA (zp_entry_ptr_lo),y ; Get length high
B271 STA wksp_ch_ext_h,x ; Store as channel EXT high
B274 .copy_allocation_from_entry←1← B380 JMP
LDY #&12 ; Y=&12: allocation size (4 bytes)
B276 LDX zp_channel_offset ; Get channel index
B278 LDA (zp_entry_ptr_lo),y ; Get allocation low from entry
B27A STA wksp_ch_alloc_l,x ; Store as channel allocation low
B27D INY ; Y=&13
B27E LDA (zp_entry_ptr_lo),y ; Get allocation mid-low
B280 STA wksp_ch_alloc_ml,x ; Store as channel alloc mid-low
B283 INY ; Y=&14
B284 LDA (zp_entry_ptr_lo),y ; Get allocation mid-high
B286 STA wksp_ch_alloc_mh,x ; Store as channel alloc mid-high
B289 INY ; Y=&15
B28A LDA (zp_entry_ptr_lo),y ; Get allocation high
B28C STA wksp_ch_alloc_h,x ; Store as channel alloc high
B28F INY ; Y=&16: start sector (3 bytes)
B290 LDA (zp_entry_ptr_lo),y ; Get start sector low
B292 STA wksp_ch_start_sec_ml,x ; Store as channel start sector low
B295 INY ; Y=&17: start sector mid
B296 LDA (zp_entry_ptr_lo),y ; Get start sector mid
B298 STA wksp_ch_start_sec_mh,x ; Store as channel start sector mid
B29B INY ; Y=&18: start sector high
B29C LDA (zp_entry_ptr_lo),y ; Get start sector high from entry
B29E ORA wksp_current_drive ; OR with current drive number
B2A1 STA wksp_ch_start_sec_h,x ; Store as channel start+drive
B2A4 INY ; Y=&19: sequence number
B2A5 LDA (zp_entry_ptr_lo),y ; Get sequence number
B2A7 STA wksp_ch_seq_num,x ; Store for channel
B2AA LDA wksp_csd_sector_lo ; Get parent dir sector low
B2AD STA wksp_ch_dir_sec_ml,x ; Store for channel
B2B0 LDA wksp_csd_sector_mid ; Get parent dir sector mid
B2B3 STA wksp_ch_dir_sec_mh,x ; Store for channel
B2B6 LDA wksp_csd_sector_hi ; Get parent dir sector high
B2B9 STA wksp_ch_dir_sec_h,x ; Store for channel
B2BC LDA #0 ; A=0: set PTR to start of file
B2BE STA wksp_ch_ptr_l,x ; Clear PTR low
B2C1 STA wksp_ch_ptr_ml,x ; Clear PTR mid-low
B2C4 STA wksp_ch_ptr_mh,x ; Clear PTR mid-high
B2C7 STA wksp_ch_ptr_h,x ; Clear PTR high
B2CA LDA wksp_ch_buf_sector ; Get open mode flags
B2CD STA wksp_ch_flags,x ; Store as channel flags
B2D0 TXA ; Transfer channel index to A
B2D1 CLC ; Clear carry for addition
B2D2 ADC #&30 ; Add &30 to get file handle
B2D4 PHA ; Push file handle on stack
B2D5 JSR update_channel_flags_for_ptr ; Ensure buffer state is consistent
B2D8 PLA ; Restore file handle
B2D9 .save_and_return_handle←2← B215 JMP← B2ED BNE
JSR save_wksp_and_return ; Save workspace and return A=handle
B2DC LDX zp_osfind_x ; Restore X from saved value
B2DE LDY zp_osfind_y ; Restore Y from saved value
B2E0 RTS ; Return
B2E1 .check_random_access_mode←1← B20B JMP
BIT wksp_ch_buf_sector ; Check open mode for random access
B2E4 BVC open_for_output_new ; Bit 6 clear: open for output only
B2E6 JSR find_first_matching_entry ; Random: search for existing file
B2E9 PHP ; Save search result flags
B2EA LDA #0 ; A=0: default no-file result
B2EC PLP ; Restore flags from search
B2ED BNE save_and_return_handle ; Not found: return A=0
B2EF JSR check_open ; Check if file is open
B2F2 LDY #1 ; Y=1: check first name byte
B2F4 LDA (zp_entry_ptr_lo),y ; Get name byte
B2F6 BMI open_for_random_access ; Bit 7 set: has attributes
B2F8 .check_random_access_attr←1← B30D BPL
JMP validate_found_entry ; No attributes: access violation
B2FB .open_for_random_access←1← B2F6 BMI
JMP check_read_conflicts ; Jump to check for open conflicts
B2FE .open_for_output_new←1← B2E4 BVC
JSR set_up_gsinit_path ; Parse destination path
B301 JSR find_first_matching_entry ; Search for existing file
B304 BNE clear_new_file_osfile ; Not found: create new
B306 JSR check_file_not_open ; Found: check it's not open
B309 LDY #1 ; Y=1: check access byte
B30B LDA (zp_entry_ptr_lo),y ; Get first name byte
B30D BPL check_random_access_attr ; Bit 7 clear: access violation
B30F JMP set_ext_zero_for_new ; Jump to open with existing entry
B312 .clear_new_file_osfile←1← B304 BNE
LDA #0 ; A=0: clear OSFILE block
B314 LDX #&0f ; X=&0F: clear 16 bytes
B316 .clear_osfile_block_loop2←1← B31A BPL
STA wksp_osfile_load_addr,x ; Clear OSFILE block byte
B319 DEX ; Next byte
B31A BPL clear_osfile_block_loop2 ; Loop for 16 bytes
B31C LDX fsm_s1_total_sectors_lo ; Get FSM end-of-list pointer
B31F LDA #0 ; A=0: initial max size = 0
B321 .find_best_free_space_loop←1← B335 BNE
ORA fsm_s0_end_of_list_ptr,x ; OR FSM entry address bytes
B324 ORA fsm_s0_checksum,x ; Continue OR-ing
B327 LDY fsm_s0_boot_option,x ; Get FSM entry length
B32A CPY wksp_osfile_end_addr_1 ; Compare with current max
B32D BCC store_default_allocation ; Smaller: skip
B32F STY wksp_osfile_end_addr_1 ; Larger: update max
B332 .store_default_allocation←1← B32D BCC
DEX ; Back up 3 bytes to prev entry
B333 DEX ; Continue
B334 DEX ; Continue
B335 BNE find_best_free_space_loop ; Loop for all entries
B337 TAY ; Transfer A to Y (non-zero check)
B338 BEQ set_ffffffff_load_addr ; Zero: no free space at all
B33A STX wksp_osfile_end_addr_1 ; Store 0 as max (will use default)
B33D INX ; X=1
B33E STX wksp_osfile_end_addr_2 ; Store default allocation
B341 .set_ffffffff_load_addr←1← B338 BEQ
LDA #&ff ; A=&FF: fill OSFILE block
B343 STA wksp_osfile_exec_addr ; Set load addr to &FFFFFFFF
B346 STA wksp_osfile_exec_addr_1 ; Second byte
B349 STA wksp_osfile_exec_addr_2 ; Third byte
B34C STA wksp_osfile_exec_addr_3 ; Fourth byte
B34F LDX #&40 ; X=&40: OSFILE block offset
B351 STX zp_osfile_ptr_lo ; Store block pointer low
B353 LDY #&10 ; Y=&10: OSFILE block page
B355 STY zp_osfile_ptr_hi ; Store block pointer high
B357 JSR save_wksp_and_return ; Save workspace
B35A JSR validate_not_locked ; Create directory entry for new file
B35D JSR write_dir_and_validate ; Write directory to disc
B360 JSR get_object_type_result ; Save workspace after dir write
B363 LDA wksp_osfile_block ; Restore original filename pointer
B366 STA zp_text_ptr_lo ; Store in (&B4)
B368 LDA wksp_osfile_block_1 ; Get filename high byte
B36B STA zp_text_ptr_hi ; Store in (&B5)
B36D JSR find_first_matching_entry ; Search for newly created entry
B370 .set_ext_zero_for_new←1← B30F JMP
LDA #0 ; A=0: new file has zero length
B372 LDX zp_channel_offset ; Get channel index
B374 STA wksp_ch_ext_l,x ; Set EXT low = 0
B377 STA wksp_ch_ext_ml,x ; Set EXT mid-low = 0
B37A STA wksp_ch_ext_mh,x ; Set EXT mid-high = 0
B37D STA wksp_ch_ext_h,x ; Set EXT high = 0
B380 JMP copy_allocation_from_entry ; Jump to copy allocation and finish
B383 .close_file_handler←1← B1D1 JMP
LDY zp_osfind_y ; Get channel number (Y) from saved
B385 BNE close_all_complete ; Y non-zero: close specific channel
B387 TXA ; Y=0: close all - save X first
B388 PHA ; Push X on stack
B389 LDA #osbyte_close_spool_exec ; OSBYTE &77: close SPOOL and EXEC
B38B JSR osbyte ; Close any *SPOOL and *EXEC files
B38E PLA ; Restore X
B38F STA zp_osfind_x ; Store as saved X
B391 LDX #9 ; X=9: scan all channels
B393 .close_all_scan_loop←1← B399 BPL
LDA wksp_ch_flags,x ; Get channel flags
B396 BNE close_single_channel ; Flags=0: not open, skip
B398 .close_next_channel_loop←1← B3B1 BPL
DEX ; Next channel
B399 BPL close_all_scan_loop ; Loop for all 10 channels
B39B JSR wait_ensuring ; Wait while files are being ensured
B39E LDA #0 ; A=0: all closed
B3A0 LDX zp_osfind_x ; Restore X
B3A2 TAY ; Y=&00
B3A3 RTS ; Return
B3A4 .close_single_channel←1← B396 BNE
TXA ; Channel is open: get file handle
B3A5 CLC ; Clear carry for addition
B3A6 ADC #&30 ; Add &30 to get handle
B3A8 STA zp_text_ptr_hi ; Store handle
B3AA STX zp_channel_offset ; Store channel index
B3AC JSR close_and_update_dir ; Close this channel
B3AF LDX zp_channel_offset ; Restore channel index
B3B1 BPL close_next_channel_loop ; Continue scanning
B3B3 .close_all_complete←1← B385 BNE
JSR check_set_channel_y ; Validate and set channel number from Y
B3B6 .close_and_update_dir←1← B3AC JSR
JSR sync_ext_to_ptr ; Flush buffer if dirty
B3B9 LDY wksp_ch_flags,x ; Get channel flags
B3BC LDA #0 ; A=0: clear channel flags (closed)
B3BE STA wksp_ch_flags,x ; Mark channel as closed
B3C1 TYA ; Transfer old flags to A
B3C2 BPL close_read_only ; Bit 7 clear: was read-only
B3C4 LDA wksp_ch_ext_l,x ; Was writable: check if EXT changed
B3C7 CMP wksp_ch_alloc_l,x ; Compare EXT low with allocation low
B3CA BNE update_dir_entry_on_close ; Different: need to update dir entry
B3CC LDA wksp_ch_ext_ml,x ; Compare EXT mid-low
B3CF CMP wksp_ch_alloc_ml,x ; With allocation mid-low
B3D2 BNE update_dir_entry_on_close ; Different: update needed
B3D4 LDA wksp_ch_ext_mh,x ; Compare EXT mid-high
B3D7 CMP wksp_ch_alloc_mh,x ; With allocation mid-high
B3DA BNE update_dir_entry_on_close ; Different: update needed
B3DC LDA wksp_ch_ext_h,x ; Compare EXT high
B3DF CMP wksp_ch_alloc_h,x ; With allocation high
B3E2 BNE update_dir_entry_on_close ; Different: update needed
B3E4 .close_read_only←2← B3C2 BPL← B465 JMP
JSR validate_and_set_ptr ; EXT == alloc: no update needed
B3E7 JSR save_wksp_and_return ; Save workspace
B3EA LDA #0 ; A=0: success
B3EC LDY zp_osfind_y ; Restore Y
B3EE LDX zp_osfind_x ; Restore X
B3F0 RTS ; Return

Update directory entry on file close

Switch to the file's drive, calculate actual sectors used from EXT, then release unused allocation back to the free space map.

B3F1 .update_dir_entry_on_close←4← B3CA BNE← B3D2 BNE← B3DA BNE← B3E2 BNE
JSR switch_to_channel_drive ; Switch to file's drive
B3F4 LDA wksp_ch_ext_l,x ; Calculate sectors used from EXT
B3F7 CMP #1 ; Compare low byte with 1
B3F9 LDA wksp_object_sector ; Get object sector low
B3FC ADC wksp_ch_ext_ml,x ; Add EXT mid-low + carry
B3FF STA wksp_object_sector ; Store updated sector low
B402 LDA wksp_object_sector_mid ; Get sector mid
B405 ADC wksp_ch_ext_mh,x ; Add EXT mid-high + carry
B408 STA wksp_object_sector_mid ; Store updated sector mid
B40B LDA wksp_object_sector_hi ; Get sector high
B40E ADC wksp_ch_ext_h,x ; Add EXT high + carry
B411 STA wksp_object_sector_hi ; Store updated sector high
B414 LDA wksp_ch_alloc_l,x ; Calculate unused sectors to release
B417 CMP #1 ; Compare alloc low with 1
B419 LDA wksp_ch_alloc_ml,x ; Get alloc mid-low
B41C SBC wksp_ch_ext_ml,x ; Subtract EXT mid-low
B41F STA wksp_object_size ; Store unused low
B422 LDA wksp_ch_alloc_mh,x ; Get alloc mid-high
B425 SBC wksp_ch_ext_mh,x ; Subtract EXT mid-high
B428 STA wksp_object_size_mid ; Store unused mid
B42B LDA wksp_ch_alloc_h,x ; Get alloc high
B42E SBC wksp_ch_ext_h,x ; Subtract EXT high
B431 STA wksp_object_size_hi ; Store unused high
B434 LDA wksp_ch_ext_l,x ; Check if EXT has fractional sector
B437 BNE update_entry_length ; Non-zero: adjust sector count
B439 INC wksp_object_size ; Increment unused sector count
B43C BNE update_entry_length ; No wrap
B43E INC wksp_object_size_mid ; Wrap: increment mid
B441 BNE update_entry_length ; No wrap
B443 INC wksp_object_size_hi ; Wrap: increment high
B446 .update_entry_length←3← B437 BNE← B43C BNE← B441 BNE
LDA wksp_ch_ext_l,x ; Update dir entry with actual length
B449 LDY #&12 ; Y=&12: length field in entry
B44B STA (zp_osfile_ptr_lo),y ; Store EXT low in entry
B44D LDA wksp_ch_ext_ml,x ; Get EXT mid-low
B450 INY ; Y=&13
B451 STA (zp_osfile_ptr_lo),y ; Store in entry
B453 LDA wksp_ch_ext_mh,x ; Get EXT mid-high
B456 INY ; Y=&14
B457 STA (zp_osfile_ptr_lo),y ; Store in entry
B459 LDA wksp_ch_ext_h,x ; Get EXT high
B45C INY ; Y=&15
B45D STA (zp_osfile_ptr_lo),y ; Store in entry
B45F JSR release_disc_space ; Release disc space back to free space map
B462 JSR write_dir_and_validate ; Write updated directory to disc
B465 JMP close_read_only ; Jump to release space and return
B468 .check_channels_on_drive←1← 88B5 JSR
LDX #9 ; X=9: scan all channels
B46A .scan_drive_channels_loop←1← B47A BPL
LDA wksp_ch_flags,x ; Get channel flags
B46D BEQ no_channels_on_drive ; Not open: skip
B46F LDA wksp_ch_start_sec_h,x ; Get channel drive number
B472 AND #&e0 ; Isolate drive bits
B474 CMP wksp_current_drive ; Same drive as current?
B477 BEQ read_clock_and_compare ; Same drive: found one
B479 .no_channels_on_drive←1← B46D BEQ
DEX ; Next channel
B47A BPL scan_drive_channels_loop ; Loop for all 10
B47C .check_disc_changed←1← 9C26 JSR
LDA wksp_current_drive ; No channels on this drive
B47F JSR convert_drive_to_slot ; Get drive slot index
B482 LDA fsm_s1_disc_id_lo ; Get disc ID low from FSM
B485 STA wksp_disc_id_lo,x ; Store for comparison later
B488 LDA fsm_s1_disc_id_hi ; Get disc ID high from FSM
B48B STA wksp_disc_id_hi,x ; Store for comparison
B48E .read_clock_and_compare←2← AE06 JSR← B477 BEQ
JSR read_clock_for_timing ; Read clock and compare
B491 .get_drive_slot_index←1← B50D JMP
LDA wksp_current_drive ; Get current drive
B494 JSR convert_drive_to_slot ; Get drive slot index
B497 LDA fsm_s1_disc_id_lo ; Get disc ID low again
B49A CMP wksp_disc_id_lo,x ; Compare with saved
B49D BNE check_disc_id_changed ; Different: disc changed!
B49F LDA fsm_s1_disc_id_hi ; Get disc ID high
B4A2 CMP wksp_disc_id_hi,x ; Compare with saved
B4A5 BNE check_disc_id_changed ; Different: disc changed!
B4A7 JSR get_drive_bit_mask ; Get channel bit mask
B4AA STA wksp_drive_change_mask ; Store in workspace
B4AD RTS ; Return
B4AE .check_disc_id_changed←2← B49D BNE← B4A5 BNE
JSR reload_fsm_and_dir_then_brk ; Reload FSM and directory then raise error
B4B1 EQUB &C8 ; Error &C8: Disc changed
B4B2 EQUS "Disc changed."
B4BF .read_clock_for_timing←3← B48E JSR← B4F5 JSR← B525 JSR
LDA #osword_read_clock ; OSWORD 1: read system clock
B4C1 LDX #<(wksp_clock) ; X: control block low
B4C3 LDY #>(wksp_clock) ; Y: control block high
B4C5 JSR osword ; Read system clock
B4C8 LDX #0 ; X=0: compare 5 clock bytes
B4CA LDY #4 ; Y=4: 5 bytes to compare
B4CC SEC ; Set carry for subtraction
B4CD .compare_clock_bytes_loop←1← B4DD BPL
LDA wksp_clock,x ; Get current clock byte
B4D0 PHA ; Save current value
B4D1 SBC wksp_prev_clock,x ; Subtract previous value
B4D4 STA wksp_clock,x ; Store difference
B4D7 PLA ; Restore current value
B4D8 STA wksp_prev_clock,x ; Save as new previous
B4DB INX ; Next byte
B4DC DEY ; Decrement counter
B4DD BPL compare_clock_bytes_loop ; Loop for 5 bytes
B4DF LDA wksp_clock_4 ; Check if elapsed time > threshold
B4E2 ORA wksp_clock_3 ; OR with byte 3
B4E5 ORA wksp_clock_2 ; OR with byte 2
B4E8 BNE disc_probably_changed ; Non-zero high bytes: long time
B4EA LDA wksp_clock_1 ; Check byte 1
B4ED CMP #2 ; Less than 2 ticks?
B4EF BCC return_40 ; Yes: disc probably not changed
B4F1 .disc_probably_changed←1← B4E8 BNE
STY wksp_drive_change_mask ; Long time: set change flag
B4F4 .return_40←2← B4EF BCC← B504 BEQ
RTS ; Return

Check disc changed and reload FSM if needed

Read the system clock for disc-change timing, then check whether the current drive's disc has changed since last access. If changed, reload the FSM from disc.

B4F5 .check_drive_and_reload_fsm←4← 88CF JSR← 9FFD JSR← A252 JSR← B551 JSR
JSR read_clock_for_timing ; Read clock and check disc
B4F8 LDA wksp_current_drive ; Get current drive
B4FB JSR convert_drive_to_slot ; Get drive slot index
B4FE JSR get_drive_bit_mask ; Get channel bit mask
B501 EOR wksp_drive_change_mask ; XOR with stored mask
B504 BEQ return_40 ; Same: disc not changed
B506 LDX #&0c ; Changed: reload FSM
B508 LDY #&88 ; Y=&88: FSM control block
B50A JSR exec_disc_command ; Read FSM from disc
B50D JMP get_drive_slot_index ; Continue checking

Get bit mask for drive slot

Build a bit mask by rotating based on the drive slot index, then AND with drive-change flags.

On ExitAbit mask ANDed with wksp_drive_change_mask
Xcorrupted
Ypreserved
B510 .get_drive_bit_mask←3← B4A7 JSR← B4FE JSR← B52E JSR
LDA #&ff ; A=&FF: start with all bits set
B512 CLC ; Clear carry for shift
B513 .shift_drive_mask_loop←1← B516 BPL
ROL ; Shift left (rotate 0 in)
B514 DEX ; Decrement drive index by 2
B515 DEX ; Continue
B516 BPL shift_drive_mask_loop ; Loop until index < 0
B518 AND wksp_drive_change_mask ; AND with current change flags
B51B RTS ; Return bit mask in A

Set current drive from channel's drive

Extract drive bits from A, check disc-change timing, and reload the FSM if the drive's disc has changed.

B51C .set_drive_from_channel←3← AB2A JSR← AC8F JSR← B5B8 JSR
AND #&e0 ; Isolate drive bits from A
B51E STA wksp_clock_5 ; Store drive for later
B521 TXA ; Save X
B522 PHA ; Push on stack
B523 TYA ; Save Y
B524 PHA ; Push on stack
B525 JSR read_clock_for_timing ; Read clock for timing check
B528 LDA wksp_clock_5 ; Get stored drive
B52B JSR convert_drive_to_slot ; Get drive slot index
B52E JSR get_drive_bit_mask ; Get bit mask for this drive
B531 EOR wksp_drive_change_mask ; XOR with change flags
B534 BEQ restore_saved_drive ; Same: disc hasn't changed
B536 LDA wksp_clock_5 ; Different: need to reload FSM
B539 TAX ; Transfer to X
B53A PHA ; Save drive on stack
B53B LDA wksp_current_drive ; Save current drive
B53E STA wksp_clock_5 ; Store as temp drive
B541 LDY wksp_saved_drive ; Check saved drive
B544 CPY #&ff ; &FF: not set
B546 BNE save_and_restore_drive ; Set: don't overwrite
B548 STA wksp_saved_drive ; Save current as saved drive
B54B STY wksp_clock_5 ; Set temp to &FF
B54E .save_and_restore_drive←1← B546 BNE
STX wksp_current_drive ; Set current to new drive
B551 JSR check_drive_and_reload_fsm ; Reload FSM for new drive
B554 LDY wksp_clock_5 ; Get temp drive back
B557 STY wksp_current_drive ; Set as current drive
B55A CPY #&ff ; Was it &FF?
B55C BNE reload_fsm_for_drive ; No: keep it
B55E LDA wksp_saved_drive ; Restore saved drive
B561 STA wksp_current_drive ; Set as current
B564 STY wksp_saved_drive ; Restore saved drive as &FF
B567 .reload_fsm_for_drive←1← B55C BNE
PLA ; Restore original drive from stack
B568 CMP wksp_current_drive ; Compare with current
B56B BEQ restore_saved_drive ; Same: no FSM reload needed
B56D LDX #&0c ; Different: reload FSM for current
B56F LDY #&88 ; Y=&88: FSM control block
B571 JSR exec_disc_command ; Read FSM from disc
B574 .restore_saved_drive←2← B534 BEQ← B56B BEQ
PLA ; Restore Y from stack
B575 TAY ; Transfer to Y
B576 PLA ; Restore X from stack
B577 TAX ; Transfer to X
B578 RTS ; Return

Convert drive number to slot index

Shift drive number in A right 4 bits to produce a slot index in X.

On EntryAdrive number (bits 5-7)
On ExitAcorrupted
Xslot index (drive >> 4)
Ypreserved
B579 .convert_drive_to_slot←5← 8FB4 JSR← B47F JSR← B494 JSR← B4FB JSR← B52B JSR
LSR ; Shift drive right 4 positions
B57A LSR ; Second shift
B57B LSR ; Third shift
B57C LSR ; Fourth shift
B57D TAX ; Transfer to X as index
B57E RTS ; Return

OSGBPB handler

Handle OSGBPB calls for reading and writing groups of bytes.

B57F .osgbpb_handler
JSR save_workspace_state ; Save registers for restore
B582 STA wksp_osgbpb_func ; Store OSGBPB function code
B585 STA wksp_osgbpb_mode ; Store mode flag copy
B588 STY zp_gspb_ptr_hi ; Save control block pointer
B58A STX zp_gspb_ptr_lo ; Store control block pointer low
B58C LDY #1 ; Y=1: copy 4 bytes of memory addr
B58E LDX #3 ; X=3: 4 bytes to copy
B590 .copy_data_addr_loop←1← B597 BPL
LDA (zp_gspb_ptr_lo),y ; Copy data address from control blk
B592 STA wksp_osgbpb_end,y ; Store in workspace
B595 INY ; Next byte
B596 DEX ; Decrement counter
B597 BPL copy_data_addr_loop ; Loop for 4 bytes
B599 LDA wksp_osgbpb_func ; Get function code
B59C CMP #5 ; Function >= 5?
B59E BCC dispatch_dir_operations ; No, file I/O operations (1-4)
B5A0 JMP dispatch_dir_info_handler ; Yes, directory operations (5-8)
B5A3 .return_41←1← B5A5 BEQ
RTS ; Return (function 0: do nothing)
B5A4 .dispatch_dir_operations←1← B59E BCC
TAY ; Transfer function to Y
B5A5 BEQ return_41 ; Function 0: do nothing
B5A7 LDY #0 ; Y=0: get file handle from block
B5A9 LDA (zp_gspb_ptr_lo),y ; Read channel number from block+0
B5AB TAY ; Transfer function to Y
B5AC JSR check_set_channel_y ; Validate file handle
B5AF PHP ; Save flags for write check
B5B0 JSR sync_ext_to_ptr ; Flush buffer if dirty
B5B3 LDX zp_channel_offset ; Get channel index
B5B5 LDA wksp_ch_start_sec_h,x ; Get channel drive+sector
B5B8 JSR set_drive_from_channel ; Check disc change for drive
B5BB PLP ; Restore flags from earlier
B5BC BMI get_function_and_set_ptr ; Bit 7 set: writable channel
B5BE LDA wksp_osgbpb_func ; Get function code
B5C1 CMP #3 ; A >= 3 (read operation)?
B5C3 BCS get_function_and_set_ptr ; Yes: skip write check
B5C5 JMP not_open_for_update_error ; Write to read-only: error
B5C8 .get_function_and_set_ptr←2← B5BC BMI← B5C3 BCS
LDA wksp_osgbpb_func ; Get function code
B5CB AND #1 ; Bit 0 set = use new PTR (A=1,3)
B5CD BEQ set_ptr_from_temp ; Bit 0 clear = use current PTR
B5CF LDY #&0c ; Y=&0C: copy new PTR from block
B5D1 LDX #3 ; X=3: 4 PTR bytes
B5D3 .copy_new_ptr_loop←1← B5D9 BPL
LDA (zp_gspb_ptr_lo),y ; Get PTR byte from control block
B5D5 STA zp_temp_ptr,x ; Store in zp_c8-cb (temp PTR)
B5D7 DEY ; Next block byte (decreasing)
B5D8 DEX ; Next ZP byte (decreasing)
B5D9 BPL copy_new_ptr_loop ; Loop for 4 bytes
B5DB LDA #1 ; A=1: flag for new PTR
B5DD .set_ptr_from_temp←1← B5CD BEQ
LDY zp_save_y ; Restore Y from saved value
B5DF LDX #&c8 ; X=&C8: point to temp PTR in ZP
B5E1 JSR set_channel_and_dispatch ; Set PTR from temp PTR
B5E4 CLC ; Clear carry for addition
B5E5 LDX #3 ; X=3: add byte count to end PTR
B5E7 LDY #5 ; Y=5: byte count in control block
B5E9 .calc_end_position_loop←1← B5F3 BPL
LDA (zp_gspb_ptr_lo),y ; Get byte count byte
B5EB ADC zp_save_x,y ; Add to start PTR byte
B5EE STA wksp_osgbpb_end_ptr,y ; Store end position
B5F1 INY ; Next byte
B5F2 DEX ; Next count byte
B5F3 BPL calc_end_position_loop ; Loop for 4 bytes
B5F5 LDA wksp_osgbpb_func ; Get function code
B5F8 STA wksp_osgbpb_mode ; Store in mode flag
B5FB CMP #3 ; A >= 3 (read)?
B5FD BCS store_new_ptr_in_channel ; Yes: skip extent check
B5FF JSR extend_file_if_needed ; Write: extend file if needed
B602 .store_new_ptr_in_channel←1← B5FD BCS
LDY #9 ; Y=9: PTR offset in control block
B604 LDX zp_channel_offset ; Get channel index
B606 LDA wksp_new_ptr_lo ; Get new PTR low from workspace
B609 STA wksp_ch_ptr_l,x ; Store in channel PTR low
B60C STA (zp_gspb_ptr_lo),y ; Store in control block too
B60E INY ; Y=&0A: next byte
B60F LDA wksp_new_ptr_mid ; Get PTR mid-low
B612 STA wksp_ch_ptr_ml,x ; Store in channel
B615 STA (zp_gspb_ptr_lo),y ; Store in control block
B617 INY ; Y=&0B
B618 LDA wksp_new_ptr_mid_hi ; Get PTR mid-high
B61B STA wksp_ch_ptr_mh,x ; Store in channel
B61E STA (zp_gspb_ptr_lo),y ; Store in control block
B620 INY ; Y=&0C
B621 LDA wksp_new_ptr_hi ; Get PTR high
B624 STA wksp_ch_ptr_h,x ; Store in channel
B627 STA (zp_gspb_ptr_lo),y ; Store in control block
B629 LDA wksp_osgbpb_func ; Get function code
B62C CMP #3 ; A >= 3 (read)?
B62E BCS compare_ext_with_ptr ; Yes: skip to byte transfer
B630 .save_byte_count_for_write←2← B647 BCS← B649 BEQ
LDX #3 ; X=3: save 4-byte count
B632 LDY #5 ; Y=5: byte count in block
B634 .save_and_clear_count_loop←1← B63F BPL
LDA (zp_gspb_ptr_lo),y ; Get byte count from block
B636 STA wksp_saved_count,y ; Save in workspace
B639 LDA #0 ; A=0: clear byte count in block
B63B STA (zp_gspb_ptr_lo),y ; Store zero in block
B63D INY ; Next byte
B63E DEX ; Decrement counter
B63F BPL save_and_clear_count_loop ; Loop for 4 bytes
B641 JMP setup_disc_transfer ; Jump to byte transfer loop
B644 .compare_ext_with_ptr←1← B62E BCS
JSR compare_ext_to_ptr ; Compare EXT with PTR
B647 BCS save_byte_count_for_write ; C set: EXT > PTR, data available
B649 BEQ save_byte_count_for_write ; Equal: at EOF already
B64B LDA #0 ; A=0: clear mode flag (partial read)
B64D STA wksp_osgbpb_mode ; Store cleared mode
B650 LDX zp_channel_offset ; Get channel index
B652 SEC ; Calculate available = EXT - PTR
B653 LDA wksp_ch_ext_l,x ; EXT low - PTR low
B656 SBC zp_temp_ptr ; Subtract PTR byte
B658 STA wksp_osfile_block ; Store available low
B65B LDA wksp_ch_ext_ml,x ; EXT mid-low
B65E SBC zp_temp_ptr_1 ; Subtract PTR mid-low
B660 STA wksp_osfile_block_1 ; Store available mid-low
B663 LDA wksp_ch_ext_mh,x ; EXT mid-high
B666 SBC zp_temp_ptr_2 ; Subtract PTR mid-high
B668 STA wksp_osfile_load_addr ; Store available mid-high
B66B LDA wksp_ch_ext_h,x ; EXT high
B66E SBC zp_temp_ptr_3 ; Subtract PTR high
B670 STA wksp_osfile_load_addr_1 ; Store available high
B673 LDX #3 ; X=3: reduce requested by unavail
B675 LDY #5 ; Y=5: byte count in control block
B677 SEC ; Set carry for subtraction
B678 .reduce_count_to_available_loop←1← B681 BPL
LDA (zp_gspb_ptr_lo),y ; Get requested count byte
B67A SBC wksp_saved_count,y ; Subtract saved count byte
B67D STA (zp_gspb_ptr_lo),y ; Store reduced count in block
B67F INY ; Next byte
B680 DEX ; Next count byte
B681 BPL reduce_count_to_available_loop ; Loop for 4 bytes
B683 LDX zp_channel_offset ; Get channel index
B685 LDA wksp_ch_ext_l,x ; Get EXT low
B688 STA wksp_new_ptr_lo ; Store as new PTR low
B68B STA wksp_ch_ptr_l,x ; Update channel PTR low
B68E STA (zp_gspb_ptr_lo),y ; Store in control block
B690 INY ; Y=next byte
B691 LDA wksp_ch_ext_ml,x ; Get EXT mid-low
B694 STA wksp_new_ptr_mid ; Store as new PTR mid-low
B697 STA wksp_ch_ptr_ml,x ; Update channel PTR mid-low
B69A STA (zp_gspb_ptr_lo),y ; Store in control block
B69C INY ; Y=next byte
B69D LDA wksp_ch_ext_mh,x ; Get EXT mid-high
B6A0 STA wksp_new_ptr_mid_hi ; Store as new PTR mid-high
B6A3 STA wksp_ch_ptr_mh,x ; Update channel PTR mid-high
B6A6 STA (zp_gspb_ptr_lo),y ; Store in control block
B6A8 INY ; Y=next byte
B6A9 LDA wksp_ch_ext_h,x ; Get EXT high
B6AC STA wksp_new_ptr_hi ; Store as new PTR high
B6AF STA wksp_ch_ptr_h,x ; Update channel PTR high
B6B2 STA (zp_gspb_ptr_lo),y ; Store in control block
B6B4 .setup_disc_transfer←1← B641 JMP
LDY #1 ; Y=1: memory address in control block
B6B6 LDX #3 ; X=3: 4 address bytes
B6B8 CLC ; Clear carry for addition
B6B9 .update_control_block_addr_loop←1← B6C2 BPL
LDA wksp_alloc_size_hi,y ; Get transferred bytes count
B6BC ADC (zp_gspb_ptr_lo),y
B6BE STA (zp_gspb_ptr_lo),y ; Store updated memory address
B6C0 INY ; Next address byte
B6C1 DEX ; Next count byte
B6C2 BPL update_control_block_addr_loop ; Loop for 4 address bytes
B6C4 LDA zp_temp_ptr ; Get PTR high byte
B6C6 BNE calc_disc_sector_for_channel ; Non-zero: multi-sector possible
B6C8 JMP check_full_sectors_remain ; PTR high=0: no full sectors remain
B6CB .calc_disc_sector_for_channel←1← B6C6 BNE
LDX zp_channel_offset ; Get channel index for sector calc
B6CD CLC ; Clear carry for sector addition
B6CE LDA wksp_ch_start_sec_ml,x ; Get channel start sector low
B6D1 ADC zp_temp_ptr_1 ; Add PTR mid-low for disc sector
B6D3 STA wksp_osgbpb_sector_lo ; Store disc operation sector low
B6D6 LDA wksp_ch_start_sec_mh,x ; Get channel start sector mid
B6D9 ADC zp_temp_ptr_2 ; Add PTR mid-high
B6DB STA wksp_osgbpb_sector_mid ; Store disc operation sector mid
B6DE LDA wksp_ch_start_sec_h,x ; Get channel start sector+drive
B6E1 ADC zp_temp_ptr_3 ; Add PTR high byte
B6E3 STA wksp_osgbpb_sector_hi ; Store disc operation sector high
B6E6 LDA #2 ; A=2: compare against function code
B6E8 CMP wksp_osgbpb_func ; C set if A=1/2 (write), clear if 3/4
B6EB LDA #&80 ; A=&80: base for disc command
B6ED ROR ; Rotate C into bit 0: &40=read, &80=write
B6EE JSR find_buffer_for_sector ; Find/load buffer for current sector
B6F1 LDA zp_temp_ptr ; Get current byte offset in sector
B6F3 STA wksp_osgbpb_start ; Store as transfer start position
B6F6 LDA #0 ; A=0: default end position
B6F8 STA wksp_osgbpb_end ; Clear transfer end position
B6FB LDX #2 ; X=2: compare 3-byte buffer sector
B6FD .compare_buffer_sector_loop←1← B705 BPL
LDA wksp_new_ptr_mid,x ; Get buffered sector address byte
B700 CMP zp_temp_ptr_1,x ; Compare with requested sector byte
B702 BNE handle_buffer_mismatch ; Mismatch: different sector in buffer
B704 DEX ; Next sector address byte
B705 BPL compare_buffer_sector_loop ; Loop for 3-byte sector comparison
B707 LDA wksp_new_ptr_lo ; Sector match: get bytes remaining
B70A STA wksp_osgbpb_end ; Store as transfer end position
B70D JSR transfer_sector_bytes ; Transfer bytes within this sector
B710 .save_and_flush_after_transfer←2← B7E8 JMP← B822 JMP
JSR save_wksp_and_return ; Save workspace state
B713 JSR update_channel_flags_for_ptr ; Flush buffer if modified
B716 .prepare_osgbpb_return←1← B8DE JMP
LDA #0 ; A=0: prepare return status
B718 CMP wksp_osgbpb_mode ; Compare against mode flag for C
B71B LDX zp_gspb_ptr_lo ; Restore control block pointer low
B71D LDY zp_gspb_ptr_hi ; Restore control block pointer high
B71F RTS ; Return to OSGBPB caller
B720 .handle_buffer_mismatch←1← B702 BNE
JSR transfer_sector_bytes ; Buffer mismatch: handle partial xfer
B723 LDA #0 ; A=0: compute bytes already done
B725 SEC ; Set carry for subtraction
B726 SBC wksp_osgbpb_start ; Subtract start position
B729 STA wksp_osgbpb_start ; Store bytes transferred this pass
B72C CLC ; Clear carry for addition
B72D ADC wksp_osgbpb_data_addr ; Add to cumulative data address low
B730 STA wksp_osgbpb_data_addr ; Store updated address low
B733 BCC adjust_remaining_count ; No carry: skip higher bytes
B735 INC wksp_osgbpb_data_addr_1 ; Propagate carry to address byte 2
B738 BNE adjust_remaining_count ; No carry: skip
B73A INC wksp_osgbpb_data_addr_2 ; Propagate carry to address byte 3
B73D BNE adjust_remaining_count ; No carry: skip
B73F INC wksp_osgbpb_data_addr_3 ; Propagate carry to address byte 4
B742 .adjust_remaining_count←3← B733 BCC← B738 BNE← B73D BNE
SEC ; Subtract from remaining byte count
B743 LDA wksp_osfile_block ; Get remaining count low
B746 SBC wksp_osgbpb_start ; Subtract bytes transferred
B749 STA wksp_osfile_block ; Store updated remaining count
B74C BCS check_full_sectors_remain ; No borrow: count still positive
B74E LDY #1 ; Y=1: propagate borrow to higher bytes
B750 .propagate_borrow_loop←1← B75B BNE
LDA wksp_osfile_block,y ; Get remaining count byte
B753 SBC #0 ; Subtract borrow
B755 STA wksp_osfile_block,y ; Store updated count byte
B758 BCS check_full_sectors_remain ; No borrow: done adjusting
B75A INY ; Next count byte
B75B BNE propagate_borrow_loop ; Loop for remaining bytes
B75D .check_full_sectors_remain←3← B6C8 JMP← B74C BCS← B758 BCS
LDA wksp_osfile_block_1 ; Check if any full sectors to transfer
B760 ORA wksp_osfile_load_addr ; OR mid-low count byte
B763 ORA wksp_osfile_load_addr_1 ; OR mid-high count byte
B766 BNE setup_disc_op_block ; Non-zero: full sectors remain
B768 JMP check_remaining_buffered ; No full sectors: finish transfer
B76B .setup_disc_op_block←1← B766 BNE
LDA #1 ; A=1: flag multi-sector disc operation
B76D STA wksp_disc_op_result ; Store in disc op result field
B770 LDY #3 ; Y=3: copy 4-byte data address
B772 .copy_data_addr_to_disc_op_loop←1← B779 BPL
LDA wksp_osgbpb_data_addr,y ; Get data address byte
B775 STA wksp_disc_op_mem_addr,y ; Store in disc op memory address
B778 DEY ; Next byte (decreasing)
B779 BPL copy_data_addr_to_disc_op_loop ; Loop for 4 bytes
B77B LDA #2 ; A=2: compare against function code
B77D CMP wksp_osgbpb_func ; C set if write (A<=2), clear if read
B780 LDA #2 ; A=2: base for disc command
B782 ROL ; Rotate C into bit 0
B783 ROL ; Shift to command position
B784 STA wksp_disc_op_command ; Store read/write disc command
B787 LDX zp_channel_offset ; Get channel index
B789 LDA zp_temp_ptr ; Get PTR low (byte offset in sector)
B78B CMP #1 ; Compare with 1 to set carry
B78D LDA wksp_ch_start_sec_ml,x ; Get channel start sector low
B790 ADC zp_temp_ptr_1 ; Add PTR mid-low for disc sector
B792 STA wksp_disc_op_sector_lo ; Store disc op sector low byte
B795 LDA wksp_ch_start_sec_mh,x ; Get channel start sector mid
B798 ADC zp_temp_ptr_2 ; Add PTR mid-high
B79A STA wksp_disc_op_sector_mid ; Store disc op sector mid byte
B79D LDA wksp_ch_start_sec_h,x ; Get channel start sector+drive
B7A0 ADC zp_temp_ptr_3 ; Add PTR high
B7A2 STA wksp_disc_op_sector ; Store disc op sector high byte
B7A5 LDY #4 ; Y=4: save 5 bytes of CSD state
B7A7 .save_csd_state_loop←1← B7AE BNE
LDA wksp_csd_sector,y ; Get CSD sector/drive byte
B7AA STA wksp_csd_sector_temp,y ; Save in temp workspace
B7AD DEY ; Next byte (decreasing)
B7AE BNE save_csd_state_loop ; Loop for 5 bytes
B7B0 STY wksp_current_drive ; Clear current drive (Y=0)
B7B3 STY wksp_disc_op_sector_count ; Clear disc op sector count
B7B6 STY wksp_disc_op_control ; Clear disc op control byte
B7B9 STY wksp_disc_op_transfer_len ; Clear disc op transfer length
B7BC CLC ; Clear carry for sector calculation
B7BD LDX #2 ; X=2: add 3-byte sector count
B7BF .add_sector_count_loop←1← B7CD BPL
LDA wksp_osfile_block_1,y ; Get remaining count byte
B7C2 STA wksp_disc_op_xfer_len_1,y ; Copy to disc op transfer length
B7C5 ADC wksp_osgbpb_data_addr_1,y ; Add to cumulative address
B7C8 STA wksp_osgbpb_data_addr_1,y ; Store updated address
B7CB INY ; Next byte
B7CC DEX ; Next sector byte
B7CD BPL add_sector_count_loop ; Loop for 3 bytes
B7CF JSR validate_and_set_ptr ; Flush channel ensure buffers
B7D2 JSR multi_sector_disc_command ; Execute multi-sector disc command
B7D5 LDA wksp_saved_drive ; Restore saved drive number
B7D8 STA wksp_current_drive ; Set as current drive
B7DB LDA #&ff ; A=&FF: mark saved drive as unused
B7DD STA wksp_saved_drive ; Store in saved drive slot
B7E0 STA wksp_alt_sector_hi ; Mark alt workspace as unused
B7E3 .check_remaining_buffered←1← B768 JMP
LDA wksp_new_ptr_lo ; Check for remaining buffered bytes
B7E6 BNE calc_remaining_sector ; Non-zero: more bytes in buffer
B7E8 JMP save_and_flush_after_transfer ; Zero: finish via save and return
B7EB .calc_remaining_sector←1← B7E6 BNE
LDX zp_channel_offset ; Get channel index
B7ED CLC ; Clear carry for sector addition
B7EE LDA wksp_ch_start_sec_ml,x ; Get channel start sector low
B7F1 ADC wksp_new_ptr_mid ; Add remaining PTR low
B7F4 STA wksp_osgbpb_sector_lo ; Store result sector low
B7F7 LDA wksp_ch_start_sec_mh,x ; Get channel start sector mid
B7FA ADC wksp_new_ptr_mid_hi ; Add remaining PTR mid
B7FD STA wksp_osgbpb_sector_mid ; Store result sector mid
B800 LDA wksp_ch_start_sec_h,x ; Get channel start sector+drive
B803 ADC wksp_new_ptr_hi ; Add remaining PTR high
B806 STA wksp_osgbpb_sector_hi ; Store result sector high
B809 LDA #2 ; A=2: compare against function code
B80B CMP wksp_osgbpb_func ; C set if write, clear if read
B80E LDA #&80 ; A=&80: base disc command
B810 ROR ; Rotate C to form read/write command
B811 JSR find_buffer_for_sector ; Find/load buffer for remaining sector
B814 LDA #0 ; A=0: clear start position
B816 STA wksp_osgbpb_start ; Store start at beginning of sector
B819 LDA wksp_new_ptr_lo ; Get bytes remaining in buffer
B81C STA wksp_osgbpb_end ; Store as transfer end position
B81F JSR transfer_sector_bytes ; Transfer remaining bytes in sector
B822 JMP save_and_flush_after_transfer ; Finish via save and return

Set up OSGBPB output buffer

Configure the output buffer for OSGBPB A=5-8. Claims the Tube if the target address is in second processor memory.

B825 .setup_osgbpb_output_buffer←4← B8A1 JSR← B8E1 JSR← B905 JSR← B920 JSR
BIT zp_adfs_flags ; Tube in use (bit 7 of flags)?
B827 BPL setup_output_pointer ; No Tube: skip to buffer setup
B829 LDA wksp_osgbpb_data_addr_2 ; Get output address byte 3
B82C CMP #&fe ; Address < &FE00?
B82E BCC claim_tube_for_output ; Yes: second processor, claim Tube
B830 LDA wksp_osgbpb_data_addr_3 ; Get output address byte 4
B833 CMP #&ff ; Address = &FFxx (host memory)?
B835 BEQ setup_output_pointer ; Yes: skip Tube claim
B837 .claim_tube_for_output←1← B82E BCC
PHP ; Save flags for restore after Tube
B838 SEI ; Disable interrupts for Tube claim
B839 JSR claim_tube_retry ; Claim Tube for transfer
B83C LDA zp_adfs_flags ; Set bit 6: Tube data transfer active
B83E ORA #&40 ; OR with &40 flag
B840 STA zp_adfs_flags ; Store updated flags
B842 LDA #1 ; A=1: Tube read transfer type
B844 LDX #&b8 ; X=&B8: Tube address workspace low
B846 LDY #&10 ; Y=&10: Tube address workspace high
B848 JSR tube_entry ; Start Tube transfer
B84B PLP ; Restore flags (re-enable interrupts)
B84C .setup_output_pointer←2← B827 BPL← B835 BEQ
LDA #0 ; A=0: clear output byte counter
B84E STA zp_buf_src_hi ; Store zero in output byte counter
B850 LDA wksp_osgbpb_data_addr ; Get output address low byte
B853 STA zp_mem_ptr_lo ; Store in output pointer low
B855 LDA wksp_osgbpb_data_addr_1 ; Get output address high byte
B858 STA zp_mem_ptr_hi ; Store in output pointer high
B85A RTS ; Return (buffer ready)

Output byte to Tube or host buffer

Write byte in A to the OSGBPB output destination. If Tube is active, sends via Tube R3; otherwise stores via (zp_mem_ptr) indirect and advances the byte counter.

On EntryAbyte to output
On ExitApreserved
Xpreserved
Ypreserved
B85B .output_byte_to_buffer←9← B874 JSR← B889 JSR← B8B5 JSR← B8C4 JSR← B8CE JSR← B8D8 JSR← B8E6 JSR← B902 JMP← B90A JSR
BIT zp_adfs_flags ; Tube active (V flag)?
B85D BVC output_byte_direct ; No: write to host memory
B85F STA tube_data_register_3 ; Write byte to Tube R3 data register
B862 RTS ; Return
B863 .output_byte_direct←1← B85D BVC
STY zp_buf_src_lo ; Save Y (caller's index)
B865 LDY zp_buf_src_hi ; Get output byte counter as offset
B867 STA (zp_mem_ptr_lo),y ; Store byte at (zp_b2)+offset
B869 INC zp_buf_src_hi ; Increment output byte counter
B86B BNE restore_caller_y ; No page crossing: restore Y
B86D INC zp_mem_ptr_hi ; Page crossed: increment pointer high
B86F .restore_caller_y←1← B86B BNE
LDY zp_buf_src_lo ; Restore Y (caller's index)
B871 RTS ; Return

Output 10-byte directory entry name

Write name length byte then 10 characters from (zp_text_ptr), replacing control chars with spaces.

B872 .output_dir_entry_name←3← B8F7 JSR← B91B JSR← B95D JSR
LDA #&0a ; A=&0A: name is 10 bytes long
B874 JSR output_byte_to_buffer ; Output name length byte
B877 SEC ; Set carry for first iteration
B878 LDX #9 ; X=9: countdown for 10 name bytes
B87A LDY #&ff ; Y=&FF: will increment to 0 first
B87C .output_name_char_loop←1← B88D BPL
INY ; Next name byte position
B87D BCC output_printable_char ; C clear from prev: skip fetch
B87F LDA (zp_text_ptr_lo),y ; Get name byte from entry
B881 AND #&7f ; Strip bit 7 (attribute flags)
B883 CMP #&21 ; Printable character (>= '!')?
B885 BCS output_printable_char ; Yes: output as-is
B887 LDA #&20 ; Control char: replace with space
B889 .output_printable_char←2← B87D BCC← B885 BCS
JSR output_byte_to_buffer ; Output character to buffer/Tube
B88C DEX ; Next character
B88D BPL output_name_char_loop ; Loop for 10 characters
B88F RTS ; Return
B890 .dispatch_dir_info_handler←1← B5A0 JMP
SBC #5 ; Subtract 5 to get sub-function 0-3
B892 TAY ; Transfer to Y for dispatch
B893 BEQ read_dir_title_handler ; Y=0 (A=5): read title/boot/drive
B895 DEY ; Decrement for next check
B896 BEQ read_csd_name_handler ; Y=0 (A=6): read CSD name
B898 DEY ; Decrement for next check
B899 BEQ read_lib_name_handler ; Y=0 (A=7): read library name
B89B DEY ; Decrement for next check
B89C BNE release_tube_and_return ; Y!=0: invalid sub-function, exit
B89E JMP read_filenames_handler ; A=8: read filenames from CSD
B8A1 .read_dir_title_handler←1← B893 BEQ
JSR setup_osgbpb_output_buffer ; Set up output buffer/Tube
B8A4 LDY #&ff ; Y=&FF: will increment to 0 first
B8A6 .scan_title_length_loop←1← B8B2 BNE
INY ; Next title byte
B8A7 LDA dir_title,y ; Get directory title character
B8AA AND #&7f ; Strip bit 7
B8AC CMP #&20 ; Printable (>= space)?
B8AE BCC output_title_length ; Control char: end of title
B8B0 CPY #&13 ; Reached max 19 chars?
B8B2 BNE scan_title_length_loop ; No: continue scanning title
B8B4 .output_title_length←1← B8AE BCC
TYA ; Output title length byte
B8B5 JSR output_byte_to_buffer ; Write length to buffer/Tube
B8B8 LDY #&ff ; Y=&FF: will increment to 0 first
B8BA .output_title_chars_loop←1← B8C9 BNE
INY ; Next title byte
B8BB LDA dir_title,y ; Get directory title character
B8BE AND #&7f ; Strip bit 7
B8C0 CMP #&20 ; Printable (>= space)?
B8C2 BCC output_boot_and_drive ; Control char: done outputting title
B8C4 JSR output_byte_to_buffer ; Output title character
B8C7 CPY #&13 ; Reached max 19 chars?
B8C9 BNE output_title_chars_loop ; No: continue outputting
B8CB .output_boot_and_drive←1← B8C2 BCC
LDA fsm_s1_boot_option ; Get boot option from FSM sector 1
B8CE JSR output_byte_to_buffer ; Output boot option byte
B8D1 LDA wksp_current_drive ; Get current drive number
B8D4 ASL ; Shift drive into low 3 bits
B8D5 ROL ; Second shift
B8D6 ROL ; Third shift
B8D7 ROL ; Fourth shift (now in bits 0-2)
B8D8 JSR output_byte_to_buffer ; Output drive number byte
B8DB .release_tube_and_return←6← B89C BNE← B8FA BMI← B91E BMI← B933 BEQ← B93D BCS← B97D JMP
JSR release_tube ; Release Tube if in use
B8DE JMP prepare_osgbpb_return ; Return via OSGBPB exit path
B8E1 .read_csd_name_handler←1← B896 BEQ
JSR setup_osgbpb_output_buffer ; Set up output buffer/Tube
B8E4 LDA #1 ; A=1: drive prefix is 1 char long
B8E6 JSR output_byte_to_buffer ; Output drive prefix length
B8E9 LDA wksp_current_drive ; Get current drive number
B8EC JSR drive_to_ascii_digit ; Convert drive to ASCII digit
B8EF LDA #0 ; A=0: CSD name starts at offset 0
B8F1 STA zp_text_ptr_lo ; Store CSD name pointer low
B8F3 LDA #&11 ; A=&11: CSD name is at &1100
B8F5 STA zp_text_ptr_hi ; Store CSD name pointer high
B8F7 JSR output_dir_entry_name ; Output 10-byte CSD directory name
B8FA BMI release_tube_and_return ; Exit via cleanup
B8FC .drive_to_ascii_digit←2← B8EC JSR← B910 JSR
ASL ; Shift drive into high nibble
B8FD ROL ; Continue shift
B8FE ROL ; Continue shift
B8FF ROL ; Continue shift (now in bits 4-7)
B900 ADC #&30 ; Add &30 for ASCII '0'
B902 JMP output_byte_to_buffer ; Output via cb85b
B905 .read_lib_name_handler←1← B899 BEQ
JSR setup_osgbpb_output_buffer ; Set up output buffer/Tube
B908 LDA #1 ; A=1: drive prefix is 1 char long
B90A JSR output_byte_to_buffer ; Output drive prefix length
B90D LDA wksp_lib_sector_hi ; Get library drive number
B910 JSR drive_to_ascii_digit ; Convert drive to ASCII digit
B913 LDA #&0a ; A=&0A: library name at offset &0A
B915 STA zp_text_ptr_lo ; Store library name pointer low
B917 LDA #&11 ; A=&11: library name is at &110A
B919 STA zp_text_ptr_hi ; Store library name pointer high
B91B JSR output_dir_entry_name ; Output 10-byte library dir name
B91E BMI release_tube_and_return ; Exit via cleanup
B920 .read_filenames_handler←1← B89E JMP
JSR setup_osgbpb_output_buffer ; Set up output buffer/Tube
B923 LDY #0 ; Y=0: clear result counter
B925 STY wksp_osgbpb_mode ; Clear result file count
B928 LDA dir_master_sequence ; Get directory sequence number
B92B STA (zp_gspb_ptr_lo),y ; Store in control block byte 0
B92D LDY #5 ; Y=5: get requested count from block
B92F LDA (zp_gspb_ptr_lo),y ; Get requested entry count
B931 STA zp_ctrl_blk_lo ; Store as entries remaining
B933 BEQ release_tube_and_return ; Zero entries requested: done
B935 LDY #9 ; Y=9: get start index from block
B937 LDA (zp_gspb_ptr_lo),y ; Get starting entry index
B939 STA zp_ctrl_blk_hi ; Store as current entry counter
B93B CMP #&2f ; Index >= 47? Past max entries
B93D BCS release_tube_and_return ; Yes: exit (no more entries)
B93F TAX ; Transfer index to X for loop
B940 CLC ; Clear carry for pointer arithmetic
B941 LDA #5 ; A=5: first entry at offset &1205
B943 LDY #&12 ; Y=&12: directory buffer page
B945 .skip_to_start_entry←2← B94A BCC← B94E BCC
DEX ; Decrement entries to skip
B946 BMI set_entry_pointer ; Skipped enough: start reading
B948 ADC #&1a ; Add &1A (26 bytes per dir entry)
B94A BCC skip_to_start_entry ; No page crossing: continue
B94C INY ; Page crossing: increment page
B94D CLC ; Clear carry for next addition
B94E BCC skip_to_start_entry ; Continue skipping entries
B950 .set_entry_pointer←1← B946 BMI
STY zp_text_ptr_hi ; Store entry pointer high
B952 STA zp_text_ptr_lo ; Store entry pointer low
B954 .output_entries_loop←1← B96F BNE
LDY #0 ; Y=0: check first byte of entry
B956 LDA (zp_text_ptr_lo),y ; Get entry name byte 0
B958 STA wksp_osgbpb_mode ; Store as non-zero check for output
B95B BEQ store_remaining_count ; Zero: end of directory entries
B95D JSR output_dir_entry_name ; Output 10-byte entry name
B960 LDA zp_text_ptr_lo ; Get entry pointer low
B962 CLC ; Clear carry for addition
B963 ADC #&1a ; Add &1A to advance to next entry
B965 STA zp_text_ptr_lo ; Store updated entry pointer low
B967 BCC advance_entry_index ; No page crossing
B969 INC zp_text_ptr_hi ; Page crossing: increment high byte
B96B .advance_entry_index←1← B967 BCC
INC zp_ctrl_blk_hi ; Increment current entry index
B96D DEC zp_ctrl_blk_lo ; Decrement remaining count
B96F BNE output_entries_loop ; More entries to read: continue
B971 .store_remaining_count←1← B95B BEQ
LDY #5 ; Y=5: update remaining count in block
B973 LDA zp_ctrl_blk_lo ; Get remaining entries count
B975 STA (zp_gspb_ptr_lo),y ; Store in control block byte 5
B977 LDY #9 ; Y=9: update current index in block
B979 LDA zp_ctrl_blk_hi ; Get current entry index
B97B STA (zp_gspb_ptr_lo),y ; Store in control block byte 9
B97D JMP release_tube_and_return ; Exit via cleanup and return

Transfer sector bytes between buffer and memory

Copy bytes from position l10b6 to l10b7 within the current sector buffer, routing through direct memory, indirect via (zp_buf_dest), or the Tube.

B980 .transfer_sector_bytes←3← B70D JSR← B720 JSR← B81F JSR
LDA wksp_osgbpb_start ; Get transfer start position
B983 CMP wksp_osgbpb_end ; Compare with end position
B986 BNE claim_tube_for_sector ; Not equal: bytes to transfer
B988 RTS ; Equal: no bytes to transfer, return
B989 .claim_tube_for_sector←1← B986 BNE
PHP ; Save flags for Tube check
B98A SEI ; Disable interrupts for Tube setup
B98B BIT zp_adfs_flags ; Tube in use (bit 7 of flags)?
B98D BPL setup_buffer_pointers ; No Tube: skip to direct transfer
B98F LDA wksp_osgbpb_data_addr_2 ; Get Tube address byte 3
B992 CMP #&fe ; Address < &FE00?
B994 BCC set_tube_transfer_flag ; Yes: Tube address, claim it
B996 LDA wksp_osgbpb_data_addr_3 ; Get Tube address byte 4
B999 CMP #&ff ; Address = &FFxx (host memory)?
B99B BEQ setup_buffer_pointers ; Yes: skip Tube claim
B99D .set_tube_transfer_flag←1← B994 BCC
LDA zp_adfs_flags ; Set bit 6: Tube transfer active
B99F ORA #&40 ; OR with &40 flag
B9A1 STA zp_adfs_flags ; Store updated flags
B9A3 JSR claim_tube_retry ; Claim Tube for transfer
B9A6 LDA wksp_osgbpb_func ; Get OSGBPB function code
B9A9 CMP #3 ; C set if A>=3 (read from file)
B9AB LDA #0 ; A=0: base for Tube direction
B9AD ROL ; Rotate C to set direction bit
B9AE LDX #&b8 ; X=&B8: Tube address workspace low
B9B0 LDY #&10 ; Y=&10: Tube address workspace high
B9B2 JSR tube_entry ; Start Tube transfer
B9B5 .setup_buffer_pointers←2← B98D BPL← B99B BEQ
PLP ; Restore flags
B9B6 LDA wksp_osgbpb_data_addr ; Get data address low
B9B9 SEC ; Set carry for subtraction
B9BA SBC wksp_osgbpb_start ; Subtract start offset for buffer ptr
B9BD STA zp_mem_ptr_lo ; Store buffer pointer low
B9BF LDA wksp_osgbpb_data_addr_1 ; Get data address high
B9C2 SBC #0 ; Subtract borrow
B9C4 STA zp_mem_ptr_hi ; Store buffer pointer high
B9C6 LDA wksp_osgbpb_func ; Get OSGBPB function code
B9C9 CMP #3 ; C set if A>=3 (read from file)
B9CB LDY wksp_osgbpb_start ; Get start position as byte index
B9CE PHP ; Save read/write direction flag
B9CF .copy_byte_loop←1← B9F8 BNE
PLP ; Restore direction flag
B9D0 BIT zp_adfs_flags ; Tube active (V flag)?
B9D2 BVS tube_byte_transfer ; Yes: use Tube data path
B9D4 BCC write_byte_from_memory ; C set: reading from file to memory
B9D6 LDA (zp_buf_dest_lo),y ; Read: get byte from sector buffer
B9D8 STA (zp_mem_ptr_lo),y ; Write to user memory
B9DA BCS advance_byte_position ; Always branch to advance
B9DC .write_byte_from_memory←1← B9D4 BCC
LDA (zp_mem_ptr_lo),y ; Write: get byte from user memory
B9DE STA (zp_buf_dest_lo),y ; Store in sector buffer
B9E0 BCC advance_byte_position ; Always branch to advance
B9E2 .tube_byte_transfer←1← B9D2 BVS
JSR tube_delay2 ; Tube: delay for synchronisation
B9E5 BCC read_byte_from_tube ; C clear: writing to file from Tube
B9E7 LDA (zp_buf_dest_lo),y ; Read file: get byte from buffer
B9E9 STA tube_data_register_3 ; Write to Tube R4
B9EC BCS advance_byte_position ; Always branch to advance
B9EE .read_byte_from_tube←1← B9E5 BCC
LDA tube_data_register_3 ; Write file: read byte from Tube R4
B9F1 STA (zp_buf_dest_lo),y ; Store in sector buffer
B9F3 .advance_byte_position←3← B9DA BCS← B9E0 BCC← B9EC BCS
INY ; Next byte position
B9F4 PHP ; Save direction flag for next byte
B9F5 CPY wksp_osgbpb_end ; Reached end position?
B9F8 BNE copy_byte_loop ; No: continue copying
B9FA PLP ; Restore flags
B9FB JMP release_tube ; Release Tube and return
B9FE EQUS ".." ; Unused "." + CR: dead remnant

Floppy disc command (indirect entry)

Indirect entry point for floppy disc operations. Jumps through to floppy_command.

BA00 .floppy_command_ind←1← 80CC JSR
JMP floppy_command ; Execute floppy disc command
BA03 .exec_floppy_partial_sector_buf_ind←1← 8B3E JMP
JMP exec_floppy_partial_sector_buf
BA06 .exec_floppy_write_bput_sector_ind←1← AB3F JSR
JMP exec_floppy_write_bput_sector
BA09 .exec_floppy_read_bput_sector_ind←1← ACA6 JSR
JMP exec_floppy_read_bput_sector
BA0C .mark_partial_transfer←1← 9BCA JSR
LDA #&ff ; A=&FF: mark transfer state
BA0E STA wksp_fdc_head_state ; Store in transfer workspace
fall through ↓

Check floppy disc hardware present

Test whether the WD1770 floppy disc controller is present by probing its registers.

On ExitAcorrupted (C set if present, clear if not)
Xpreserved
Ypreserved
BA11 .floppy_check_present←1← 9ACF JSR
LDA #&5a ; Write &5A to WD1770 track register
BA13 STA fdc_1770_track ; Write to FDC track register
BA16 LDA fdc_1770_track ; Read back from track register
BA19 CMP #&5a ; Does it match &5A?
BA1B BNE return_42 ; No: WD1770 not present, return C=1
BA1D LDA fdc_1770_drive_control ; Read drive control register
BA20 AND #3 ; Check drive select bits (0-1)
BA22 BEQ return_42 ; Both zero: no drive, return C=1
BA24 CLC ; WD1770 present: C=0
BA25 .return_42←2← BA1B BNE← BA22 BEQ
RTS ; Return (C=1: not present)
BA26 .exec_floppy_write_bput_sector←1← BA06 JMP
LDA #&40 ; A=&40: write direction flag
BA28 BNE store_direction_flag ; ALWAYS branch
BA2A .exec_floppy_read_bput_sector←1← BA09 JMP
LDA #&c0 ; A=&C0: read direction flag
BA2C .store_direction_flag←1← BA28 BNE
STA wksp_fdc_xfer_mode ; Store direction in workspace
BA2F TXA ; Transfer X to A
BA30 TSX ; Save current stack pointer
BA31 STX wksp_stack_save ; For error recovery
BA34 PHA ; Save X on stack
BA35 JSR floppy_get_step_rate ; Get disc step rate from settings
BA38 JSR claim_nmi_and_init ; Set up drive select and step rate
BA3B PLA ; Restore X
BA3C TAX ; Transfer to X
BA3D BIT zp_floppy_control ; Check read/write direction
BA3F BMI set_buffer_addr_for_read ; Reading: set up read buffer address
BA41 LDA zp_buf_src_lo ; Writing: use zp_bc,bd as buffer
BA43 STA nmi_write_addr_lo ; Patch NMI handler buffer addr low
BA46 LDA zp_buf_src_hi ; Buffer address high byte
BA48 STA nmi_write_addr_hi ; Patch NMI handler buffer addr high
BA4B BNE get_sector_count ; Always branch (high byte non-zero)
BA4D .set_buffer_addr_for_read←1← BA3F BMI
LDA zp_buf_dest_lo ; Reading: use zp_be,bf as buffer
BA4F STA nmi_read_addr_lo ; Patch NMI read buffer addr low
BA52 LDA zp_buf_dest_hi ; Get read buffer addr high
BA54 STA nmi_read_addr_hi ; Patch NMI read buffer addr high
BA57 .get_sector_count←1← BA4B BNE
LDA wksp_buf_sec_hi,x ; Get sector count from control block
BA5A PHA ; Save sector count on stack
BA5B AND #&1f ; Check drive number bits
BA5D BEQ set_drive_1_select ; Drive 0: continue
BA5F .check_drive_number←1← BA67 BNE
PLA ; Pop and discard
BA60 JMP bad_address_error ; Jump to error: bad drive number
BA63 .set_drive_1_select←1← BA5D BEQ
PLA ; Pop sector count
BA64 PHA ; Re-push for later
BA65 AND #&40 ; Check format bit
BA67 BNE check_drive_number ; Non-zero format bit: error
BA69 PLA ; Pop sector count
BA6A AND #&20 ; Check verify bit
BA6C BNE check_format_command ; Non-zero verify bit: use verify cmd
BA6E LDA #&21 ; Not verify: seek+read (&21)
BA70 BNE set_read_write_command ; ALWAYS branch
BA72 .check_format_command←1← BA6C BNE
LDA #&22 ; Verify: seek+read (&22)
BA74 .set_read_write_command←1← BA70 BNE
STA nmi_drive_ctrl ; Store in NMI control byte
BA77 ROR wksp_fdc_head_state ; Set head-loaded flag in state
BA7A SEC ; Set carry
BA7B ROL wksp_fdc_head_state ; Restore head-loaded flag
BA7E LDA wksp_buf_sec_lo,x ; Get sector address from control blk
BA81 PHA ; Save sector address on stack
BA82 LDA wksp_buf_sec_mid,x ; Get sector address mid byte
BA85 TAX ; X = sector address high byte
BA86 PLA ; Restore sector address
BA87 LDY #&ff ; Y=&FF: init track counter
BA89 JSR xa_div_16_to_ya ; Convert sector to track/sector
BA8C STA zp_floppy_sector ; Store sector number
BA8E STY zp_floppy_track_num ; Store track number
BA90 TYA ; Track to A for side check
BA91 SEC ; Set carry for track calculation
BA92 SBC #&50 ; Subtract 80 (side 0 tracks)
BA94 BMI set_fdc_control_byte ; Track < 80: side 0
BA96 STA zp_floppy_track_num ; Track >= 80: adjust for side 1
BA98 JSR floppy_set_side_1 ; Select side 1
BA9B .set_fdc_control_byte←1← BA94 BMI
LDA nmi_drive_ctrl ; Get NMI control byte
BA9E STA fdc_1770_drive_control ; Write to FDC control register
BAA1 ROR ; Rotate drive select into carry
BAA2 BCC set_track_and_sector ; C=0: not last sector, continue
BAA4 LDA wksp_fdc_track_0 ; Get previous track for drive
BAA7 STA zp_floppy_track ; Store as target track
BAA9 BIT wksp_fdc_head_state ; Check head-loaded state
BAAC BPL setup_nmi_for_transfer ; Head loaded: skip restore
BAAE BMI seek_to_track_0 ; ALWAYS branch
BAB0 .set_track_and_sector←1← BAA2 BCC
LDA wksp_fdc_track_1 ; Get alternative track for drive
BAB3 STA zp_floppy_track ; Store as target track
BAB5 BIT wksp_fdc_head_state ; Check head-loaded state
BAB8 BVC setup_nmi_for_transfer ; Not loaded: skip restore
BABA .seek_to_track_0←1← BAAE BMI
JSR floppy_restore_track_0 ; Seek to track 0 first
BABD .setup_nmi_for_transfer←2← BAAC BPL← BAB8 BVC
JSR setup_fdc_and_seek ; Set up sector parameters
BAC0 JSR select_fdc_rw_command ; Set up NMI handler
BAC3 JMP floppy_error ; Process result/error

Set up FDC registers and seek to track

Write track and sector to the WD1770 registers with readback verify, then seek to the target track.

BAC6 .setup_fdc_and_seek←3← BABD JSR← BD63 JSR← BDA6 JSR
JSR clear_transfer_complete ; Clear seek-complete flag
BAC9 LDX #0 ; X=0: first FDC register
BACB JSR fdc_write_register_verify ; Write to WD1770 register with readback verify
BACE INX ; X=1: track register
BACF JSR fdc_write_register_verify ; Write to WD1770 register with readback verify
BAD2 INX ; X=2: sector register
BAD3 JSR fdc_write_register_verify ; Write to WD1770 register with readback verify
BAD6 CMP zp_floppy_track ; Compare with target track
BAD8 BEQ retry_after_error ; Already on track: skip seek
BADA ROR wksp_fdc_head_state ; Set head-loaded flag
BADD SEC ; Set carry
BADE ROL wksp_fdc_head_state ; Restore head-loaded flag
BAE1 LDA #&14 ; FDC seek command (&14)
BAE3 ORA nmi_drive_cmd ; OR in drive select bits
BAE6 STA fdc_1770_command_or_status ; Issue seek command to FDC
BAE9 JSR floppy_wait_nmi_finish ; Wait for floppy NMI transfer to complete
BAEC LDA zp_floppy_control ; Get control flags
BAEE ROR ; Rotate verify flag to carry
BAEF BCC retry_after_error ; C=0: no verify, proceed to data
BAF1 .check_floppy_error_code←2← BB04 BEQ← BB23 BEQ
JMP floppy_error ; Handle floppy disc error

Set up track for floppy retry

After a floppy error, set up the track for a retry attempt by copying target sector to current track.

BAF4 .retry_after_error←2← BAD8 BEQ← BAEF BCC
LDA zp_floppy_track_num ; Set sector number as target
BAF6 STA zp_floppy_track ; Store as current track
BAF8 BIT zp_floppy_control ; Check transfer direction
BAFA BVS return_floppy_result ; V set: multi-sector operation
BAFC LDY #5 ; Y=5: check command byte
BAFE LDA (zp_ctrl_blk_lo),y ; Get command from control block
BB00 CMP #&0b ; Is it &0B (verify)?
BB02 BNE return_floppy_result ; No, proceed with data transfer
BB04 BEQ check_floppy_error_code ; ALWAYS branch
BB06 .return_floppy_result←2← BAFA BVS← BB02 BNE
JMP clear_transfer_complete ; Clear seek flag and return

Write to WD1770 register with readback verify

Write value from zp_a3+X to FDC register at &FE85+X, then read back and loop until the value matches. This handles the WD1770's register write timing.

On EntryXFDC register index (0=track, 1=sector, 2=data)
On ExitAvalue written to register
Xpreserved
Ypreserved
BB09 .fdc_write_register_verify←5← BACB JSR← BACF JSR← BAD3 JSR← BB11 BNE← BE19 JSR
LDA zp_floppy_track,x ; Get value to write
BB0B STA fdc_1770_track,x ; Write to FDC register
BB0E CMP fdc_1770_track,x ; Read back from register
BB11 BNE fdc_write_register_verify ; Loop until value sticks
BB13 RTS ; Return

Execute floppy disc command

Execute a disc operation on the floppy disc using the WD1770 controller. Handles sector read, write, and format operations.

BB14 .floppy_command←1← BA00 JMP
TSX ; Save stack pointer for error recovery
BB15 STX wksp_stack_save ; Save stack for error recovery
BB18 LDA #&10 ; Set transfer mode flags
BB1A STA wksp_fdc_xfer_mode ; Store transfer mode
BB1D JSR floppy_init_transfer ; Set up NMI handler and drive select
BB20 JSR issue_fdc_track_command ; Execute the read/write operation
BB23 BEQ check_floppy_error_code ; Error: jump to floppy error handler
BB25 .exec_floppy_partial_sector_buf←1← BA03 JMP
STA wksp_format_page ; Partial sector buffer: save count
BB28 TSX ; Save stack for error recovery
BB29 STX wksp_stack_save ; Save stack for error recovery
BB2C LDA #&10 ; Workspace page for control block
BB2E STA zp_ctrl_blk_hi ; Point (&B0) to workspace control blk
BB30 LDA #&15 ; Control block offset
BB32 STA zp_ctrl_blk_lo ; Store in (&B0)
BB34 LDA #0 ; Clear transfer mode flags
BB36 STA wksp_fdc_xfer_mode ; Clear transfer mode for format
BB39 JSR floppy_init_transfer ; Set up NMI handler
BB3C JSR floppy_format_track ; Execute format track operation
BB3F JMP floppy_error ; Process result/error

Initialise floppy disc transfer

Set up for a floppy disc operation: clear error number, copy the transfer address and control parameters from the control block, claim NMI, set step rate, and copy the NMI handler code to NMI workspace.

BB42 .floppy_init_transfer←2← BB1D JSR← BB39 JSR
LDA #0 ; Clear error number
BB44 STA wksp_err_number ; Clear error number
BB47 LDY #1 ; Y=1: get transfer address from blk
BB49 LDA (zp_ctrl_blk_lo),y ; Transfer address low
BB4B STA zp_mem_ptr_lo ; Store transfer addr low in (&B2)
BB4D INY ; Y=&02
BB4E LDA (zp_ctrl_blk_lo),y ; Transfer address high
BB50 STA zp_mem_ptr_hi ; Store transfer addr high in (&B3)
BB52 INY ; Y=&03
BB53 LDA (zp_ctrl_blk_lo),y ; Get control byte 3
BB55 TAX ; Transfer to X
BB56 INY ; Y=&04
BB57 LDA (zp_ctrl_blk_lo),y ; Get control byte 4
BB59 INX ; Check X+1 for zero (was &FF)
BB5A BEQ check_host_memory ; X was &FF: check A for &FF too
BB5C INX ; Check X for zero (wrap from &FF)
BB5D BNE check_tube_present ; X non-zero: check Tube flag
BB5F .check_host_memory←1← BB5A BEQ
CMP #&ff ; A == &FF?
BB61 BEQ validate_disc_command ; Both &FF: host memory, skip Tube
BB63 .check_tube_present←1← BB5D BNE
BIT zp_adfs_flags ; Check if Tube is present
BB65 BPL validate_disc_command ; No Tube: skip Tube setup
BB67 JSR claim_tube ; Claim Tube if present
BB6A .validate_disc_command←2← BB61 BEQ← BB65 BPL
LDY #5 ; Y=5: get command byte from block
BB6C LDA (zp_ctrl_blk_lo),y ; Read command byte
BB6E CMP #8 ; Command 8 (read)?
BB70 BEQ set_read_transfer_mode ; Yes, valid command
BB72 CMP #&0a ; Command &0A (write)?
BB74 BEQ setup_nmi_and_step_rate ; Yes, valid command
BB76 CMP #&0b ; Command &0B (verify)?
BB78 BEQ set_read_transfer_mode ; Yes, valid command
BB7A LDA #&67 ; Error &67: bad command
BB7C STA wksp_err_number ; Store error code
BB7F JMP floppy_error ; Handle floppy disc error

Set read mode and initialise floppy

Set bit 7 of transfer mode for read, get step rate, claim NMI, and set up the track.

BB82 .set_read_transfer_mode←2← BB70 BEQ← BB78 BEQ
ROL wksp_fdc_xfer_mode ; Set bit 7 of transfer mode
BB85 SEC ; Set carry for rotate
BB86 ROR wksp_fdc_xfer_mode ; Restore bit 7 set
BB89 .setup_nmi_and_step_rate←1← BB74 BEQ
JSR floppy_get_step_rate ; Get floppy step rate
BB8C JSR claim_nmi_and_init ; Set up drive select and NMI
BB8F JMP setup_track_for_rw ; Jump to floppy track setup

Claim NMI and initialise floppy transfer

Claim the NMI vector via service call 12, set FDC step rate, clear error flags, and copy the NMI handler code into NMI workspace.

BB92 .claim_nmi_and_init←2← BA38 JSR← BB8C JSR
JSR claim_nmi ; Claim NMI via service call 12
BB95 LDA wksp_fdc_cmd_step ; Get FDC step rate setting
BB98 STA nmi_drive_cmd ; Store in NMI control byte
BB9B LDA #0 ; A=0: clear error flag
BB9D STA zp_floppy_error ; Clear error code
BB9F STA zp_floppy_state ; Clear transfer state
BBA1 LDA wksp_fdc_xfer_mode ; Get transfer mode flags
BBA4 ORA #&20 ; Set bit 5 (NMI active)
BBA6 STA wksp_fdc_xfer_mode ; Store updated mode
BBA9 STA zp_floppy_control ; Store as control flags
BBAB LDA zp_adfs_flags ; Get ADFS flags
BBAD STA nmi_adfs_flags ; Store in NMI workspace
BBB0 JSR copy_code_to_nmi_space ; Copy NMI handler code to NMI workspace
BBB3 RTS ; Return

Get floppy step rate

Fetch the startup options byte via OSBYTE &FF and use bits 4 and 5 to set the FDC step rate and head settle time in milliseconds.

BBB4 .floppy_get_step_rate←2← BA35 JSR← BB89 JSR
LDA #0 ; Clear side select flag
BBB6 STA nmi_step_rate ; Store in NMI side select
BBB9 STA wksp_fdc_cmd_step ; Clear FDC step rate command bits
BBBC LDA #osbyte_read_write_startup_options ; OSBYTE &FF: read startup options
BBBE LDX #0 ; X=0: read current value
BBC0 TAY ; Y=&FF: read current value
BBC1 JSR osbyte ; Read start-up option byte
BBC4 TXA ; Get startup byte to A
BBC5 PHA ; Save startup byte
BBC6 AND #&20 ; Test bit 5 (step rate high)
BBC8 BEQ step_rate_fast ; Clear: fast step rate
BBCA LDA #3 ; Bit 5 set: slow step (rate=3)
BBCC STA wksp_fdc_cmd_step ; Store in FDC command step field
BBCF .step_rate_fast←1← BBC8 BEQ
PLA ; Restore startup byte
BBD0 AND #&10 ; Test bit 4 (settle time)
BBD2 BEQ return_43 ; Clear: short settle
BBD4 LDA #2 ; Bit 4 set: long settle (rate=2)
BBD6 STA nmi_step_rate ; Store in NMI workspace
BBD9 .return_43←1← BBD2 BEQ
RTS ; Return

Claim NMI via service call 12

Issue service call 12 (NMI claim) via OSBYTE &8F to claim exclusive use of the NMI handler for floppy disc operations. Saves the return argument for later release.

BBDA .claim_nmi←1← BB92 JSR
LDA #osbyte_issue_service_request ; OSBYTE &8F: issue service request
BBDC LDX #&0c ; X=&0C: service 12 (NMI claim)
BBDE LDY #&ff ; Y = NMI owner return value
BBE0 JSR osbyte ; Issue service call
BBE3 STY wksp_nmi_owner ; Save NMI owner for release
BBE6 RTS ; Return

Release NMI via service call 11

Issue service call 11 (NMI released) via OSBYTE &8F to release the NMI handler after floppy disc operations.

BBE7 .release_nmi←1← BFDD JSR
LDY wksp_nmi_owner ; Retrieve NMI owner
BBEA LDA #osbyte_issue_service_request ; OSBYTE &8F: issue service request
BBEC LDX #&0b ; X=&0B: service 11 (NMI released)
BBEE JMP osbyte ; Issue service call

Copy NMI handler code to NMI workspace

Copy the NMI handler routine from ROM to the NMI workspace at &0D00. The NMI handler is used for byte-by-byte data transfer between the WD1770 and memory.

BBF1 .copy_code_to_nmi_space←1← BBB0 JSR
LDY #&48 ; Y=&48: copy 73 bytes of NMI code
BBF3 .copy_nmi_code_loop←1← BBFA BPL
LDA nmi_handler_rom,y ; Read NMI handler byte from ROM
BBF6 STA nmi_workspace,y ; Write to NMI workspace
BBF9 DEY ; Next byte (loop back)
BBFA BPL copy_nmi_code_loop ; Loop until all bytes copied
BBFC LDY #1 ; Y=1: get memory address low from blk
BBFE LDA (zp_ctrl_blk_lo),y ; Get transfer address low byte
BC00 STA nmi_read_addr_lo ; Patch NMI handler with address low
BC03 INY ; Y=&02
BC04 LDA (zp_ctrl_blk_lo),y ; Get transfer address high byte
BC06 STA nmi_read_addr_hi ; Patch NMI handler with address high
BC09 BIT zp_floppy_control ; Check control flags
BC0B BMI check_tube_for_nmi ; Bit 7 set: reading from disc
BC0D LDA #&5f ; Writing: patch NMI with STA opcode
BC0F STA nmi_rw_opcode ; Store at NMI read/write instruction
BC12 .check_tube_for_nmi←1← BC0B BMI
BIT zp_adfs_flags ; Tube in use?
BC14 BVC setup_direct_nmi ; No, use direct memory NMI handler
BC16 LDA zp_floppy_control ; Get control flags for Tube setup
BC18 AND #&fd ; Clear bit 1 (read/write direction)
BC1A STA zp_floppy_control ; Store updated control flags
BC1C JSR setup_tube_nmi_transfer ; Set up Tube transfer parameters
BC1F BMI store_nmi_completion ; Tube read: use read NMI handler
BC21 .setup_direct_nmi←1← BC14 BVC
JSR setup_direct_write_nmi ; Set up direct memory NMI handler
BC24 .store_nmi_completion←1← BC1F BMI
STA nmi_completion ; Store NMI completion flag
BC27 LDA romsel_copy ; Get current ROM number
BC29 STA nmi_saved_rom ; Patch NMI handler with ROM number
BC2C RTS ; Return
BC2D .setup_tube_nmi_transfer←1← BC1C JSR
LDA zp_floppy_control ; Get control flags
BC2F ROL ; Rotate bit 7 into carry
BC30 LDA #0 ; A=0 (will become direction flag)
BC32 ROL ; Rotate carry into bit 0
BC33 LDY #&10 ; Y=&10: Tube workspace page
BC35 LDX #&27 ; X=&27: Tube workspace offset
BC37 JSR tube_entry ; Start Tube transfer
BC3A LDA zp_floppy_control ; Get control flags again
BC3C AND #&10 ; Bit 4 set (sector count specified)?
BC3E BEQ return_44 ; No, return (single sector)
BC40 BIT zp_floppy_control ; Check read/write direction
BC42 BMI setup_tube_read_nmi ; Bit 7 set: reading from disc
BC44 LDY #7 ; Y=7: copy 8 bytes of Tube write NMI
BC46 .copy_tube_write_nmi_loop←1← BC4D BPL
LDA nmi_tube_write_code,y ; Get Tube write NMI handler byte
BC49 STA nmi_rw_code,y ; Copy to NMI workspace
BC4C DEY ; Next byte
BC4D BPL copy_tube_write_nmi_loop ; Loop for 8 bytes
BC4F .return_44←1← BC3E BEQ
RTS ; Return
BC50 .setup_tube_read_nmi←1← BC42 BMI
LDY #7 ; Y=7: copy 8 bytes of Tube read NMI
BC52 .copy_tube_read_nmi_loop←1← BC59 BPL
LDA nmi_tube_read_code,y ; Get Tube read NMI handler byte
BC55 STA nmi_rw_code,y ; Copy to NMI workspace
BC58 DEY ; Next byte
BC59 BPL copy_tube_read_nmi_loop ; Loop for 8 bytes
BC5B RTS ; Return
BC5C .setup_direct_write_nmi←1← BC21 JSR
BIT zp_floppy_control ; Check read/write direction
BC5E BMI return_45 ; Reading: use default NMI handler
BC60 LDY #&0d ; Y=&0D: copy 14 bytes of write NMI
BC62 .copy_write_nmi_loop←1← BC69 BPL
LDA nmi_write_code,y ; Get direct memory write NMI byte
BC65 STA nmi_rw_code,y ; Copy to NMI workspace
BC68 DEY ; Next byte
BC69 BPL copy_write_nmi_loop ; Loop for 14 bytes
BC6B LDY #1 ; Y=1: patch transfer address
BC6D LDA (zp_ctrl_blk_lo),y ; Get transfer addr low from block
BC6F STA nmi_write_addr_lo ; Patch NMI handler with addr low
BC72 INY ; Y=&02
BC73 LDA (zp_ctrl_blk_lo),y ; Get transfer addr high from block
BC75 STA nmi_write_addr_hi ; Patch NMI handler with addr high
BC78 .return_45←1← BC5E BMI
RTS ; Return

NMI handler code (copied to &0D00)

NMI handler for floppy disc byte-by-byte data transfer. Copied from ROM to the NMI workspace at &0D00 before each floppy operation. The WD1770 fires an NMI on each byte transferred (DRQ) and on command completion.

The handler has three paths: 1. DRQ (status & &1F = 3): transfer one byte between the WD1770 data register and memory. The code at &0D0A-&0D17 is patched with one of three variants: nmi_write_code (direct memory write to disc), nmi_tube_write_code (Tube to disc), or nmi_tube_read_code (disc to Tube). The default (nmi_code_rw) is direct memory read from disc. 2. Error (status & &58 != 0): store the error status and set bit 0 of zp_floppy_control and zp_floppy_state to signal the error to the caller. 3. Completion (no DRQ, no error): if multi-sector mode is active (bit 6 of zp_floppy_state), switch to ROM 0 and call the track-stepping routine to set up the next sector. Otherwise mark transfer complete.

ROMExec
BC79 0D00 .nmi_workspace←1← BBF6 STA
.nmi_handler_rom←1← BBF6 STA
PHA ; Save A (NMI must preserve all regs)
BC7A 0D01 LDA fdc_1770_command_or_status ; Read WD1770 status register
BC7D 0D04 AND #&1f ; Mask to low 5 status bits
BC7F 0D06 CMP #3 ; Status = 3 (data request)?
BC81 0D08 BNE nmi_check_status_error ; No: check for error or completion
BC83 0D0A .nmi_rw_code←3← BC49 STA← BC55 STA← BC65 STA
LDA fdc_1770_data ; Read byte from WD1770 data register
BC86 0D0D STA nmi_patched_addr ; Store at transfer address (patched)
BC89 0D10 INC nmi_read_addr_lo ; Increment transfer address low byte
BC8C 0D13 BNE nmi_transfer_done ; No page crossing: skip high byte
BC8E 0D15 INC nmi_read_addr_hi ; Increment transfer address high byte
BC91 0D18 .nmi_transfer_done←4← 0D10 BCS← 0D10 BCS← 0D13 BNE← 0D13 BNE
PLA ; Restore A
BC92 0D19 RTI ; Return from NMI

NMI status/error handler

Not a DRQ: check WD1770 status for error bits. Bits 6 (write protect), 4 (record not found), and 3 (CRC error) are tested via AND #&58. If any are set, store the error code and set the error flag in the control byte.

ROMExec
BC93 0D1A .nmi_check_status_error←1← 0D08 BNE
AND #&58 ; Test error bits: WP, RNF, CRC
BC95 0D1C BEQ nmi_check_end_of_operation ; No errors: check for end of operation
BC97 0D1E STA zp_floppy_error ; Store error status for caller
BC99 0D20 ROR zp_floppy_control ; Rotate control flags right
BC9B 0D22 SEC ; Set carry for error flag
BC9C 0D23 ROL zp_floppy_control ; Set bit 0: error occurred
BC9E 0D25 .nmi_set_transfer_complete←1← 0D2E BVC
ROR zp_floppy_state ; Rotate state flags right
BCA0 0D27 SEC ; Set carry for complete flag
BCA1 0D28 ROL zp_floppy_state ; Set bit 0: transfer complete
BCA3 0D2A PLA ; Restore A
BCA4 0D2B RTI ; Return from NMI

NMI end-of-operation handler

No error and no DRQ: the WD1770 command has completed. If multi-sector mode (bit 6 of zp_floppy_state) is not active, just mark the transfer complete. Otherwise, save the current ROM state, switch to ROM 0, and call the track-stepping routine to prepare the next sector for transfer.

ROMExec
BCA5 0D2C .nmi_check_end_of_operation←1← 0D1C BEQ
BIT zp_floppy_state ; Multi-sector mode active? (bit 6)
BCA7 0D2E BVC nmi_set_transfer_complete ; No: mark complete and return
BCA9 0D30 LDA romsel_copy ; Save current ROM bank number
BCAB 0D32 PHA ; Push onto stack
BCAC 0D33 LDA #0 ; Select ROM bank 0
BCAE 0D35 STA romsel_copy ; Update ROM select shadow copy
BCB0 0D37 STA romsel ; Switch to ROM bank 0
BCB3 0D3A TXA ; Save X register
BCB4 0D3B PHA ; Push onto stack
BCB5 0D3C JSR floppy_next_sector ; Advance to next sector
BCB8 0D3F PLA ; Restore X register
BCB9 0D40 TAX ; Pull from stack
BCBA 0D41 PLA ; Restore original ROM bank
BCBB 0D42 STA romsel_copy ; Update ROM select shadow copy
BCBD 0D44 STA romsel ; Switch back to original ROM bank
BCC0 0D47 PLA ; Restore A
BCC1 0D48 RTI ; Return from NMI

Wait for floppy NMI transfer to complete

Wait for the WD1770 floppy disc controller to complete a data transfer. Polls the controller status register.

BCC2 .floppy_wait_nmi_finish←6← BAE9 JSR← BCCD BEQ← BCD1 BPL← BD16 JMP← BD49 JMP← BE33 JSR
LDA zp_floppy_state ; Check if transfer already complete
BCC4 ROR ; Bit 0 of zp_a2 into carry
BCC5 BCC poll_nmi_complete ; Carry set: already done
BCC7 RTS ; Transfer already complete, return
BCC8 .poll_nmi_complete←1← BCC5 BCC
LDA nmi_adfs_flags ; Read NMI completion flag
BCCB AND #&10 ; Bit 4 set = DRQ complete?
BCCD BEQ floppy_wait_nmi_finish ; Not yet, keep waiting
BCCF BIT zp_escape_flag ; Check for Escape condition
BCD1 BPL floppy_wait_nmi_finish ; Bit 7 clear: no Escape
BCD3 LDA #0 ; Escape pressed: stop drive
BCD5 STA fdc_1770_drive_control ; Write 0 to FDC control
BCD8 LDA #&6f ; Error &6F: drive overrun/Escape
BCDA STA zp_floppy_error ; Store drive overrun error code
BCDC JMP floppy_error ; Handle floppy error
fall through ↓

NMI patch: write memory to disc

Patched into &0D0A when writing to floppy disc from host memory. Reads a byte from the self-modifying transfer address and writes it to the WD1770 data register.

ROMExec
BCDF 0D0A .nmi_rw_code←3← BC49 STA← BC55 STA← BC65 STA
.nmi_write_code←3← BC49 STA← BC55 STA← BC65 STA
LDA nmi_patched_addr ; Read byte from transfer address
BCE2 0D0D STA fdc_1770_data ; Write to WD1770 data register
BCE5 0D10 INC nmi_write_addr_lo ; Increment transfer address low
BCE8 0D13 BNE nmi_transfer_done ; No page crossing: done
BCEA 0D15 INC nmi_write_addr_hi ; Increment transfer address high

NMI patch: write Tube to disc

Patched into &0D0A when writing to floppy disc via the Tube. Reads a byte from Tube data register 3 and writes it to the WD1770 data register.

ROMExec
BCED 0D0A .nmi_rw_code←3← BC49 STA← BC55 STA← BC65 STA
.nmi_tube_write_code←3← BC49 STA← BC55 STA← BC65 STA
LDA tube_data_register_3 ; Read byte from Tube R3
BCF0 0D0D STA fdc_1770_data ; Write to WD1770 data register
BCF3 0D10 BCS nmi_transfer_done ; Always branch: done

NMI patch: read disc to Tube

Patched into &0D0A when reading from floppy disc via the Tube. Reads a byte from the WD1770 data register and writes it to Tube data register 3.

ROMExec
BCF5 0D0A .nmi_rw_code←3← BC49 STA← BC55 STA← BC65 STA
.nmi_tube_read_code←3← BC49 STA← BC55 STA← BC65 STA
LDA fdc_1770_data ; Read byte from WD1770
BCF8 0D0D STA tube_data_register_3 ; Write to Tube R3
BCFB 0D10 BCS nmi_transfer_done ; Always branch: done

Select and issue FDC read/write command

Choose WD1770 read (&80) or write (&A0) command based on transfer direction. Apply head load delay and step rate, then issue the command.

BCFD .select_fdc_rw_command←2← BAC0 JSR← BD66 JSR
BIT zp_floppy_control ; Check read/write direction
BCFF BMI set_read_command ; Reading: use read command
BD01 LDA zp_floppy_track ; Get current track
BD03 CMP #&14 ; Track >= 20?
BD05 LDA #&a0 ; A=&A0: write command base
BD07 BCC issue_fdc_command ; Track < 20: no step rate delay
BD09 ORA nmi_step_rate ; OR in step rate from settings
BD0C BNE issue_fdc_command ; Always branch (non-zero result)
BD0E .set_read_command←1← BCFF BMI
LDA #&80 ; A=&80: read command base
BD10 .issue_fdc_command←2← BD07 BCC← BD0C BNE
JSR apply_head_load_flag ; Apply head load delay to FDC command
BD13 STA fdc_1770_command_or_status ; Issue FDC command
BD16 JMP floppy_wait_nmi_finish ; Wait for floppy NMI transfer to complete

Unused: select floppy disc side 0

Unreferenced routine that clears bit 2 of the NMI drive control byte at &0D5E, selecting side 0 of a double-sided floppy disc. The inverse of floppy_set_side_1 which sets bit 2. Dead code — side 0 is the default so no explicit selection is needed.

BD19 .floppy_set_side_0_unused
LDA nmi_drive_ctrl ; Get NMI drive control byte
BD1C AND #&fb ; Clear bit 2 (select side 0)
BD1E STA nmi_drive_ctrl ; Store updated control byte
BD21 RTS ; Return

Select floppy disc side 1

Select side 1 (the second side) of a double-sided floppy disc by setting the appropriate control register bit.

BD22 .floppy_set_side_1←4← BA98 JSR← BE5C JSR← BEC0 JSR← BF9F JMP
LDA nmi_drive_ctrl ; Set side select flag
BD25 ORA #4 ; Set bit 2 (side 1 flag)
BD27 STA nmi_drive_ctrl ; Store in NMI drive control byte
BD2A RTS ; Return

Clear floppy transfer complete flag

Clear bit 0 of the floppy transfer state byte.

BD2B .clear_transfer_complete←6← BAC6 JSR← BB06 JMP← BE2B JSR← BE3C JSR← BE54 JSR← BE69 JSR
ROR zp_floppy_state ; Clear bit 0 of transfer state
BD2D CLC ; Clear carry
BD2E ROL zp_floppy_state ; Restore bit 0 cleared
BD30 RTS ; Return
BD31 .clear_side_flag←1← BE57 JSR
LDA zp_floppy_state ; Get transfer state
BD33 AND #&f7 ; Clear bit 3 (side flag)
BD35 STA zp_floppy_state ; Store updated state
BD37 RTS ; Return

Clear floppy seek-in-progress flag

Clear bit 1 of the floppy transfer state byte.

BD38 .clear_seek_flag←2← BE3F JSR← BE78 JSR
LDA zp_floppy_state ; Get transfer state
BD3A AND #&fd ; Clear bit 1 (seek flag)
BD3C STA zp_floppy_state ; Store updated state
BD3E RTS ; Return

Seek floppy head to track 0

Issue a restore command to the WD1770 to seek the read/write head to track 0.

BD3F .floppy_restore_track_0←2← BABA JSR← BF51 JSR
LDA #0 ; A=0: target track number = 0
BD41 STA zp_floppy_track ; Store as target track
BD43 ORA nmi_drive_cmd ; OR with drive select bits
BD46 STA fdc_1770_command_or_status ; Issue restore command to WD1770
BD49 JMP floppy_wait_nmi_finish ; Wait for command to complete

Apply head load delay to FDC command

If the head-loaded flag is set in the transfer state, OR bit 2 into A (the head load delay bit in WD1770 step/seek commands).

On EntryAFDC command byte
On ExitAcommand with bit 2 set if head loaded
Xpreserved
Ypreserved
BD4C .apply_head_load_flag←2← BD10 JSR← BE7D JSR
ROR wksp_fdc_head_state ; Rotate head-loaded flag to carry
BD4F BCC restore_head_flag ; Not loaded: skip
BD51 ORA #4 ; Set bit 2: head load delay
BD53 CLC ; Clear carry (was set by SEC)
BD54 .restore_head_flag←1← BD4F BCC
ROL wksp_fdc_head_state ; Restore head-loaded flag
BD57 RTS ; Return

Format a floppy disc track

Set up NMI handler addresses for a format operation, then write the track format data to disc.

BD58 .floppy_format_track←1← BB3C JSR
LDA wksp_format_page ; Get format page number
BD5B STA nmi_read_addr_hi ; Store as NMI buffer high byte
BD5E LDA #0 ; A=0: NMI buffer low byte
BD60 STA nmi_read_addr_lo ; Store as NMI buffer low byte
BD63 JSR setup_fdc_and_seek ; Set up FDC registers for operation
BD66 JSR select_fdc_rw_command ; Set up FDC command and issue
BD69 LDA zp_floppy_track ; Save current track
BD6B PHA ; Push on stack
BD6C LDA wksp_disc_op_mem_addr ; Get transfer address low
BD6F STA zp_floppy_track_num ; Store as dest address low
BD71 LDA wksp_disc_op_mem_addr_1 ; Get transfer address high
BD74 STA zp_floppy_dest_page ; Store as dest address high
BD76 LDA #0 ; Source address low = 0
BD78 STA zp_floppy_track ; Store source low
BD7A LDA wksp_format_page ; Get format buffer page
BD7D STA zp_floppy_sector ; Store source high (format data page)
BD7F BIT zp_adfs_flags ; Is Tube active?
BD81 BVC direct_format_copy ; No Tube: use direct memory copy
BD83 LDY #0 ; Y=0: Tube transfer byte index
BD85 .tube_format_xfer_loop←1← BD93 BNE
LDA (zp_floppy_track),y ; Get format data byte from source
BD87 LDX #7 ; X=7: timing delay loop
BD89 .tube_format_delay_loop←1← BD8A BNE
DEX ; Delay
BD8A BNE tube_format_delay_loop ; Loop for delay
BD8C STA tube_data_register_3 ; Send byte to Tube R3
BD8F INY ; Next byte
BD90 CPY wksp_disc_op_sector_count ; Transferred all bytes?
BD93 BNE tube_format_xfer_loop ; No, continue transfer
BD95 BEQ format_track_data_ready ; ALWAYS branch
BD97 .direct_format_copy←1← BD81 BVC
LDY wksp_disc_op_sector_count ; Direct copy: get byte count
BD9A .direct_format_copy_loop←1← BDA0 BNE
DEY ; Adjust for 0-based index
BD9B LDA (zp_floppy_track),y ; Get last byte from source
BD9D STA (zp_floppy_track_num),y ; Store at dest
BD9F TYA ; Transfer Y to A
BDA0 BNE direct_format_copy_loop ; Loop until all bytes copied
BDA2 .format_track_data_ready←1← BD95 BEQ
PLA ; Restore saved track
BDA3 STA zp_floppy_track ; Store back as current track
BDA5 RTS ; Return
BDA6 .issue_fdc_track_command←1← BB20 JSR
JSR setup_fdc_and_seek ; Set up FDC registers
BDA9 LDA zp_floppy_state ; Get transfer state flags
BDAB ORA #&40 ; Set bit 6 (multi-sector flag)
BDAD STA zp_floppy_state ; Store updated state
BDAF LDY #7 ; Y=7: get sector address from block
BDB1 LDA (zp_ctrl_blk_lo),y ; Get sector address mid byte
BDB3 STA nmi_secs_this_track ; Store in NMI workspace
BDB6 INY ; Y=8: sector address low
BDB7 LDA (zp_ctrl_blk_lo),y ; Get sector address low
BDB9 INY ; Y=9: sector count
BDBA CLC ; Clear carry for addition
BDBB ADC (zp_ctrl_blk_lo),y ; Add sector count to start sector
BDBD STA nmi_secs_last_track ; Store end sector in NMI workspace
BDC0 BCC wait_format_track_complete ; No carry: no wrap
BDC2 INC nmi_secs_this_track ; Increment mid byte on carry
BDC5 .wait_format_track_complete←1← BDC0 BCC
LDA nmi_secs_this_track ; Get end sector mid byte
BDC8 TAX ; Transfer to X
BDC9 LDA nmi_secs_last_track ; Get end sector low byte
BDCC LDY #&ff ; Y=&FF: init for divide
BDCE JSR xa_div_16_to_ya ; Divide end sector by 16
BDD1 CMP #0 ; Remainder = 0?
BDD3 BNE format_next_track ; No: adjust sectors per track
BDD5 LDA #&10 ; Yes: use full 16 sectors/track
BDD7 .format_next_track←1← BDD3 BNE
LDY #9 ; Y=9: get sector count from block
BDD9 SEC ; Set carry for subtraction
BDDA SBC (zp_ctrl_blk_lo),y ; Subtract sector count
BDDC BCS format_double_sided ; Result >= 0: fits in remaining
BDDE LDA #&10 ; Need to cross track boundary
BDE0 SEC ; Set carry for subtraction
BDE1 SBC zp_floppy_sector ; Subtract start sector position
BDE3 STA nmi_secs_this_track ; Store sectors remaining this track
BDE6 LDA (zp_ctrl_blk_lo),y ; Get sector count from block
BDE8 SEC ; Set carry
BDE9 SBC nmi_secs_this_track ; Subtract sectors done this track
BDEC LDX #0 ; X=0: init result
BDEE LDY #&ff ; Y=&FF: init for divide
BDF0 JSR xa_div_16_to_ya ; Divide remaining by 16
BDF3 STY nmi_tracks_remaining ; Store full tracks to process
BDF6 STA nmi_secs_last_track ; Store partial sectors on last track
BDF9 BPL set_format_sector_id ; Branch always (positive)
BDFB .format_double_sided←1← BDDC BCS
LDY #9 ; Y=9: get sector count
BDFD LDA (zp_ctrl_blk_lo),y ; Get sector count from block
BDFF STA nmi_secs_this_track ; Store in NMI workspace
BE02 LDA #&ff ; A=&FF: no additional tracks
BE04 STA nmi_tracks_remaining ; Store track count
BE07 LDA #0 ; A=0: no partial sectors
BE09 STA nmi_secs_last_track ; Store partial count
BE0C .set_format_sector_id←1← BDF9 BPL
LDA #0 ; Clear sector position counter
BE0E STA nmi_sec_position ; Store in NMI workspace
BE11 INC nmi_tracks_remaining ; Increment full track count
BE14 DEC nmi_secs_this_track ; Decrement sectors this track
BE17 LDX #1 ; X=1: write sector register
BE19 JSR fdc_write_register_verify ; Write sector to FDC with verify
BE1C BIT zp_floppy_control ; Check read/write direction
BE1E BMI check_format_complete ; Reading: use read command
BE20 LDA #&a0 ; A=&A0: write command base
BE22 ORA nmi_step_rate ; OR in step rate
BE25 BNE format_track_loop ; Branch (always non-zero)
BE27 .check_format_complete←1← BE1E BMI
LDA #&80 ; A=&80: read command base
BE29 .format_track_loop←1← BE25 BNE
STA zp_floppy_dest_page ; Store FDC command in workspace
BE2B JSR clear_transfer_complete ; Clear seek flag
BE2E LDA zp_floppy_dest_page ; Get FDC command
BE30 STA fdc_1770_command_or_status ; Issue command to FDC
BE33 .wait_format_nmi_complete←2← BE4C BNE← BE67 BPL
JSR floppy_wait_nmi_finish ; Wait for NMI completion
BE36 LDA zp_floppy_state ; Get transfer state
BE38 AND #2 ; Bit 1 set: need track step
BE3A BEQ format_verify_pass ; No step needed: check side switch
BE3C JSR clear_transfer_complete ; Clear seek flag
BE3F JSR clear_seek_flag ; Clear track-step flag
BE42 LDA #&54 ; FDC step-in command (&54)
BE44 ORA nmi_drive_cmd ; OR in drive select bits
BE47 STA fdc_1770_command_or_status ; Issue step-in command
BE4A INC zp_floppy_track ; Increment current track
BE4C BNE wait_format_nmi_complete ; Continue multi-sector loop
BE4E .format_verify_pass←1← BE3A BEQ
LDA zp_floppy_state ; Check bit 3: side switch needed?
BE50 AND #8 ; Check if set
BE52 BEQ return_46 ; Not set: operation complete
BE54 JSR clear_transfer_complete ; Clear seek flag for side switch
BE57 JSR clear_side_flag ; Clear side-switch flag
BE5A INC zp_floppy_track ; Increment track for side 1
BE5C JSR floppy_set_side_1 ; Select side 1
BE5F LDA #0 ; FDC restore command (seek to trk 0)
BE61 ORA nmi_drive_cmd ; OR in drive select
BE64 STA fdc_1770_command_or_status ; Issue restore command
BE67 BPL wait_format_nmi_complete ; Continue loop (always branches)
fall through ↓

Advance multi-sector transfer to next sector

Called from the NMI end-of-operation handler during multi-sector transfers. Clears the transfer-complete flag, then calls the FDC seek routine to check whether a track boundary has been crossed and step the head if needed. If the seek routine returns zero, all sectors have been transferred and the completion flag is set.

BE69 .floppy_next_sector←1← 0D3C JSR
JSR clear_transfer_complete ; Clear seek flag
BE6C JSR execute_fdc_seek ; Check for next track boundary
BE6F TXA ; Transfer result to A
BE70 BNE clear_verify_seek_flag ; Non-zero: more sectors to transfer
BE72 ROR zp_floppy_state ; Set completion flag bit 0
BE74 SEC ; Set carry
BE75 ROL zp_floppy_state ; Store completion flag
BE77 RTS ; Return (operation complete)
BE78 .clear_verify_seek_flag←1← BE70 BNE
JSR clear_seek_flag ; Clear track-step flag
BE7B LDA zp_floppy_dest_page ; Get FDC command
BE7D JSR apply_head_load_flag ; Apply head load delay
BE80 STA fdc_1770_command_or_status ; Issue FDC command
BE83 .return_46←1← BE52 BEQ
RTS ; Return
BE84 .execute_fdc_seek←1← BE6C JSR
LDA nmi_secs_this_track ; Get sectors remaining this track
BE87 BNE issue_step_command ; Non-zero: not at boundary
BE89 LDA nmi_tracks_remaining ; Get full tracks remaining
BE8C BNE check_seek_error ; Non-zero: need track step
BE8E LDA nmi_secs_last_track ; Get partial sectors on last track
BE91 BNE wait_seek_complete ; Non-zero: still have partial track
BE93 LDX #0 ; X=0: all done
BE95 BEQ return_47 ; Branch to return
BE97 .wait_seek_complete←1← BE91 BNE
DEC nmi_secs_last_track ; Decrement partial sector count
BE9A JMP step_track_counter ; Jump to update sector position
BE9D .check_seek_error←1← BE8C BNE
LDA nmi_sec_position ; Get sector position counter
BEA0 BNE step_inward ; Non-zero: continue processing
BEA2 ROR wksp_fdc_head_state ; Set head-loaded flag
BEA5 SEC ; Set carry
BEA6 ROL wksp_fdc_head_state ; Restore head-loaded flag
BEA9 LDA fdc_1770_track ; Read current track from FDC
BEAC CMP #&4f ; Track >= 79 (&4F)?
BEAE BCC begin_step_sequence ; No: normal track step
BEB0 LDA nmi_drive_ctrl ; Get NMI control byte
BEB3 AND #4 ; Bit 2 set (double-sided)?
BEB5 BEQ seek_with_stepping ; Not set: single-sided disc
BEB7 LDX #0 ; X=0: operation ending
BEB9 JMP steps_remaining_check ; Jump to track position update
BEBC .seek_with_stepping←1← BEB5 BEQ
LDA #&ff ; Track &4F: switch to side 1
BEBE STA zp_floppy_track ; Set track to &FF (will be 0 after inc)
BEC0 JSR floppy_set_side_1 ; Select side 1
BEC3 LDA nmi_drive_ctrl ; Get NMI drive control byte
BEC6 STA fdc_1770_drive_control ; Write to FDC control register
BEC9 LDA zp_floppy_state ; Get transfer state
BECB ORA #8 ; Set bit 3 (side switch flag)
BECD BNE check_step_direction ; Branch (always non-zero)
BECF .begin_step_sequence←1← BEAE BCC
LDA zp_floppy_state ; Get transfer state
BED1 ORA #2 ; Set bit 1 (track step flag)
BED3 .check_step_direction←1← BECD BNE
STA zp_floppy_state ; Store updated state
BED5 DEC nmi_tracks_remaining ; Decrement full track count
BED8 BEQ step_outward ; Zero: check for partial track
BEDA LDA #&10 ; Sectors per track = &10 (16)
BEDC STA nmi_sec_position ; Store in sector counter
BEDF .step_outward←1← BED8 BEQ
LDA #&fe ; A=&FE: sector position reset
BEE1 STA zp_floppy_sector ; Store sector position
BEE3 LDX #0 ; X=0: continue processing
BEE5 BEQ steps_remaining_check ; Branch to update (always)
BEE7 .step_inward←1← BEA0 BNE
DEC nmi_sec_position ; Decrement sector position counter
BEEA JMP step_track_counter ; Jump to update sector position
BEED .issue_step_command←1← BE87 BNE
DEC nmi_secs_this_track ; Decrement sectors this track
BEF0 .step_track_counter←2← BE9A JMP← BEEA JMP
LDX #&ff ; X=&FF: more sectors to do
BEF2 .steps_remaining_check←2← BEB9 JMP← BEE5 BEQ
INC zp_floppy_sector ; Increment sector position
BEF4 .step_loop←1← BEFC BNE
LDA zp_floppy_sector ; Get current sector position
BEF6 STA fdc_1770_sector ; Write to FDC sector register
BEF9 CMP fdc_1770_sector ; Read back to verify
BEFC BNE step_loop ; Loop until value sticks
BEFE .return_47←1← BE95 BEQ
RTS ; Return
BEFF .setup_track_for_rw←1← BB8F JMP
LDY #6 ; Y=6: get drive+sector from block
BF01 LDA (zp_ctrl_blk_lo),y ; Get drive+sector byte
BF03 ORA wksp_current_drive ; OR with current drive
BF06 STA zp_floppy_dest_page ; Store as drive control byte
BF08 AND #&1f ; Isolate drive number bits
BF0A BEQ get_sector_from_block ; Drive 0? OK
BF0C JMP bad_address_error ; Non-zero: bad drive error
BF0F .get_sector_from_block←1← BF0A BEQ
BIT zp_floppy_dest_page ; Check drive select bits
BF11 BVC adjust_for_partial_sector ; Bit 6: invalid drive?
BF13 LDA #&65 ; Error &65: volume error (bad drive)
BF15 STA zp_floppy_error ; Store error code
BF17 BNE branch_to_floppy_error ; Branch to floppy error
BF19 .adjust_for_partial_sector←1← BF11 BVC
LDA zp_floppy_dest_page ; Get drive control byte
BF1B AND #&20 ; Check bit 5 (drive 1 select)
BF1D BNE check_sectors_remaining ; Not set: drive 0, use &21
BF1F LDA #&21 ; Drive 1: control byte &21
BF21 BNE issue_multi_sector_rw ; Branch (always)
BF23 .check_sectors_remaining←1← BF1D BNE
LDA #&22 ; Drive 0: control byte &22
BF25 .issue_multi_sector_rw←1← BF21 BNE
STA nmi_drive_ctrl ; Store in NMI drive control
BF28 ROR wksp_fdc_head_state ; Set head-loaded flag
BF2B SEC ; Set carry
BF2C ROL wksp_fdc_head_state ; Restore head-loaded flag
BF2F JSR floppy_ts_block_check_range ; Calculate track/sector with range chk
BF32 LDA nmi_drive_ctrl ; Get NMI drive control byte
BF35 STA fdc_1770_drive_control ; Write to FDC control register
BF38 ROR ; Rotate bit 0 to carry
BF39 BCC handle_sector_error ; C=0: last access was other drive
BF3B LDA wksp_fdc_track_0 ; Get saved track for this drive
BF3E STA zp_floppy_track ; Store as current track
BF40 BIT wksp_fdc_head_state ; Check head-loaded state
BF43 BPL return_48 ; Head loaded: no seek needed
BF45 BMI restore_track_zero ; Branch (always)
BF47 .handle_sector_error←1← BF39 BCC
LDA wksp_fdc_track_1 ; Get saved track for other drive
BF4A STA zp_floppy_track ; Store as current track
BF4C BIT wksp_fdc_head_state ; Check head-loaded state
BF4F BVC return_48 ; Not loaded: need seek
BF51 .restore_track_zero←1← BF45 BMI
JSR floppy_restore_track_0 ; Seek to track 0 and re-seek
BF54 .return_48←2← BF43 BPL← BF4F BVC
RTS ; Return

Calculate track/sector from block with range check

Convert a logical block number to a physical track and sector number for the floppy disc, checking that the block is within the valid range for the disc.

BF55 .floppy_ts_block_check_range←1← BF2F JSR
LDY #7 ; Y=7: offset to sector mid byte
BF57 LDA (zp_ctrl_blk_lo),y ; Get sector address mid byte
BF59 CMP #&0a ; Sector mid >= &0A (2560 sectors)?
BF5B BCC floppy_ts_b0_block ; Below limit, calculate track/sector
BF5D BNE bad_address_error ; Above &0A: definitely out of range
BF5F INY ; Exactly &0A: check low byte too
BF60 LDA (zp_ctrl_blk_lo),y ; Get sector address low byte
BF62 CMP #0 ; Low byte < 0? (always false)
BF64 BCC check_multi_sector_range ; Compare always false (A >= 0)
BF66 .bad_address_error←3← BA60 JMP← BF0C JMP← BF5D BNE
LDA #&61 ; Error &61: bad address
BF68 STA zp_floppy_error ; Store error code
BF6A .branch_to_floppy_error←1← BF17 BNE
BNE floppy_error ; Branch to floppy error handler
BF6C .check_multi_sector_range←1← BF64 BCC
LDA zp_floppy_control ; Check if multi-sector operation
BF6E AND #&10 ; Bit 4 set: sector count specified?
BF70 BEQ floppy_ts_b0_block ; No, just calculate track/sector
BF72 LDY #9 ; Y=9: offset to sector count
BF74 LDA (zp_ctrl_blk_lo),y ; Get sector count
BF76 DEY ; Y=8: back to sector low byte
BF77 CLC ; Clear carry for addition
BF78 ADC (zp_ctrl_blk_lo),y ; Add sector count to start sector
BF7A BCS volume_error ; Carry set: overflow, error
BF7C CMP #1 ; End sector < 1? (no sectors)
BF7E BCC floppy_ts_b0_block ; OK, calculate track/sector
BF80 .volume_error←1← BF7A BCS
LDA #&63 ; Error &63: volume error
BF82 STA zp_floppy_error ; Store error code
BF84 BNE floppy_error ; Branch to floppy error handler
fall through ↓

Calculate track/sector from block at &B0

Convert the logical block number at (&B0) to a physical track and sector number.

BF86 .floppy_ts_b0_block←3← BF5B BCC← BF70 BEQ← BF7E BCC
LDY #7 ; Y=7: offset to sector mid byte
BF88 LDA (zp_ctrl_blk_lo),y ; Get sector address mid byte (X)
BF8A TAX ; Transfer low byte to X
BF8B INY ; Y=8: offset to sector low byte
BF8C LDA (zp_ctrl_blk_lo),y ; Get sector address low byte (A)
BF8E .floppy_ts_xa
LDY #&ff ; Y=&FF: init quotient to 0 (+1 later)
BF90 JSR xa_div_16_to_ya ; Divide X:A by 16 sectors/track
BF93 STA zp_floppy_sector ; A = sector within track
BF95 STY zp_floppy_track_num ; Y = track number
BF97 TYA ; Copy track to A
BF98 SEC ; Set carry for subtraction
BF99 SBC #&50 ; Subtract 80 tracks (side 0)
BF9B BMI return_49 ; Track < 80: side 0, done
BF9D STA zp_floppy_track_num ; Track >= 80: save adjusted track
BF9F JMP floppy_set_side_1 ; Select side 1

Divide X:A by 16, result in Y:A

Divide the 16-bit value X:A by 16 (shift right 4 places). Result quotient in Y, remainder in A.

On EntryXdividend high byte
Adividend low byte
Ymust be &FF (initial quotient)
On ExitAremainder (0-15)
Xcorrupted
Yquotient
BFA2 .xa_div_16_to_ya←6← BA89 JSR← BDCE JSR← BDF0 JSR← BF90 JSR← BFA6 BCS← BFA9 BPL
SEC ; Set carry for subtraction
BFA3 SBC #&10 ; Subtract 16
BFA5 INY ; Increment quotient
BFA6 BCS xa_div_16_to_ya ; No underflow, subtract again
BFA8 DEX ; Underflow: borrow from high byte
BFA9 BPL xa_div_16_to_ya ; High byte >= 0, continue subtracting
BFAB ADC #&10 ; Add back the last 16 (remainder)
BFAD .return_49←1← BF9B BMI
RTS ; Return

Handle floppy disc error

Process an error from the WD1770 floppy disc controller. Translates the controller error code into an ADFS error code and stores the error information in workspace.

BFAE .floppy_error←7← BAC3 JMP← BAF1 JMP← BB3F JMP← BB7F JMP← BCDC JMP← BF6A BNE← BF84 BNE
LDX wksp_stack_save ; Restore stack pointer from saved val
BFB1 TXS ; Restore stack from saved pointer
BFB2 LDA wksp_fdc_xfer_mode ; Check if NMI was in use
BFB5 AND #&20 ; Bit 5: NMI active?
BFB7 BEQ release_tube_after_floppy ; No NMI, skip to Tube release
BFB9 LDA nmi_drive_ctrl ; Get NMI status byte
BFBC ROR ; Rotate bit 0 into carry
BFBD LDA zp_floppy_track ; Get partial transfer count
BFBF BCC store_second_partial ; C=0: store as second count
BFC1 STA wksp_fdc_track_0 ; C=1: store as first count
BFC4 ROL wksp_fdc_head_state ; Set bit 6 of transfer flag
BFC7 CLC ; Clear carry
BFC8 ROR wksp_fdc_head_state ; Clear bit 0 of transfer flag
BFCB BCS save_error_and_release_nmi ; Branch if first partial sector
BFCD .store_second_partial←1← BFBF BCC
STA wksp_fdc_track_1 ; Store as second count
BFD0 LDA wksp_fdc_head_state ; Get transfer state flags
BFD3 AND #&bf ; Clear bit 6
BFD5 STA wksp_fdc_head_state ; Store updated flags
BFD8 .save_error_and_release_nmi←1← BFCB BCS
LDA zp_floppy_error ; Get error code from zp_a0
BFDA STA wksp_err_number ; Save as error number
BFDD JSR release_nmi ; Release NMI
BFE0 .release_tube_after_floppy←1← BFB7 BEQ
JSR release_tube ; Release Tube if in use
BFE3 LDX zp_ctrl_blk_lo ; Restore control block ptr low
BFE5 LDA wksp_err_number ; Get error number
BFE8 BEQ return_error_code ; Zero = no error, return success
BFEA ORA #&40 ; Set bit 6: disc error flag
BFEC LDY #&ff ; Y=&FF: mark transfer incomplete
BFEE STY wksp_fdc_head_state ; Mark transfer as incomplete
BFF1 .return_error_code←1← BFE8 BEQ
LDY zp_ctrl_blk_hi ; Restore control block ptr high
BFF3 AND #&7f ; Mask to 7-bit error code
BFF5 RTS ; Return

ROM footer text

The text 'and Hugo.' followed by CR. This fills the last 10 bytes of the ROM, a credit to Hugo Tyson who wrote ADFS. The 'Hugo' string also serves as the 4-byte magic number at both ends of every ADFS directory structure.

BFF6 .str_rom_footer
EQUS "and Hugo.." ; "and Hugo." + CR: ROM footer text