1;-----------------------------------------------------------------------------
2; fat32.s
3; Copyright (C) 2020 Frank van den Hoef
4; Copyright (C) 2020 Michael Steil
5;-----------------------------------------------------------------------------
6
7	.include "fat32.inc"
8	.include "lib.inc"
9	.include "sdcard.inc"
10	.include "text_input.inc"
11
12	.import sector_buffer, sector_buffer_end, sector_lba
13
14	.import filename_char_ucs2_to_internal, filename_char_internal_to_ucs2
15	.import filename_cp437_to_internal, filename_char_internal_to_cp437
16	.import match_name, match_type
17
18	; mkfs.s
19	.export load_mbr_sector, write_sector, clear_buffer, set_errno, unmount
20
21
22FLAG_IN_USE = 1<<0  ; Context in use
23FLAG_DIRTY  = 1<<1  ; Buffer is dirty
24FLAG_DIRENT = 1<<2  ; Directory entry needs to be updated on close
25
26.struct context
27flags           .byte    ; Flag bits
28start_cluster   .dword   ; Start cluster
29cluster         .dword   ; Current cluster
30lba             .dword   ; Sector of current cluster
31cluster_sector  .byte    ; Sector index within current cluster
32bufptr          .word    ; Pointer within sector_buffer
33file_size       .dword   ; Size of current file
34file_offset     .dword   ; Offset in current file
35dirent_lba      .dword   ; Sector containing directory entry for this file
36dirent_bufptr   .word    ; Offset to start of directory entry
37eof             .byte    ; =$ff: EOF has been reached
38.endstruct
39
40CONTEXT_SIZE = 32
41
42.if CONTEXT_SIZE * FAT32_CONTEXTS > 256
43.error "Too many FAT32_CONTEXTS to fit into 256 bytes!"
44.endif
45
46.if .sizeof(context) > CONTEXT_SIZE
47.error "struct context too big!"
48.endif
49
50.struct fs
51; Static filesystem parameters
52mounted              .byte         ; Flag to indicate the volume is mounted
53rootdir_cluster      .dword        ; Cluster of root directory
54sectors_per_cluster  .byte         ; Sectors per cluster
55cluster_shift        .byte         ; Log2 of sectors_per_cluster
56lba_partition        .dword        ; Start sector of FAT32 partition
57fat_size             .dword        ; Size in sectors of each FAT table
58lba_fat              .dword        ; Start sector of first FAT table
59lba_data             .dword        ; Start sector of first data cluster
60cluster_count        .dword        ; Total number of cluster on volume
61lba_fsinfo           .dword        ; Sector number of FS info
62; Variables
63free_clusters        .dword        ; Number of free clusters (from FS info)
64free_cluster         .dword        ; Cluster to start search for free clusters, also holds result of find_free_cluster
65cwd_cluster          .dword        ; Cluster of current directory
66.endstruct
67
68FS_SIZE      = 64
69
70.if FS_SIZE * FAT32_VOLUMES > 256
71.error "Too many FAT32_VOLUMES to fit into 256 bytes!"
72.endif
73
74.if .sizeof(fs) > FS_SIZE
75.error "struct fs too big!"
76.endif
77
78	.bss
79_fat32_bss_start:
80
81fat32_time_year:     .byte 0
82fat32_time_month:    .byte 0
83fat32_time_day:      .byte 0
84fat32_time_hours:    .byte 0
85fat32_time_minutes:  .byte 0
86fat32_time_seconds:  .byte 0
87
88; Temp
89bytecnt:             .word 0       ; Used by fat32_write
90tmp_buf:             .res 4        ; Used by save_sector_buffer, fat32_rename
91next_sector_arg:     .byte 0       ; Used by next_sector to store argument
92tmp_bufptr:          .word 0       ; Used by next_sector
93tmp_sector_lba:      .dword 0      ; Used by next_sector
94name_offset:         .byte 0
95tmp_dir_cluster:     .dword 0
96tmp_attrib:          .byte 0       ; temporary: attribute when creating a dir entry
97tmp_dirent_flag:     .byte 0
98shortname_buf:       .res 11       ; Used for shortname creation
99tmp_timestamp:       .byte 0
100tmp_filetype:        .byte 0       ; Used to match file type in find_dirent
101
102; Temp - LFN
103lfn_index:           .byte 0       ; counter when collecting/decoding LFN entries
104lfn_count:           .byte 0       ; number of LFN dir entries when reading/creating
105lfn_checksum:        .byte 0       ; created or expected LFN checksum
106lfn_char_count:      .byte 0       ; counter when decoding LFN characters
107lfn_name_index:      .byte 0       ; counter when decoding LFN characters
108tmp_sfn_case:        .byte 0       ; flags when decoding SFN characters
109free_entry_count:    .byte 0       ; counter when looking for contig. free dir entries
110marked_entry_lba:    .res 4        ; mark/rewind data for directory entries
111marked_entry_cluster:.res 4
112marked_entry_offset: .res 2
113tmp_entry:           .res 21       ; SFN entry fields except name, saved during rename
114lfn_buf:             .res 20*32    ; create/collect LFN; 20 dirents (13c * 20 > 255c)
115
116; API arguments and return data
117fat32_dirent:        .tag dirent   ; Buffer containing decoded directory entry
118fat32_size:          .res 4        ; Used for fat32_read, fat32_write, fat32_get_offset, fat32_get_free_space
119fat32_errno:         .byte 0       ; Last error
120fat32_readonly:      .byte 0       ; User-accessible read-only flag
121
122; Contexts
123context_idx:         .byte 0       ; Index of current context
124cur_context:         .tag context  ; Current file descriptor state
125contexts_inuse:      .res FAT32_CONTEXTS
126.if ::FAT32_VOLUMES > 1
127volume_for_context:  .res FAT32_CONTEXTS
128.endif
129
130; Volumes
131volume_idx:          .byte 0       ; Index of current filesystem
132cur_volume:          .tag fs       ; Current file descriptor state
133
134.if FAT32_CONTEXTS > 1
135contexts:            .res CONTEXT_SIZE * FAT32_CONTEXTS
136.endif
137
138.if FAT32_VOLUMES > 1
139volumes:             .res FS_SIZE * FAT32_VOLUMES
140.endif
141
142_fat32_bss_end:
143
144	.code
145
146;-----------------------------------------------------------------------------
147; set_volume
148;
149; In:  a  volume
150;      c  =1: don't mount
151;
152; * c=0: failure
153;-----------------------------------------------------------------------------
154set_volume:
155	php ; mount flag
156
157	; Already selected?
158	cmp volume_idx
159	bne @0
160	plp
161	sec
162	rts
163
164@0:
165	; Valid volume index?
166	cmp #FAT32_VOLUMES
167	bcc @ok
168
169	plp
170	lda #ERRNO_NO_FS
171	jmp set_errno
172
173@ok:
174.if ::FAT32_VOLUMES > 1
175	; Save new volume index
176	pha
177
178	.assert FS_SIZE = 64, error
179	; Copy current volume back
180	lda volume_idx
181	bmi @dont_write_back ; < 0 = no current volume
182	asl ; X=A*64
183	asl
184	asl
185	asl
186	asl
187	asl
188	tax
189
190	ldy #0
191@1:	lda cur_volume, y
192	sta volumes, x
193	inx
194	iny
195	cpy #(.sizeof(fs))
196	bne @1
197
198@dont_write_back:
199	; Copy new volume to current
200	pla              ; Get new volume idx
201	pha
202	asl ; X=A*64
203	asl
204	asl
205	asl
206	asl
207	asl
208	tax
209
210	ldy #0
211@2:	lda volumes, x
212	sta cur_volume, y
213	inx
214	iny
215	cpy #(.sizeof(fs))
216	bne @2
217
218	pla
219.endif
220
221	sta volume_idx
222
223	plp
224	bcs @done ; don't mount
225	bit cur_volume + fs::mounted
226	bmi @done
227	lda volume_idx
228	jmp mount
229@done:
230	sec
231	rts
232
233;-----------------------------------------------------------------------------
234; set_errno
235;
236; Only set errno if it wasn't already set.
237; If a read error causes a file not found error, it's still a read error.
238;-----------------------------------------------------------------------------
239set_errno:
240	clc
241	pha
242	lda fat32_errno
243	bne @1
244	pla
245	sta fat32_errno
246	rts
247
248@1:	pla
249	rts
250
251;-----------------------------------------------------------------------------
252; sync_sector_buffer
253;
254; * c=0: failure; sets errno
255;-----------------------------------------------------------------------------
256sync_sector_buffer:
257	; Write back sector buffer if dirty
258	lda cur_context + context::flags
259	bit #FLAG_DIRTY
260	beq @done
261	jmp save_sector_buffer
262
263@done:	sec
264	rts
265
266;-----------------------------------------------------------------------------
267; load_sector_buffer
268;
269; * c=0: failure; sets errno
270;-----------------------------------------------------------------------------
271load_sector_buffer:
272	; Check if sector is already loaded
273	cmp32_ne cur_context + context::lba, sector_lba, @do_load
274	sec
275	rts
276
277@do_load:
278	jsr sync_sector_buffer
279	set32 sector_lba, cur_context + context::lba
280	jsr sdcard_read_sector
281	bcc @1
282	rts
283
284@1:
285	lda #ERRNO_READ
286	jmp set_errno
287
288;-----------------------------------------------------------------------------
289; write_sector
290;
291; * c=0: failure; sets errno
292;-----------------------------------------------------------------------------
293write_sector:
294	lda fat32_readonly
295	bne @error
296	jmp sdcard_write_sector
297
298@error:	lda #ERRNO_WRITE_PROTECT_ON
299	jmp set_errno
300
301;-----------------------------------------------------------------------------
302; save_sector_buffer
303;
304; * c=0: failure; sets errno
305;-----------------------------------------------------------------------------
306save_sector_buffer:
307	; Determine if this is FAT area write (sector_lba - lba_fat < fat_size)
308	sub32 tmp_buf, sector_lba, cur_volume + fs::lba_fat
309	lda tmp_buf + 2
310	ora tmp_buf + 3
311	bne @normal
312	sec
313	lda tmp_buf + 0
314	sbc cur_volume + fs::fat_size + 0
315	lda tmp_buf + 1
316	sbc cur_volume + fs::fat_size + 1
317	bcs @normal
318
319	; Write second FAT
320	set32 tmp_buf, sector_lba
321	add32 sector_lba, sector_lba, cur_volume + fs::fat_size
322	jsr write_sector
323	php
324	set32 sector_lba, tmp_buf
325	plp
326	bcc @error_write
327
328@normal:
329	jsr write_sector
330	bcc @error_write
331
332	; Clear dirty bit
333	lda cur_context + context::flags
334	and #(FLAG_DIRTY ^ $FF)
335	sta cur_context + context::flags
336
337	sec
338	rts
339
340@error_write:
341	lda #ERRNO_WRITE
342	jmp set_errno
343
344;-----------------------------------------------------------------------------
345; calc_cluster_lba
346;-----------------------------------------------------------------------------
347calc_cluster_lba:
348	; lba = lba_data + ((cluster - 2) << cluster_shift)
349	sub32_val cur_context + context::lba, cur_context + context::cluster, 2
350	ldy cur_volume + fs::cluster_shift
351	beq @shift_done
352@1:	shl32 cur_context + context::lba
353	dey
354	bne @1
355@shift_done:
356
357	add32 cur_context + context::lba, cur_context + context::lba, cur_volume + fs::lba_data
358	stz cur_context + context::cluster_sector
359	rts
360
361;-----------------------------------------------------------------------------
362; load_fat_sector_for_cluster
363;
364; Load sector that hold cluster entry for cur_context.cluster
365; On return fat32_bufptr points to cluster entry in sector_buffer.
366;
367; * c=0: failure; sets errno
368;-----------------------------------------------------------------------------
369load_fat_sector_for_cluster:
370	; Calculate sector where cluster entry is located
371
372	; lba = lba_fat + (cluster / 128)
373	lda cur_context + context::cluster + 1
374	sta cur_context + context::lba + 0
375	lda cur_context + context::cluster + 2
376	sta cur_context + context::lba + 1
377	lda cur_context + context::cluster + 3
378	sta cur_context + context::lba + 2
379	stz cur_context + context::lba + 3
380	lda cur_context + context::cluster + 0
381	asl	; upper bit in C
382	rol cur_context + context::lba + 0
383	rol cur_context + context::lba + 1
384	rol cur_context + context::lba + 2
385	rol cur_context + context::lba + 3
386	add32 cur_context + context::lba, cur_context + context::lba, cur_volume + fs::lba_fat
387
388	; Read FAT sector
389	jsr load_sector_buffer
390	bcs @1
391	rts	; Failure
392@1:
393	; fat32_bufptr = sector_buffer + (cluster & 127) * 4
394	lda cur_context + context::cluster
395	asl
396	asl
397	sta fat32_bufptr + 0
398	lda #0
399	bcc @2
400	lda #1
401@2:	sta fat32_bufptr + 1
402	add16_val fat32_bufptr, fat32_bufptr, sector_buffer
403
404	; Success
405	sec
406	rts
407
408;-----------------------------------------------------------------------------
409; is_end_of_cluster_chain
410;-----------------------------------------------------------------------------
411is_end_of_cluster_chain:
412	; Check if this is the end of cluster chain (entry >= 0x0FFFFFF8)
413	lda cur_context + context::cluster + 3
414	and #$0F	; Ignore upper 4 bits
415	cmp #$0F
416	bne @no
417	lda cur_context + context::cluster + 2
418	cmp #$FF
419	bne @no
420	lda cur_context + context::cluster + 1
421	cmp #$FF
422	bne @no
423	lda cur_context + context::cluster + 0
424	cmp #$F8
425	bcs @yes
426@no:	clc
427@yes:	rts
428
429;-----------------------------------------------------------------------------
430; next_cluster
431;
432; * c=0: failure; sets errno
433;-----------------------------------------------------------------------------
434next_cluster:
435	; End of cluster chain?
436	jsr is_end_of_cluster_chain
437	bcs @error
438
439	; Load correct FAT sector
440	jsr load_fat_sector_for_cluster
441	bcc @error
442
443	; Copy next cluster from FAT
444	ldy #0
445@1:	lda (fat32_bufptr), y
446	sta cur_context + context::cluster, y
447	iny
448	cpy #4
449	bne @1
450
451	sec
452	rts
453
454@error:	clc
455	rts
456
457;-----------------------------------------------------------------------------
458; unlink_cluster_chain
459;
460; * c=0: failure; sets errno
461;-----------------------------------------------------------------------------
462unlink_cluster_chain:
463	; Don't unlink cluster 0
464	lda cur_context + context::cluster + 0
465	ora cur_context + context::cluster + 1
466	ora cur_context + context::cluster + 2
467	ora cur_context + context::cluster + 3
468	bne @next
469	sec
470	rts
471
472@next:	jsr next_cluster
473	bcs @0
474	lda fat32_errno
475	beq @done
476	clc
477	rts
478
479@0:
480	; Set this cluster as new search start point if lower than current start point
481	ldy #3
482	lda cur_volume + fs::free_cluster + 3
483	cmp (fat32_bufptr), y
484	bcc @2
485	dey
486	lda cur_volume + fs::free_cluster + 2
487	cmp (fat32_bufptr), y
488	bcc @2
489	dey
490	lda cur_volume + fs::free_cluster + 1
491	cmp (fat32_bufptr), y
492	bcc @2
493	dey
494	lda cur_volume + fs::free_cluster + 0
495	cmp (fat32_bufptr), y
496	bcc @2
497	beq @2
498
499	ldy #0
500@1:	lda (fat32_bufptr), y
501	sta cur_volume + fs::free_cluster, y
502	iny
503	cpy #4
504	bne @1
505@2:
506	; Set entry as free
507	lda #0
508	ldy #0
509	sta (fat32_bufptr), y
510	iny
511	sta (fat32_bufptr), y
512	iny
513	sta (fat32_bufptr), y
514	iny
515	sta (fat32_bufptr), y
516
517	; Increment free clusters
518	inc32 cur_volume + fs::free_clusters
519
520	; Set sector as dirty
521	lda cur_context + context::flags
522	ora #FLAG_DIRTY
523	sta cur_context + context::flags
524
525	bra @next
526
527	; Make sure dirty sectors are written to disk
528@done:	jsr sync_sector_buffer
529	bcs @3
530	rts
531
532@3:	jmp update_fs_info
533
534;-----------------------------------------------------------------------------
535; find_free_cluster
536;
537; * c=0: failure; sets errno
538;-----------------------------------------------------------------------------
539find_free_cluster:
540	; Start search at free_cluster
541	set32 cur_context + context::cluster, cur_volume + fs::free_cluster
542	jsr load_fat_sector_for_cluster
543	bcs @next
544	rts
545
546@next:	; Check for free entry
547	ldy #3
548	lda (fat32_bufptr), y
549	and #$0F	; Ignore upper 4 bits of 32-bit entry
550	dey
551	ora (fat32_bufptr), y
552	dey
553	ora (fat32_bufptr), y
554	dey
555	ora (fat32_bufptr), y
556	bne @not_free
557
558	; Return found free cluster
559	set32 cur_volume + fs::free_cluster, cur_context + context::cluster
560	sec
561	rts
562
563@not_free:
564	; fat32_bufptr += 4
565	add16_val fat32_bufptr, fat32_bufptr, 4
566
567	; cluster += 1
568	inc32 cur_context + context::cluster
569
570	; Check if at end of FAT table
571	cmp32_ne cur_context + context::cluster, cur_volume + fs::cluster_count, @1
572	clc
573	rts
574@1:
575	; Load next FAT sector if at end of buffer
576	cmp16_val_ne fat32_bufptr, sector_buffer_end, @next
577	inc32 cur_context + context::lba
578	jsr load_sector_buffer
579	bcs @2
580	rts
581@2:	set16_val fat32_bufptr, sector_buffer
582	jmp @next
583
584;-----------------------------------------------------------------------------
585; fat32_alloc_context
586;
587; In:  a     volume
588; Out: a     context
589;      c     =0: failure
590;      errno =ERRNO_OUT_OF_RESOURCES: all contexts in use
591;            =ERRNO_READ            : error mounting volume
592;            =ERRNO_WRITE           : error mounting volume
593;            =ERRNO_NO_FS           : error mounting volume
594;            =ERRNO_FS_INCONSISTENT : error mounting volume
595;-----------------------------------------------------------------------------
596fat32_alloc_context:
597	stz fat32_errno
598
599.if FAT32_VOLUMES > 1
600	tay ; volume
601.endif
602	ldx #0
603@1:	lda contexts_inuse, x
604	beq @found_free
605	inx
606	cpx #FAT32_CONTEXTS
607	bne @1
608
609	lda #ERRNO_OUT_OF_RESOURCES
610	jmp set_errno
611
612@found_free:
613	lda #1
614	sta contexts_inuse, x
615
616.if FAT32_VOLUMES > 1
617	tya
618	sta volume_for_context, x
619	phx
620	cmp #$ff
621	sec
622	beq @2
623	clc
624	jsr set_volume
625@2:	pla
626	bcs @rts
627	jsr fat32_free_context
628	clc
629	rts
630.else
631	txa
632	sec
633.endif
634@rts:
635	rts
636
637;-----------------------------------------------------------------------------
638; fat32_free_context
639;
640; In:  a     context
641; Out: c     =0: failure
642;-----------------------------------------------------------------------------
643fat32_free_context:
644	cmp #FAT32_CONTEXTS
645	bcc @1
646@fail:	clc
647	rts
648@1:
649	tax
650	lda contexts_inuse, x
651	beq @fail
652	stz contexts_inuse, x
653	sec
654	rts
655
656;-----------------------------------------------------------------------------
657; fat32_get_num_contexts
658;
659; Out: a     number of contexts in use
660;-----------------------------------------------------------------------------
661fat32_get_num_contexts:
662	ldy #0
663	ldx #0
664@1:	lda contexts_inuse,x
665	beq @2
666	iny
667@2:	inx
668	cpx #FAT32_CONTEXTS
669	bne @1
670	tya
671	rts
672
673;-----------------------------------------------------------------------------
674; update_fs_info
675;
676; * c=0: failure; sets errno
677;-----------------------------------------------------------------------------
678update_fs_info:
679	; Load FS info sector
680	set32 cur_context + context::lba, cur_volume + fs::lba_fsinfo
681	jsr load_sector_buffer
682	bcs @1
683	rts
684@1:
685	; Get number of free clusters
686	set32 sector_buffer + 488, cur_volume + fs::free_clusters
687
688	; Save sector
689	jmp save_sector_buffer
690
691;-----------------------------------------------------------------------------
692; allocate_cluster
693;
694; * c=0: failure; sets errno
695;-----------------------------------------------------------------------------
696allocate_cluster:
697	; Find free entry
698	jsr find_free_cluster
699	bcs @1
700	rts
701@1:
702	; Set cluster as end-of-chain
703	ldy #0
704	lda #$FF
705	sta (fat32_bufptr), y
706	iny
707	sta (fat32_bufptr), y
708	iny
709	sta (fat32_bufptr), y
710	iny
711	lda (fat32_bufptr), y
712	ora #$0F	; Preserve upper 4 bits
713	sta (fat32_bufptr), y
714
715	; Save FAT sector
716	jsr save_sector_buffer
717	bcs @2
718	rts
719@2:
720	; Decrement free clusters and update FS info
721	dec32 cur_volume + fs::free_clusters
722	jmp update_fs_info
723
724;-----------------------------------------------------------------------------
725; validate_char
726;-----------------------------------------------------------------------------
727validate_char:
728	cmp #$22 ; quote
729	beq @not_ok
730	cmp #'*'
731	beq @not_ok
732	cmp #'/'
733	beq @not_ok
734	cmp #':'
735	beq @not_ok
736	cmp #'<'
737	beq @not_ok
738	cmp #'>'
739	beq @not_ok
740	cmp #'?'
741	beq @not_ok
742	cmp #'\'
743	beq @not_ok
744	cmp #'|'
745	beq @not_ok
746
747	sec
748	rts
749
750@not_ok:
751	clc
752	rts
753
754;-----------------------------------------------------------------------------
755; create_shortname
756;
757; * c=0: failure; sets errno
758;-----------------------------------------------------------------------------
759create_shortname:
760	ldx #0
761	lda marked_entry_lba + 3
762	jsr hexbuf8
763	lda marked_entry_lba + 2
764	jsr hexbuf8
765	lda marked_entry_lba + 1
766	jsr hexbuf8
767	lda marked_entry_lba + 0
768	jsr hexbuf8
769	lda #'~'
770	sta shortname_buf, x
771	inx
772	lda fat32_bufptr + 0
773	sec
774	sbc #<sector_buffer
775	pha
776	lda fat32_bufptr + 1
777	sbc #>sector_buffer
778	lsr
779	pla
780	ror
781	lsr
782	lsr
783	lsr
784	lsr
785	jsr hexbuf4
786	lda #'~'
787	sta shortname_buf, x
788
789	; Checksum
790	lda #0
791	tay
792@checksum_loop:
793	tax
794	lsr
795	txa
796	ror
797	clc
798	adc shortname_buf, y
799	iny
800	cpy #11
801	bne @checksum_loop
802	sta lfn_checksum
803	rts
804
805hexbuf8:
806	pha
807	lsr
808	lsr
809	lsr
810	lsr
811	jsr hexbuf4
812	pla
813hexbuf4:
814	and #$0f
815	cmp #$0a
816	bcc :+
817	adc #$66
818:	eor #$30
819	sta shortname_buf, x
820	inx
821	rts
822
823;-----------------------------------------------------------------------------
824; open_cluster
825;
826; * c=0: failure; sets errno
827;-----------------------------------------------------------------------------
828open_cluster:
829	; Check if cluster == 0 -> modify into root dir
830	lda cur_context + context::cluster + 0
831	ora cur_context + context::cluster + 1
832	ora cur_context + context::cluster + 2
833	ora cur_context + context::cluster + 3
834	bne readsector
835
836open_rootdir:
837	set32 cur_context + context::cluster, cur_volume + fs::rootdir_cluster
838
839readsector:
840	; Read first sector of cluster
841	jsr calc_cluster_lba
842	jsr load_sector_buffer
843	bcc @done
844
845	; Reset buffer pointer
846	set16_val fat32_bufptr, sector_buffer
847
848	sec
849@done:	rts
850
851;-----------------------------------------------------------------------------
852; clear_buffer
853;-----------------------------------------------------------------------------
854clear_buffer:
855	ldy #0
856	tya
857@1:	sta sector_buffer, y
858	sta sector_buffer + 256, y
859	iny
860	bne @1
861	rts
862
863;-----------------------------------------------------------------------------
864; clear_cluster
865;
866; * c=0: failure; sets errno
867;-----------------------------------------------------------------------------
868clear_cluster:
869	; Fill sector buffer with 0
870	jsr clear_buffer
871
872	; Write sectors
873	jsr calc_cluster_lba
874@2:	set32 sector_lba, cur_context + context::lba
875	jsr write_sector
876	bcs @3
877	rts
878@3:	lda cur_context + context::cluster_sector
879	inc
880	cmp cur_volume + fs::sectors_per_cluster
881	beq @wrdone
882	sta cur_context + context::cluster_sector
883	inc32 cur_context + context::lba
884	bra @2
885
886@wrdone:
887	sec
888	rts
889
890;-----------------------------------------------------------------------------
891; next_sector
892; A: bit0 - allocate cluster if at end of cluster chain
893;    bit1 - clear allocated cluster
894;
895; * c=0: failure; sets errno
896;-----------------------------------------------------------------------------
897next_sector:
898	; Save argument
899	sta next_sector_arg
900
901	; Last sector of cluster?
902	lda cur_context + context::cluster_sector
903	inc
904	cmp cur_volume + fs::sectors_per_cluster
905	beq @end_of_cluster
906	sta cur_context + context::cluster_sector
907
908	; Load next sector
909	inc32 cur_context + context::lba
910@read_sector:
911	jsr load_sector_buffer
912	bcc @error
913	set16_val fat32_bufptr, sector_buffer
914	sec
915	rts
916
917@end_of_cluster:
918	jsr next_cluster
919	bcc @error
920	jsr is_end_of_cluster_chain
921	bcs @end_of_chain
922@read_cluster:
923	jsr calc_cluster_lba
924	bra @read_sector
925
926@end_of_chain:
927	; Request to allocate new cluster?
928	lda next_sector_arg
929	bit #$01
930	beq @error
931
932	; Save location of cluster entry in FAT
933	set16 tmp_bufptr, fat32_bufptr
934	set32 tmp_sector_lba, sector_lba
935
936	; Allocate a new cluster
937	jsr allocate_cluster
938	bcc @error
939
940	; Load back the cluster sector
941	set32 cur_context + context::lba, tmp_sector_lba
942	jsr load_sector_buffer
943	bcs @1
944@error:	clc
945	rts
946@1:
947	set16 fat32_bufptr, tmp_bufptr
948
949	; Write allocated cluster number in FAT
950	ldy #0
951@2:	lda cur_volume + fs::free_cluster, y
952	sta (fat32_bufptr), y
953	iny
954	cpy #4
955	bne @2
956
957	; Save FAT sector
958	jsr save_sector_buffer
959	bcc @error
960
961	; Set allocated cluster as current
962	set32 cur_context + context::cluster, cur_volume + fs::free_cluster
963
964	; Request to clear new cluster?
965	lda next_sector_arg
966	bit #$02
967	beq @wrdone
968	jsr clear_cluster
969	bcc @error
970
971@wrdone:
972	; Retry
973	jmp @read_cluster
974
975;-----------------------------------------------------------------------------
976; find_dirent
977;
978; Find directory entry with path specified in string pointed to by fat32_ptr
979;
980; In:  a  =$00 allow files and directories
981;         =$80 only allow files
982;         =$40 only allow directories
983;
984; * c=0: failure; sets errno
985;-----------------------------------------------------------------------------
986find_dirent:
987	sta tmp_filetype
988	stz name_offset
989
990	; If path starts with a slash, use root directory as base,
991	; otherwise the current directory.
992	lda (fat32_ptr)
993	cmp #'/'
994	bne @use_current
995	set32_val cur_context + context::cluster, 0
996	inc name_offset
997
998	; Does path only consists of a slash?
999	ldy name_offset
1000	lda (fat32_ptr), y
1001	bne @open
1002
1003	; Fake a directory entry for the root directory
1004	lda #'/'
1005	sta fat32_dirent + dirent::name
1006	stz fat32_dirent + dirent::name + 1
1007	lda #$10
1008	sta fat32_dirent + dirent::attributes
1009	.assert dirent::start < dirent::size, error ; must be next to each other
1010	ldx #0
1011@clr:	stz fat32_dirent + dirent::start, x
1012	inx
1013	cpx #8
1014	bne @clr
1015
1016	sec
1017	rts
1018
1019@use_current:
1020	set32 cur_context + context::cluster, cur_volume + fs::cwd_cluster
1021
1022@open:	set32 tmp_dir_cluster, cur_context + context::cluster
1023
1024	jsr open_cluster
1025	bcc @error
1026
1027@next:	; Read entry
1028	jsr fat32_read_dirent
1029	bcc @error
1030
1031	ldy name_offset
1032	jsr match_name
1033	bcc @next
1034
1035	; Check for '/'
1036	lda (fat32_ptr), y
1037	cmp #'/'
1038	beq @chdir
1039
1040	lda fat32_dirent + dirent::attributes
1041	bit #$10
1042	bne @is_dir
1043	; is file
1044	bit tmp_filetype
1045	bvs @next
1046	bra @ok
1047@is_dir:
1048	bit tmp_filetype
1049	bmi @next
1050@ok:	jsr match_type
1051	bcc @next
1052
1053@found:	; Found
1054	sec
1055	rts
1056
1057@error:	clc
1058	rts
1059
1060@chdir:	iny
1061	lda (fat32_ptr), y
1062	beq @found
1063
1064	; Is this a directory?
1065	lda fat32_dirent + dirent::attributes
1066	bit #$10
1067	beq @error
1068
1069	sty name_offset
1070
1071	set32 cur_context + context::cluster, fat32_dirent + dirent::start
1072	set32 tmp_dir_cluster, fat32_dirent + dirent::start
1073	jmp @open
1074
1075;-----------------------------------------------------------------------------
1076; find_file
1077;
1078; Same as find_dirent, but with file type check
1079;
1080; * c=0: failure; sets errno
1081;-----------------------------------------------------------------------------
1082find_file:
1083	lda #$80 ; files only
1084	jmp find_dirent
1085
1086;-----------------------------------------------------------------------------
1087; find_dir
1088;
1089; Same as find_dirent, but with directory type check
1090;
1091; * c=0: failure; sets errno
1092;-----------------------------------------------------------------------------
1093find_dir:
1094	lda #$40 ; directories only
1095	jmp find_dirent
1096
1097;-----------------------------------------------------------------------------
1098; delete_entry
1099;
1100; Delete a directory entry. Requires one of find_dirent/find_file/find_dir to
1101; be called before.
1102;
1103; C: 1= ignore read-only bit
1104;
1105; * c=0: failure; sets errno
1106;-----------------------------------------------------------------------------
1107delete_entry:
1108	set16 fat32_bufptr, cur_context + context::dirent_bufptr
1109
1110	bcs @1 ; ignore read-only
1111
1112	ldy #11
1113	lda (fat32_bufptr),y
1114	and #1
1115	beq @1
1116
1117	; read-only file
1118	lda #ERRNO_FILE_READ_ONLY
1119	jmp set_errno
1120
1121@1:
1122	lda lfn_count
1123	beq @delete_lfn_loop
1124
1125	; rewind to first LFN entry
1126	jsr rewind_dir_entry
1127
1128@delete_lfn_loop:
1129	lda #$E5
1130	sta (fat32_bufptr)
1131
1132	jsr save_sector_buffer
1133	bcc @ret
1134
1135	dec lfn_count
1136	bmi @end ; lfn_count + 1 iterations (#LFNs + 1 SFN)
1137
1138	add16_val fat32_bufptr, fat32_bufptr, 32
1139	cmp16_val_ne fat32_bufptr, sector_buffer_end, @delete_lfn_loop
1140
1141	lda #0
1142	jsr next_sector
1143	bcs @delete_lfn_loop
1144	rts
1145
1146@end:
1147	sec
1148@ret:
1149	rts
1150
1151;-----------------------------------------------------------------------------
1152; delete_file
1153;
1154; * c=0: failure; sets errno
1155; * does not set errno = ERRNO_FILE_NOT_FOUND!
1156;-----------------------------------------------------------------------------
1157delete_file:
1158	; Find file
1159	jsr find_file
1160	bcs delete_file2
1161@error:
1162	rts
1163
1164delete_file2:
1165	clc ; respect read-only bit
1166	jsr delete_entry
1167	bcs @1
1168	rts
1169
1170@1:
1171	; Unlink cluster chain
1172	set32 cur_context + context::cluster, fat32_dirent + dirent::start
1173	jmp unlink_cluster_chain
1174
1175;-----------------------------------------------------------------------------
1176; fat32_init
1177;
1178; * c=0: failure; sets errno
1179;-----------------------------------------------------------------------------
1180fat32_init:
1181	; Clear FAT32 BSS
1182	set16_val fat32_bufptr, _fat32_bss_start
1183	lda #0
1184@1:	sta (fat32_bufptr)
1185	inc fat32_bufptr + 0
1186	bne @2
1187	inc fat32_bufptr + 1
1188@2:	ldx fat32_bufptr + 0
1189	cpx #<_fat32_bss_end
1190	bne @1
1191	ldx fat32_bufptr + 1
1192	cpx #>_fat32_bss_end
1193	bne @1
1194
1195	; Make sure sector_lba is non-zero
1196	; (was overwritten by sdcard_init)
1197	lda #$FF
1198	sta sector_lba
1199
1200	; No current volume
1201	sta volume_idx
1202
1203	; No time set up
1204	sta fat32_time_year
1205
1206	sec
1207	rts
1208
1209;-----------------------------------------------------------------------------
1210; mount
1211;
1212; In:  a  partition number (0+)
1213;
1214; * c=0: failure; sets errno
1215;-----------------------------------------------------------------------------
1216mount:
1217	pha ; partition number
1218
1219	jsr load_mbr_sector
1220	pla
1221	bcs @2a
1222@error:	clc
1223	rts
1224
1225@2a:	asl ; *16
1226	asl
1227	asl
1228	asl
1229	tax
1230
1231	; Check partition type
1232	lda sector_buffer + $1BE + 4, x
1233	cmp #$0B
1234	beq @3
1235	cmp #$0C
1236	beq @3
1237	lda #ERRNO_NO_FS
1238	jmp set_errno
1239
1240@3:
1241	; Get LBA of partition
1242	lda sector_buffer + $1BE + 8 + 0, x
1243	sta cur_volume + fs::lba_partition + 0
1244	lda sector_buffer + $1BE + 8 + 1, x
1245	sta cur_volume + fs::lba_partition + 1
1246	lda sector_buffer + $1BE + 8 + 2, x
1247	sta cur_volume + fs::lba_partition + 2
1248	lda sector_buffer + $1BE + 8 + 3, x
1249	sta cur_volume + fs::lba_partition + 3
1250
1251	; Read first sector of partition
1252	set32 cur_context + context::lba, cur_volume + fs::lba_partition
1253	jsr load_sector_buffer
1254	bcc @error
1255
1256	; Some sanity checks
1257	lda sector_buffer + 510 ; Check signature
1258	cmp #$55
1259	beq :+
1260@fs_inconsistent:
1261	lda #ERRNO_FS_INCONSISTENT
1262	jmp set_errno
1263:	lda sector_buffer + 511
1264	cmp #$AA
1265	bne @fs_inconsistent
1266	lda sector_buffer + 16 ; # of FATs should be 2
1267	cmp #2
1268	bne @fs_inconsistent
1269	lda sector_buffer + 17 ; Root entry count = 0 for FAT32
1270	bne @fs_inconsistent
1271	lda sector_buffer + 18
1272	bne @fs_inconsistent
1273
1274	; Get sectors per cluster
1275	lda sector_buffer + 13
1276	sta cur_volume + fs::sectors_per_cluster
1277	beq @fs_inconsistent
1278
1279	; Calculate shift amount based on sectors per cluster
1280	; cluster_shift already 0
1281@4:	lsr
1282	beq @5
1283	inc cur_volume + fs::cluster_shift
1284	bra @4
1285@5:
1286	; FAT size in sectors
1287	set32 cur_volume + fs::fat_size, sector_buffer + 36
1288
1289	; Root cluster
1290	set32 cur_volume + fs::rootdir_cluster, sector_buffer + 44
1291
1292	; Calculate LBA of first FAT
1293	add32_16 cur_volume + fs::lba_fat, cur_volume + fs::lba_partition, sector_buffer + 14
1294
1295	; Calculate LBA of first data sector
1296	add32 cur_volume + fs::lba_data, cur_volume + fs::lba_fat, cur_volume + fs::fat_size
1297	add32 cur_volume + fs::lba_data, cur_volume + fs::lba_data, cur_volume + fs::fat_size
1298
1299	; Calculate number of clusters on volume: (total_sectors - lba_data) >> cluster_shift
1300	set32 cur_volume + fs::cluster_count, sector_buffer + 32
1301	sub32 cur_volume + fs::cluster_count, cur_volume + fs::cluster_count, cur_volume + fs::lba_data
1302	ldy cur_volume + fs::cluster_shift
1303	beq @7
1304@6:	shr32 cur_volume + fs::cluster_count
1305	dey
1306	bne @6
1307@7:
1308	; Get FS info sector
1309	add32_16 cur_volume + fs::lba_fsinfo, cur_volume + fs::lba_partition, sector_buffer + 48
1310
1311	; Load FS info sector
1312	set32 cur_context + context::lba, cur_volume + fs::lba_fsinfo
1313	jsr load_sector_buffer
1314	bcs @8
1315	rts
1316@8:
1317	; Get number of free clusters
1318	set32 cur_volume + fs::free_clusters, sector_buffer + 488
1319
1320	; Set initial start point for free cluster search
1321	set32_val cur_volume + fs::free_cluster, 2
1322
1323	; Success
1324	lda #$80
1325	sta cur_volume + fs::mounted
1326	sec
1327	rts
1328
1329;-----------------------------------------------------------------------------
1330; unmount
1331;
1332; In:  a  partition number (0+)
1333;
1334; * c=0: failure; sets errno
1335;-----------------------------------------------------------------------------
1336unmount:
1337	sec ; don't mount
1338	jsr set_volume
1339	; Set unmounted
1340	stz cur_volume + fs::mounted
1341	; No current volume
1342	lda #$ff
1343	sta volume_idx
1344	rts
1345
1346;-----------------------------------------------------------------------------
1347; fat32_set_context
1348;
1349; context index in A
1350;
1351; * c=0: failure; sets errno
1352;-----------------------------------------------------------------------------
1353; TODO: even in the error case, the context must always been set, otherwise
1354; we are stuck.
1355fat32_set_context:
1356	stz fat32_errno
1357
1358	; Already selected?
1359	cmp context_idx
1360	beq @done
1361
1362	; Valid context index?
1363	cmp #FAT32_CONTEXTS
1364	bcs @error
1365
1366.if ::FAT32_CONTEXTS > 1
1367	; Save new context index
1368	pha
1369
1370	; Save dirty sector
1371	jsr sync_sector_buffer
1372	bcc @error2
1373
1374	; Put zero page variables in current context
1375	set16 cur_context + context::bufptr, fat32_bufptr
1376
1377	.assert CONTEXT_SIZE = 32, error
1378	; Copy current context back
1379	lda context_idx   ; X=A*32
1380	asl
1381	asl
1382	asl
1383	asl
1384	asl
1385	tax
1386
1387	ldy #0
1388@1:	lda cur_context, y
1389	sta contexts, x
1390	inx
1391	iny
1392	cpy #(.sizeof(context))
1393	bne @1
1394
1395	; Copy new context to current
1396	pla              ; Get new context idx
1397	sta context_idx  ; X=A*32
1398	asl
1399	asl
1400	asl
1401	asl
1402	asl
1403	tax
1404
1405	ldy #0
1406@2:	lda contexts, x
1407	sta cur_context, y
1408	inx
1409	iny
1410	cpy #(.sizeof(context))
1411	bne @2
1412
1413	; Restore zero page variables from current context
1414	set16 fat32_bufptr, cur_context + context::bufptr
1415
1416	ldx context_idx
1417	lda volume_for_context, x
1418	cmp #$ff
1419	beq @no_volume
1420	clc
1421	jsr set_volume
1422	bcc @error
1423
1424@no_volume:
1425	; Reload sector
1426	lda cur_context + context::flags
1427	bit #FLAG_IN_USE
1428	beq @reload_done
1429	jsr load_sector_buffer
1430	bcc @error
1431@reload_done:
1432.endif
1433
1434@done:	sec
1435	rts
1436@error2:
1437	pla
1438@error:	clc
1439	rts
1440
1441;-----------------------------------------------------------------------------
1442; fat32_get_context
1443;-----------------------------------------------------------------------------
1444fat32_get_context:
1445	lda context_idx
1446	rts
1447
1448;-----------------------------------------------------------------------------
1449; fat32_open_dir
1450;
1451; Open current working directory
1452;
1453; * c=0: failure; sets errno
1454;-----------------------------------------------------------------------------
1455fat32_open_dir:
1456	stz fat32_errno
1457
1458	; Check if context is free
1459	lda cur_context + context::flags
1460	bne @error
1461
1462	; Use current directory if fat32_ptr is zero
1463	cmp16_z fat32_ptr, @cur_dir
1464
1465	; Find directory and use it
1466	jsr find_dir
1467	bcs @1
1468	lda #ERRNO_FILE_NOT_FOUND
1469	jmp set_errno
1470
1471@1:
1472	set32 cur_context + context::cluster, fat32_dirent + dirent::start
1473	bra @open
1474
1475@cur_dir:
1476	; Open current directory
1477	set32 cur_context + context::cluster, cur_volume + fs::cwd_cluster
1478
1479@open:	jsr open_cluster
1480	bcc @error
1481
1482	; Set context as in-use
1483	lda #FLAG_IN_USE
1484	sta cur_context + context::flags
1485
1486	; Success
1487	sec
1488	rts
1489
1490@error:	clc
1491	rts
1492
1493;-----------------------------------------------------------------------------
1494; fat32_find_dirent
1495;
1496; same args as find_dirent
1497;-----------------------------------------------------------------------------
1498fat32_find_dirent:
1499	; Check if context is free
1500	ldx cur_context + context::flags
1501	bne @error
1502
1503	; Open current directory
1504	jmp find_dirent
1505
1506@error:	clc
1507	rts
1508
1509;-----------------------------------------------------------------------------
1510; fat32_read_dirent
1511;
1512; * c=0: failure; sets errno
1513;-----------------------------------------------------------------------------
1514fat32_read_dirent:
1515	stz fat32_errno
1516
1517	sec
1518	jmp read_dirent
1519
1520;-----------------------------------------------------------------------------
1521; read_dirent
1522;
1523; In:   c=1: return next file entry
1524;       c=0: return next volume label entry
1525;
1526; * c=0: failure; sets errno
1527;-----------------------------------------------------------------------------
1528read_dirent:
1529	ror
1530	sta tmp_dirent_flag
1531	stz lfn_index
1532	stz lfn_count
1533
1534@fat32_read_dirent_loop:
1535	; Load next sector if at end of buffer
1536	cmp16_val_ne fat32_bufptr, sector_buffer_end, @1
1537	lda #0
1538	jsr next_sector
1539	bcs @1
1540@error:	clc     ; Indicate error
1541	rts
1542@1:
1543	; Last entry?
1544	lda (fat32_bufptr)
1545	beq @error
1546
1547	; Skip empty entries
1548	cmp #$E5
1549	bne @3
1550	jmp @next_entry_clear_lfn_buffer
1551@3:
1552
1553	; Volume label entry?
1554	ldy #11
1555	lda (fat32_bufptr), y
1556	sta fat32_dirent + dirent::attributes
1557	cmp #8
1558	bne @2
1559	bit tmp_dirent_flag
1560	bpl @2b
1561@2a:
1562	jmp @next_entry
1563@2:
1564	bit tmp_dirent_flag
1565	bpl @2a
1566
1567@2b:
1568	; check for LFN entry
1569	lda fat32_dirent + dirent::attributes
1570	cmp #$0f
1571	beq @lfn_entry
1572	bra @short_entry
1573
1574@lfn_entry:
1575
1576	; does it have the right index?
1577	jsr check_lfn_index
1578	bcs @index_ok
1579	jmp @next_entry_clear_lfn_buffer
1580@index_ok:
1581
1582	; first LFN entry?
1583	lda lfn_index
1584	bne @not_first_lfn_entry
1585
1586; first LFN entry
1587	; init buffer
1588	set16_val fat32_lfn_bufptr, lfn_buf
1589
1590	; save checksum
1591	ldy #13
1592	lda (fat32_bufptr), y
1593	sta lfn_checksum
1594
1595	; prepare expected index
1596	lda (fat32_bufptr)
1597	and #$1f
1598	sta lfn_index
1599	sta lfn_count
1600
1601	; add entry to buffer
1602	jsr add_lfn_entry
1603
1604	; remember dir entry
1605	jsr mark_dir_entry
1606
1607	; continue with next entry
1608	jmp @next_entry
1609
1610; followup LFN entry
1611@not_first_lfn_entry:
1612
1613	; compare checksum
1614	ldy #13
1615	lda (fat32_bufptr), y
1616	cmp lfn_checksum
1617	beq @checksum_ok
1618	jmp @next_entry_clear_lfn_buffer
1619
1620@checksum_ok:
1621	dec lfn_index
1622
1623	; add entry to buffer
1624	jsr add_lfn_entry
1625
1626	; continue with next entry
1627	jmp @next_entry
1628
1629
1630;*******
1631@short_entry:
1632	; is there a LFN?
1633	lda lfn_index
1634	cmp #1
1635	bne @is_short
1636
1637	; Compare checksum
1638	lda #0
1639	tay
1640@checksum_loop:
1641	tax
1642	lsr
1643	txa
1644	ror
1645	clc
1646	adc (fat32_bufptr), y
1647	iny
1648	cpy #11
1649	bne @checksum_loop
1650
1651	cmp lfn_checksum
1652	bne @is_short
1653
1654	lda lfn_count
1655	sta lfn_index
1656
1657	ldx #0
1658@decode_lfn_loop:
1659	sub16_val fat32_lfn_bufptr, fat32_lfn_bufptr, 32
1660
1661	ldy #1
1662	lda #5
1663	jsr decode_lfn_chars
1664	bcc @name_done2
1665	ldy #14
1666	lda #6
1667	jsr decode_lfn_chars
1668	bcc @name_done2
1669	ldy #28
1670	lda #2
1671	jsr decode_lfn_chars
1672	dec lfn_index
1673	bne @decode_lfn_loop
1674@name_done2:
1675	bra @name_done ; yes, we need to zero terminate!
1676
1677@is_short:
1678	; Volume label decoding
1679	bit tmp_dirent_flag
1680	bmi @4b
1681	jsr decode_volume_label
1682	bra @name_done_z
1683
1684@4b:	; get upper/lower case flags
1685	ldy #12
1686	lda (fat32_bufptr), y
1687	asl
1688	asl
1689	asl ; bits 7 and 6
1690	sta tmp_sfn_case
1691
1692	; Copy first part of file name
1693	ldy #0
1694@4:	lda (fat32_bufptr), y
1695	cmp #' '
1696	beq @skip_spaces
1697	cmp #$05 ; $05 at first character translates into $E5
1698	bne @n05
1699	cpy #0
1700	bne @n05
1701	lda #$E5
1702@n05:	bit tmp_sfn_case
1703	bvc @ucase1
1704	jsr to_lower
1705@ucase1:
1706	jsr filename_cp437_to_internal
1707	sta fat32_dirent + dirent::name, y
1708	iny
1709	cpy #8
1710	bne @4
1711
1712	; Skip any following spaces
1713@skip_spaces:
1714	tya
1715	tax
1716@5:	cpy #8
1717	beq @6
1718	lda (fat32_bufptr), y
1719	iny
1720	cmp #' '
1721	beq @5
1722@6:
1723	; If extension starts with a space, we're done
1724	lda (fat32_bufptr), y
1725	cmp #' '
1726	beq @name_done
1727
1728	; Add dot to output
1729	lda #'.'
1730	sta fat32_dirent + dirent::name, x
1731	inx
1732
1733	; Copy extension part of file name
1734@7:	lda (fat32_bufptr), y
1735	cmp #' '
1736	beq @name_done
1737	bit tmp_sfn_case
1738	bpl @ucase2
1739	jsr to_lower
1740@ucase2:
1741	phx
1742	jsr filename_cp437_to_internal
1743	plx
1744	sta fat32_dirent + dirent::name, x
1745	iny
1746	inx
1747	cpy #11
1748	bne @7
1749
1750@name_done:
1751	; Add zero-termination to output
1752	stz fat32_dirent + dirent::name, x
1753
1754@name_done_z:
1755	; Decode mtime timestamp
1756	ldy #$16
1757	lda (fat32_bufptr), y
1758	iny
1759	ora (fat32_bufptr), y
1760	iny
1761	ora (fat32_bufptr), y
1762	iny
1763	ora (fat32_bufptr), y
1764	bne @ts1
1765	stz fat32_dirent + dirent::mtime_seconds
1766	stz fat32_dirent + dirent::mtime_minutes
1767	stz fat32_dirent + dirent::mtime_hours
1768	stz fat32_dirent + dirent::mtime_day
1769	lda #$ff ; year 2235 signals "no date"
1770	bra @ts2
1771@ts1:	ldy #$16
1772	lda (fat32_bufptr), y
1773	sta tmp_timestamp
1774	and #31
1775	asl
1776	sta fat32_dirent + dirent::mtime_seconds
1777	iny
1778	lda (fat32_bufptr), y
1779	asl tmp_timestamp
1780	rol
1781	asl tmp_timestamp
1782	rol
1783	asl tmp_timestamp
1784	rol
1785	and #63
1786	sta fat32_dirent + dirent::mtime_minutes
1787	lda (fat32_bufptr), y
1788	lsr
1789	lsr
1790	lsr
1791	sta fat32_dirent + dirent::mtime_hours
1792	iny
1793	lda (fat32_bufptr), y
1794	tax
1795	and #31
1796	sta fat32_dirent + dirent::mtime_day
1797	iny
1798	lda (fat32_bufptr), y
1799	sta tmp_timestamp
1800	txa
1801	lsr tmp_timestamp
1802	ror
1803	lsr
1804	lsr
1805	lsr
1806	lsr
1807	sta fat32_dirent + dirent::mtime_month
1808	lda (fat32_bufptr), y
1809	lsr
1810@ts2:	sta fat32_dirent + dirent::mtime_year
1811
1812	; Copy file size
1813	ldy #28
1814	ldx #0
1815@8:	lda (fat32_bufptr), y
1816	sta fat32_dirent + dirent::size, x
1817	iny
1818	inx
1819	cpx #4
1820	bne @8
1821
1822	; Copy cluster
1823	ldy #26
1824	lda (fat32_bufptr), y
1825	sta fat32_dirent + dirent::start + 0
1826	iny
1827	lda (fat32_bufptr), y
1828	sta fat32_dirent + dirent::start + 1
1829	ldy #20
1830	lda (fat32_bufptr), y
1831	sta fat32_dirent + dirent::start + 2
1832	iny
1833	lda (fat32_bufptr), y
1834	sta fat32_dirent + dirent::start + 3
1835
1836	; Save lba + fat32_bufptr
1837	set32 cur_context + context::dirent_lba,    cur_context + context::lba
1838	set16 cur_context + context::dirent_bufptr, fat32_bufptr
1839
1840	; Increment buffer pointer to next entry
1841	add16_val fat32_bufptr, fat32_bufptr, 32
1842
1843	sec
1844	rts
1845
1846@next_entry_clear_lfn_buffer:
1847	stz lfn_index
1848
1849@next_entry:
1850	add16_val fat32_bufptr, fat32_bufptr, 32
1851	jmp @fat32_read_dirent_loop
1852
1853;-----------------------------------------------------------------------------
1854; decode_volume_label
1855;-----------------------------------------------------------------------------
1856decode_volume_label:
1857	ldy #0
1858@1:	lda (fat32_bufptr), y
1859	jsr filename_cp437_to_internal
1860	sta fat32_dirent + dirent::name, y
1861	iny
1862	cpy #11
1863	bne @1
1864	dey
1865	lda #$20
1866@2:	cmp fat32_dirent + dirent::name, y
1867	bne @3
1868	dey
1869	bpl @2
1870@3:	iny
1871	lda #0
1872	sta fat32_dirent + dirent::name, y
1873	rts
1874
1875;-----------------------------------------------------------------------------
1876; check_lfn_index
1877;
1878; * c=1: ok
1879;-----------------------------------------------------------------------------
1880check_lfn_index:
1881	lda lfn_index
1882	beq @expect_start
1883
1884	lda lfn_index
1885	dec
1886	cmp (fat32_bufptr)
1887	beq @ok
1888
1889	stz lfn_index
1890@expect_start:
1891	lda (fat32_bufptr)
1892	asl
1893	asl ; bit #6 -> C
1894	rts
1895
1896@ok:
1897	sec
1898	rts
1899
1900;-----------------------------------------------------------------------------
1901; add_lfn_entry
1902;-----------------------------------------------------------------------------
1903add_lfn_entry:
1904	ldy #31
1905:	lda (fat32_bufptr), y
1906	sta (fat32_lfn_bufptr), y
1907	dey
1908	bpl :-
1909	add16_val fat32_lfn_bufptr, fat32_lfn_bufptr, 32
1910	rts
1911
1912;-----------------------------------------------------------------------------
1913; decode_lfn_chars
1914;
1915; Convert 16 bit UCS-2-encoded LFN characters to private 8 bit encoding.
1916;
1917; In:   a  number of characters
1918;       x  target index (offset in fat32_dirent + dirent::name)
1919;       y  source index (offset in (fat32_lfn_bufptr))
1920; Out:  x  updated target index
1921;       y  updated source index
1922;       c  =0: terminating 0 character encountered
1923;-----------------------------------------------------------------------------
1924decode_lfn_chars:
1925	stx lfn_name_index
1926	sta lfn_char_count
1927@loop:
1928 	lda (fat32_lfn_bufptr), y
1929 	iny
1930 	pha
1931 	pha
1932 	lda (fat32_lfn_bufptr), y
1933	iny
1934 	plx
1935 	pha
1936 	jsr filename_char_ucs2_to_internal
1937	ldx lfn_name_index
1938	sta fat32_dirent + dirent::name, x
1939	inc lfn_name_index
1940 	pla
1941 	plx
1942 	bne @cont
1943 	tax
1944 	beq @end
1945@cont:
1946	dec lfn_char_count
1947	bne @loop
1948	ldx lfn_name_index
1949	sec
1950	rts
1951@end:	ldx lfn_name_index
1952	clc
1953	rts
1954
1955;-----------------------------------------------------------------------------
1956; encode_lfn_chars
1957;
1958; Convert characters in private 8 bit encoding to 16 bit UCS-2 (LFN) encoding.
1959;
1960; In:   a  number of characters
1961;       x  target index (offset in (fat32_lfn_bufptr))
1962;       y  source index (offset in (fat32_ptr))
1963; Out:  x  updated target index
1964;       y  updated source index
1965;       c  =0: terminating 0 character encountered
1966;-----------------------------------------------------------------------------
1967encode_lfn_chars:
1968	sta lfn_char_count
1969@loop:
1970 	lda (fat32_ptr), y
1971 	pha
1972 	phy
1973
1974 	phx
1975	jsr filename_char_internal_to_ucs2
1976 	ply
1977	sta (fat32_lfn_bufptr), y
1978	iny
1979	txa
1980	sta (fat32_lfn_bufptr), y
1981	iny
1982 	phy
1983 	plx
1984
1985 	ply
1986	pla
1987	beq @end
1988	iny
1989	dec lfn_char_count
1990	bne @loop
1991	sec
1992	rts
1993@end:	clc
1994	rts
1995
1996;-----------------------------------------------------------------------------
1997; create_lfn
1998;
1999; * c=0: failure; sets errno
2000;-----------------------------------------------------------------------------
2001create_lfn:
2002	; validate that filename is legal
2003	ldy name_offset
2004@validate_loop:
2005	lda (fat32_ptr), y
2006	beq @validate_ok
2007	jsr validate_char
2008	iny
2009	bcs @validate_loop
2010	lda #ERRNO_ILLEGAL_FILENAME
2011	jmp set_errno
2012
2013@validate_ok:
2014	; init buffer
2015	set16_val fat32_lfn_bufptr, lfn_buf
2016
2017	lda #1
2018	sta lfn_index
2019
2020	ldy name_offset
2021
2022@create_lfn_loop:
2023	phy
2024
2025	; create FLN template
2026
2027	; fill remainder bytes with $FF
2028	ldy #31
2029:	lda #$ff
2030	sta (fat32_lfn_bufptr), y
2031	dey
2032	bne :-
2033
2034	; fill in special bytes
2035	ldy #0
2036	lda lfn_index
2037	sta (fat32_lfn_bufptr), y
2038	ldy #11
2039	lda #$0f
2040	sta (fat32_lfn_bufptr), y
2041	iny
2042	lda #0
2043	sta (fat32_lfn_bufptr), y
2044	; leave checksum at offset 13 empty for now
2045	ldy #26
2046	lda #0
2047	sta (fat32_lfn_bufptr), y
2048	iny
2049	sta (fat32_lfn_bufptr), y
2050
2051	ply
2052
2053	; put 13 chars into entry
2054	ldx #1
2055	lda #5
2056	jsr encode_lfn_chars
2057	bcc @name_done
2058	ldx #14
2059	lda #6
2060	jsr encode_lfn_chars
2061	bcc @name_done
2062	ldx #28
2063	lda #2
2064	jsr encode_lfn_chars
2065	bcc @name_done
2066
2067	add16_val fat32_lfn_bufptr, fat32_lfn_bufptr, 32
2068
2069	inc lfn_index
2070
2071	bra @create_lfn_loop
2072
2073@name_done:
2074	lda (fat32_lfn_bufptr)
2075	sta lfn_count
2076	ora #$40
2077	sta (fat32_lfn_bufptr)
2078
2079	sec
2080	rts
2081
2082;-----------------------------------------------------------------------------
2083; fat32_read_dirent_filtered
2084;
2085; Returns next dirent that matches the name/pattern in (fat32_ptr)
2086;
2087; * c=0: failure; sets errno
2088;-----------------------------------------------------------------------------
2089fat32_read_dirent_filtered:
2090	stz fat32_errno
2091
2092	jsr fat32_read_dirent
2093	bcc @error
2094
2095	cmp16_z fat32_ptr, @ok
2096
2097	ldy #0
2098	jsr match_name
2099	bcc fat32_read_dirent_filtered
2100@ok:
2101	sec
2102	rts
2103
2104@error:
2105	clc
2106	rts
2107
2108;-----------------------------------------------------------------------------
2109; fat32_chdir
2110;
2111; * c=0: failure; sets errno
2112;-----------------------------------------------------------------------------
2113fat32_chdir:
2114	stz fat32_errno
2115
2116	; Check if context is free
2117	lda cur_context + context::flags
2118	bne @error
2119
2120	; Find directory
2121	jsr find_dir
2122	bcs @1
2123	lda #ERRNO_FILE_NOT_FOUND
2124	jmp set_errno
2125
2126@1:
2127	; Set as current directory
2128	set32 cur_volume + fs::cwd_cluster, fat32_dirent + dirent::start
2129
2130	sec
2131	rts
2132
2133@error:	clc
2134	rts
2135
2136;-----------------------------------------------------------------------------
2137; fat32_rename
2138;
2139; * c=0: failure; sets errno
2140;-----------------------------------------------------------------------------
2141fat32_rename:
2142	stz fat32_errno
2143
2144	; Check if context is free
2145	lda cur_context + context::flags
2146	beq @0
2147@error:	clc
2148	rts
2149
2150@0:
2151	; Save first argument
2152	set16 tmp_buf, fat32_ptr
2153
2154	; Make sure target name doesn't exist
2155	set16 fat32_ptr, fat32_ptr2
2156	lda #0 ; allow files and directories
2157	jsr find_dirent
2158	bcc @1
2159	; Error, file exists
2160	lda #ERRNO_FILE_EXISTS
2161	jmp set_errno
2162
2163@1:
2164	; Find file to rename
2165	set16 fat32_ptr, tmp_buf
2166	lda #0 ; allow files and directories
2167	jsr find_dirent
2168	bcs @3
2169	lda #ERRNO_FILE_NOT_FOUND
2170	jmp set_errno
2171
2172@3:
2173	; rescue shortname entry
2174	set16 fat32_bufptr, cur_context + context::dirent_bufptr
2175
2176	ldy #11
2177@loop:	lda (fat32_bufptr), y
2178	sta tmp_entry - 11, y
2179	iny
2180	cpy #32
2181	bne @loop
2182
2183	; delete
2184	sec ; ignore read-only bit
2185	jsr delete_entry
2186	bcs @4
2187	rts
2188@4:
2189
2190	; target name
2191	set16 fat32_ptr, fat32_ptr2
2192
2193	; Find space
2194	jsr find_space_for_lfn
2195	bcc @error
2196
2197	; Create short name
2198	jsr create_shortname
2199	bcc @error
2200
2201	; Write LFN entries
2202	jsr write_lfn_entries
2203	bcc @error
2204
2205	; Copy new shortname into sector buffer
2206	ldy #0
2207@2:	lda shortname_buf, y
2208	sta (fat32_bufptr), y
2209	iny
2210	cpy #11
2211	bne @2
2212
2213	; restore remainder of short name entry
2214@5:	lda tmp_entry - 11, y
2215	sta (fat32_bufptr), y
2216	iny
2217	cpy #32
2218	bne @5
2219
2220	; Write sector buffer to disk
2221	jmp save_sector_buffer
2222
2223;-----------------------------------------------------------------------------
2224; fat32_set_attribute
2225;
2226; A: File attribute
2227;
2228; * c=0: failure; sets errno
2229;-----------------------------------------------------------------------------
2230fat32_set_attribute:
2231	stz fat32_errno
2232
2233	and #$ff-$10 ; clear directory bit
2234	sta tmp_buf
2235
2236	; Check if context is free
2237	lda cur_context + context::flags
2238	beq @0
2239@error:	clc
2240	rts
2241
2242@0:
2243	; Find file
2244	lda #0 ; allow files and directories
2245	jsr find_dirent
2246	bcs @3
2247	lda #ERRNO_FILE_NOT_FOUND
2248	jmp set_errno
2249
2250@3:
2251	; Set attribute
2252	set16 fat32_bufptr, cur_context + context::dirent_bufptr
2253	ldy #11
2254	lda (fat32_bufptr), y
2255	and #$10 ; preserve directory bit
2256	ora tmp_buf
2257	sta (fat32_bufptr), y
2258
2259	; Write sector buffer to disk
2260	jmp save_sector_buffer
2261
2262;-----------------------------------------------------------------------------
2263; fat32_delete
2264;-----------------------------------------------------------------------------
2265fat32_delete:
2266	stz fat32_errno
2267
2268	; Check if context is free
2269	lda cur_context + context::flags
2270	bne @error
2271
2272	jsr delete_file
2273	bcs @1
2274	lda #ERRNO_FILE_NOT_FOUND
2275	jmp set_errno
2276
2277@error:	clc
2278@1:	rts
2279
2280;-----------------------------------------------------------------------------
2281; fat32_rmdir
2282;
2283; * c=0: failure; sets errno
2284;-----------------------------------------------------------------------------
2285fat32_rmdir:
2286	stz fat32_errno
2287
2288	; Check if context is free
2289	lda cur_context + context::flags
2290	beq @1
2291@error:	clc
2292	rts
2293@1:
2294	; Find directory
2295	jsr find_dir
2296	bcs @2
2297@fnf:
2298	lda #ERRNO_FILE_NOT_FOUND
2299	jmp set_errno
2300
2301@2:
2302	; make sure user isn't trying to remove '.' or '..' entries
2303	jsr check_dot_or_dotdot
2304	bcs @fnf
2305
2306	; Open directory
2307	set32 cur_context + context::cluster, fat32_dirent + dirent::start
2308	jsr open_cluster
2309	bcc @error
2310
2311	; Make sure directory is empty
2312@next:	jsr fat32_read_dirent
2313	bcs @3
2314	lda fat32_errno
2315	beq @done
2316	clc
2317	rts
2318
2319@3:	; Allow for '.' and '..' entries
2320	jsr check_dot_or_dotdot
2321	bcs @next
2322	lda #ERRNO_DIR_NOT_EMPTY
2323	jmp set_errno
2324
2325@done:
2326	; Find directory
2327	jsr find_dir
2328	bcc @error
2329
2330	clc ; respect read-only bit
2331	jsr delete_entry
2332	bcs @4
2333	rts
2334
2335@4:
2336	; Unlink cluster chain
2337	set32 cur_context + context::cluster, fat32_dirent + dirent::start
2338	jmp unlink_cluster_chain
2339
2340check_dot_or_dotdot:
2341	lda fat32_dirent + dirent::name
2342	cmp #'.'
2343	bne @no
2344	lda fat32_dirent + dirent::name + 1
2345	beq @yes
2346	cmp #'.'
2347	bne @no
2348	lda fat32_dirent + dirent::name + 2
2349	beq @yes
2350@no:	clc
2351	rts
2352@yes:	sec
2353	rts
2354
2355;-----------------------------------------------------------------------------
2356; fat32_open
2357;
2358; Open file specified in string pointed to by fat32_ptr
2359;
2360; * c=0: failure; sets errno
2361;-----------------------------------------------------------------------------
2362fat32_open:
2363	stz fat32_errno
2364
2365	; Check if context is free
2366	lda cur_context + context::flags
2367	bne @error
2368
2369	; Find file
2370	jsr find_file
2371	bcs @1
2372	lda #ERRNO_FILE_NOT_FOUND
2373	jmp set_errno
2374
2375@1:
2376	; Open file
2377	stz cur_context + context::eof
2378	set32_val cur_context + context::file_offset, 0
2379	set32 cur_context + context::file_size, fat32_dirent + dirent::size
2380	set32 cur_context + context::start_cluster, fat32_dirent + dirent::start
2381	set32 cur_context + context::cluster, fat32_dirent + dirent::start
2382	jsr open_cluster
2383	bcc @error
2384
2385	; Set context as in-use
2386	lda #FLAG_IN_USE
2387	sta cur_context + context::flags
2388
2389	; Success
2390	sec
2391	rts
2392
2393@error:	clc
2394	rts
2395
2396;-----------------------------------------------------------------------------
2397; find_space_for_lfn
2398;
2399; * c=0: failure; sets errno
2400;-----------------------------------------------------------------------------
2401find_space_for_lfn:
2402	; Create LFN
2403	jsr create_lfn
2404	bcc @error
2405
2406	stz free_entry_count
2407
2408	; Find free directory entry
2409	set32 cur_context + context::cluster, tmp_dir_cluster
2410	jsr open_cluster
2411	bcc @error
2412
2413@next_entry:
2414	; Load next sector if at end of buffer (allocate and clear new cluster if needed)
2415	cmp16_val_ne fat32_bufptr, sector_buffer_end, @1
2416	lda #3
2417	jsr next_sector
2418	bcs @1
2419@error:	clc
2420	rts
2421@1:
2422	; Is this entry free?
2423	lda (fat32_bufptr)
2424	beq @free_entry
2425	cmp #$E5
2426	beq @free_entry
2427
2428	stz free_entry_count
2429
2430@try_next:
2431	; Increment buffer pointer to next entry
2432	add16_val fat32_bufptr, fat32_bufptr, 32
2433	bra @next_entry
2434
2435	; Free directory entry found
2436@free_entry:
2437	lda free_entry_count
2438	bne @not_first_free_entry
2439
2440	; remember where the first free one was
2441	jsr mark_dir_entry
2442
2443@not_first_free_entry:
2444	lda free_entry_count
2445	inc free_entry_count
2446	cmp lfn_count
2447	bne @try_next ; not reached lfn_count+1 yet
2448
2449	; enough consecutive entries found
2450	; -> set pointer to first free entry
2451	jmp rewind_dir_entry
2452
2453;-----------------------------------------------------------------------------
2454; mark_dir_entry
2455;
2456; Save current cluster, LBA and directory entry index.
2457;-----------------------------------------------------------------------------
2458mark_dir_entry:
2459	; save cluster
2460	lda cur_context + context::cluster + 0
2461	sta marked_entry_cluster + 0
2462	lda cur_context + context::cluster + 1
2463	sta marked_entry_cluster + 1
2464	lda cur_context + context::cluster + 2
2465	sta marked_entry_cluster + 2
2466	lda cur_context + context::cluster + 3
2467	sta marked_entry_cluster + 3
2468	; save LBA
2469	lda cur_context + context::lba + 0
2470	sta marked_entry_lba + 0
2471	lda cur_context + context::lba + 1
2472	sta marked_entry_lba + 1
2473	lda cur_context + context::lba + 2
2474	sta marked_entry_lba + 2
2475	lda cur_context + context::lba + 3
2476	sta marked_entry_lba + 3
2477	; save offset
2478	lda fat32_bufptr + 0
2479	sta marked_entry_offset + 0
2480	lda fat32_bufptr + 1
2481	sta marked_entry_offset + 1
2482	rts
2483
2484;-----------------------------------------------------------------------------
2485; rewind_dir_entry
2486;
2487; Restore cluster, LBA and directory entry index.
2488;-----------------------------------------------------------------------------
2489rewind_dir_entry:
2490	; restore cluster
2491	lda marked_entry_cluster + 0
2492	sta cur_context + context::cluster + 0
2493	lda marked_entry_cluster + 1
2494	sta cur_context + context::cluster + 1
2495	lda marked_entry_cluster + 2
2496	sta cur_context + context::cluster + 2
2497	lda marked_entry_cluster + 3
2498	sta cur_context + context::cluster + 3
2499	; restore LBA
2500	lda marked_entry_lba + 0
2501	sta cur_context + context::lba + 0
2502	lda marked_entry_lba + 1
2503	sta cur_context + context::lba + 1
2504	lda marked_entry_lba + 2
2505	sta cur_context + context::lba + 2
2506	lda marked_entry_lba + 3
2507	sta cur_context + context::lba + 3
2508	; restore entry
2509	lda marked_entry_offset + 0
2510	sta fat32_bufptr + 0
2511	lda marked_entry_offset + 1
2512	sta fat32_bufptr + 1
2513
2514	; load
2515	jmp load_sector_buffer
2516
2517;-----------------------------------------------------------------------------
2518; write_lfn_entries
2519;
2520; * c=0: failure; sets errno
2521;-----------------------------------------------------------------------------
2522write_lfn_entries:
2523	dec lfn_count
2524	bpl @1
2525	sec
2526	rts
2527
2528@1:
2529	; Copy LFN entry
2530	ldy #31
2531@2b:	lda (fat32_lfn_bufptr), y
2532	sta (fat32_bufptr), y
2533	dey
2534	bpl @2b
2535
2536	; set checksum
2537	ldy #13
2538	lda lfn_checksum
2539	sta (fat32_bufptr), y
2540
2541	jsr save_sector_buffer
2542	bcs @ok
2543@error:
2544	clc
2545	rts
2546
2547@ok:
2548	add16_val fat32_bufptr, fat32_bufptr, 32
2549	sub16_val fat32_lfn_bufptr, fat32_lfn_bufptr, 32
2550
2551	cmp16_val_ne fat32_bufptr, sector_buffer_end, @1b
2552	lda #0
2553	jsr next_sector
2554	bcc @error
2555@1b:
2556	bra write_lfn_entries
2557
2558@write_lfn_entries_end:
2559	rts
2560
2561;-----------------------------------------------------------------------------
2562; create_dir_entry
2563;
2564; A: File attribute
2565;
2566; * c=0: failure; sets errno
2567;-----------------------------------------------------------------------------
2568create_dir_entry:
2569	sta tmp_attrib
2570
2571	; Find space
2572	jsr find_space_for_lfn
2573	bcs @1
2574@error:
2575	clc
2576	rts
2577
2578@1:
2579	; Create short name
2580	jsr create_shortname
2581	bcc @error
2582
2583	; Write LFN entries
2584	jsr write_lfn_entries
2585	bcc @error
2586
2587	; Write short name entry
2588
2589	; Copy shortname in new entry
2590	ldy #0
2591@2:	lda shortname_buf, y
2592	sta (fat32_bufptr), y
2593	iny
2594	cpy #11
2595	bne @2
2596
2597	; File attribute
2598	lda tmp_attrib
2599	sta (fat32_bufptr), y
2600	iny
2601
2602	; Zero fill rest of entry
2603	lda #0
2604@3:	sta (fat32_bufptr), y
2605	iny
2606	cpy #32
2607	bne @3
2608
2609	; Save lba + fat32_bufptr
2610	set32 cur_context + context::dirent_lba,    cur_context + context::lba
2611	set16 cur_context + context::dirent_bufptr, fat32_bufptr
2612
2613	; Write sector buffer to disk
2614	jsr save_sector_buffer
2615	bcc @error
2616
2617	; Set context as in-use
2618	lda #FLAG_IN_USE
2619	sta cur_context + context::flags
2620
2621	; Set up fat32_bufptr to trigger cluster allocation at first write
2622	set16_val fat32_bufptr, sector_buffer_end
2623
2624	sec
2625	rts
2626
2627;-----------------------------------------------------------------------------
2628; fat32_create
2629;
2630; Create file.
2631;
2632; c=1: Delete it if it already exists.
2633;-----------------------------------------------------------------------------
2634fat32_create:
2635	php ; overwrite flag
2636	stz fat32_errno
2637
2638	; Check if context is free
2639	lda cur_context + context::flags
2640	beq @1
2641	plp ; overwrite flag
2642@error:	clc
2643	rts
2644@1:
2645	; Check if directory entry already exists?
2646	lda #0 ; allow files and directories
2647	jsr find_dirent
2648	bcs @exists
2649	plp ; overwrite flag
2650	lda fat32_errno
2651	bne @error
2652	bra @ok
2653
2654@exists:
2655	plp ; overwrite flag
2656	bcs @overwrite
2657
2658	lda #ERRNO_FILE_EXISTS
2659	jmp set_errno
2660
2661@overwrite:
2662	; Delete file first if it exists
2663	jsr delete_file2
2664	bcc @error
2665
2666@ok:	; Create directory entry
2667	lda #0
2668	jmp create_dir_entry
2669
2670;-----------------------------------------------------------------------------
2671; fat32_mkdir
2672;
2673; * c=0: failure; sets errno
2674;-----------------------------------------------------------------------------
2675fat32_mkdir:
2676	stz fat32_errno
2677
2678	; Check if context is free
2679	lda cur_context + context::flags
2680	bne @error
2681
2682	; Check if directory doesn't exist yet
2683	lda #0 ; allow files and directories
2684	jsr find_dirent
2685	bcc @0
2686	lda #ERRNO_FILE_EXISTS
2687	jsr set_errno
2688	bra @error
2689
2690@0:
2691	; Create directory entry
2692	lda #$10
2693	jsr create_dir_entry
2694	bcc @error
2695
2696	; Allocate the cluster
2697	jsr allocate_first_cluster
2698	bcc @error
2699	jsr clear_cluster
2700	bcc @error
2701	jsr open_cluster
2702	bcs @1
2703@error:	jmp error_clear_context
2704
2705@1:
2706	; Create '.' and '..' entries
2707	ldy #0
2708	lda #' '
2709@2:	sta sector_buffer + 0, y
2710	sta sector_buffer + 32, y
2711	iny
2712	cpy #11
2713	bne @2
2714
2715	lda #'.'	; Name
2716	sta sector_buffer + 0
2717	sta sector_buffer + 32 + 0
2718	sta sector_buffer + 32 + 1
2719
2720	lda #$10	; Directory attribute
2721	sta sector_buffer + 11
2722	sta sector_buffer + 32 + 11
2723
2724	lda cur_volume + fs::free_cluster + 0
2725	sta sector_buffer + 26
2726	lda cur_volume + fs::free_cluster + 1
2727	sta sector_buffer + 27
2728	lda cur_volume + fs::free_cluster + 2
2729	sta sector_buffer + 20
2730	lda cur_volume + fs::free_cluster + 3
2731	sta sector_buffer + 21
2732
2733	lda tmp_dir_cluster + 0
2734	sta sector_buffer + 32 + 26
2735	lda tmp_dir_cluster + 1
2736	sta sector_buffer + 32 + 27
2737	lda tmp_dir_cluster + 2
2738	sta sector_buffer + 32 + 20
2739	lda tmp_dir_cluster + 3
2740	sta sector_buffer + 32 + 21
2741
2742	; Set sector as dirty
2743	lda cur_context + context::flags
2744	ora #FLAG_DIRTY
2745	sta cur_context + context::flags
2746
2747	jmp fat32_close
2748
2749;-----------------------------------------------------------------------------
2750; fat32_close
2751;
2752; Close current file
2753;
2754; * c=0: failure; sets errno
2755;-----------------------------------------------------------------------------
2756fat32_close:
2757	stz fat32_errno
2758
2759	lda cur_context + context::flags
2760	bne :+
2761	jmp @done
2762:
2763	; Write current sector if dirty
2764	jsr sync_sector_buffer
2765	bcs :+
2766	jmp error_clear_context
2767:
2768	; Update directory entry with new size and mdate if needed
2769	lda cur_context + context::flags
2770	bit #FLAG_DIRENT
2771	bne :+
2772	jmp @done
2773:	and #(FLAG_DIRENT ^ $FF)	; Clear bit
2774	sta cur_context + context::flags
2775
2776	; Load sector of directory entry
2777	set32 cur_context + context::lba, cur_context + context::dirent_lba
2778	jsr load_sector_buffer
2779	bcs :+
2780	jmp error_clear_context
2781:
2782	; Write size to directory entry
2783	set16 fat32_bufptr, cur_context + context::dirent_bufptr
2784	ldy #28
2785	lda cur_context + context::file_size + 0
2786	sta (fat32_bufptr), y
2787	iny
2788	lda cur_context + context::file_size + 1
2789	sta (fat32_bufptr), y
2790	iny
2791	lda cur_context + context::file_size + 2
2792	sta (fat32_bufptr), y
2793	iny
2794	lda cur_context + context::file_size + 3
2795	sta (fat32_bufptr), y
2796
2797	; Encode mtime timestamp
2798@ts1:	lda fat32_time_year
2799	inc
2800	bne @ts3
2801	; no time set up
2802	lda #0
2803	ldy #$16
2804	sta (fat32_bufptr), y
2805	iny
2806	sta (fat32_bufptr), y
2807	iny
2808	sta (fat32_bufptr), y
2809	iny
2810	sta (fat32_bufptr), y
2811	bra @ts2
2812
2813@ts3:	ldy #$16
2814	lda fat32_time_minutes
2815	tax
2816	asl
2817	asl
2818	asl
2819	asl
2820	asl
2821	sta (fat32_bufptr), y
2822	lda fat32_time_seconds
2823	lsr
2824	ora (fat32_bufptr), y
2825	sta (fat32_bufptr), y
2826	iny
2827	txa
2828	lsr
2829	lsr
2830	lsr
2831	sta (fat32_bufptr), y
2832	lda fat32_time_hours
2833	asl
2834	asl
2835	asl
2836	ora (fat32_bufptr), y
2837	sta (fat32_bufptr), y
2838	iny
2839	lda fat32_time_month
2840	tax
2841	asl
2842	asl
2843	asl
2844	asl
2845	asl
2846	ora fat32_time_day
2847	sta (fat32_bufptr), y
2848	iny
2849	txa
2850	lsr
2851	lsr
2852	lsr
2853	sta (fat32_bufptr), y
2854	lda fat32_time_year
2855	asl
2856	ora (fat32_bufptr), y
2857	sta (fat32_bufptr), y
2858@ts2:
2859
2860	; Fill creation date if empty
2861	ldy #$0e
2862	lda (fat32_bufptr), y
2863	iny
2864	ora (fat32_bufptr), y
2865	iny
2866	ora (fat32_bufptr), y
2867	iny
2868	ora (fat32_bufptr), y
2869	bne @ts4
2870	ldy #$16
2871	lda (fat32_bufptr), y
2872	ldy #$0e
2873	sta (fat32_bufptr), y
2874	ldy #$17
2875	lda (fat32_bufptr), y
2876	ldy #$0f
2877	sta (fat32_bufptr), y
2878	ldy #$18
2879	lda (fat32_bufptr), y
2880	ldy #$10
2881	sta (fat32_bufptr), y
2882	ldy #$19
2883	lda (fat32_bufptr), y
2884	ldy #$11
2885	sta (fat32_bufptr), y
2886@ts4:
2887
2888	; Write directory sector
2889	jsr save_sector_buffer
2890	bcc error_clear_context
2891@done:
2892	clear_bytes cur_context, .sizeof(context)
2893
2894	sec
2895	rts
2896
2897;-----------------------------------------------------------------------------
2898; error_clear_context
2899;
2900; Call this instead of fat32_close if there has been an error to avoid cached
2901; writes and possible further inconsistencies.
2902;-----------------------------------------------------------------------------
2903error_clear_context:
2904	clear_bytes cur_context, .sizeof(context)
2905	clc
2906	rts
2907
2908;-----------------------------------------------------------------------------
2909; fat32_read_byte
2910;
2911; Out:  a      byte
2912;       x      =$ff: EOF after this byte
2913;       c      =0: success
2914;              =1: failure (includes reading past EOF)
2915;       errno  =0: no error, or reading past EOF
2916;              =ERRNO_READ: read error
2917;-----------------------------------------------------------------------------
2918fat32_read_byte:
2919	stz fat32_errno
2920
2921	; Bytes remaining?
2922	bit cur_context + context::eof
2923	bmi @error
2924
2925	; At end of buffer?
2926	cmp16_val_ne fat32_bufptr, sector_buffer_end, @2
2927	lda #0
2928	jsr next_sector
2929	bcc @error
2930@2:
2931	; Increment offset within file
2932	inc32 cur_context + context::file_offset
2933
2934	ldx #0   ; no EOF
2935	cmp32_ne cur_context + context::file_offset, cur_context + context::file_size, @3
2936	ldx #$ff ; EOF
2937	stx cur_context + context::eof
2938@3:
2939	; Get byte from buffer
2940	lda (fat32_bufptr)
2941	inc16 fat32_bufptr
2942
2943	sec	; Indicate success
2944	rts
2945
2946@error:	clc
2947	rts
2948
2949;-----------------------------------------------------------------------------
2950; fat32_read
2951;
2952; fat32_ptr          : pointer to store read data
2953; fat32_size (16-bit): size of data to read
2954;
2955; On return fat32_size reflects the number of bytes actually read
2956;
2957; * c=0: failure; sets errno
2958;-----------------------------------------------------------------------------
2959fat32_read:
2960	stz fat32_errno
2961
2962	set16 fat32_ptr2, fat32_size
2963
2964@again:	; Calculate number of bytes remaining in file
2965	sub32 tmp_buf, cur_context + context::file_size, cur_context + context::file_offset
2966	lda tmp_buf + 0
2967	ora tmp_buf + 1
2968	ora tmp_buf + 2
2969	ora tmp_buf + 3
2970	bne @1
2971	clc		; End of file
2972	jmp @done
2973@1:
2974	; Calculate number of bytes remaining in buffer
2975	sec
2976	lda #<sector_buffer_end
2977	sbc fat32_bufptr + 0
2978	sta bytecnt + 0
2979	lda #>sector_buffer_end
2980	sbc fat32_bufptr + 1
2981	sta bytecnt + 1
2982	ora bytecnt + 0	; Check if 0
2983	bne @nonzero
2984
2985	; At end of buffer, read next sector
2986	lda #0
2987	jsr next_sector
2988	bcs @2
2989	; No sectors left (this shouldn't happen with a correct file size)
2990	lda #ERRNO_FS_INCONSISTENT
2991	jsr set_errno
2992	sec
2993	jmp @done
2994@2:	lda #2
2995	sta bytecnt + 1
2996
2997@nonzero:
2998	; if (fat32_size - bytecnt < 0) bytecnt = fat32_size
2999	sec
3000	lda fat32_size + 0
3001	sbc bytecnt + 0
3002	lda fat32_size + 1
3003	sbc bytecnt + 1
3004	bcs @3
3005	set16 bytecnt, fat32_size
3006@3:
3007	; if (bytecnt > 256) bytecnt = 256
3008	lda bytecnt + 1
3009	beq @4		; <256?
3010	stz bytecnt + 0	; 256 bytes
3011	lda #1
3012	sta bytecnt + 1
3013@4:
3014	; if (tmp_buf - bytecnt < 0) bytecnt = tmp_buf
3015	sec
3016	lda tmp_buf + 0
3017	sbc bytecnt + 0
3018	lda tmp_buf + 1
3019	sbc bytecnt + 1
3020	lda tmp_buf + 2
3021	sbc #0
3022	lda tmp_buf + 3
3023	sbc #0
3024	bpl @5
3025	set16 bytecnt, tmp_buf
3026@5:
3027	; Copy bytecnt bytes from buffer
3028	ldy bytecnt
3029	dey
3030	beq @6b
3031@6:	lda (fat32_bufptr), y
3032	sta (fat32_ptr), y
3033	dey
3034	bne @6
3035@6b:	lda (fat32_bufptr), y
3036	sta (fat32_ptr), y
3037
3038	; fat32_ptr += bytecnt, fat32_bufptr += bytecnt, fat32_size -= bytecnt, file_offset += bytecnt
3039	add16 fat32_ptr, fat32_ptr, bytecnt
3040	add16 fat32_bufptr, fat32_bufptr, bytecnt
3041	sub16 fat32_size, fat32_size, bytecnt
3042	add32_16 cur_context + context::file_offset, cur_context + context::file_offset, bytecnt
3043
3044	; Check if done
3045	lda fat32_size + 0
3046	ora fat32_size + 1
3047	beq @7
3048	jmp @again		; Not done yet
3049@7:
3050	sec	; Indicate success
3051
3052@done:	; Calculate number of bytes read
3053	php
3054	sub16 fat32_size, fat32_ptr2, fat32_size
3055	plp
3056
3057	rts
3058
3059;-----------------------------------------------------------------------------
3060; allocate_first_cluster
3061;
3062; * c=0: failure; sets errno
3063;-----------------------------------------------------------------------------
3064allocate_first_cluster:
3065	jsr allocate_cluster
3066	bcs @1
3067@error:	rts
3068@1:
3069	; Load sector of directory entry
3070	set32 cur_context + context::lba, cur_context + context::dirent_lba
3071	jsr load_sector_buffer
3072	bcc @error
3073	set16 fat32_bufptr, cur_context + context::dirent_bufptr
3074
3075	; Write cluster number to directory entry
3076	ldy #26
3077	lda cur_volume + fs::free_cluster + 0
3078	sta (fat32_bufptr), y
3079	iny
3080	lda cur_volume + fs::free_cluster + 1
3081	sta (fat32_bufptr), y
3082	ldy #20
3083	lda cur_volume + fs::free_cluster + 2
3084	sta (fat32_bufptr), y
3085	iny
3086	lda cur_volume + fs::free_cluster + 3
3087	sta (fat32_bufptr), y
3088
3089	; Write directory sector
3090	jsr save_sector_buffer
3091	bcc @error
3092
3093	; Set allocated cluster as current
3094	set32 cur_context + context::cluster, cur_volume + fs::free_cluster
3095	; Set allocated cluster as start cluster
3096	set32 cur_context + context::start_cluster, cur_volume + fs::free_cluster
3097	sec
3098	rts
3099
3100;-----------------------------------------------------------------------------
3101; write__end_of_buffer
3102;
3103; * c=0: failure; sets errno
3104;-----------------------------------------------------------------------------
3105write__end_of_buffer:
3106	; Is this the first cluster?
3107	lda cur_context + context::file_size + 0
3108	ora cur_context + context::file_size + 1
3109	ora cur_context + context::file_size + 2
3110	ora cur_context + context::file_size + 3
3111	beq @first_cluster
3112
3113	; Go to next sector (allocate cluster if needed)
3114	lda #1
3115	jmp next_sector
3116
3117@first_cluster:
3118	jsr allocate_first_cluster
3119	bcs @1
3120	rts
3121@1:
3122	; Load in cluster
3123	jmp open_cluster
3124
3125;-----------------------------------------------------------------------------
3126; fat32_write_byte
3127;
3128; * c=0: failure; sets errno
3129;-----------------------------------------------------------------------------
3130fat32_write_byte:
3131	stz fat32_errno
3132
3133	; At end of buffer? (preserve A)
3134	ldx fat32_bufptr + 0
3135	cpx #<sector_buffer_end
3136	bne @write_byte
3137	ldx fat32_bufptr + 1
3138	cpx #>sector_buffer_end
3139	bne @write_byte
3140
3141	; Handle end of buffer condition
3142	pha
3143	jsr write__end_of_buffer
3144	pla
3145	bcs @write_byte
3146	rts
3147
3148@write_byte:
3149	; Write byte
3150	sta (fat32_bufptr)
3151	inc16 fat32_bufptr
3152
3153	; Set sector as dirty, dirent needs update
3154	lda cur_context + context::flags
3155	ora #(FLAG_DIRTY | FLAG_DIRENT)
3156	sta cur_context + context::flags
3157
3158	inc32 cur_context + context::file_offset
3159
3160	; if (file_size - file_offset < 0) file_size = file_offset
3161	sec
3162	lda cur_context + context::file_size + 0
3163	sbc cur_context + context::file_offset + 0
3164	lda cur_context + context::file_size + 1
3165	sbc cur_context + context::file_offset + 1
3166	lda cur_context + context::file_size + 2
3167	sbc cur_context + context::file_offset + 2
3168	lda cur_context + context::file_size + 3
3169	sbc cur_context + context::file_offset + 3
3170	bpl @1
3171	set32 cur_context + context::file_size, cur_context + context::file_offset
3172@1:
3173	sec	; Indicate success
3174	rts
3175
3176;-----------------------------------------------------------------------------
3177; fat32_write
3178;
3179; fat32_ptr          : pointer to data to write
3180; fat32_size (16-bit): size of data to write
3181;
3182; * c=0: failure; sets errno
3183;-----------------------------------------------------------------------------
3184fat32_write:
3185	stz fat32_errno
3186
3187	; Calculate number of bytes remaining in buffer
3188	sec
3189	lda #<sector_buffer_end
3190	sbc fat32_bufptr + 0
3191	sta bytecnt + 0
3192	lda #>sector_buffer_end
3193	sbc fat32_bufptr + 1
3194	sta bytecnt + 1
3195	ora bytecnt + 0	; Check if 0
3196	bne @nonzero
3197
3198	; Handle end of buffer condition
3199	jsr write__end_of_buffer
3200	bcs @1
3201	rts
3202@1:	lda #2
3203	sta bytecnt + 1
3204
3205@nonzero:
3206	; if (fat32_size - bytecnt < 0) bytecnt = fat32_size
3207	sec
3208	lda fat32_size + 0
3209	sbc bytecnt + 0
3210	lda fat32_size + 1
3211	sbc bytecnt + 1
3212	bcs @2
3213	set16 bytecnt, fat32_size
3214@2:
3215	; if (bytecnt > 256) bytecnt = 256
3216	lda bytecnt + 1
3217	beq @3		; <256?
3218	stz bytecnt + 0	; 256 bytes
3219	lda #1
3220	sta bytecnt + 1
3221@3:
3222	; Copy bytecnt bytes into buffer
3223	ldy bytecnt
3224	dey
3225	beq @4b
3226@4:	lda (fat32_ptr), y
3227	sta (fat32_bufptr), y
3228	dey
3229	bne @4
3230@4b:	lda (fat32_ptr), y
3231	sta (fat32_bufptr), y
3232
3233	; fat32_ptr += bytecnt, fat32_bufptr += bytecnt, fat32_size -= bytecnt, file_offset += bytecnt
3234	add16 fat32_ptr, fat32_ptr, bytecnt
3235	add16 fat32_bufptr, fat32_bufptr, bytecnt
3236	sub16 fat32_size, fat32_size, bytecnt
3237	add32_16 cur_context + context::file_offset, cur_context + context::file_offset, bytecnt
3238
3239	; if (file_size - file_offset < 0) file_size = file_offset
3240	sec
3241	lda cur_context + context::file_size + 0
3242	sbc cur_context + context::file_offset + 0
3243	lda cur_context + context::file_size + 1
3244	sbc cur_context + context::file_offset + 1
3245	lda cur_context + context::file_size + 2
3246	sbc cur_context + context::file_offset + 2
3247	lda cur_context + context::file_size + 3
3248	sbc cur_context + context::file_offset + 3
3249	bpl @5
3250	set32 cur_context + context::file_size, cur_context + context::file_offset
3251@5:
3252	; Set sector as dirty, dirent needs update
3253	lda cur_context + context::flags
3254	ora #(FLAG_DIRTY | FLAG_DIRENT)
3255	sta cur_context + context::flags
3256
3257	; Check if done
3258	lda fat32_size + 0
3259	ora fat32_size + 1
3260	beq @6
3261	jmp fat32_write		; Not done yet
3262@6:
3263	sec	; Indicate success
3264	rts
3265
3266;-----------------------------------------------------------------------------
3267; fat32_get_free_space
3268;-----------------------------------------------------------------------------
3269fat32_get_free_space:
3270	set32 fat32_size, cur_volume + fs::free_clusters
3271
3272	lda cur_volume + fs::cluster_shift
3273	cmp #0	; 512B cluster
3274	beq @512b
3275
3276	sec
3277	sbc #1
3278	tax
3279	cpx #0
3280	beq @done
3281@1:	shl32 fat32_size
3282	dex
3283	bne @1
3284
3285@done:	sec
3286	rts
3287
3288@512b:	shr32 fat32_size
3289	bra @done
3290
3291;-----------------------------------------------------------------------------
3292; fat32_next_sector
3293;
3294; * c=0: failure; sets errno
3295;-----------------------------------------------------------------------------
3296fat32_next_sector:
3297	stz fat32_errno
3298
3299	lda #0
3300	jsr next_sector
3301	bcs @1
3302	rts
3303@1:
3304	add32 cur_context + context::file_offset, cur_context + context::file_offset, 512
3305	sec
3306	rts
3307
3308;-----------------------------------------------------------------------------
3309; fat32_get_offset
3310;-----------------------------------------------------------------------------
3311fat32_get_offset:
3312	set32 fat32_size, cur_context + context::file_offset
3313	sec
3314	rts
3315
3316;-----------------------------------------------------------------------------
3317; fat32_get_vollabel
3318;
3319; Get the "volume label", i.e. the name of the filesystem.
3320;
3321; Out: fat32_dirent::name  name
3322;
3323; * If a directory volume label exists, it will be returned.
3324; * Otherwise, the boot sector volume label will be returned.
3325;
3326; * c=0: failure; sets errno
3327;-----------------------------------------------------------------------------
3328fat32_get_vollabel:
3329	stz fat32_errno
3330
3331	; Check if context is free
3332	lda cur_context + context::flags
3333	bne @error
3334
3335	; Get directory volume label
3336	jsr open_rootdir
3337	bcc @error
3338	clc
3339	jsr read_dirent
3340	bcc @no_dir_vollabel
3341
3342	sec
3343	rts
3344
3345	; Fall back to boot sector volume label
3346@no_dir_vollabel:
3347	; Read first sector of partition
3348	set32 cur_context + context::lba, cur_volume + fs::lba_partition
3349	jsr load_sector_buffer
3350	bcc @error
3351
3352	set16_val fat32_bufptr, (sector_buffer + $47)
3353	jsr decode_volume_label
3354	sec
3355	rts
3356
3357@error:	clc
3358	rts
3359
3360;-----------------------------------------------------------------------------
3361; fat32_set_vollabel
3362;
3363; Set the "volume label", i.e. the name of the filesystem.
3364;
3365; In:  fat32_ptr  name
3366;
3367; * The string can be up to 11 characters; extra characters will be ignored.
3368; * Allowed characters:
3369;   - Context: Windows and Mac encode it as CP437 on disk, Mac does not allow
3370;     non-ASCII characters, Windows does, but converts them to uppercase.
3371;   - This function allows all CP437-encodable characters, without a case
3372;     change.
3373;   - Non-encodable characters will cause an error.
3374; * The volume label will always be written into the boot sector. If a
3375;   directory volume label exists, it will be removed.
3376;
3377; * c=0: failure; sets errno
3378;-----------------------------------------------------------------------------
3379fat32_set_vollabel:
3380	stz fat32_errno
3381
3382	; Check if context is free
3383	lda cur_context + context::flags
3384	bne @error
3385
3386	; Get directory volume label
3387	jsr open_rootdir
3388	bcc @error
3389	clc
3390	jsr read_dirent
3391	bcc @no_dir_vollabel
3392
3393	sec ; ignore read-only bit
3394	jsr delete_entry
3395	bcc @error
3396
3397@no_dir_vollabel:
3398	; Read first sector of partition
3399	set32 cur_context + context::lba, cur_volume + fs::lba_partition
3400	jsr load_sector_buffer
3401	bcc @error
3402
3403	ldy #0
3404@1:	lda (fat32_ptr), y
3405	beq @2
3406	jsr filename_char_internal_to_cp437
3407	beq @fn_error
3408	sta sector_buffer + $47, y
3409	iny
3410	cpy #11
3411	bne @1
3412
3413	; pad with spaces
3414@2:	cpy #11
3415	beq @3
3416	lda #$20
3417	sta sector_buffer + $47, y
3418	iny
3419	bra @2
3420
3421@3:	jmp save_sector_buffer
3422
3423@fn_error:
3424	lda #ERRNO_ILLEGAL_FILENAME
3425	jmp set_errno
3426
3427@error:	clc
3428	rts
3429
3430;-----------------------------------------------------------------------------
3431; load_mbr_sector
3432;
3433; Read partition table (sector 0)
3434;
3435; * c=0: failure; sets errno
3436;-----------------------------------------------------------------------------
3437load_mbr_sector:
3438	set32_val cur_context + context::lba, 0
3439	jmp load_sector_buffer
3440
3441;-----------------------------------------------------------------------------
3442; fat32_get_ptable_entry
3443;
3444; Returns a given partition table entry
3445;
3446; In:  a  index
3447;
3448; * c=0: failure; sets errno
3449;-----------------------------------------------------------------------------
3450fat32_get_ptable_entry:
3451	stz fat32_errno
3452
3453	cmp #$4
3454	bcs @error ; end of list
3455
3456	asl
3457	asl
3458	asl
3459	asl
3460	pha
3461
3462	jsr load_mbr_sector
3463	plx
3464	bcs @1
3465@error:	clc
3466	rts
3467
3468@1:	; start LBA
3469	phx
3470	ldy #0
3471@2:	lda sector_buffer + $1BE + 8, x
3472	sta fat32_dirent + dirent::start, y
3473	inx
3474	iny
3475	cpy #4
3476	bne @2
3477	plx
3478
3479	; size
3480	phx
3481	ldy #0
3482@3:	lda sector_buffer + $1BE + 12, x
3483	sta fat32_dirent + dirent::size, y
3484	inx
3485	iny
3486	cpy #4
3487	bne @3
3488	plx
3489
3490	; type
3491	lda sector_buffer + $1BE + 4, x
3492	sta fat32_dirent + dirent::attributes
3493
3494	stz fat32_dirent + dirent::name
3495
3496	cmp #$0b
3497	beq @read_name
3498	cmp #$0c
3499	bne @done
3500
3501@read_name:
3502	; Read first sector of partition
3503	set32 cur_context + context::lba, fat32_dirent + dirent::start
3504	jsr load_sector_buffer
3505	bcc @error
3506
3507	set16_val fat32_bufptr, (sector_buffer + $47)
3508	jsr decode_volume_label
3509
3510@done:
3511	sec
3512	rts
3513
3514;-----------------------------------------------------------------------------
3515; fat32_seek
3516;
3517; In:  fat32_size: offset
3518;
3519; * c=0: failure; sets errno
3520;-----------------------------------------------------------------------------
3521fat32_seek:
3522	stz fat32_errno
3523
3524	; Empty file: seek is a no-op
3525	lda cur_context + context::file_size + 0
3526	ora cur_context + context::file_size + 1
3527	ora cur_context + context::file_size + 2
3528	ora cur_context + context::file_size + 3
3529	bne :+
3530	sec
3531	rts
3532:
3533
3534	; Set file_offset = MIN(desired_offset, file_size)
3535	lda cur_context + context::file_size + 0
3536	sec
3537	sbc fat32_size + 0
3538	lda cur_context + context::file_size + 1
3539	sbc fat32_size + 1
3540	lda cur_context + context::file_size + 2
3541	sbc fat32_size + 2
3542	lda cur_context + context::file_size + 3
3543	sbc fat32_size + 3
3544	bcs @0a
3545	set32 fat32_size, cur_context + context::file_size
3546@0a:	set32 cur_context + context::file_offset, fat32_size
3547
3548	; If file_offset == file_size, set EOF flag
3549	ldx #0 ; no EOF
3550	cmp32_ne fat32_size, cur_context + context::file_size, @0c
3551	ldx #$ff ; EOF
3552@0c:	stx cur_context + context::eof
3553
3554	; Special case: bufptr == 0 && eof?
3555	; -> Make bufptr point to $0200 of last sector
3556	;    instead of $0000 of next (non-existent) sector
3557	lda fat32_size + 0
3558	bne @a
3559	lda fat32_size + 1
3560	and #1
3561	bne @a
3562	bit cur_context + context::eof
3563	bpl @a
3564
3565	; Make bufptr point to end of sector_buffer
3566	lda #<(sector_buffer+$200)
3567	sta cur_context + context::bufptr + 0
3568	lda #>(sector_buffer+$200)
3569	sta cur_context + context::bufptr + 1
3570	; Decrement sector
3571	lda fat32_size + 1
3572	sec
3573	sbc #2 ; $0200
3574	sta fat32_size + 1
3575	lda fat32_size + 2
3576	sbc #0
3577	sta fat32_size + 2
3578	lda fat32_size + 3
3579	sbc #0
3580	sta fat32_size + 3
3581	bra @b
3582
3583@a:	; Extract offset within sector
3584	lda fat32_size + 0
3585	clc
3586	adc #<sector_buffer
3587	sta cur_context + context::bufptr + 0 ; temp location
3588	lda fat32_size + 1
3589	and #1
3590	adc #>sector_buffer
3591	sta cur_context + context::bufptr + 1
3592
3593@b:	; Extract sector number
3594	lda fat32_size + 1
3595	sta fat32_size + 0
3596	lda fat32_size + 2
3597	sta fat32_size + 1
3598	lda fat32_size + 3
3599	sta fat32_size + 2
3600	stz fat32_size + 3
3601	lsr fat32_size + 2
3602	ror fat32_size + 1
3603	ror fat32_size + 0
3604
3605	; Extract sector within cluster
3606	lda cur_volume + fs::sectors_per_cluster
3607	dec
3608	and fat32_size + 0
3609	pha
3610
3611	; Calculate cluster index
3612	ldx cur_volume + fs::cluster_shift
3613	beq @2
3614@1:	lsr fat32_size + 2
3615	ror fat32_size + 1
3616	ror fat32_size + 0
3617	dex
3618	bne @1
3619
3620	; TODO: It would be a significant optimization to fast forward from
3621	;       the current position, it is lower than the target position.
3622
3623@2:	; Go to start cluster
3624	set32 cur_context + context::cluster, cur_context + context::start_cluster
3625
3626@2a:	; Fast forward clusters
3627	lda fat32_size + 0
3628	ora fat32_size + 1
3629	ora fat32_size + 2
3630	ora fat32_size + 3
3631	beq @3
3632
3633	jsr next_cluster
3634	bcc @error1
3635	dec32 fat32_size
3636	bra @2a
3637
3638	;
3639@3:
3640	jsr calc_cluster_lba
3641
3642	pla
3643	sta cur_context + context::cluster_sector
3644
3645	clc
3646	adc cur_context + context::lba
3647	sta cur_context + context::lba
3648	bcc @4
3649	inc cur_context + context::lba + 1
3650	bne @4
3651	inc cur_context + context::lba + 2
3652	bne @4
3653	inc cur_context + context::lba + 3
3654@4:
3655	jsr load_sector_buffer
3656	bcc @error
3657
3658	; Set bufptr
3659	lda cur_context + context::bufptr + 0
3660	sta fat32_bufptr
3661	lda cur_context + context::bufptr + 1
3662	sta fat32_bufptr + 1
3663
3664	sec
3665	rts
3666
3667@error1:
3668	pla
3669@error:	clc
3670	rts
3671