OXYGENHUB
◆ rrdc-perf enhanced-x16-emulator / src / sdcard_fat.c
26.1 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
9120e874 Andy McCall 1
// Commander X16 Emulator — SD card FAT index implementation
2
// Copyright (c) 2024 X16Emu ADD contributors
3
// All rights reserved. License: 2-clause BSD
4
//
5
// Parses FAT16/FAT32 structures inside an SD card image (512-byte sectors).
6
// Opens its own independent file handle — never touches sdcard.c's handle.
7
8
#include "sdcard_fat.h"
9
#include "compat.h"
10
#include "files.h"
11
12
#include <ctype.h>
13
#include <stdint.h>
14
#include <stdbool.h>
15
#include <stdlib.h>
16
#include <string.h>
17
#include <stdio.h>
18
19
// strdup is POSIX, not ISO C: under a strict -std= it is not declared, and gcc
20
// with -Werror then treats the implicit declaration as an error. Rather than
21
// widen the feature-test macros for the whole translation unit, duplicate the
22
// three lines it would have provided.
23
static char *
24
fat_strdup(const char *s)
25
{
26
	const size_t n = strlen(s) + 1;
27
	char        *p = (char *)malloc(n);
28
	if (p != NULL) {
29
		memcpy(p, s, n);
30
	}
31
	return p;
32
}
33
34
// Join a parent directory and a leaf into `out`, reporting whether it fit.
35
// `parent` is "/" for the root, otherwise has no trailing slash.
36
static bool
37
join_path(char *out, size_t outsz, const char *parent, const char *name)
38
{
39
	const size_t plen = (parent[1] == '\0') ? 0 : strlen(parent);
40
	const size_t nlen = strlen(name);
41
	if (plen + 1 + nlen + 1 > outsz) {
42
		return false;
43
	}
44
	memcpy(out, parent, plen);
45
	out[plen] = '/';
46
	memcpy(out + plen + 1, name, nlen);
47
	out[plen + 1 + nlen] = '\0';
48
	return true;
49
}
50
51
// ── Caps ─────────────────────────────────────────────────────────────────────
52
#define SDCARD_FAT_MAX_FILES   4096
53
#define SDCARD_FAT_MAX_EXTENTS 16384
54
#define SDCARD_FAT_MAX_DEPTH   16
55
#define SDCARD_FAT_MAX_CLUSTERS_PER_FILE 131072  // 64 MB / 512 bytes
56
57
// ── Internal types ────────────────────────────────────────────────────────────
58
59
typedef enum {
60
	FAT_TYPE_NONE = 0,
61
	FAT_TYPE_12,
62
	FAT_TYPE_16,
63
	FAT_TYPE_32,
64
} fat_type_t;
65
66
// An LBA extent belonging to a file/dir (all inclusive).
67
typedef struct {
68
	uint32_t first_lba;
69
	uint32_t last_lba;
70
	int      file_index;   // index into g_files
71
	uint64_t file_offset;  // byte offset in file at first_lba
72
} fat_extent_t;
73
74
// Internal mutable entry — sdcard_fat_file_t is the public const view.
75
typedef struct {
76
	char    *path;         // heap-allocated "/FOO/BAR.PRG"
77
	uint32_t size;
78
	uint32_t first_cluster;
79
	bool     is_dir;
80
	uint64_t bytes_read;
81
	uint64_t bytes_written;
82
} fat_file_entry_t;
83
84
// ── Module state ──────────────────────────────────────────────────────────────
85
86
static struct x16file *g_img        = NULL;  // borrowed from caller; never closed here
87
static bool            g_ready      = false;
88
static bool            g_stale      = false;
89
static bool            g_truncated  = false;
90
static fat_type_t      g_fat_type   = FAT_TYPE_NONE;
91
static uint32_t        g_bps        = 512;   // bytes per sector (always 512)
92
static uint32_t        g_spc        = 0;     // sectors per cluster
93
static uint32_t        g_fat_lba    = 0;     // first LBA of FAT region
94
static uint32_t        g_fat_count  = 0;     // number of FATs
95
static uint32_t        g_fat_size   = 0;     // sectors per FAT
96
static uint32_t        g_rootdir_lba= 0;     // FAT16 fixed root dir LBA
97
static uint32_t        g_rootdir_sectors = 0;// FAT16 root dir size in sectors
98
static uint32_t        g_first_data_lba = 0; // LBA of cluster 2
99
static uint32_t        g_root_cluster = 0;   // FAT32 root cluster
100
static uint32_t        g_total_sectors = 0;
101
static uint32_t        g_partition_lba = 0;  // absolute LBA of VBR
102
103
static fat_file_entry_t  g_files[SDCARD_FAT_MAX_FILES];
104
static int               g_file_count = 0;
105
106
static fat_extent_t      g_extents[SDCARD_FAT_MAX_EXTENTS];
107
static int               g_extent_count = 0;
108
109
// ── Helpers ───────────────────────────────────────────────────────────────────
110
111
static uint16_t le16(const uint8_t *p)
112
{
113
	return (uint16_t)(p[0] | ((uint16_t)p[1] << 8));
114
}
115
116
static uint32_t le32(const uint8_t *p)
117
{
118
	return (uint32_t)(p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24));
