DEVICE ZXSPECTRUM48 ; From-scratch open SD/FAT32 driver for ZiFi, replacing the closed ; WDFCVBI2.COD -- built and hardware-tested incrementally in ; D:\ST\sjasmplus\sdtest\sdtest.asm across this whole investigation ; (see [[project-zifi-custom-sd-driver]]), this file assembles the ; SAME proven code into the jump-table shape zifi.asm's CORE equ #2002 ; expects. ; ; Table layout confirmed by cross-referencing WC's own KERNEL.ASM ; START table (E:\zx-evo-master\pentevo\soft\WC\source\core32\ ; KERNEL.ASM) -- byte-for-byte matching offsets (DEV_INI=+3, HDD=+9, ; LOAD512=+21, SAVE512=+24, DOS_SWP=+27, MKFILE=+57, MKDIR=+60, ; DELFL=+63, RENAME=+66, FENTRY=+78, LOADNON=+84, NXTETY=+87) for the ; first 31 entries, PLUS three more (SETDIR=+93, SETROOT=+96, SEEK0= ; +99) that WC's own core32 table does NOT have -- WDFCVBI2.COD's own ; table is a superset/independent implementation, not a live reference ; into whatever WC happens to be installed (confirmed by the user ; directly: Wild-Commander-Improved, which has the LFN bug already ; fixed in ITS OWN core32, did NOT fix ZiFi's bug -- so ZiFi's driver ; is definitely self-contained, not calling into WC's shared kernel). ; ; Loaded via spgbld's Block directive at #E000/page matching the ; original WDFCVBI2.COD placement -- the exact mechanism by which that ; maps to the fixed #2002 jump-table address is still not 100% proven ; (see [[project-zifi-custom-sd-driver]] for the open question), but ; the original driver's own DOS_SWP/LOAD512-real-address evidence ; found earlier in this investigation, PLUS this file being structured ; identically (jump table at the very start of the loaded block), ; should reproduce whatever that mechanism is. THIS IS THE FIRST REAL ; HARDWARE TEST of that hypothesis -- expect to iterate. ; ; Entries zifi.asm never calls (grepped for every CORE+n EQU it ; actually uses) are safe no-op/stub implementations: RDD/SDD/GIPAG/ ; GLSTCAT/TLSTCAT/SRHFCL/MKSG/RFRH/GENTRY/TENTRY/DLSG/CHTOSE/LOAD256/ ; NXTETY2/REINI, and the three raw "Z0" slots WC's own table leaves ; unimplemented too. NXTETY (directory enumeration, a different job ; from FENTRY's single-name search) is ALSO a stub for now -- zifi.asm's ; only reference to it (VYGREB/VYG) is commented-out dead code ; ("Выгребаем каталог, чисто по приколу" -- "just for fun"), never ; actually called by the real running program. ; NOTE: this code is invoked via zifi.asm's init_sd_card (line ~4134), ; which does "ld a,sd_driver_page(#0f) / jp set_page0" -- i.e. maps ; page #0F into PAGE0 (CPU slot 0, addresses #0000-#3FFF), NOT slot 3. ; CORE=#2002 is a slot-0 address, so every internal absolute JP/CALL in ; this file must be assembled as if it lives at #2002, not #E000 -- ; otherwise the driver hangs the instant it's entered (confirmed on ; hardware: first attempt used ORG #E000 and hung on the boot banner). ; ; spgbld's Block directive only accepts 512-byte-aligned addresses, and ; #2002 isn't one -- so we can't just tell it to load at offset #2002 ; directly (Block = #e002 -> "Block address is not a 512 multiple!"). ; Instead we load at the aligned #e000 (page-offset #2000, same as the ; original WDFCVBI2.COD placement) and pad 2 bytes here so the actual ; jump table lands exactly on #2002 once loaded. This is almost ; certainly what WDFCVBI2.COD's own "DOS_SWP; DEPACK Driver" call was ; really doing at runtime (relocating itself those same 2 bytes) -- ; padding at assembly time is simpler and needs no runtime relocator. ORG #2000 DS 2 ; pad offset #2000-#2001; jump table ; below starts at #2002 = CORE CONF EQU #77 DATA EQU #57 ; Body-routine addresses (sdinit, dma_set_addr, sdread_lba, parse_bpb, ; core_fentry, get_next_cluster, write_fat_entry, find_by_name, ; sdwrite_lba, cur_cluster_hi/lo, target_name, dot_name, ; clear_dir_entry, and everything else from zc_sd_driver_body.asm) -- ; imported here as plain EQU constants (generated from that file's own ; --sym dump by build.ps1) so every JP/CALL/reference below resolves ; correctly even though the actual body code isn't present in this ; file -- it's compressed and depacked at runtime by drv_dos_swp ; instead. See the fuller comment further down and ; [[project-zifi-custom-sd-driver]]. INCLUDE "body_syms.inc" ;--- Build-time guard: the depacked body (ORG #0000..body_end, written ;--- by drv_dos_swp at runtime) MUST stay strictly under #2002 (CORE ;--- itself, this file's own resident jump table) -- otherwise depacking ;--- silently overwrites the START of THIS file's own resident code, ;--- every single time it runs. This isn't hypothetical: body growth ;--- crossed #2000 by 198 bytes in this exact project (body_end reached ;--- #20C6) and every CORE dispatch call (DEV_INI, HDD, FENTRY, ;--- everything) silently jumped to garbage as a result -- diagnosed ;--- the hard way via PEEKPHYS/checkpoint instrumentation after ;--- "zifi.ini not found" started happening. The threshold is #2002, not ;--- #2000: the 2 bytes at #2000-#2001 (see "DS 2" above) are pure ;--- alignment padding, never read by anything, so the depacked body may ;--- safely fill them too -- body_end==#2002 means the last byte written ;--- is at #2001, one below CORE. Never remove this check; if it fires, ;--- shrink zc_sd_driver_body.asm (or increase this IF condition's ;--- threshold together with confirming real headroom exists all the way ;--- to code_end below #4000) before doing anything else. See ;--- [[project-zifi-custom-sd-driver]]. --- IF body_end > #2002 DEFB 1/0 ; BODY OVERLAPS RESIDENT CODE -- FIX THIS, see comment above ENDIF ;===================================================================== ; Jump table -- 34 entries x 3 bytes (JP nnnn), offsets 0..99, must ; stay in this exact order/spacing to match every CORE+n EQU in ; zifi.asm. ;===================================================================== JP drv_seldev ; +0 JP wrap_dev_init ; +3 JP drv_stub_ok ; +6 REINI JP wrap_hdd ; +9 JP drv_stub_fail ; +12 RDD JP drv_stub_fail ; +15 SDD JP drv_stub_fail ; +18 GIPAG JP wrap_load512 ; +21 JP wrap_save512 ; +24 JP wrap_dos_swp ; +27 JP drv_stub_fail ; +30 GLSTCAT JP drv_stub_fail ; +33 TLSTCAT JP drv_stub_fail ; +36 SRHFCL JP drv_stub_fail ; +39 Z0 JP drv_stub_fail ; +42 MKSG JP drv_stub_fail ; +45 Z0 JP drv_stub_fail ; +48 RFRH JP drv_stub_fail ; +51 GENTRY JP drv_stub_fail ; +54 TENTRY JP wrap_mkfile ; +57 JP wrap_mkdir ; +60 JP wrap_delfl ; +63 DELFL JP wrap_renam ; +66 RENAM JP drv_stub_fail ; +69 DLSG JP drv_stub_fail ; +72 CHTOSE JP drv_stub_fail ; +75 Z0 JP wrap_fentry ; +78 JP drv_stub_fail ; +81 LOAD256 JP wrap_loadnon ; +84 JP drv_nxtety ; +87 JP drv_stub_fail ; +90 NXTETY2 JP wrap_setdir ; +93 JP wrap_setroot ; +96 JP wrap_seek0 ; +99 ;===================================================================== ; di/ei wrappers around every entry point that does real work while ; executing from CPU slot 0 (this whole driver only exists there while ; PAGE0 points at page #0F -- see the ORG note above). Found on ; hardware: zifi.asm's own "off_int_dma"/save_mode flag (which init_sd_ ; card/sd_exit toggle around SD operations) does NOT cover everything -- ; int_main's pt_play (zifi.asm ~line 2350, gated by a SEPARATE "music_sw" ; flag, not save_mode) calls set_music_pages_lite EVERY interrupt ; whenever a track is playing, which repages PAGE0 *and* PAGE1 *and* ; PAGE3 for the music player's own use. If that fires while we're mid- ; CORE-call: PAGE0 changing while we're actively EXECUTING CODE FROM ; SLOT 0 crashes/jumps to garbage the instant the interrupt returns ; (next instruction fetch reads the wrong page); PAGE3 changing mid- ; LOAD512/SAVE512 silently swaps the transfer's source/dest to whatever ; the music player last pointed it at instead of the real caller data -- ; confirmed on hardware as the cause of downloaded files playing back as ; a constant garbage pattern instead of the real track. Wrapping the ; real logic in a plain di/ei critical section (bodies below are ; UNCHANGED, just called instead of jumped to) closes both holes ; regardless of music_sw/save_mode state. The original hardware-tested ; function bodies keep their own names/all internal early-return paths ; exactly as they were -- only the outer entry/exit changed. ;===================================================================== ; Every wrap_* below preserves IX across the real call (push/pop around ; it) -- ZiFi.asm's own CORE-ABI convention relies on IX surviving a ; CORE call unmolested (save_downloaded_file sets "ld ix,read_threads" ; ONCE, then keeps using (ix+thread.n) addressing across MULTIPLE CORE ; calls, including AFTER MKFILE returns, with no re-load in between). ; filter_name_to_83 (zc_sd_driver_body.asm, used by core_mkfile/ ; core_mkdir's short-name generation) uses IX internally as a plain ; scratch pointer (ld ix,sn_base / ld ix,sn_ext) and never restores it ; -- so a caller relying on IX surviving MKFILE was silently reading ; save_64's (ix+thread.page)/(ix+thread.adress) from wherever sn_base/ ; sn_ext happened to leave IX pointing (this driver's own low, #0000- ; based body addresses, still mapped into slot 0 at that point) instead ; of zifi's real read_threads struct -- confirmed as the actual root ; cause of "every download saves as identical, page/track-independent ; garbage" (2026-09-09): read_threads.page/adress were correct the ; WHOLE time, SAVE512 was just told to read from the wrong place by a ; stale IX. See [[project-zifi-custom-sd-driver]]. --- wrap_dev_init di push ix call drv_dev_init pop ix ei ret wrap_hdd di push ix call drv_hdd pop ix ei ret ;--- NO di/ei needed here, matching what a real disassembly of the ;--- ORIGINAL closed WDFCVBI2.COD driver's own SAVE512/LOAD512 turned ;--- out to do (hand-verified: zero real DI/EI opcodes anywhere in its ;--- transfer routine at #22DB). That disassembly also showed WHY it ;--- didn't need any: its transfer uses the TS-Conf DMA controller, not ;--- a CPU-driven page1_port+LDIR loop. core_save512/core_load512 (in ;--- zc_sd_driver_body.asm) now do the same -- DMA straight between the ;--- SD card and the caller's page, addressed by physical page number, ;--- never touching the CPU's slot mapping at all. Two di/ei variants ;--- around the OLD LDIR-based version were tried first and both made ;--- things WORSE on real hardware (di/ei around the whole transfer: ;--- audio noise + hangs on track switch; di/ei only around the brief ;--- PAGE1-touch+LDIR: full hangs with garbage on screen) -- di/ei ;--- was never going to fix it, since the LDIR's dependence on PAGE1 ;--- staying stable across the whole copy was the real hazard, not a ;--- timing race di/ei could close (a write-only page port can't be ;--- restored after set_music_pages_lite clobbers it mid-transfer, ;--- di/ei or not). See [[project-zifi-custom-sd-driver]]. --- wrap_load512 push ix call core_load512 pop ix ret wrap_save512 push ix call core_save512 pop ix ret ; Runtime filesystem calls are already bracketed by zifi.asm's ; init_sd_card/sd_exit. That disables every IRQ job which can repage CPU ; slots or touch DMA, while leaving the raster interrupts themselves alive. ; Keeping DI across FAT scans and card busy waits skipped the text-mode raster ; lines and produced full-height bands / flashes of the backing splash. wrap_mkfile push ix call core_mkfile pop ix ret wrap_mkdir push ix call core_mkdir pop ix ret wrap_fentry push ix call drv_fentry pop ix ret wrap_loadnon push ix call drv_loadnon pop ix ret wrap_setdir push ix call core_setdir pop ix ret wrap_setroot push ix call core_setroot pop ix ret wrap_seek0 push ix call stream_open pop ix ret wrap_delfl push ix call core_delfl pop ix ret wrap_renam push ix call core_renam pop ix ret ; DOS_SWP now does real work (depacking the body -- see drv_dos_swp ; below), unlike every other entry here it previously had NO di/ei ; protection at all (harmless when it was a no-op). Real interrupts ; MUST stay off for the whole unpack: page #0F is mapped into slot 0 ; the entire time, and zifi.asm's pt_play interrupt handler repages ; PAGE0/1/3 for the music player if it fires mid-CORE-call (see the ; header comment above) -- exactly the same risk this wrapping already ; protects every other substantive entry against. ; No `ei` here (unlike every other wrap_*): DOS_SWP is ALWAYS ; immediately followed by another di-guarded CORE call in zifi.asm's ; own fixed sequence (sd_init: CALL DOS_SWP; CALL DEV_INI; ...) -- so ; re-enabling interrupts here just to have the very next call disable ; them again is pointless, and genuinely risky while depacking: an ; interrupt landing in the gap would fire with PAGE0 still pointing at ; page #0F, jumping to #0038 (IM1 vector) which -- mid-depack -- can ; be anything from still-compressed garbage to not-yet-written body ; code, not a real ROM/interrupt handler. Confirmed by direct testing: ; this exact gap crashed a standalone depack test (wild PC, eventual ; HALT) until `ei` was removed here. wrap_dos_swp di call drv_dos_swp ret ;--- drv_stub_ok / drv_stub_fail: safe stubs for every CORE-table slot ;--- zifi.asm never actually calls (confirmed via its own CORE+n EQU ;--- list) -- Z (success-shaped) or NZ (not-found/error-shaped) ;--- respectively, whichever is the safer default for a function ;--- nothing should ever invoke. --- drv_stub_ok xor a ret drv_stub_fail xor a inc a ret drv_seldev xor a ret ;--- drv_dev_init: CORE+3, DEV_INI -- raw SD card init only (matches ;--- zifi.asm's own "CALL DEV_INI:JP NZ,ER0" -- HDD/partition search is ;--- a separate, later call). Z=ok, NZ=fail. --- drv_dev_init call sdinit ret ;--- drv_hdd: CORE+9, HDD ("SEARCH PARTITION" per WC's own naming) -- ;--- reads sector 0, detects/validates the partition (detect_partition ;--- already does its own verify_fat32 check internally, falling back ;--- to sector 0 itself if no MBR partition table is present), then ;--- parses the real BPB. Z=ok, NZ=fail. --- drv_hdd ld hl,0 ld de,0 call set_lba32 call sdread_lba ret nz call detect_partition ret nz call parse_bpb ; Pre-warm the RTC read HERE, during sd_init's quiet pre-EI ; window (zifi.asm calls sd_init at line ~56, then "ei" at ; line 57 -- HDD runs before interrupts are even on and ; before any active UI/video updates). rtc_to_fat_datetime's ; own caching means every LATER call (from core_mkdir/ ; core_mkfile during the active WiFi-connect/download phase) ; just reuses this result instead of touching the RTC chip ; again. This matters because [[project-zifi-rtc-time-sync]] ; already found -- the hard way, on this exact board -- that ; touching #EFF7 (which this RTC read also does, to select ; the CMOS/RTC register space) during an active UI phase can ; cause real corruption even with correct di/ei and a proper ; read-modify-write, for reasons never fully root-caused; ; the only fix that actually worked there was moving WHEN ; the touch happens, not how. Doing it here avoids the same ; class of risk instead of re-discovering it the hard way. call rtc_to_fat_datetime xor a ret ;--- drv_dos_swp: CORE+27, DOS_SWP ("SELDEVnDRIVERinit" / "DEPACK ;--- Driver" per WC's/zifi's own naming) -- the original closed driver ;--- was compressed and self-relocated here; this driver now does the ;--- same thing for real, not just in spirit: the "body" (everything ;--- from sdinit onward -- DMA/SD/FAT32/CORE-ABI logic, built and ;--- hardware-tested as zc_sd_driver_body.asm) is compressed and stored ;--- as body_packed_start..body_packed_end below. Decompressing it to ;--- its true addresses (#0000-body_end, see body_syms.inc) is what ;--- makes those addresses (and every JP/CALL into them from this ;--- resident half, including the jump table indirectly via wrap_hdd ;--- etc, and DELFL/RENAM below) valid -- this MUST run before any ;--- other CORE entry point does real work, matching zifi.asm's own ;--- fixed call order (sd_init: CALL DOS_SWP; DEPACK Driver -- always ;--- immediately after paging page #0F into slot 0, always before ;--- DEV_INI/HDD/etc). ;--- ;--- Hrust1, not ZX0: ZX0 (both the "standard" forward AND backward ;--- Z80 decoders, dzx0_standard(_back).asm by Einar Saukas) was tried ;--- first and, after extensive isolated Z80-harness testing (see ;--- [[project-zifi-custom-sd-driver]]), both directions turned out to ;--- decompress small/simple payloads (even ones with real ;--- back-references) correctly but produce garbage specifically on ;--- this driver's full 8027-byte body -- root cause never found ;--- despite real effort, abandoned as too large a time sink for an ;--- optional space optimization. ;--- ;--- Chased a real, never-root-caused bug here for a long time: every ;--- attempt to fix load_ini's address-0 collision FROM THIS SIDE ;--- (padding the body with a sacrifice_buf, later an ORG shift to ;--- #0200 with zero added bytes) reliably broke BOTH Z80 Hrust ;--- decoders tried (hand-transcribed classic dehst.asm DEHRUST, ;--- confirmed byte-for-byte correct against source via automated ;--- diff; and hrust13.exe's own "-depacker" self-relocating output) ;--- the instant core_load512's fix (in zc_sd_driver_body.asm) grew ;--- the body by its own ~53 bytes, regardless of padding/positioning ;--- -- see [[project-zifi-custom-sd-driver]] for the full history. ;--- Root cause in either decoder never found under real time ;--- pressure. Fixed properly instead: zifi.asm's load_ini/parse_ini ;--- now use LOAD512's SAME slot-1/#4000/PAGE1 convention as SAVE512 ;--- (see core_load512's own comment in zc_sd_driver_body.asm), so ;--- load_ini never touches address 0 at all anymore. ;--- ;--- Hrust1 fully abandoned at this point (both the classic hand- ;--- transcribed dehst.asm DEHRUST -- confirmed byte-for-byte correct ;--- against source via automated diff, TWICE -- and hrust13.exe's own ;--- "-depacker" output kept producing wrong Z80 decompression results ;--- on data that a PC-side reference tool, dehrust1.exe, decoded ;--- correctly every single time, on EVERY size/content tried, ;--- including this exact 8018-byte body with no changes at all except ;--- core_load512's own content -- so dehrust1.exe was never actually ;--- validating what was assumed; root cause never found under real ;--- time pressure -- see [[project-zifi-custom-sd-driver]]). ;--- ;--- Replaced with a trivial, self-written, fully-verified PackBits- ;--- style RLE (round-tripped byte-for-byte on the PC first, in ;--- PowerShell, before ever touching Z80 code): control byte C, ;--- C=128 is the end marker; C<128 is a literal run of C+1 bytes ;--- (copied verbatim); C>128 is a repeat run of (257-C) copies of the ;--- single byte that follows (257-C covers 2..128 for C=255..129; C=1 ;--- unused/invalid, C=128 reserved as the terminator instead of a ;--- literal-run-of-1 encoding). Only ~12.5% compression is actually ;--- needed here (8018-byte body must fit under ~7076 bytes to leave ;--- room for the #2000-#3FFF dispatcher) -- this achieves 5929 bytes ;--- (~74%), comfortable margin, on real driver code (long runs of ;--- identical bytes are common in data tables/padding). --- drv_dos_swp ld ix,body_packed_start ld de,0 call PB_DECODE xor a ret ;===================================================================== ; PB_DECODE: PackBits-style RLE decoder (see drv_dos_swp's comment for ; the format). IX=source, DE=dest. Doesn't touch SP. ;===================================================================== PB_DECODE PB_LOOP ld a,(ix+0) inc ix cp 128 ret z ; terminator bit 7,a jr z,PB_LITERAL neg ; a = 256-ctrl (ctrl in 129..255) inc a ; a = 257-ctrl (mod 256) = repeat count (2..128) ld b,a ld a,(ix+0) inc ix PB_REP_LOOP ld (de),a inc de djnz PB_REP_LOOP jr PB_LOOP PB_LITERAL inc a ; a = ctrl+1 = literal count (1..128) ld b,a PB_LIT_LOOP ld a,(ix+0) inc ix ld (de),a inc de djnz PB_LIT_LOOP jr PB_LOOP ;--- drv_fentry: CORE+78, FENTRY -- real ABI is HL -> {flag(1), ;--- name(1-255),0} (flag = file-vs-dir attribute filter, not yet ;--- implemented -- see [[project-zifi-custom-sd-driver]] Next steps). ;--- On a match, automatically opens a stream on the found entry's ;--- cluster (matching the real ABI's own doc: "SEEK0 is automatically ;--- called") so a caller can go straight into LOAD512/SAVE512/LOADNON ;--- without any extra setup, exactly like zifi.asm's own real call ;--- sites do (FENTRY then LOAD512, nothing in between). Z=NOT FOUND, ;--- NZ=FOUND + [DE,HL]=length. --- drv_fentry inc hl ; skip the flag byte (filter not ; implemented yet -- matches by name ; only) call core_fentry or a ret z ; not found ld hl,(fbn_cluster_hi) ld (cur_cluster_hi),hl ld hl,(fbn_cluster_lo) ld (cur_cluster_lo),hl call stream_open ld de,(fbn_size_hi) ld hl,(fbn_size_lo) ld a,1 or a ; NZ (A=1, nonzero) -- found ret ;--- drv_loadnon: CORE+84, LOADNON -- advances the CURRENTLY OPEN ;--- stream (opened by the last drv_fentry match) by B sectors without ;--- reading/writing data. Real ABI doesn't document a return value ;--- beyond implicit success. --- drv_loadnon ld a,b or a ret z dln_loop push bc call strm_advance pop bc djnz dln_loop xor a ret ;--- drv_nxtety: CORE+87, NXTETY -- real ABI is a directory-listing ;--- iterator (GetNextEntryFromActiveDir), a genuinely different job ;--- from FENTRY's single-name search -- NOT YET BUILT. Stubbed as ;--- "always EndOfDir" (Z) since zifi.asm's only reference to it ;--- (VYGREB/VYG) is commented-out dead code, never actually called by ;--- the real running program -- revisit if that changes. --- drv_nxtety xor a ret ; CORE+99, SEEK0 (per FENTRY's own doc, "SEEK0 is automatically ; called" by FENTRY -- nothing in zifi.asm calls this directly) is ; wired straight to stream_open via wrap_seek0 in the jump-table ; wrapper block above -- no separate drv_seek0 needed. ;===================================================================== ; Everything that used to live inline below this point (ported, ; hardware-tested code from D:\ST\sjasmplus\sdtest\sdtest.asm -- DMA/ ; SD/FAT32/CORE-ABI logic) now lives in zc_sd_driver_body.asm instead, ; compiled to its own true addresses (#0000 upward), compressed with ; ZX0 and depacked here at runtime by drv_dos_swp (see above) -- this ; is what freed enough room in this #2000-#3FFF half for DELFL/RENAM ; to fit alongside everything already here. body_syms.inc (included ; near the top of this file) supplies every body label as a plain EQU ; constant. See [[project-zifi-custom-sd-driver]]. ;===================================================================== ;===================================================================== ; CORE ABI: DELFL/RENAM -- never actually called by zifi.asm (grepped ; the whole file for CALL DELFL/CALL RENAM, zero hits), but built ; anyway per explicit user request ("для полноты драйвера"). Both need ; to know exactly which directory entries to mark deleted (0xE5) -- ; the short entry AND any preceding LFN run -- so find_by_name now ; tracks fbn_match_index_lo/fbn_match_run_start_lo (see above) for ; this purpose. ;===================================================================== ;--- core_free_chain: (cur_cluster_hi,cur_cluster_lo) -> frees every ;--- cluster in this FAT chain (FAT entry := 0, mirrored to all copies ;--- via write_fat_entry) by following get_next_cluster until chain_ ;--- ended. A=0 ok, A=1 fail (disk error -- chain may be partially ;--- freed). --- cfc_cur_hi dw 0 cfc_cur_lo dw 0 cfc_next_hi dw 0 cfc_next_lo dw 0 cfc_has_next db 0 core_free_chain ld hl,(cur_cluster_hi) ld (cfc_cur_hi),hl ld hl,(cur_cluster_lo) ld (cfc_cur_lo),hl cfc_loop ld hl,(cfc_cur_hi) ld (cur_cluster_hi),hl ld hl,(cfc_cur_lo) ld (cur_cluster_lo),hl call get_next_cluster ld a,(chain_ended) or a jr nz,cfc_nonext ld hl,(cur_cluster_hi) ld (cfc_next_hi),hl ld hl,(cur_cluster_lo) ld (cfc_next_lo),hl ld a,1 ld (cfc_has_next),a jr cfc_free_this cfc_nonext xor a ld (cfc_has_next),a cfc_free_this ld hl,(cfc_cur_hi) ld (cur_cluster_hi),hl ld hl,(cfc_cur_lo) ld (cur_cluster_lo),hl ld hl,0 ld (new_val_hi),hl ld (new_val_lo),hl call write_fat_entry or a jr nz,cfc_fail ld a,(cfc_has_next) or a jr z,cfc_done ld hl,(cfc_next_hi) ld (cfc_cur_hi),hl ld hl,(cfc_next_lo) ld (cfc_cur_lo),hl jr cfc_loop cfc_done xor a ret cfc_fail xor a inc a ret ;--- core_mark_deleted_range: (cur_cluster_hi,cur_cluster_lo already ;--- set by caller to the directory) + cmdr_start_lo/cmdr_end_lo ;--- (logical 32-byte-entry-index range, inclusive) -> marks each ;--- entry's first byte 0xE5 (deleted). A=0 ok, A=1 fail (disk error). --- cmdr_start_lo dw 0 cmdr_end_lo dw 0 cmdr_cur_index_lo dw 0 cmdr_entptr dw 0 cmdr_dirty db 0 cmdr_this_sec_hi dw 0 cmdr_this_sec_lo dw 0 core_mark_deleted_range call stream_open ld hl,0 ld (cmdr_cur_index_lo),hl cmdr_sector_loop ld a,(stream_eoc) or a jp nz,cmdr_done call strm_calc_sector ld hl,(strm_sector_hi) ld (cmdr_this_sec_hi),hl ld hl,(strm_sector_lo) ld (cmdr_this_sec_lo),hl ld hl,(strm_sector_hi) ld de,(strm_sector_lo) call set_lba32 call sdread_lba or a jr nz,cmdr_fail xor a ld (cmdr_dirty),a ld hl,sdbuf ld (cmdr_entptr),hl ld b,16 cmdr_entloop ld hl,(cmdr_cur_index_lo) ld de,(cmdr_start_lo) or a sbc hl,de jr c,cmdr_notinrange ld hl,(cmdr_cur_index_lo) ld de,(cmdr_end_lo) or a sbc hl,de jr c,cmdr_inrange jr nz,cmdr_notinrange cmdr_inrange ld hl,(cmdr_entptr) ld (hl),#e5 ld a,1 ld (cmdr_dirty),a cmdr_notinrange ld hl,(cmdr_cur_index_lo) inc hl ld (cmdr_cur_index_lo),hl ld hl,(cmdr_entptr) ld de,32 add hl,de ld (cmdr_entptr),hl djnz cmdr_entloop ld a,(cmdr_dirty) or a jr z,cmdr_skip_write ld hl,(cmdr_this_sec_hi) ld de,(cmdr_this_sec_lo) call set_lba32 call sdwrite_lba or a jr nz,cmdr_fail cmdr_skip_write ld hl,(cmdr_cur_index_lo) ld de,(cmdr_end_lo) or a sbc hl,de jr c,cmdr_continue jr nz,cmdr_done cmdr_continue call strm_advance jp cmdr_sector_loop cmdr_done xor a ret cmdr_fail xor a inc a ret ;--- core_delfl: HL -> {flag(1),name(1-255),0} -> finds the entry (via ;--- find_by_name in the active directory), frees its FAT chain, and ;--- marks its short entry + any preceding LFN run deleted. Z=NOT ;--- FOUND, NZ=FILE DELETED -- matches real DELFL's documented ABI ;--- (note the inverted sense vs MKDIR/MKFILE's Z=success). --- core_delfl inc hl ; skip flag byte ld (cmk_name_ptr),hl ld hl,(active_dir_hi) ld (cur_cluster_hi),hl ld hl,(active_dir_lo) ld (cur_cluster_lo),hl ld hl,(cmk_name_ptr) call find_by_name or a ret z ; not found -- Z, matches real DELFL ld hl,(fbn_cluster_hi) ld (cur_cluster_hi),hl ld hl,(fbn_cluster_lo) ld (cur_cluster_lo),hl call core_free_chain ld hl,(fbn_match_run_start_lo) ld (cmdr_start_lo),hl ld hl,(fbn_match_index_lo) ld (cmdr_end_lo),hl ld hl,(active_dir_hi) ld (cur_cluster_hi),hl ld hl,(active_dir_lo) ld (cur_cluster_lo),hl call core_mark_deleted_range xor a inc a ; NZ -- FILE DELETED ret ;--- core_renam: HL -> {flag(1),oldname(1-255),0}, DE -> {newname ;--- (1-255),0} -> renames an entry in the active directory, preserving ;--- its cluster/size/attr (re-stamps date/time fresh). Z=NOT FOUND, ;--- NZ=SUCCESS -- matches real RENAM's documented ABI. Not ;--- transactional: the old entry is marked deleted before the new one ;--- is written, matching this driver's existing failure-mode ;--- conventions elsewhere (no rollback attempted) -- the real ABI has ;--- no code for "found but couldn't rename" to report anyway. --- crn_newname_ptr dw 0 crn_old_attr db 0 crn_old_size_hi dw 0 crn_old_size_lo dw 0 crn_old_run_start_lo dw 0 crn_old_match_index_lo dw 0 core_renam inc hl ; skip flag byte -- oldname starts here ld (cmk_name_ptr),hl ld (crn_newname_ptr),de ld hl,(active_dir_hi) ld (cur_cluster_hi),hl ld hl,(active_dir_lo) ld (cur_cluster_lo),hl ld hl,(cmk_name_ptr) call find_by_name or a ret z ; not found -- Z, matches real RENAM ld hl,(fbn_cluster_hi) ld (cmk_cluster_hi),hl ld hl,(fbn_cluster_lo) ld (cmk_cluster_lo),hl ld a,(fbn_attr) ld (crn_old_attr),a ld hl,(fbn_size_hi) ld (crn_old_size_hi),hl ld hl,(fbn_size_lo) ld (crn_old_size_lo),hl ld hl,(fbn_match_run_start_lo) ld (crn_old_run_start_lo),hl ld hl,(fbn_match_index_lo) ld (crn_old_match_index_lo),hl ; refuse if the NEW name already exists -- generate_unique_ ; shortname only avoids SHORT-name collisions, so without this ; explicit check RENAM could create a SECOND entry displaying ; the same long name as an existing one (confirmed on hardware: ; renaming into an already-used name did exactly that -- same ; bug class as core_mkdir/core_mkfile's own duplicate-guard, ; just missed here originally). Checked BEFORE touching the ; old entry at all, so a refusal here leaves everything as-is. ld hl,(active_dir_hi) ld (cur_cluster_hi),hl ld hl,(active_dir_lo) ld (cur_cluster_lo),hl ld hl,(crn_newname_ptr) call find_by_name or a ret nz ; new name already exists -- refuse ; (Z, matching RENAM's two-outcome ABI) ld hl,(crn_old_run_start_lo) ld (cmdr_start_lo),hl ld hl,(crn_old_match_index_lo) ld (cmdr_end_lo),hl ld hl,(active_dir_hi) ld (cur_cluster_hi),hl ld hl,(active_dir_lo) ld (cur_cluster_lo),hl call core_mark_deleted_range call rtc_to_fat_datetime call clear_dir_entry call stamp_dir_entry_datetime ld a,(crn_old_attr) ld (dir_entry_buf+11),a ld hl,(cmk_cluster_hi) ld (dir_entry_buf+20),hl ld hl,(cmk_cluster_lo) ld (dir_entry_buf+26),hl ld hl,(crn_old_size_lo) ld (dir_entry_buf+28),hl ld hl,(crn_old_size_hi) ld (dir_entry_buf+30),hl ld hl,(active_dir_hi) ld (cur_cluster_hi),hl ld hl,(active_dir_lo) ld (cur_cluster_lo),hl ld hl,(crn_newname_ptr) call filter_name_to_83 call generate_unique_shortname or a jr nz,crn_done ; degenerate failure -- old entry ; already deleted, nothing sane left ; to roll back to; ABI has no code ; for this anyway (see header comment) ld hl,(crn_newname_ptr) call build_lfn_entries ld hl,(active_dir_hi) ld (cur_cluster_hi),hl ld hl,(active_dir_lo) ld (cur_cluster_lo),hl call find_free_run or a jr nz,crn_done ld hl,(active_dir_hi) ld (cur_cluster_hi),hl ld hl,(active_dir_lo) ld (cur_cluster_lo),hl call write_name_entries crn_done xor a inc a ; NZ -- SUCCESS (per the real ABI's ; two-outcome contract; see header ; comment about the no-rollback ; degenerate case) ret ;--- The PackBits-compressed body blob (see drv_dos_swp's comment for ;--- the format and PB_DECODE for the decoder). Produced by ;--- packbits_encode.ps1 (D:\Temp\claude\E--Claude\ ;--- a54de7f1-2eb7-418e-8cf2-9a46028fb01b\scratchpad) -- no header, no ;--- footer, just the encoded stream ending in its own 0x80 terminator ;--- byte, so PB_DECODE needs no extra length/tail parameters at all. --- body_packed_start: INCBIN "zc_sd_driver_body.pb" body_packed_end: code_end: SAVEBIN "zc_sd_driver.bin",#2000,code_end-#2000