Acorn ADFS 1.30
Updated 1 Apr 2026
- Disassembly source on GitHub
- Acorn ADFS 1.30 in The BBC Micro ROM Library
- Discuss this disassembly on the Stardot Forums thread: A new annotated disassembly of Acorn ADFS
- A Stardot request for a disassembly of ADFS 1.30
- Hoglet's ADFS 1.30 disassembly (BeebAsm, byte-identical reassembly)
- Dominic Beesley's ADFS multi-target reassembly (ca65, multiple ADFS versions)
- J.G. Harston's ADFS disassembly (BBC BASIC, annotated)
- Found a mistake or a comment that could be clearer? Report an issue.
| ; 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 presentClaim 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 useRelease 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 settlingRead 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.
|
||||||||
| 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 phaseSelect 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 operationSet 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.
|
|||||||||||||
| 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 commandExecute 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.
|
||||||||
| 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 statusRelease the Tube, then read the SCSI status and message bytes to determine the outcome of the command.
|
||||||||
| 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 transferTransfer 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 disabledDisable interrupts then call the Tube host code at &0406 to initiate a data transfer.
|
||||
| 81EF | .tube_start_xfer_sei←2← 820D JSR← 8225 JSR | |||
| SEI ; Disable interrupts for Tube xfer | ||||
| fall through ↓ | ||||
Start Tube transferCall the Tube host code at &0406 to initiate a data transfer. Followed by a delay for Tube synchronisation.
|
||||
| 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 commandSend 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.
|
||||||||
| 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 blockExecute 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 errorExecute disc command via command_exec_xy. On error, generate a BRK (never returns). On success, restore saved drive and return.
|
||||||
| 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 errorGenerate a BRK error from the disc error code in A. Never returns to caller.
|
||||
| 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 phaseWait for SCSI REQ, then write byte A to the SCSI data bus. Returns only on success; generates BRK on error.
|
||||
| 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 ensuredIf 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 signalPoll 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.
|
||||||||||||
| 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 REQWait for SCSI REQ then write A to the SCSI data register. May not return if MSG phase detected (unwinds call stack to command_done).
|
||||
| 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 recoverySave 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 errorReload 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 suffixGenerate 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 XGenerate a BRK error from the inline error data following the JSR. X controls whether the drive:sector suffix is appended. Never returns.
|
||||
| 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 constantsReversed 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 blockWrite the byte in A as two ASCII hex digits into the error block at the current position Y.
|
|||||||||||||
| 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 digitConvert a 4-bit value in A to an ASCII hex character ('0'-'9' or 'A'-'F'). The low nibble of A is used.
|
|||||||||||
| 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 blockWrite the byte in A as up to three decimal digits into the error block at the current position Y, suppressing leading zeros.
|
|||||||||||||
| 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 invalidSet flags to indicate that the in-memory free space map and directory buffer may be stale and need reloading from disc.
|
||||||||
| 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 stringsCR-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 valueCall OSBYTE with Y=&FF and X=0 to read the current value of the variable specified in A.
|
|||||||||||
| 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 XCall OSCLI with the command string address in X (low byte).
|
|||||||||||
| 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 mapReturn 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.
|
|||||||||||
| 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 entryCopy the object sector address and add the released block size to an existing FSM length entry, merging adjacent free regions.
|
|||||||||||
| 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 FSMCheck 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.
|
|||||||||||
| 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 | |||||||||||
| 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 |
| 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 characterIncrement 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 searchSkip leading spaces, set up directory search state, and clear the search result workspace. Falls through to check_char_is_terminator.
|
||||||||
| 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 terminatorTest whether the character at (&B4),Y is a filename terminator: space, dot, double-quote, or control character.
|
|||||||||||
| 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 limitScan 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.
|
||||||||
| 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 wildcardsCompare the object name in workspace against the pattern at (&B4),Y. Supports '#' (match one char) and '*' (match rest) wildcards. Case-insensitive comparison.
|
|||||||||||||
| 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 exhaustedAfter 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 '*' matchingSkip 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 matchSet 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 '*' matchAfter 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 readsTwo 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 characterConvert 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.
|
|||||||||||
| 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 driveJump 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 lineParse a filename from (&B4) including drive specifier, root, and parent directory references.
|
||||||||
| 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 typeAfter 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 entryAdvance (&B6) by 26 bytes to the next directory entry, then check whether it matches the current search pattern.
|
||||||||
| 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 workspaceLoad object type from workspace and fall through to save_wksp_and_return.
|
||||||||
| 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 resultRestore 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.
|
|||||||||||
| 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 commandSet up sector count and execute a disc read or write command. Rounds up partial counts for writes. Generates BRK on error.
|
||||||||
| 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 countFor 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 batchesFor 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 transferTransfer 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 transferTransfer 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 fileParse a filename and search the current directory for a matching non-directory entry.
|
||||||||
| 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 errorCheck 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 saveEntry 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 fileCopy 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 saveSearch 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 searchExtract filename from the OSFILE control block, parse the path, and search the current directory.
|
||||||||
| 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 openCheck 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 openCheck 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 foundAll 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 wildcardsScan 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 terminatorRead 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 errorReload 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 charactersSix 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 entryCopy 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 entryAllocate 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 entryCopy 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 entryCheck 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 discVerify 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 entryParse a filename from the command line and search the current directory for the first entry matching the parsed filename pattern.
|
||||||||
| 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 dirtyValidate 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 errorGenerate 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 checksumsCompute 8-bit checksums of FSM sectors 0 and 1 by summing all 255 bytes of each sector.
|
||||||||
| 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 templateAn 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 handlerHandle 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 handlerRemove 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 entryCheck 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 handlerHandle 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 | |
| 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 stringPoint (zp_entry_ptr) to a pathname format string in ROM and prepare to print up to 12 characters.
|
|||||||||||
| 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 stringPop 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.
|
||||||||
| 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 registersWrite A via OSASCI while preserving A, X, and (&B6). Used during catalogue printing.
|
|||||||||||
| 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 stringPrint 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 tableFive-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 digitsPrint the value in A as two hexadecimal ASCII digits via OSWRCH, high nibble first.
|
||||
| 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 headerVerify 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 handlerDisplay 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) directoryCheck if the first character of the argument is ^ (parent directory) or @ (current directory). Sets (&B6) to point to the appropriate directory footer area.
|
||||||||
| 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 argumentIf 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 directoryParse 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 handlerDisplay 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 verboseCheck *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 entryPrint 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 handlerChange 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 handlerCreate 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 |
|
| 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 formatSet 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 handlerChange 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 entryStrip 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 stringClear 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 handlerDelete 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 vectorJump indirectly through the filing system control vector. |
|
| 9A43 | .jmp_indirect_fscv←1← 9B89 JSR |
| JMP (fscv) ; Jump through filing system control | |
Default workspace initialisation template29-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 hardwareCheck whether a SCSI hard drive is present by attempting to read the SCSI status register.
|
||||||||
| 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 stringsThree-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) | |
| 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 handlerMain 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 claimInitialise 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 ADFSReturn 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 claimClaim 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-bootHandle 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 addressesSeven 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 tableSeven 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 stringThe 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 commandHandle 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 onClean 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 OSWORDHandle 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 linePrint 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: *HELPHandle *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 listPrint 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 tableEight 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 handlerHandle 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 | |
| 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 commandMatch 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 tableTable 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 stringsSeven 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 rangeReturn 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).
|
||||||||
| 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 handlerHandle 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.
|
||||||
| 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 characterPrint 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 handlerDisplay 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 handlerDisplay 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 recommendedAfter *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 handlerDelete 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 handlerClose 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 blockDisc 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 argumentParse 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 handlerClose 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 workspaceCopy 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.
|
||||
| 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 handlerMount 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 blockDisc 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 discSum 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 decimalPrint 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 handlerChange 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 handlerCompact 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 errorReload 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 byteTake high nibble from workspace, shift left 4, and OR with low nibble to produce a combined byte.
|
|||||||||||
| 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 lineSkip 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 handlerLoad 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 handlerChange 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 directorySave 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 copyRestore 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 handlerDisplay 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 handlerDisplay 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 handlerSwitch 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 stringAdvance (&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 argumentAdvance (&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 colonCheck 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 handlerRename 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 loadedCheck 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 integrityCheck 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 errorGenerate 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 &BALoad 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 checksumCalculate 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 checksumCalculate 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 checksumCheck 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 workspaceSave 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 directoryRestore 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 loadCopy 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 ↓ | |
| 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 handlerCopy 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 selectedHandle 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 handlerHandle 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 buffersIterate 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 pointerScan 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/BPUTRead or write a single sector via the SCSI interface for byte-level file access (BGET/BPUT channel operations).
|
||||||
| 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 busTransfer 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 sectorScan channel buffer table for a buffer matching the target sector. If not found, evict the oldest buffer for reuse.
|
|||||||||||
| 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 SCSIIssue 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 offsetDivide 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 entriesStep 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 YCheck that Y contains a valid file handle and set the channel offset workspace variable.
|
|||||||||||
| 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 PTRCompare the file extent (EXT) with the current pointer (PTR) for the channel in the workspace.
|
||||||||
| 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 errorClear 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 handlerHandle 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 BGETCompute 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/OSave 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 pointerAdd 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 EXTIf 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 valueCopy 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 handlerHandle 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 writeIncrement 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 EOFIf 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 handlerClose 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 handlerHandle 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 scanAdvance 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 closeSwitch 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 neededRead 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 slotBuild a bit mask by rotating based on the drive slot index, then AND with drive-change flags.
|
||||||||
| 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 driveExtract 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 indexShift drive number in A right 4 bits to produce a slot index in X.
|
|||||||||||
| 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 handlerHandle 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 bufferConfigure 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 bufferWrite 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.
|
|||||||||||
| 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 nameWrite 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 memoryCopy 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 presentTest whether the WD1770 floppy disc controller is present by probing its registers.
|
||||||||
| 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 trackWrite 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 retryAfter 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 verifyWrite 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.
|
|||||||||||
| 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 commandExecute 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 transferSet 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 floppySet 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 transferClaim 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 rateFetch 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 12Issue 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 11Issue 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 workspaceCopy 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. |
||
| ROM | Exec | |
|---|---|---|
| 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 handlerNot 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. |
||
| ROM | Exec | |
|---|---|---|
| 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 handlerNo 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. |
||
| ROM | Exec | |
|---|---|---|
| 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 completeWait 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 discPatched 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. |
||
| ROM | Exec | |
|---|---|---|
| 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 discPatched 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. |
||
| ROM | Exec | |
|---|---|---|
| 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 TubePatched 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. |
||
| ROM | Exec | |
|---|---|---|
| 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 commandChoose 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 0Unreferenced 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 1Select 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 flagClear 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 flagClear 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 0Issue 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 commandIf 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).
|
|||||||||||
| 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 trackSet 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 sectorCalled 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 checkConvert 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 &B0Convert 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:ADivide the 16-bit value X:A by 16 (shift right 4 places). Result quotient in Y, remainder in A.
|
|||||||||||||||
| 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 errorProcess 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 textThe 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 | |