119
}
120
121
// Read exactly one 512-byte sector at absolute LBA into buf.
122
// Saves and restores the stream position so the caller's (card's) file offset
123
// is unchanged after this call.  Returns true on success.
124
static bool read_sector(uint32_t abs_lba, uint8_t buf[512])
125
{
126
	if (g_img == NULL) {
127
		return false;
128
	}
129
	int64_t saved  = x16tell(g_img);
130
	int64_t offset = (int64_t)abs_lba * 512;
131
	// x16seek(), like the SDL_RWseek() it wraps, returns the resulting stream
132
	// position -- not a status. Only a negative result is an error; testing
133
	// against zero would reject every seek to a non-zero offset, which is to
134
	// say every sector but the first.
135
	if (x16seek(g_img, offset, XSEEK_SET) < 0) {
136
		if (saved >= 0) {
137
			x16seek(g_img, saved, XSEEK_SET);
138
		}
139
		return false;
140
	}
141
	bool ok = (x16read(g_img, buf, 1, 512) == 512);
142
	// Restore position regardless of read outcome.
143
	if (saved >= 0) {
144
		x16seek(g_img, saved, XSEEK_SET);
145
	}
146
	return ok;
147
}
148
149
// ── Path string helpers ───────────────────────────────────────────────────────
150
151
// Emit a UCS-2 code unit as UTF-8 into buf (must have space for 4 bytes + NUL).
152
// Returns number of bytes written.
153
static int ucs2_to_utf8(uint16_t cp, char *out)
154
{
155
	if (cp == 0xFFFF || cp == 0x0000) {
156
		return 0; // end-of-name sentinel
157
	}
158
	if (cp < 0x80) {
159
		out[0] = (char)(uint8_t)cp;
160
		return 1;
161
	}
162
	if (cp < 0x800) {
163
		out[0] = (char)(uint8_t)(0xC0 | (cp >> 6));
164
		out[1] = (char)(uint8_t)(0x80 | (cp & 0x3F));
165
		return 2;
166
	}
167
	out[0] = (char)(uint8_t)(0xE0 | (cp >> 12));
168
	out[1] = (char)(uint8_t)(0x80 | ((cp >> 6) & 0x3F));
169
	out[2] = (char)(uint8_t)(0x80 | (cp & 0x3F));
170
	return 3;
171
}
172
173
// Build a 13-character LFN fragment from a directory entry into out_utf8.
174
// out_utf8 must hold at least 40 bytes (13 chars × 3 UTF-8 bytes + NUL).
175
// Returns the number of UTF-8 bytes written (stops at first 0x0000/0xFFFF).
176
static int lfn_chars(const uint8_t *dirent, char *out_utf8)
177
{
178
	static const int offsets[13] = {1,3,5,7,9, 14,16,18,20,22,24, 28,30};
179
	int n = 0;
180
	for (int i = 0; i < 13; i++) {
181
		uint16_t cp = (uint16_t)(dirent[offsets[i]] | ((uint16_t)dirent[offsets[i]+1] << 8));
182
		if (cp == 0x0000 || cp == 0xFFFF) {
183
			break;
184
		}
185
		int w = ucs2_to_utf8(cp, out_utf8 + n);
186
		n += w;
187
	}
188
	return n;
189
}
190
191
// Decode an 8.3 short name into name_buf (null-terminated, no trailing spaces,
192
// extension separated by '.'). name_buf must be >= 13 bytes.
193
static void decode_83(const uint8_t *dirent, char *name_buf)
194
{
195
	char base[9], ext[4];
196
	int  bi = 0, ei = 0;
197
198
	for (int i = 0; i < 8; i++) {
199
		uint8_t c = dirent[i];
200
		if (c == ' ') {
201
			break;
202
		}
203
		base[bi++] = (char)c;
204
	}
205
	base[bi] = '\0';
206
207
	for (int i = 0; i < 3; i++) {
208
		uint8_t c = dirent[8 + i];
209
		if (c == ' ') {
210
			break;
211
		}
212
		ext[ei++] = (char)c;
213
	}
214
	ext[ei] = '\0';
215
216
	if (ei > 0) {
217
		snprintf(name_buf, 13, "%s.%s", base, ext);
218
	} else {
219
		snprintf(name_buf, 13, "%s", base);
220
	}
221
}
222
223
// Append an extent, staying within the cap.  Returns false when cap hit.
224
static bool add_extent(uint32_t first_lba, uint32_t last_lba,
225
                       int file_index, uint64_t file_offset)
