forked from scarybeasts/beebjit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdisc_fsd.c
630 lines (554 loc) · 21.9 KB
/
disc_fsd.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
#include "disc_fsd.h"
#include "disc.h"
#include "ibm_disc_format.h"
#include "log.h"
#include "util.h"
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
enum {
k_disc_fsd_max_sectors = 32,
};
struct disc_fsd_sector {
char sector_spec[32];
uint8_t sector_error;
uint8_t logical_track;
uint8_t head;
uint8_t logical_sector;
uint8_t logical_size;
uint32_t logical_size_bytes;
uint32_t actual_size_bytes;
uint32_t truncated_size_bytes;
uint32_t write_size_bytes;
uint8_t* p_data;
int is_deleted;
int is_crc_error;
int is_crc_included;
int is_format_bytes;
};
static uint32_t
disc_fsd_calculate_track_total_bytes(uint32_t fsd_sectors,
uint32_t track_data_bytes,
uint32_t gap1_ff_count,
uint32_t gap2_ff_count,
uint32_t gap3_ff_count) {
uint32_t ret = track_data_bytes;
/* Each sector's data has a marker and CRC. */
ret += (fsd_sectors * 3);
/* Each sector has a sector header with marker and CRC. */
ret += (fsd_sectors * 7);
/* Each sector has GAP2, between the header and data. */
ret += (fsd_sectors * (gap2_ff_count + 6));
/* Each sector, bar the last, has GAP3, the inter-sector gap. */
ret += ((fsd_sectors - 1) * (gap3_ff_count + 6));
/* Before the first sector is GAP1. */
ret += (gap1_ff_count + 6);
return ret;
}
static int
disc_fsd_sector_has_weak_bits(uint32_t* p_out_weak_bits_start,
struct disc_fsd_sector* p_sector,
uint8_t* p_data,
uint32_t data_length) {
/* Sector error $0E only applies weak bits if the real and declared
* sector sizes match. Otherwise various Sherston Software titles
* fail. This also matches the logic in:
* https://github.com/stardot/beebem-windows/blob/fsd-disk-support/Src/disc8271.cpp
*/
if (!p_sector->is_crc_error) {
return 0;
}
/* Only sector error $0E needs to trigger. */
if (p_sector->sector_error != 0x0E) {
return 0;
}
if (data_length < 128) {
return 0;
}
/* Examples:
* 1) 621 THREE LITTLE PIGS AT HOME - THE STORY.log
* Track 39 / sector 9 / logical size 256. Physical size 128.
*
* 2) 186 THE TEDDY BEARS' PICNIC SIDE 1.FSD
* Track 39 / sector 9 / logical size 256. Physical size 256.
*
* 3) 267 THE CIRCUS (80 TRACK).FSD
* Track 0 / sector 2 / logical size 256. Physical size 256.
*/
/* All known weak bits are track 39, sector 9 or track 0, sector 2. */
if ((p_sector->logical_track == 39) && (p_sector->logical_sector == 9)) {
/* Usually Sherston Software but one known other example, Folio, which
* requires the weak bits to start later in the sector.
*/
if ((data_length > 128) && !strcmp(((char*) p_data + 0x20), "Folio")) {
*p_out_weak_bits_start = 128;
} else {
*p_out_weak_bits_start = 24;
}
return 1;
} else if ((p_sector->logical_track == 0) &&
(p_sector->logical_sector == 2)) {
*p_out_weak_bits_start = 24;
return 1;
} else {
return 0;
}
}
static void
disc_fsd_parse_sectors(struct disc_fsd_sector* p_sectors,
uint32_t* p_track_data_bytes,
uint32_t* p_track_truncatable_bytes,
uint32_t* p_track_truncatable_sectors,
uint8_t** p_p_buf,
size_t* p_file_remaining,
uint32_t fsd_sectors) {
uint32_t i_sector;
int readable = 1;
uint8_t* p_buf = *p_p_buf;
size_t file_remaining = *p_file_remaining;
(void) memset(p_sectors,
'\0',
(sizeof(struct disc_fsd_sector) * k_disc_fsd_max_sectors));
*p_track_data_bytes = 0;
*p_track_truncatable_bytes = 0;
*p_track_truncatable_sectors = 0;
if (fsd_sectors > k_disc_fsd_max_sectors) {
util_bail("fsd file excessive sectors");
}
if (file_remaining == 0) {
util_bail("fsd file missing readable flag");
}
if (*p_buf == 0) {
/* "unreadable" track. */
readable = 0;
} else if (*p_buf != 0xFF) {
util_bail("fsd file unknown readable byte value");
}
p_buf++;
file_remaining--;
for (i_sector = 0; i_sector < fsd_sectors; ++i_sector) {
uint8_t logical_sector;
uint8_t logical_size;
uint32_t actual_size_bytes;
uint32_t logical_size_bytes;
uint8_t sector_error;
struct disc_fsd_sector* p_sector = &p_sectors[i_sector];
if (file_remaining < 4) {
util_bail("fsd file missing sector header");
}
p_sector->logical_track = p_buf[0];
p_sector->head = p_buf[1];
logical_sector = p_buf[2];
p_sector->logical_sector = logical_sector;
logical_size = p_buf[3];
p_sector->logical_size = logical_size;
p_buf += 4;
file_remaining -= 4;
(void) snprintf(p_sector->sector_spec,
sizeof(p_sector->sector_spec),
"$%.2x/$%.2x/$%.2x/$%.2x",
p_sector->logical_track,
p_sector->head,
logical_sector,
p_sector->logical_size);
if (!readable) {
/* Invent some "unreadable" data. I looked at Exile's "unreadable"
* track and the bytes are just format bytes, i.e. 0xE5.
*/
uint32_t num_format_bytes = 128;
if (fsd_sectors <= 10) {
num_format_bytes = 256;
}
p_sector->is_format_bytes = 1;
p_sector->write_size_bytes = num_format_bytes;
continue;
}
if (file_remaining < 2) {
util_bail("fsd file missing sector header");
}
actual_size_bytes = p_buf[0];
sector_error = p_buf[1];
p_sector->sector_error = sector_error;
p_buf += 2;
file_remaining -= 2;
if (actual_size_bytes > 4) {
util_bail("fsd file excessive sector size");
}
actual_size_bytes = (1 << (7 + actual_size_bytes));
p_sector->actual_size_bytes = actual_size_bytes;
p_sector->truncated_size_bytes = actual_size_bytes;
p_sector->write_size_bytes = actual_size_bytes;
if (file_remaining < actual_size_bytes) {
util_bail("fsd file missing sector data");
}
p_sector->p_data = p_buf;
p_buf += actual_size_bytes;
file_remaining -= actual_size_bytes;
*p_track_data_bytes += actual_size_bytes;
logical_size_bytes = (1 << (7 + logical_size));
if (logical_size_bytes < actual_size_bytes) {
p_sector->truncated_size_bytes = logical_size_bytes;
}
if (sector_error == 0x20) {
/* Deleted data. */
p_sector->is_deleted = 1;
} else if (sector_error == 0x0E) {
/* Sector has data CRC error. */
/* CRC error in the FSD format appears to also imply weak bits. See:
* https://stardot.org.uk/forums/viewtopic.php?f=4&t=4353&start=30#p74208
*/
p_sector->is_crc_error = 1;
} else if (sector_error == 0x2E) {
/* $2E isn't documented and neither are $20 / $0E documented as bit
* fields, but it shows up anyway in The Wizard's Return.
*/
p_sector->is_crc_error = 1;
p_sector->is_deleted = 1;
} else if ((sector_error >= 0xE0) && (sector_error <= 0xE2)) {
p_sector->is_crc_error = 1;
if (sector_error == 0xE0) {
p_sector->truncated_size_bytes = 128;
} else if (sector_error == 0xE1) {
if (p_sector->actual_size_bytes < 256) {
util_bail("bad size for sector error $E1");
}
p_sector->truncated_size_bytes = 256;
} else {
if (p_sector->actual_size_bytes < 512) {
util_bail("bad size for sector error $E2");
}
p_sector->truncated_size_bytes = 512;
}
} else if (sector_error == 0x18) {
/* Some FSD files have spurious error $18 (sector not found). Ignore.
* Example: 571 Philosopher's Quest 40track.FSD
*/
} else if (sector_error != 0) {
util_bail("fsd file sector error %d unsupported", sector_error);
}
if (p_sector->truncated_size_bytes != p_sector->actual_size_bytes) {
(*p_track_truncatable_sectors)++;
*p_track_truncatable_bytes += (p_sector->actual_size_bytes -
p_sector->truncated_size_bytes);
}
}
*p_p_buf = p_buf;
*p_file_remaining = file_remaining;
}
static void
disc_fsd_perform_track_adjustments(struct disc_fsd_sector* p_sectors,
uint32_t* p_gap1_ff_count,
uint32_t* p_gap3_ff_count,
uint32_t gap2_ff_count,
uint32_t fsd_sectors,
uint32_t track_data_bytes,
uint32_t track_truncatable_bytes,
uint32_t track_truncatable_sectors,
uint32_t track) {
uint32_t track_total_bytes;
uint32_t num_bytes_over;
uint32_t overread_bytes_per_sector;
uint32_t i_sector;
track_total_bytes = disc_fsd_calculate_track_total_bytes(fsd_sectors,
track_data_bytes,
*p_gap1_ff_count,
gap2_ff_count,
*p_gap3_ff_count);
if (track_total_bytes <= k_ibm_disc_bytes_per_track) {
return;
}
log_do_log(k_log_disc,
k_log_unusual,
"FSD: excessive length track %d: %d (%d sectors %d data)",
track,
track_total_bytes,
fsd_sectors,
track_data_bytes);
/* This is ugly because the FSD format is ambiguous.
* Where a sector's "real" size push us over the limit of 3125 bytes per
* track, there are a couple of reasons this could be the case:
* 1) The sectors are squished closer together than normal via small
* inter-sector gaps.
* 2) The "real" size isn't really the real size of data present, it's
* just the next biggest size so that a small number of post-sector bytes
* hidden in the inter-sector gap can be catered for.
*
* 2) is pretty common but if we follow it blindly we'll get incorrect
* data for the case where 1) is going on.
*/
/* First attempt -- see if it fits if we lower the gap sizes a lot. */
/* NOTE: the 1770 and 8271 controllers are very sensitive to GAP2 size so we
* leave it alone. (In tests, 9 0xFF's is ok, 7 is not. 11 is the default.)
* These gaps here are sufficient to enable certain tricky Tynesoft titles to
* work (Winter Olympiad, Boulderdash, etc.), which have very small inter-
* sector gaps. On the Winter Olympiad disc, the packed tracks have total
* gap sizes (FFs + 00s) as small as 4, but more typically 7 (1x FFs, 6x 00s).
* The total gap sizes here will be 9, which is still small enough to work.
*/
*p_gap1_ff_count = 3;
*p_gap3_ff_count = 3;
track_total_bytes = disc_fsd_calculate_track_total_bytes(fsd_sectors,
track_data_bytes,
*p_gap1_ff_count,
gap2_ff_count,
*p_gap3_ff_count);
if (track_total_bytes <= k_ibm_disc_bytes_per_track) {
return;
}
log_do_log(k_log_disc,
k_log_warning,
"FSD: small gaps, still excessive length: %d, truncating",
track_total_bytes);
num_bytes_over = (track_total_bytes - k_ibm_disc_bytes_per_track);
if (num_bytes_over >= track_truncatable_bytes) {
util_bail("fsd sectors really cannot fit");
}
track_total_bytes -= track_truncatable_bytes;
overread_bytes_per_sector = (k_ibm_disc_bytes_per_track -
track_total_bytes -
1);
overread_bytes_per_sector /= track_truncatable_sectors;
for (i_sector = 0; i_sector < fsd_sectors; ++i_sector) {
struct disc_fsd_sector* p_sector = &p_sectors[i_sector];
if (p_sector->write_size_bytes == p_sector->truncated_size_bytes) {
continue;
}
/* After truncating all the truncatable sectors, there is likely space left
* in the track to include sector overread bytes. Spread these available
* overread bytes across all sectors that were truncated.
* When we add overread bytes, the CRC is part of the overread bytes, and
* no longer something we manually calculate and append.
*/
p_sector->write_size_bytes = p_sector->truncated_size_bytes;
p_sector->write_size_bytes += overread_bytes_per_sector;
p_sector->is_crc_included = 1;
}
}
void
disc_fsd_load(struct disc_struct* p_disc, int has_file_name) {
/* The most authoritative "documentation" for the FSD format appears to be:
* https://stardot.org.uk/forums/viewtopic.php?f=4&t=4353&start=60#p195518
*/
static const size_t k_max_fsd_size = (1024 * 1024);
uint8_t* p_file_buf;
size_t len;
size_t file_remaining;
uint8_t* p_buf;
uint32_t fsd_tracks;
uint32_t i_track;
uint8_t title_char;
struct util_file* p_file = disc_get_file(p_disc);
assert(p_file != NULL);
p_file_buf = util_malloc(k_max_fsd_size);
len = util_file_read(p_file, p_file_buf, k_max_fsd_size);
if (len == k_max_fsd_size) {
util_bail("fsd file too large");
}
p_buf = p_file_buf;
file_remaining = len;
if (file_remaining < 8) {
util_bail("fsd file no header");
}
if (memcmp(p_buf, "FSD", 3) != 0) {
util_bail("fsd file incorrect header");
}
p_buf += 8;
file_remaining -= 8;
if (has_file_name) {
do {
if (file_remaining == 0) {
util_bail("fsd file missing title");
}
title_char = *p_buf;
p_buf++;
file_remaining--;
} while (title_char != 0);
}
if (file_remaining == 0) {
util_bail("fsd file missing tracks");
}
/* This appears to actually be "max zero-indexed track ID" so we add 1. */
fsd_tracks = *p_buf;
fsd_tracks++;
p_buf++;
file_remaining--;
if (fsd_tracks > k_ibm_disc_tracks_per_disc) {
util_bail("fsd file too many tracks: %d", fsd_tracks);
}
for (i_track = 0; i_track < fsd_tracks; ++i_track) {
struct disc_fsd_sector sectors[k_disc_fsd_max_sectors];
uint32_t fsd_sectors;
uint32_t i_sector;
uint32_t track_remaining = k_ibm_disc_bytes_per_track;
uint32_t track_data_bytes = 0;
uint32_t track_truncatable_bytes = 0;
uint32_t track_truncatable_sectors = 0;
/* Acorn format command standards for 256 byte sectors. The 8271 datasheet
* generally agrees but does suggest 21 for GAP3.
*/
uint32_t gap1_ff_count = 16;
uint32_t gap2_ff_count = 11;
uint32_t gap3_ff_count = 16;
/* Some files end prematurely but cleanly on a track boundary. These files
* seem to work ok if the remainder of tracks are left unformatted.
* Examples:
* 255 Felix meets the Evil Weevils.FSD
* 518 THE WORST WITCH.log
*/
if (file_remaining == 0) {
break;
}
if (file_remaining < 2) {
util_bail("fsd file missing track header");
}
if (p_buf[0] != i_track) {
util_bail("fsd file unmatched track id");
}
disc_build_track(p_disc, 0, i_track);
fsd_sectors = p_buf[1];
p_buf += 2;
file_remaining -= 2;
if (fsd_sectors == 0) {
/* NOTE: "unformatted" track could mean a few different possibilities.
* What it definitely means is that there are no detectable sector ID
* markers. But it doesn't say why.
* For example, my original Elite and Castle Quest discs have a genuinely
* unformatted track, i.e. no flux transitions. On the other hand, my
* original Labyrinth disc has flux transitions (of varinging width)!
* Let's go with a genuinely unformatted track.
*/
disc_build_append_repeat_fm_byte_with_clocks(p_disc,
0,
0,
k_ibm_disc_bytes_per_track);
continue;
}
(void) memset(sectors, '\0', sizeof(sectors));
disc_fsd_parse_sectors(sectors,
&track_data_bytes,
&track_truncatable_bytes,
&track_truncatable_sectors,
&p_buf,
&file_remaining,
fsd_sectors);
if (fsd_sectors > 18) {
/* 256 VECTOR 2 V140 ACORN 1770.FSD uses 19 sectors; make it fit. */
gap1_ff_count = 6;
gap3_ff_count = 3;
} else if (fsd_sectors > 10) {
/* Standard for 128 byte sectors. If we didn't lower the value here, the
* track wouldn't fit.
*/
gap3_ff_count = 11;
}
disc_fsd_perform_track_adjustments(sectors,
&gap1_ff_count,
&gap3_ff_count,
gap2_ff_count,
fsd_sectors,
track_data_bytes,
track_truncatable_bytes,
track_truncatable_sectors,
i_track);
/* Sync pattern at start of track, as the index pulse starts, aka GAP 1.
* Note that GAP 5 (with index address mark) is typically not used in BBC
* formatted discs.
*/
disc_build_append_repeat_fm_byte(p_disc, 0xFF, gap1_ff_count);
disc_build_append_repeat_fm_byte(p_disc, 0x00, 6);
track_remaining -= (gap1_ff_count + 6);
for (i_sector = 0; i_sector < fsd_sectors; ++i_sector) {
uint32_t weak_bits_start;
struct disc_fsd_sector* p_sector = §ors[i_sector];
uint32_t write_size_bytes = p_sector->write_size_bytes;
uint8_t* p_data = p_sector->p_data;
uint8_t sector_mark = k_ibm_disc_data_mark_data_pattern;
if (track_remaining < (7 + (gap2_ff_count + 6))) {
util_bail("fsd file track no space for sector header and gap");
}
/* Sector header, aka. ID. */
disc_build_reset_crc(p_disc);
disc_build_append_fm_data_and_clocks(p_disc,
k_ibm_disc_id_mark_data_pattern,
k_ibm_disc_mark_clock_pattern);
disc_build_append_fm_byte(p_disc, p_sector->logical_track);
disc_build_append_fm_byte(p_disc, p_sector->head);
disc_build_append_fm_byte(p_disc, p_sector->logical_sector);
disc_build_append_fm_byte(p_disc, p_sector->logical_size);
disc_build_append_crc(p_disc, 0);
/* Sync pattern between sector header and sector data, aka. GAP 2. */
disc_build_append_repeat_fm_byte(p_disc, 0xFF, gap2_ff_count);
disc_build_append_repeat_fm_byte(p_disc, 0x00, 6);
track_remaining -= (7 + (gap2_ff_count + 6));
if (p_sector->is_deleted) {
sector_mark = k_ibm_disc_deleted_data_mark_data_pattern;
}
if (track_remaining < (write_size_bytes + 3)) {
util_bail("fsd file track no space for sector data");
}
disc_build_reset_crc(p_disc);
disc_build_append_fm_data_and_clocks(p_disc,
sector_mark,
k_ibm_disc_mark_clock_pattern);
if (p_sector->is_format_bytes) {
disc_build_append_repeat_fm_byte(p_disc, 0xE5, write_size_bytes);
} else if (!disc_fsd_sector_has_weak_bits(&weak_bits_start,
p_sector,
p_data,
write_size_bytes)) {
disc_build_append_fm_chunk(p_disc, p_data, write_size_bytes);
} else {
/* This is icky: the titles that rely on weak bits (mostly,
* Sherston Software titles) rely on the weak bits being a little later
* in the sector as the code at the start of the sector is executed!!
*/
disc_build_append_fm_chunk(p_disc, p_data, weak_bits_start);
/* Our 8271 driver interprets empty disc surface (no data bits, no
* clock bits) as weak bits. As does my real drive + 8271 combo.
*/
disc_build_append_repeat_fm_byte_with_clocks(
p_disc, 0x00, 0x00, (write_size_bytes - weak_bits_start));
}
if (!p_sector->is_crc_included) {
if (p_sector->is_crc_error) {
disc_build_append_bad_crc(p_disc);
} else {
disc_build_append_crc(p_disc, 0);
}
}
track_remaining -= (write_size_bytes + 3);
if ((fsd_sectors == 1) && (track_data_bytes == 256)) {
log_do_log(k_log_disc,
k_log_warning,
"FSD: workaround: zero padding short track %d: %s",
i_track,
p_sector->sector_spec);
/* This is essentially a workaround for buggy FSD files, such as:
* 297 DISC DUPLICATOR 3.FSD
* The copy protection relies on zeros being returned from a sector
* overread of a single sectored short track, but the FSD file does
* not guarantee this.
* Also make sure to not accidentally create a valid CRC for a 512
* byte read. This happens if the valid 256 byte sector CRC is
* followed by all 0x00 and an 0x00 CRC.
*/
disc_build_append_repeat_fm_byte(p_disc, 0x00, (256 - 2));
disc_build_append_repeat_fm_byte(p_disc, 0xFF, 2);
track_remaining -= 256;
}
if (i_sector != (fsd_sectors - 1)) {
/* Sync pattern between sectors, aka. GAP 3. */
if (track_remaining < (gap3_ff_count + 6)) {
util_bail("fsd file track no space for inter sector gap");
}
disc_build_append_repeat_fm_byte(p_disc, 0xFF, gap3_ff_count);
disc_build_append_repeat_fm_byte(p_disc, 0x00, 6);
track_remaining -= (gap3_ff_count + 6);
}
} /* End of sectors loop. */
/* Fill until end of track, aka. GAP 4. */
disc_build_fill_fm_byte(p_disc, 0xFF);
} /* End of track loop. */
util_free(p_file_buf);
}