2 * TR-DOS filesystem API
3 * Copyright (c) 2020-2022 Ketmar Dark
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is furnished
10 * to do so, subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included in all
13 * copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
17 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
18 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
20 * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 * coded by Ketmar // Invisible Vector
24 * Understanding is not required. Only obedience.
26 #include "diskfs_trdos.h"
27 #include "diskfs_p3dos.h"
32 //==========================================================================
36 //==========================================================================
37 int flpIsValidDisk (const disk_t
*dsk
) {
40 (dsk
->sides
== 1 || dsk
->sides
== 2) &&
41 (dsk
->cylinders
>= 40 && dsk
->cylinders
<= 83) &&
47 //==========================================================================
51 //==========================================================================
52 FloppyDiskType
flpDetectDiskType (disk_t
*flp
) {
53 if (flpIsValidDisk(flp
)) {
54 if (flpIsDiskTRDOS(flp
)) return FLP_DISK_TYPE_TRDOS
;
55 if (flpIsDiskP3DOS(flp
)) return FLP_DISK_TYPE_P3DOS
;
57 return FLP_DISK_TYPE_UNKNOWN
;
61 //**************************************************************************
63 // TR-DOS low level API
65 //**************************************************************************
68 //==========================================================================
73 // performs some sanity check
74 // will copy partial sector data (and return -3 aka FLPERR_NOSPACE)
75 // cannot be used to put data to more than one sector
77 //==========================================================================
78 int flpGetSectorData (disk_t
*flp
, uint8_t tr
, uint8_t sc
, void *buf
, size_t len
) {
79 if (!flpIsValidDisk(flp
)) return FLPERR_SHIT
;
81 return (disk_read_sector(flp
, tr
, sc
, buf
, len
, &ssize
) == DISK_OK
? FLPERR_OK
: FLPERR_SHIT
);
85 //==========================================================================
89 // returns sector size, or negative on error
91 //==========================================================================
92 int flpGetSectorDataEx (disk_t
*flp
, uint8_t tr
, uint8_t sc
, void *buf
, size_t len
) {
93 if (!flpIsValidDisk(flp
)) return FLPERR_SHIT
;
95 if (disk_read_sector(flp
, tr
, sc
, buf
, len
, &ssize
) != DISK_OK
) return FLPERR_SHIT
;
100 //==========================================================================
102 // flpGetSectorDataPtrRO
104 //==========================================================================
105 const uint8_t *flpGetSectorDataPtrRO (disk_t
*flp
, uint8_t tr
, uint8_t sc
) {
106 if (!flpIsValidDisk(flp
)) return NULL
;
108 return disk_get_sector_data_ptr(flp
, tr
, sc
, &ssize
, NULL
);
112 //==========================================================================
114 // flpGetSectorDataPtrRW
116 //==========================================================================
117 uint8_t *flpGetSectorDataPtrRW (disk_t
*flp
, uint8_t tr
, uint8_t sc
) {
118 if (!flpIsValidDisk(flp
)) return NULL
;
120 return disk_get_sector_data_ptr(flp
, tr
, sc
, &ssize
, NULL
);
124 //==========================================================================
129 // performs some sanity check
130 // will completely reject too long data (no changes will be made, will return -3 aka FLPERR_NOSPACE)
131 // cannot be used to put data to more than one sector
132 // you can use zero `len` (`buf` doesn't matter in this case) to fix data CRC
133 // but it is better to call `flpFixSectorDataCRC()` instead
135 //==========================================================================
136 int flpPutSectorData (disk_t
*flp
, uint8_t tr
, uint8_t sc
, const void *buf
, size_t len
) {
137 if (!flpIsValidDisk(flp
)) return FLPERR_SHIT
;
138 return (disk_write_sector(flp
, tr
, sc
, buf
, len
) == DISK_OK
? FLPERR_OK
: FLPERR_SHIT
);
142 //==========================================================================
144 // flpFixSectorDataCRC
146 //==========================================================================
147 int flpFixSectorDataCRC (disk_t
*flp
, uint8_t tr
, uint8_t sc
) {
148 if (!flpIsValidDisk(flp
)) return FLPERR_SHIT
;
149 return (disk_write_sector(flp
, tr
, sc
, NULL
, 0) == DISK_OK
? FLPERR_OK
: FLPERR_SHIT
);
153 //==========================================================================
157 //==========================================================================
158 int flpIsDiskTRDOS (disk_t
*flp
) {
159 if (!flpIsValidDisk(flp
)) return 0;
162 if (flpGetSectorData(flp
, 0, 15, fbuf
, 0x100) != FLPERR_OK
) return 0;
163 // at least 16 sectors
164 if (flpGetSectorData(flp
, 0, 9, fbuf
, 0x100) != FLPERR_OK
) return 0;
165 return (fbuf
[0xe7] == 0x10u
);
169 //==========================================================================
173 //==========================================================================
174 static uint16_t calcHobSum (const void *hdr
) {
175 const uint8_t *buf
= (const uint8_t *)hdr
;
177 for (unsigned int f
= 0; f
< 15; ++f
) res
+= ((uint16_t)buf
[f
])*257+f
;
182 //==========================================================================
186 //==========================================================================
187 int flpIsHoBetaBuf (const void *buf
, int size
) {
188 const uint8_t *b
= (const uint8_t *)buf
;
189 if (size
< 17 || ((size
-17)&0xff)) return 0;
190 uint16_t csum
= calcHobSum(buf
);
191 if ((csum
&0xff) != b
[15] || ((csum
&0xff00)>>8) != b
[16]) return 0;
196 //==========================================================================
200 //==========================================================================
201 int flpLoadHoBeta (disk_t
*flp
, FILE *fl
) {
205 if (fl
== NULL
) return FLPERR_SHIT
;
206 if (!flpIsValidDisk(flp
)) return FLPERR_SHIT
;
207 if (flpDetectDiskType(flp
) != FLP_DISK_TYPE_TRDOS
) {
208 libfdcMsg(LIBFDC_MSG_ERROR
, "emulated floppy not a TR-DOS disk");
211 if (fread(secBuf
, 17, 1, fl
) != 1) {
212 libfdcMsg(LIBFDC_MSG_ERROR
, "cannot read HoBeta header");
215 csum
= calcHobSum(secBuf
);
216 if ((csum
&0xff) != secBuf
[15] || ((csum
&0xff00)>>8) != secBuf
[16]) {
217 libfdcMsg(LIBFDC_MSG_ERROR
, "invalid HoBeta header checksum");
218 fseek(fl
, -17, SEEK_CUR
);
221 memcpy(&nfle
, secBuf
, 13);
222 nfle
.slen
= secBuf
[14];
223 if (flpCreateFileTRD(flp
, &nfle
) != FLPERR_OK
) {
224 libfdcMsg(LIBFDC_MSG_ERROR
, "cannot create file on TR-DOS disk");
227 for (int i
= 0; i
< nfle
.slen
; ++i
) {
228 if (fread(secBuf
, 256, 1, fl
) != 1) {
229 libfdcMsg(LIBFDC_MSG_ERROR
, "cannot read HoBeta data");
232 if (flpPutSectorData(flp
, nfle
.trk
, nfle
.sec
+1, secBuf
, 256) != FLPERR_OK
) {
233 libfdcMsg(LIBFDC_MSG_ERROR
, "cannot write HoBeta data to TR-DOS disk");
247 //==========================================================================
251 //==========================================================================
252 int flpSaveHoBeta (disk_t
*flp
, FILE *fl
, int catidx
) {
253 uint8_t buf
[256]; // header/sector
257 if (fl
== NULL
) return FLPERR_SHIT
;
258 if (!flpIsValidDisk(flp
)) return FLPERR_SHIT
;
259 if (flpGetDirEntryTRD(flp
, &tfl
, catidx
) != FLPERR_OK
) return FLPERR_SHIT
;
260 memcpy(buf
, tfl
.name
, 13);
263 csum
= calcHobSum(buf
);
264 buf
[15] = (csum
&0xff);
265 buf
[16] = ((csum
&0xff00)>>8);
266 if (fwrite(buf
, 17, 1, fl
) != 1) return FLPERR_SHIT
;
269 for (unsigned int f
= 0; f
< tfl
.slen
; ++f
) {
270 if (flpGetSectorData(flp
, tr
, sc
+1, buf
, 256) != FLPERR_OK
) return FLPERR_SHIT
;
271 if (fwrite(buf
, 256, 1, fl
) != 1) return FLPERR_SHIT
;
272 if (++sc
> 15) { ++tr
; sc
= 0; }
278 //==========================================================================
282 // format whole disk as 2x84x16x256 and init as TRDOS
284 //==========================================================================
285 int flpFormatTRD (disk_t
*flp
) {
286 if (!flp
) return FLPERR_SHIT
;
289 int dres
= disk_new(flp
, 2, 80, DISK_DD
, DISK_TRD
);
290 if (dres
!= DISK_OK
) {
291 libfdcMsg(LIBFDC_MSG_ERROR
, "cannot create disk: %s", disk_strerror(dres
));
295 if (disk_format(flp
, 2, 80, 1, 16, 256, 2) != DISK_OK
) return FLPERR_SHIT
;
298 const uint8_t trd_8e0
[32] = {
299 0x00,0x00,0x01,0x16,0x00,0xf0,0x09,0x10,0x00,0x00,0x20,0x20,0x20,0x20,0x20,0x20,
300 0x20,0x20,0x20,0x00,0x00,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x00,0x00,0x00,
304 memset(buf
, 0, sizeof(buf
));
305 memcpy(buf
+(0x8e0%256), trd_8e0
, 32);
307 dres
= disk_write_sector(flp
, 0, 9, buf
, 256);
308 if (dres
!= DISK_OK
) {
309 libfdcMsg(LIBFDC_MSG_ERROR
, "cannot write directory: %s", disk_strerror(dres
));
318 //**************************************************************************
322 //**************************************************************************
324 //==========================================================================
328 //==========================================================================
329 int flpCreateFileTRD (disk_t
*flp
, TRFile
*dsc
) {
330 if (flpDetectDiskType(flp
) != FLP_DISK_TYPE_TRDOS
) return FLPERR_SHIT
;
334 if (flpGetSectorData(flp
, 0, 9, fbuf
, 256) != FLPERR_OK
) return FLPERR_SHIT
;
335 dsc
->sec
= fbuf
[0xe1];
336 dsc
->trk
= fbuf
[0xe2];
338 if (files
> 127) return FLPERR_MANYFILES
;
341 freesec
= fbuf
[0xe5]+(fbuf
[0xe6]<<8);
342 if (freesec
< dsc
->slen
) return FLPERR_NOSPACE
;
343 freesec
-= dsc
->slen
;
344 fbuf
[0xe5] = (freesec
&0xff);
345 fbuf
[0xe6] = ((freesec
&0xff00)>>8);
346 fbuf
[0xe1] += (dsc
->slen
&0x0f);
347 fbuf
[0xe2] += ((dsc
->slen
&0xf0)>>4);
348 if (fbuf
[0xe1] > 0x0f) {
352 if (flpPutSectorData(flp
, 0, 9, fbuf
, 256) != FLPERR_OK
) return FLPERR_SHIT
;
353 freesec
= ((files
&0xf0)>>4)+1;
354 if (flpGetSectorData(flp
, 0, freesec
, fbuf
, 256) != FLPERR_OK
) return FLPERR_SHIT
;
355 memmove(fbuf
+(((files
-1)&0x0f)<<4), dsc
, 16);
356 flpPutSectorData(flp
, 0, freesec
, fbuf
, 256);
362 //==========================================================================
366 //==========================================================================
367 int flpGetDirEntryTRD (disk_t
*flp
, TRFile
*dst
, int num
) {
370 if (flpDetectDiskType(flp
) != FLP_DISK_TYPE_TRDOS
) return FLPERR_SHIT
;
371 if (num
< 0 || num
> 127) return FLPERR_MANYFILES
;
372 sec
= ((num
&0xf0)>>4); // sector
373 pos
= ((num
&0x0f)<<4); // file number inside sector
374 if (flpGetSectorData(flp
, 0, sec
+1, fbuf
, 256) != FLPERR_OK
) return FLPERR_NOSPACE
;
375 if (dst
!= NULL
) memmove(dst
, fbuf
+pos
, 16);
380 //==========================================================================
382 // flpGetDirectoryTRD
384 //==========================================================================
385 int flpGetDirectoryTRD (disk_t
*flp
, TRFile
*dst
) {
387 if (flpDetectDiskType(flp
) == FLP_DISK_TYPE_TRDOS
) {
388 uint8_t *dpt
= (uint8_t *)dst
;
390 for (int sc
= 1; sc
< 9; ++sc
) {
391 if (flpGetSectorData(flp
, 0, sc
, fbuf
, 256) != FLPERR_OK
) break;
392 const uint8_t *ptr
= fbuf
;
394 for (fc
= 0; fc
< 16; ++fc
) {
395 if (*ptr
== 0) break;
397 memmove(dpt
, ptr
, 16);
411 //**************************************************************************
415 //**************************************************************************
417 int flpIsBootFCBTRD (const TRFile
*fcb
) {
419 return (memcmp(fcb
->name
, "boot B", 9) == 0);
423 //==========================================================================
427 //==========================================================================
428 int flpHasBootTRD (disk_t
*flp
) {
429 if (flp
!= NULL
&& flpDetectDiskType(flp
) == FLP_DISK_TYPE_TRDOS
) {
431 int catSize
= flpGetDirectoryTRD(flp
, cat
);
432 for (int i
= 0; i
< catSize
; ++i
) {
433 if (flpIsBootFCBTRD(&cat
[i
])) return i
;
440 //==========================================================================
444 //==========================================================================
445 int flpSetBootTRD (disk_t
*flp
, FILE *fl
, int replace
) {
446 if (flp
!= NULL
&& flpDetectDiskType(flp
) == FLP_DISK_TYPE_TRDOS
) {
447 int idx
= flpHasBootTRD(flp
);
449 if (replace
) return FLPERR_SHIT
; //TODO
452 return flpLoadHoBeta(flp
, fl
);
458 //==========================================================================
460 // flpFindFirstBasicTRD
462 //==========================================================================
463 int flpFindFirstBasicTRD (disk_t
*flp
, TRFile
*dst
, int ffirst
) {
464 if (flp
== NULL
|| flpDetectDiskType(flp
) != FLP_DISK_TYPE_TRDOS
) return FLPERR_SHIT
;
465 if (!flpIsValidDisk(flp
)) return FLPERR_SHIT
;
466 if (ffirst
< 0) ffirst
= 0;
468 for (; ffirst
< FLP_TRDOS_DIRCOUNT_MAX
; ++ffirst
) {
469 if (flpGetDirEntryTRD(flp
, &fcb
, ffirst
) != 0) break;
470 if (fcb
.name
[0] == 0) break; // end of directory
471 if (fcb
.name
[0] == 1) continue; // deleted
472 if (fcb
.ext
!= 'B') continue;
475 for (unsigned f
= 0; f
< 8; ++f
) {
476 const uint8_t ch
= (unsigned)(fcb
.name
[f
]&0xffU
);
477 if (ch
< 32 || ch
== '"') { nameok
= 0; break; }
478 if (ch
!= 32) seennonspc
= 1;
480 if (!nameok
|| !seennonspc
) continue;
481 if (dst
) memcpy(dst
, &fcb
, sizeof(TRFile
));
488 //==========================================================================
492 //==========================================================================
493 static int isGoodBasicName (const TRFile
*fcb
) {
495 if (fcb
->ext
!= 'B') return 0;
496 if (fcb
->name
[0] <= 32 || fcb
->name
[0] >= 127) return 0;
497 for (int f
= 0; f
< 8; ++f
) {
498 if (fcb
->name
[f
] >= 'A' && fcb
->name
[f
] <= 'Z') return 1;
499 //if (fcb->name[f] >= 'a' && fcb->name[f] <= 'z') return 1;
500 if (fcb
->name
[f
] >= '0' && fcb
->name
[f
] <= '9') continue;
501 if (fcb
->name
[f
] == ' ') continue;
508 //==========================================================================
510 // flpHasAnyNonBootBasicNonBootTRD
512 //==========================================================================
513 int flpHasAnyNonBootBasicNonBootTRD (disk_t
*flp
, TRFile
*dst
) {
514 if (flp
== NULL
|| flpDetectDiskType(flp
) != FLP_DISK_TYPE_TRDOS
) return 0;
515 if (!flpIsValidDisk(flp
)) return 0;
516 // check if we have any non-boot basic file
520 fidx
= flpFindFirstBasicTRD(flp
, &fcb
, fidx
);
523 if (flpIsBootFCBTRD(&fcb
)) return 0;
524 if (!isGoodBasicName(&fcb
)) continue;
525 if (dst
) memcpy(dst
, &fcb
, sizeof(TRFile
));
532 //==========================================================================
534 // flpSetBootSimpleTRD
536 //==========================================================================
537 int flpSetBootSimpleTRD (disk_t
*flp
) {
538 if (flpDetectDiskType(flp
) != FLP_DISK_TYPE_TRDOS
) return FLPERR_SHIT
;
540 // check if we have any non-boot basic file
546 fidx
= flpFindFirstBasicTRD(flp
, &fcb
, fidx
);
548 if (flpIsBootFCBTRD(&fcb
)) return FLPERR_SHIT
; // nothing to do
549 //fprintf(stderr, "...: <%.8s : %c> : %d\n", fcb.name, fcb.ext, isGoodBasicName(&fcb));
550 if (isGoodBasicName(&fcb
)) {
551 if (seenBasic
) return FLPERR_SHIT
; // too many basic files
553 memcpy(&fcbbas
, &fcb
, sizeof(fcbbas
));
557 if (!seenBasic
) return FLPERR_SHIT
;
559 // create simple autorun boot:
560 // 10 RANDOMIZE USR VAL "15619":REM RUN "file"
563 memset(secBuf
, 0, sizeof(secBuf
));
567 secBuf
[scpos
++] = 10;
568 // line size (will be fixed later)
572 secBuf
[scpos
++] = 0xf9U
; // RANDOMIZE
573 secBuf
[scpos
++] = 0xc0U
; // USR
574 secBuf
[scpos
++] = 0xb0U
; // VAL
575 secBuf
[scpos
++] = '"';
576 secBuf
[scpos
++] = '1';
577 secBuf
[scpos
++] = '5';
578 secBuf
[scpos
++] = '6';
579 secBuf
[scpos
++] = '1';
580 secBuf
[scpos
++] = '9';
581 secBuf
[scpos
++] = '"';
582 secBuf
[scpos
++] = ':';
583 secBuf
[scpos
++] = 0xeaU
; // REM
584 secBuf
[scpos
++] = ':';
585 secBuf
[scpos
++] = 0xf7U
; // RUN // 0xefU; // LOAD
586 secBuf
[scpos
++] = '"';
589 while (nlen
> 0 && fcbbas
.name
[nlen
-1] == ' ') --nlen
;
590 if (nlen
== 0) return FLPERR_SHIT
; // just in case
591 memcpy(secBuf
+scpos
, fcbbas
.name
, nlen
); scpos
+= nlen
;
592 secBuf
[scpos
++] = '"';
594 secBuf
[scpos
++] = 13;
596 secBuf
[2] = (scpos
-4)&0xffU
;
597 secBuf
[3] = ((scpos
-4)>>8)&0xffU
;
599 secBuf
[scpos
++] = 0x80;
600 secBuf
[scpos
++] = 0xaa;
602 secBuf
[scpos
++] = 10;
605 memcpy(fcbbas
.name
, "boot ", 8);
607 // last 4 bytes are not in length
608 fcbbas
.lst
= (scpos
-4)&0xff;
609 fcbbas
.hst
= ((scpos
-4)>>8)&0xff;
610 fcbbas
.llen
= (scpos
-4)&0xff;
611 fcbbas
.hlen
= ((scpos
-4)>>8)&0xff;
612 fcbbas
.slen
= 1; // one sector
613 fcbbas
.sec
= fcbbas
.trk
= 0; // will be set in `flpCreateFileTRD()`
615 if (flpCreateFileTRD(flp
, &fcbbas
) != FLPERR_OK
) return FLPERR_SHIT
;
616 if (flpPutSectorData(flp
, fcbbas
.trk
, fcbbas
.sec
+1, secBuf
, 256) != FLPERR_OK
) return FLPERR_SHIT
;
623 //==========================================================================
629 //==========================================================================
630 int flpRemoveBootTRD (disk_t
*flp
) {
631 return FLPERR_SHIT
; //TODO