226
{
227
	if (g_extent_count >= SDCARD_FAT_MAX_EXTENTS) {
228
		g_truncated = true;
229
		return false;
230
	}
231
	fat_extent_t *e = &g_extents[g_extent_count++];
232
	e->first_lba   = first_lba;
233
	e->last_lba    = last_lba;
234
	e->file_index  = file_index;
235
	e->file_offset = file_offset;
236
	return true;
237
}
238
239
// ── FAT chain walking ─────────────────────────────────────────────────────────
240
241
// Read FAT entry for cluster N. Returns 0xFFFFFFFF on I/O error.
242
static uint32_t fat_next(uint32_t cluster)
243
{
244
	uint8_t buf[512];
245
	uint32_t byte_offset;
246
	uint32_t fat_lba;
247
248
	if (g_fat_type == FAT_TYPE_32) {
249
		byte_offset = cluster * 4;
250
		fat_lba = g_fat_lba + byte_offset / 512;
251
		uint32_t idx = byte_offset % 512;
252
		if (!read_sector(fat_lba, buf)) {
253
			return 0xFFFFFFFF;
254
		}
255
		return le32(buf + idx) & 0x0FFFFFFF;
256
	} else {
257
		// FAT16
258
		byte_offset = cluster * 2;
259
		fat_lba = g_fat_lba + byte_offset / 512;
260
		uint32_t idx = byte_offset % 512;
261
		if (!read_sector(fat_lba, buf)) {
262
			return 0xFFFFFFFF;
263
		}
264
		uint16_t val = le16(buf + idx);
265
		if (val >= 0xFFF8) {
266
			return 0x0FFFFFFF; // EOC in FAT32 encoding
267
		}
268
		return (uint32_t)val;
269
	}
270
}
271
272
static bool is_eoc(uint32_t entry)
273
{
274
	if (g_fat_type == FAT_TYPE_32) {
275
		return entry >= 0x0FFFFFF8;
276
	}
277
	return entry >= 0xFFF8 || entry == 0x0FFFFFFF;
278
}
279
280
static uint32_t cluster_to_lba(uint32_t cluster)
281
{
282
	return g_first_data_lba + (cluster - 2) * g_spc;
283
}
284
285
// Walk a cluster chain, adding extents to the index.
286
// file_index: index into g_files. Cap enforced internally.
287
static void walk_chain(uint32_t start_cluster, int file_index)
288
{
289
	if (start_cluster < 2) {
290
		return; // invalid
291
	}
292
293
	uint32_t cluster     = start_cluster;
294
	uint32_t count       = 0;
295
	uint64_t file_offset = 0;
296
297
	// For extent coalescing:
298
	uint32_t run_first_lba = 0;
299
	uint32_t run_last_lba  = 0;
300
	uint64_t run_file_off  = 0;
301
	bool     in_run        = false;
302
303
	while (!is_eoc(cluster) && cluster != 0) {
304
		if (count++ >= SDCARD_FAT_MAX_CLUSTERS_PER_FILE) {
305
			g_truncated = true;
306
			break;
307
		}
308
309
		uint32_t lba_start = cluster_to_lba(cluster);
310
		uint32_t lba_end   = lba_start + g_spc - 1;
311
312
		if (!in_run) {
313
			run_first_lba = lba_start;
314
			run_last_lba  = lba_end;
315
			run_file_off  = file_offset;
316
			in_run        = true;
317
		} else if (lba_start == run_last_lba + 1) {
318
			// Contiguous — extend current run.
319
			run_last_lba = lba_end;
320
		} else {
321
			// Gap — flush current run.
322
			if (!add_extent(run_first_lba, run_last_lba, file_index, run_file_off)) {
323
				return;
324
			}
325
			run_first_lba = lba_start;
326
			run_last_lba  = lba_end;
327
			run_file_off  = file_offset;
328
		}
329
330
		file_offset += (uint64_t)g_spc * g_bps;
331
		cluster = fat_next(cluster);
332
	}
333
334
	if (in_run) {
335
		add_extent(run_first_lba, run_last_lba, file_index, run_file_off);
336
	}
337
}
338
339
// ── Directory traversal ───────────────────────────────────────────────────────
340
341
// Forward declaration for recursion.
342
static void walk_dir(uint32_t start_cluster, bool is_fat32_root,
343
                     const char *parent_path, int depth);
