Below is a text about the C64DTV V1 filesystem posted 2004-12-22 by Jim Brain to the DTV-hacking board. http://jledger.proboards19.com/index.cgi?board=dtvhacking&action=display&thread=1103684781 /tlr ---------------------------------------------------------------------- C64DTV Kernal LOAD modifications to support ROM-based filesystem Version 1.0 2004-12-21 Jim Brain brain@jbrain.com Introduction: In it's current implementation, the Commodore 64 Direct To TV (C64DTV) unit requires the ability to load one of 30 game applications using solid state memory. Given time and cost constraints, a ROM based filesystem was created for the unit. In order to preserve compatibility with the use of the C64DTV as a complete Commodore 64 platform and to minimize the amount of KERNAL rework needed to support the ROM FS, the DTV development team decided to replace the C64 cassette tape load routines with routines implementing the new filesystem. Considering the target usage, only LOAD support is implemented. Though the ROM filesystem replaces device #1, the device implements random access device semantics, like the Commodore 15XX disk drive series. Obviously, all multi-load games were modified to load from device #1 to utilize the ROM-fs. To conserve space in the 2MB ROM, the directory is stored verbatim, but the files are RLL encoded. Efficient data transfer is accomplished by using the C64DTV extended functions, notably the DMA Engine. In short, the routine acquires some work space, scans the directory for a match, runs the RLL expansion algorithm to load the file, and then returns control to the caller. Obviously, there's a few places where the code gets a bit jumpy or a bit redundant, but I'll record here (since this may enjoy a wide distribution) that the developers were under severe time pressure to complete this. It works, and it's good enough. If folks want to pass constructive recommendations to me, I'll pass them onto the developers in case they want to make changes for the PAL unit. I'm not a schooled disassembler, but I think I caught the essence of the code. As I don't know Adrian Gonzales' (dW/Style) and Robin Harbron's (Macbeth/PSW) coding styles, I can't comment on the exact authorship. Given circumstance, I suspect it was a joint effort. Adrian did not have an actual unit to test with, so the routines were written to utilize an REU. Robin had an actual prototype unit, so he no doubt modified Adrian's code to work with the different register layout of the DTV DMA Engine. Comments and bugfixes are appreciated. The KERNAL ROM was dumped from a 041104 build date unit and disassembled via Marko Makela's d65 disassembler. ---------------------------------------------------------------------- Detailed commentary: The ROM fs directory is stored as 32 byte stuctures starting at 002000 in ROM. Each entry is organized as follows: $0-$17 filename, or 0 for end of dir $18 low byte of ROM start $19 mid byte of ROM start $1a hi byte of ROM start $1b low byte of load address $1c hi byte of load address $1d unknown $1e unknown $1f unknown The entries are loaded to $100, and checked against the current load name. If a match is found, the corresponding vectors are loading into temp registers and the load starts The data is stored in ROM in a RLL scheme. It is of the form: length : data If length:7 is 0, the code copies length bytes from source to destination, updating the temporary vectors. If length:7 = 1, then length is normalized to 0-127, and the next byte in source is copied length times to the destination. If length =0, the load is complete. The normal load indirect vector at $fd4c is rerouted from $f4a5 to $f730. Code there, in turn, jumps to $f736: F730 4C 36 F7 JMP LF736 ; patched jump The code then saves off the load/verify flag, loads the device, checks against tape, and goes to the old routine if not device 1 F736 85 93 STA L93 ; store verify flag F738 A5 BA LDA LBA ; load current device F73A C9 01 CMP #$1 ; is it tape? F73C F0 03 BEQ LF741 ; yes F73E 4C A7 F4 JMP LF4A7 ; no, go to old routine. If it is tape, flags are set for load, the DMA engine is enabled via the MAGIC VIC bit and modulo hi bytes are set to 0. F741 A9 00 LDA #$0 ; set for load F743 85 93 STA L93 ; store F745 A2 01 LDX #$1 ; enable DMA engine F747 8E 3F D0 STX LD03F F74A 8D 07 D3 STA LD307 ; source stepping HI to 0 F74D 8D 09 D3 STA LD309 ; destination stepping HI to 0 F750 20 2D F8 JSR LF82D ; save off some temp scratch space The goal here is to free up some RAM for the LOAD routine without affecting anything. As one can't know what RAM is in use, this routine swaps out 55 bytes of the current RAM footprint. To minimize interference, it uses the top end of zp ($f9-) and the low 48 bytes of stack. F82D A9 00 LDA #$0 F82F A0 01 LDY #$1 F831 8C 06 D3 STY LD306 ; source stepping to 1 F834 8C 08 D3 STY LD308 ; dest stepping to 1 F837 A0 40 LDY #$40 F839 8C 05 D3 STY LD305 ; destination is RAM at 00XXYY F83C C8 INY F83D 8C 02 D3 STY LD302 ; read from RAM at 01XXYY F840 8D 0B D3 STA LD30B ; read 00XX bytes F843 8D 04 D3 STA LD304 ; dest start is 0000XX F846 A9 80 LDA #$80 F848 8D 01 D3 STA LD301 ; source is 0180XX F84B A9 F9 LDA #$F9 F84D 8D 00 D3 STA LD300 ; source is 0180F9 F850 8D 03 D3 STA LD303 ; dest is 0000f9 F853 A9 37 LDA #$37 F855 8D 0A D3 STA LD30A ; read 0037 bytes F858 A9 0F LDA #$F ; possibly could have been $d -- jlb F85A 8D 1F D3 STA LD31F ; swap 55 bytes from 00f9 to 0180f9 F85D 4C 69 F8 JMP LF869 F869 AD 1F D3 LDA LD31F ; load flags F86C 4A LSR A F86D B0 FA BCS LF869 ; is DMA done? F86F 60 RTS ; yes. Control returns to F753, which initializes some vectors in the temp ram, prints searching, and gets an entry to check. It continues checking until the end is reached or entry is found. I think the PHP and the SEI should have been called earlier, as RAM is now corrupt, but I suspect this just mirrors the old IEC or tape routines. F753 08 PHP ; push status bits F754 78 SEI ; no irqs. F755 A9 00 LDA #$0 F757 85 FA STA LFA ; $(fc,fb,fa) is source F759 A2 20 LDX #$20 ; set source to ROM at 002000 F75B 86 FB STX LFB F75D 85 FC STA LFC F75F A9 00 LDA #$0 F761 85 FD STA LFD ; $(0:fe:fd) is destination F763 A9 01 LDA #$1 ; set destination to RAM at 0001XX F765 85 FE STA LFE F767 20 AF F5 JSR LF5AF ; print "SEARCHING FOR [NAME]" F76A 20 9E F8 JSR LF89E ; load source vector to registers: F89E A5 FA LDA LFA ; put temp Source into source vector. F8A0 8D 00 D3 STA LD300 F8A3 A5 FB LDA LFB F8A5 8D 01 D3 STA LD301 F8A8 A5 FC LDA LFC F8AA 8D 02 D3 STA LD302 F8AD 60 RTS F76D 20 AE F8 JSR LF8AE ; load dest vector to registers F8AE A5 FD LDA LFD ; put temp dest vector into dest registers F8B0 8D 03 D3 STA LD303 F8B3 A5 FE LDA LFE F8B5 8D 04 D3 STA LD304 F8B8 A9 00 LDA #$0 F8BA 8D 05 D3 STA LD305 F8BD 60 RTS F770 A9 20 LDA #$20 ; F772 20 61 F8 JSR LF861 ; set length of DMA to 32 bytes F861 20 79 F8 JSR LF879 ; set length to 00.A F879 8D 0A D3 STA LD30A F87C A9 00 LDA #$0 F87E 8D 0B D3 STA LD30B F881 60 RTS F864 A9 0D LDA #$D ; transfer bytes. F866 8D 1F D3 STA LD31F F869 AD 1F D3 LDA LD31F F86C 4A LSR A F86D B0 FA BCS LF869 ; is DMA done? F86F 60 RTS At this point, we have the first directory entry at 0100 to 011f. If the first byte is 0, we've reached the end of the dir. Otherwise, check the name. F775 AD 00 01 LDA L100 ; Did we check all the entries? F778 D0 07 BNE LF781 ; no F77A 20 2D F8 JSR LF82D ; swap the data back F77D 28 PLP F77E 4C 04 F7 JMP LF704 ; "FILE NOT FOUND" F781 A5 B7 LDA LB7 ; load name length F783 D0 07 BNE LF78C ; name is not null F785 20 2D F8 JSR LF82D ; swap temp ram back F788 28 PLP F789 4C 10 F7 JMP LF710 ; "MISSING FILE NAME" F78C C9 18 CMP #$18 ; check length against 24 bytes (24? jlb) F78E 90 02 BCC LF792 ; not more, check name. F790 B0 E8 BCS LF77A ; swap data back and FILE NOT FOUND ERROR F792 A0 00 LDY #$0 F794 B1 BB LDA (LBB),Y ; load letter F796 C9 2A CMP #$2A ; Wilcard (*) match? F798 F0 0F BEQ LF7A9 ; match F79A D9 00 01 CMP L100,Y ; does letter match? F79D D0 35 BNE LF7D4 ; no, add 32 to source, and try again F79F C8 INY F7A0 C4 B7 CPY LB7 ; did we scan entire name? F7A2 D0 F0 BNE LF794 ; no, continue scanning F7A4 B9 00 01 LDA L100,Y ; load next byte F7A7 D0 2B BNE LF7D4 ; if not name end, add $20 to source, loop F7A9 20 D2 F5 JSR LF5D2 ; print "LOADING..." F7AC A2 04 LDX #$4 F7AE BD 18 01 LDA L118,X ; load $fa,fb,fc,fd,$fe from $118-. F7B1 95 FA STA LFA,X F7B3 CA DEX F7B4 10 F8 BPL LF7AE F7B6 A5 B9 LDA LB9 ; load secondary address F7B8 D0 08 BNE LF7C2 ; we are an absolute load F7BA A5 C3 LDA LC3 ; put 0801 in $fe:$fd F7BC 85 FD STA LFD F7BE A5 C4 LDA LC4 F7C0 85 FE STA LFE F7C2 20 DC F7 JSR LF7DC At this point, we are loading the file. the sequence is to get a byte from ROM into $ff. If 0, we are done, if 1-127, copy that many bytes from source to dest. If >128, copy the next byte in source ($ff)-128 times. F7DC A9 FF LDA #$FF ; F7DE 20 82 F8 JSR LF882 ; put 0000FF into dest, step 1 F7E1 20 9E F8 JSR LF89E ; set dest to $(fc,fb,fa) F7E4 A9 01 LDA #$1 F7E6 20 70 F8 JSR LF870 ; transfer 1 byte F870 48 PHA ; save off .A F871 20 61 F8 JSR LF861 ; another jump F861 20 79 F8 JSR LF879 ; a JSR :-) (transfer 256 bytes) F879 8D 0A D3 STA LD30A ; set bytes to transfer as 1 F87C A9 00 LDA #$0 F87E 8D 0B D3 STA LD30B F881 60 RTS F864 A9 0D LDA #$D ; transfer data F866 8D 1F D3 STA LD31F F869 AD 1F D3 LDA LD31F ; load status F86C 4A LSR A F86D B0 FA BCS LF869 ; is DMA done? F86F 60 RTS F874 68 PLA ; restore .A F875 20 BE F8 JSR LF8BE ; add .A to source address ? F878 60 RTS F7E9 A5 FF LDA LFF ; load first byte of data F7EB F0 37 BEQ LF824 ; if 0, then ($fd)->$ae, ($fe)->$af, rts F7ED 30 12 BMI LF801 ; if > 127, F801 F7EF AA TAX ; save .A F7F0 20 AE F8 JSR LF8AE ; $(0,$fe,$fd) -> dest address F7F3 20 9E F8 JSR LF89E ; $(fc,fb,fa) -> source F7F6 8A TXA ; restore .A F7F7 20 70 F8 JSR LF870 ; transfer .A bytes, add .A to source. F7FA 8A TXA F7FB 20 CC F8 JSR LF8CC ; add .A to dest. F7FE 4C DC F7 JMP LF7DC ; loop if ($ff) > 127, F801 49 80 EOR #$80 ; mask high bit F803 AA TAX F804 20 AE F8 JSR LF8AE ; set dest as above F807 20 9E F8 JSR LF89E ; set source as above F80A A9 00 LDA #$0 F80C 8D 06 D3 STA LD306 ; no stepping for source F80F A9 01 LDA #$1 F811 8D 08 D3 STA LD308 ; step by 1 for dest. F814 8A TXA ; restore .A to number of loops F815 20 61 F8 JSR LF861 ; transfer 1 byte to (.A) locations. F818 A9 01 LDA #$1 F81A 20 BE F8 JSR LF8BE ; add 1 to source F81D 8A TXA F81E 20 CC F8 JSR LF8CC ; add .A to dest. F821 4C DC F7 JMP LF7DC ; loop. F7C5 20 2D F8 JSR LF82D ; swap RAM back in F7C8 A9 00 LDA #$0 F7CA 8D 3F D0 STA LD03F ; turn off DMA Engine F7CD 28 PLP ; restore status F7CE A9 00 LDA #$0 ; clear $90 F7D0 85 90 STA L90 F7D2 18 CLC ; clear carry F7D3 60 RTS ; return from LOAD. ---------------------------------------------------------------------- Notes: 1) $f858 sets DMA for swap, though I'm not sure we care about the data scoming in from high memory 2) The RLL decoder can handle chunks of data up to 127 bytes, but only 48 bytes of RAM is available (55 saved bytes - 7 vector/flag bytes). I assume Adrian's perl scripts used to encode the RLL data took this into account, but if someone should replace the ROM, be aware that decoding > 48 bytes will trash portions of the stack page. 3) My earlier caveat aside, there's plenty of room for optimization. The source and destination vectors are handled very conservativel, for instance. As well, no more than 48 bytes are ever transferred at any time, so the initial clearing of the stepping hi bytes should be all that is needed throughout the code. 4) $f78e looks like it can be safely removed. 5) Someone's coder skills seem to be coming out in $f801 :-) . AND #$7f would accomplish the same thing, I believe. 6) As noted, the SEI at $f754 looks like it should come earlier, to prevent issues if the load happens while SP is < 48 and some IRQ happens. 7) The code at $f869 is curious. I can't find any mention of the DMA engine relinquishing control back to the CPU before the DMA is completed or an error occurs. 8) I'm assuming many of these ideas came from RAMDOS or variations of it. Howver, if not, I think a RAMDOS that uses these techniques would be a very non-invasive utility. Jim Brain ----------------------------------------------------------------------