344
345
// Add a new file/dir entry. Returns the index, or -1 on cap.
346
static int add_file(const char *path, uint32_t size,
347
                    uint32_t first_cluster, bool is_dir)
348
{
349
	if (g_file_count >= SDCARD_FAT_MAX_FILES) {
350
		g_truncated = true;
351
		return -1;
352
	}
353
	fat_file_entry_t *f = &g_files[g_file_count];
354
	f->path          = fat_strdup(path);
355
	f->size          = size;
356
	f->first_cluster = first_cluster;
357
	f->is_dir        = is_dir;
358
	f->bytes_read    = 0;
359
	f->bytes_written = 0;
360
	return g_file_count++;
361
}
362
363
// Process a single 32-byte directory entry that is NOT an LFN record.
364
// lfn_buf: accumulated LFN (may be empty), lfn_len: length.
365
static void process_dirent(const uint8_t *de, const char *parent_path,
366
                            const char *lfn_buf, int lfn_len, int depth)
367
{
368
	uint8_t attr = de[11];
369
370
	// Volume label — skip.
371
	if (attr & 0x08) {
372
		return;
373
	}
374
375
	// Build name.
376
	char name[512]; // large enough for a maximal LFN
377
	if (lfn_len > 0) {
378
		if (lfn_len >= (int)sizeof(name)) {
379
			lfn_len = (int)sizeof(name) - 1;
380
		}
381
		memcpy(name, lfn_buf, (size_t)lfn_len);
382
		name[lfn_len] = '\0';
383
	} else {
384
		char sn[13];
385
		decode_83(de, sn);
386
		if (sn[0] == '\0') {
387
			return;
388
		}
389
		snprintf(name, sizeof(name), "%s", sn);
390
	}
391
392
	// Skip "." and "..".
393
	if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
394
		return;
395
	}
396
397
	// Build full path.
398
	char full_path[PATH_MAX];
399
	if (!join_path(full_path, sizeof(full_path), parent_path, name)) {
400
		// A truncated path is a wrong path, and naming files correctly is the
401
		// entire purpose of this index, so an entry that will not fit is
402
		// dropped and flagged rather than stored under a name that is not its
403
		// own. Building it by hand also keeps the compiler from having to
404
		// prove an snprintf cannot overflow, which it cannot.
405
		g_truncated = true;
406
		return;
407
	}
408
409
	uint32_t first_cluster = ((uint32_t)le16(de + 20) << 16) | le16(de + 26);
410
	uint32_t size          = le32(de + 28);
411
	bool     is_dir        = (attr & 0x10) != 0;
412
413
	int idx = add_file(full_path, size, first_cluster, is_dir);
414
	if (idx < 0) {
415
		return; // cap hit
416
	}
417
418
	walk_chain(first_cluster, idx);
419
420
	if (is_dir && depth + 1 < SDCARD_FAT_MAX_DEPTH) {
421
		walk_dir(first_cluster, false, full_path, depth + 1);
422
	}
423
}
424
425
// Walk a directory given its start cluster (or FAT16 root, indicated by
426
// is_fat16_root flag encoded in is_fat32_root=false + start_cluster==0).
427
static void walk_dir(uint32_t start_cluster, bool is_fat32_root,
428
                     const char *parent_path, int depth)
429
{
430
	uint8_t  sector[512];
431
	uint8_t  lfn_buf[512 * 3]; // generous LFN buffer
432
	int      lfn_len  = 0;
433
	char     lfn_frags[20][40]; // up to 20 LFN fragments (13 chars * 3 bytes each)
434
	int      lfn_frag_count = 0;
435
	int      lfn_expected_seq = 0;
436
437
	// Whether we're in the FAT16 fixed root dir.
438
	bool is_fat16_root = (!is_fat32_root && start_cluster == 0 &&
439
	                      g_fat_type == FAT_TYPE_16);
440
441
	uint32_t cur_cluster  = start_cluster;
442
	uint32_t sector_in_cluster = 0;
443
	uint32_t sectors_done = 0;
444
	bool     done         = false;
445
	uint32_t cluster_chain_count = 0;
446
447
	while (!done) {
448
		uint32_t lba;
449
		if (is_fat16_root) {
450
			if (sectors_done >= g_rootdir_sectors) {
451
				break;
452
			}
453
			lba = g_rootdir_lba + sectors_done;
454
		} else {
455
			if (cur_cluster < 2 || is_eoc(cur_cluster)) {
456
				break;
457
			}
458
			lba = cluster_to_lba(cur_cluster) + sector_in_cluster;
459
		}
460
461
		if (!read_sector(lba, sector)) {
462
			break;
463
		}
464
465
		// Process 16 entries per sector.
466
		for (int e = 0; e < 16 && !done; e++) {
467
			const uint8_t *de = sector + e * 32;
468
			uint8_t first_byte = de[0];
469
			uint8_t attr       = de[11];
470
471
			if (first_byte == 0x00) {
472
				// End of directory.
473
				done = true;
474
				break;
475
			}
476
			if (first_byte == 0xE5) {
477
				// Deleted — also reset LFN accumulator.
478
				lfn_frag_count   = 0;
479
				lfn_expected_seq = 0;
480
				continue;
481
			}
482
483
			if (attr == 0x0F) {
484
				// LFN entry.
485
				uint8_t seq = de[0];
486
				bool is_last = (seq & 0x40) != 0;
487
				seq &= 0x1F;
488
				if (seq == 0 || seq > 20) {
489
					// Corrupt — reset.
490
					lfn_frag_count   = 0;
491
					lfn_expected_seq = 0;
492
					continue;
493
				}
494
				if (is_last) {
495
					lfn_frag_count   = 0;
496
					lfn_expected_seq = (int)seq;
497
				}
498
				if ((int)seq != lfn_expected_seq) {
499
					// Out of order — reset.
500
					lfn_frag_count   = 0;
501
					lfn_expected_seq = 0;
502
					continue;
503
				}
504
				// Store fragment (seq is 1-based, stored in reverse order).
505
				int slot = (int)seq - 1;
506
				if (slot < 20) {
507
					int n = lfn_chars(de, lfn_frags[slot]);
508
					lfn_frags[slot][n] = '\0';
509
					if (lfn_frag_count < slot + 1) {
510
						lfn_frag_count = slot + 1;
511
					}
512
				}
513
				lfn_expected_seq--;
514
				continue;
515
			}
516
517
			// Regular entry — assemble LFN if available.
518
			if (lfn_frag_count > 0 && lfn_expected_seq == 0) {
519
				// Reassemble from fragment 0 (lowest) up.
520
				lfn_len = 0;
521
				for (int fi = 0; fi < lfn_frag_count && lfn_len < (int)sizeof(lfn_buf) - 40; fi++) {
522
					int n = (int)strlen(lfn_frags[fi]);
523
					memcpy(lfn_buf + lfn_len, lfn_frags[fi], (size_t)n);
524
					lfn_len += n;
525
				}
526
				lfn_buf[lfn_len] = '\0';
527
			} else {
528
				lfn_len = 0;
529
			}
530
531
			process_dirent(de, parent_path, (const char *)lfn_buf, lfn_len, depth);
532
533
			// Reset LFN accumulator for next entry.
534
			lfn_frag_count   = 0;
535
			lfn_expected_seq = 0;
536
			lfn_len          = 0;
537
		}
538
539
		// Advance to next sector.
540
		sectors_done++;
541
		if (is_fat16_root) {
542
			// Nothing to advance; sectors_done checked above.
543
		} else {
544
			sector_in_cluster++;
545
			if (sector_in_cluster >= g_spc) {
546
				sector_in_cluster = 0;
547
				if (++cluster_chain_count >= SDCARD_FAT_MAX_CLUSTERS_PER_FILE) {
548
					g_truncated = true;
549
					break;
550
				}
551
				cur_cluster = fat_next(cur_cluster);
552
			}
553
		}
554
	}
555
}
556
557
// ── Extent sorting ────────────────────────────────────────────────────────────
558
559
static int extent_cmp(const void *a, const void *b)
560
{
561
	const fat_extent_t *ea = (const fat_extent_t *)a;
562
	const fat_extent_t *eb = (const fat_extent_t *)b;
563
	if (ea->first_lba < eb->first_lba) return -1;
564
	if (ea->first_lba > eb->first_lba) return  1;
565
	return 0;
566
}
567
568
// ── Internal teardown ─────────────────────────────────────────────────────────
569
570
static void free_state(void)
571
{
572
	for (int i = 0; i < g_file_count; i++) {
573
		free(g_files[i].path);
574
		g_files[i].path = NULL;
575
	}
576
	g_file_count   = 0;
577
	g_extent_count = 0;
578
	g_ready        = false;
579
	g_stale        = false;
580
	g_truncated    = false;
581
	g_fat_type     = FAT_TYPE_NONE;
582
583
	// g_img is a borrowed handle — never close it here.
584
	g_img = NULL;
585
}
586
587
// ── BPB detection/parsing ─────────────────────────────────────────────────────
588
589
// Validate a 512-byte boot sector and fill geometry fields.
590
// abs_lba: absolute LBA of this boot sector.
591
// Returns FAT_TYPE_NONE if invalid.
592
static fat_type_t parse_bpb(const uint8_t *sector, uint32_t abs_lba)
593
{
594
	// Jump instruction check.
595
	bool valid_jmp = (sector[0] == 0xEB && sector[2] == 0x90) ||
596
	                 (sector[0] == 0xE9);
597
	// Some disk tools use 0x00 for superfloppy — tolerate if signature is ok.
598
	bool valid_sig = (sector[510] == 0x55 && sector[511] == 0xAA);
599
600
	if (!valid_jmp && !valid_sig) {
601
		return FAT_TYPE_NONE;
602
	}
603
	if (!valid_sig) {
604
		return FAT_TYPE_NONE;
605
	}
606
607
	uint16_t bps   = le16(sector + 11);
608
	uint8_t  spc   = sector[13];
609
	uint16_t rsvd  = le16(sector + 14);
610
	uint8_t  nfats = sector[16];
611
	uint16_t root_ent = le16(sector + 17);
612
	uint16_t tot16 = le16(sector + 19);
613
	uint16_t fat16_sz = le16(sector + 22);
614
	uint32_t tot32 = le32(sector + 32);
615
	uint32_t fat32_sz = le32(sector + 36);
616
	uint32_t root_cluster = le32(sector + 44);
617
618
	// Validate.
619
	if (bps != 512) {
620
		return FAT_TYPE_NONE;
621
	}
622
	if (spc == 0 || (spc & (spc - 1)) != 0 || spc > 128) {
623
		return FAT_TYPE_NONE;
624
	}
625
	if (nfats == 0 || nfats > 2) {
626
		return FAT_TYPE_NONE;
627
	}
628
	if (rsvd == 0) {
629
		return FAT_TYPE_NONE;
630
	}
631
632
	uint32_t fat_sz  = fat16_sz != 0 ? fat16_sz : fat32_sz;
633
	uint32_t tot_sec = tot16 != 0 ? (uint32_t)tot16 : tot32;
634
635
	if (fat_sz == 0 || tot_sec == 0) {
636
		return FAT_TYPE_NONE;
637
	}
638
639
	uint32_t root_dir_sectors = ((uint32_t)root_ent * 32 + 511) / 512;
640
	uint32_t data_start_off   = rsvd + (uint32_t)nfats * fat_sz + root_dir_sectors;
641
642
	if (data_start_off >= tot_sec) {
643
		return FAT_TYPE_NONE;
644
	}
645
646
	uint32_t data_sectors  = tot_sec - data_start_off;
647
	uint32_t cluster_count = data_sectors / spc;
648
649
	fat_type_t ft;
650
	if (cluster_count < 4085) {
651
		ft = FAT_TYPE_12; // not supported, we'll reject below
652
	} else if (cluster_count < 65525) {
653
		ft = FAT_TYPE_16;
654
	} else {
655
		ft = FAT_TYPE_32;
656
	}
657
658
	if (ft == FAT_TYPE_12) {
659
		return FAT_TYPE_NONE; // unsupported — reject cleanly
660
	}
661
662
	// Fill geometry globals.
663
	g_bps         = bps;
664
	g_spc         = spc;
665
	g_fat_lba     = abs_lba + rsvd;
666
	g_fat_count   = nfats;
667
	g_fat_size    = fat_sz;
668
	g_total_sectors = tot_sec;
669
670
	if (ft == FAT_TYPE_16) {
671
		g_rootdir_lba     = abs_lba + rsvd + (uint32_t)nfats * fat_sz;
672
		g_rootdir_sectors = root_dir_sectors;
673
		g_first_data_lba  = g_rootdir_lba + root_dir_sectors;
674
		g_root_cluster    = 0;
675
	} else {
676
		// FAT32
677
		if (root_cluster < 2) {
678
			return FAT_TYPE_NONE;
679
		}
680
		g_rootdir_lba     = 0;
681
		g_rootdir_sectors = 0;
682
		g_first_data_lba  = abs_lba + rsvd + (uint32_t)nfats * fat_sz;
683
		g_root_cluster    = root_cluster;
684
	}
685
686
	return ft;
687
}
688
689
// ── Public API ────────────────────────────────────────────────────────────────
690
691
bool sdcard_fat_autoindex = true;
692
693
bool
694
sdcard_fat_build(struct x16file *img)
695
{
696
	free_state();
697
698
	if (img == NULL) {
699
		return false;
700
	}
701
702
	g_img = img; // borrow
703
704
	int64_t img_size = x16size(g_img);
705
	if (img_size < 512) {
706
		g_img = NULL;
707
		return false;
708
	}
709
710
	uint8_t sector0[512];
711
	if (!read_sector(0, sector0)) {
712
		g_img = NULL;
713
		return false;
714
	}
715
716
	// Check for MBR (0x55AA at offset 510, partition table at 0x1BE).
717
	bool has_mbr = (sector0[510] == 0x55 && sector0[511] == 0xAA);
718
719
	// FAT type constants.
720
	static const uint8_t fat_types[] = {
721
		0x01, 0x04, 0x06, 0x0B, 0x0C, 0x0E, 0x1B, 0x1C
722
	};
723
724
	// Try MBR partitions first.
725
	uint32_t part_lba = 0;
726
	bool     found_part = false;
727
728
	if (has_mbr) {
729
		for (int p = 0; p < 4; p++) {
730
			const uint8_t *pe = sector0 + 0x1BE + p * 16;
731
			uint8_t  ptype = pe[4];
732
			uint32_t plba  = le32(pe + 8);
733
			uint32_t pcnt  = le32(pe + 12);
734
			if (pcnt == 0) {
735
				continue;
736
			}
737
			bool is_fat_part = false;
738
			for (int k = 0; k < (int)(sizeof(fat_types)/sizeof(fat_types[0])); k++) {
739
				if (ptype == fat_types[k]) {
740
					is_fat_part = true;
741
					break;
742
				}
743
			}
744
			if (is_fat_part) {
745
				part_lba   = plba;
746
				found_part = true;
747
				break;
748
			}
749
		}
750
	}
751
752
	// Try found partition or superfloppy (LBA 0).
753
	uint8_t vbr[512];
754
	fat_type_t ft = FAT_TYPE_NONE;
755
756
	if (found_part) {
757
		if (!read_sector(part_lba, vbr)) {
758
			g_img = NULL;
759
			return false;
760
		}
761
		ft = parse_bpb(vbr, part_lba);
762
	}
763
764
	if (ft == FAT_TYPE_NONE) {
765
		// Try superfloppy: VBR is at LBA 0.
766
		ft = parse_bpb(sector0, 0);
767
		if (ft != FAT_TYPE_NONE) {
768
			part_lba = 0;
769
		}
770
	}
771
772
	if (ft == FAT_TYPE_NONE) {
773
		g_img = NULL;
774
		return false;
775
	}
776
777
	g_fat_type      = ft;
778
	g_partition_lba = part_lba;
779
780
	// Walk the directory tree.
781
	if (ft == FAT_TYPE_32) {
782
		// The FAT32 root is an ordinary cluster chain, so it has to be entered
783
		// into the extent table like any other directory. Without it, root
784
		// sectors match nothing, fall through to "unallocated", and -- worse --
785
		// a write into the root does not mark the index stale, so the panel
786
		// would keep serving names for files that had since been renamed.
787
		int root_idx = add_file("/", 0, g_root_cluster, true);
788
		if (root_idx >= 0) {
789
			walk_chain(g_root_cluster, root_idx);
790
		}
791
		walk_dir(g_root_cluster, true, "/", 0);
792
	} else {
793
		// FAT16: pass start_cluster=0 as a sentinel for the fixed root dir.
794
		walk_dir(0, false, "/", 0);
795
	}
796
797
	// Sort extents by first_lba for binary search.
798
	qsort(g_extents, (size_t)g_extent_count, sizeof(fat_extent_t), extent_cmp);
799
800
	g_ready = true;
801
	return true;
802
}
803
804
void
805
sdcard_fat_free(void)
806
{
807
	free_state();
808
}
809
810
bool
811
sdcard_fat_ready(void)
812
{
813
	return g_ready;
814
}
815
816
bool
817
sdcard_fat_is_stale(void)
818
{
819
	return g_stale;
820
}
821
822
void
823
sdcard_fat_mark_stale(void)
824
{
825
	g_stale = true;
826
}
827
828
bool
829
sdcard_fat_truncated(void)
830
{
831
	return g_truncated;
832
}
833
834
const char *
835
sdcard_fat_type_name(void)
836
{
837
	if (!g_ready) {
838
		return "";
839
	}
840
	switch (g_fat_type) {
841
		case FAT_TYPE_16: return "FAT16";
842
		case FAT_TYPE_32: return "FAT32";
843
		default:          return "FAT??";
844
	}
845
}
846
847
uint32_t
848
sdcard_fat_bytes_per_cluster(void)
849
{
850
	if (!g_ready) {
851
		return 0;
852
	}
853
	return g_spc * g_bps;
854
}
855
856
int
857
sdcard_fat_file_count(void)
858
{
859
	return g_ready ? g_file_count : 0;
860
}
861
862
const sdcard_fat_file_t *
863
sdcard_fat_file_at(int i)
864
{
865
	if (!g_ready || i < 0 || i >= g_file_count) {
866
		return NULL;
867
	}
868
	// The public struct has the same layout as the internal one (same fields,
869
	// same types, same order).  Cast is safe.
870
	return (const sdcard_fat_file_t *)&g_files[i];
871
}
872
873
sdcard_fat_region_t
874
sdcard_fat_lookup(uint32_t lba,
875
                  const sdcard_fat_file_t **out_file,
876
                  uint64_t                 *out_offset)
877
{
878
	if (out_file)   *out_file   = NULL;
879
	if (out_offset) *out_offset = 0;
880
881
	if (!g_ready) {
882
		return SDCARD_FAT_REGION_UNKNOWN;
883
	}
884
885
	// Check against partition bounds (if there is a partition).
886
	uint32_t part_end = g_partition_lba + g_total_sectors;
887
	if (g_total_sectors > 0 && lba >= part_end) {
888
		return SDCARD_FAT_REGION_UNKNOWN;
889
	}
890
891
	// MBR (only valid when the partition doesn't start at LBA 0).
892
	if (g_partition_lba > 0 && lba == 0) {
893
		return SDCARD_FAT_REGION_MBR;
894
	}
895
896
	// Reserved region: from VBR to start of FAT.
897
	if (lba >= g_partition_lba && lba < g_fat_lba) {
898
		return SDCARD_FAT_REGION_RESERVED;
899
	}
900
901
	// FAT region.
902
	uint32_t fat_end = g_fat_lba + g_fat_count * g_fat_size;
903
	if (lba >= g_fat_lba && lba < fat_end) {
904
		return SDCARD_FAT_REGION_FAT;
905
	}
906
907
	// FAT16 root directory.
908
	if (g_fat_type == FAT_TYPE_16 && g_rootdir_sectors > 0) {
909
		if (lba >= g_rootdir_lba && lba < g_rootdir_lba + g_rootdir_sectors) {
910
			return SDCARD_FAT_REGION_ROOTDIR;
911
		}
912
	}
913
914
	// Binary search in extent table.
915
	int lo = 0, hi = g_extent_count - 1;
916
	while (lo <= hi) {
917
		int mid = lo + (hi - lo) / 2;
918
		const fat_extent_t *e = &g_extents[mid];
919
		if (lba < e->first_lba) {
920
			hi = mid - 1;
921
		} else if (lba > e->last_lba) {
922
			lo = mid + 1;
923
		} else {
924
			// Hit.
925
			const fat_file_entry_t *fe = &g_files[e->file_index];
926
			if (out_file) {
927
				*out_file = (const sdcard_fat_file_t *)fe;
928
			}
929
			if (out_offset) {
930
				*out_offset = e->file_offset + (uint64_t)(lba - e->first_lba) * 512;
931
			}
932
			return fe->is_dir ? SDCARD_FAT_REGION_DIR : SDCARD_FAT_REGION_FILE;
933
		}
934
	}
935
936
	// Data region but not in any file's cluster chain.
937
	if (lba >= g_first_data_lba) {
938
		return SDCARD_FAT_REGION_FREE;
939
	}
940
941
	return SDCARD_FAT_REGION_UNKNOWN;
942
}
943
944
void
945
sdcard_fat_note_access(uint32_t lba, bool is_write, uint32_t bytes)
946
{
947
	if (!g_ready) {
948
		return;
949
	}
950
951
	const sdcard_fat_file_t *f   = NULL;
952
	uint64_t                 off = 0;
953
	sdcard_fat_region_t      reg = sdcard_fat_lookup(lba, &f, &off);
954
955
	if (is_write) {
956
		// A write to FAT or directory metadata makes the index stale.
957
		if (reg == SDCARD_FAT_REGION_FAT     ||
958
		    reg == SDCARD_FAT_REGION_ROOTDIR ||
959
		    reg == SDCARD_FAT_REGION_DIR) {
960
			g_stale = true;
961
		}
962
	}
963
964
	if (f != NULL) {
965
		// The file_t pointer is actually a fat_file_entry_t* under the hood.
966
		fat_file_entry_t *fe = (fat_file_entry_t *)(uintptr_t)f;
967
		if (is_write) {
968
			fe->bytes_written += bytes;
969
		} else {
970
			fe->bytes_read += bytes;
971
		}
972
	}
973
974
	(void)off;
975
}