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
;
288 int dres
= disk_new(flp
, 2, 80, DISK_DD
, DISK_TRD
);
289 if (dres
!= DISK_OK
) {
290 libfdcMsg(LIBFDC_MSG_ERROR
, "cannot create disk: %s", disk_strerror(dres
));
294 if (disk_format(flp
, 2, 80, 1, 16, 256, 2) != DISK_OK
) return FLPERR_SHIT
;
297 const uint8_t trd_8e0
[32] = {
298 0x00,0x00,0x01,0x16,0x00,0xf0,0x09,0x10,0x00,0x00,0x20,0x20,0x20,0x20,0x20,0x20,
299 0x20,0x20,0x20,0x00,0x00,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x00,0x00,0x00,
303 memset(buf
, 0, sizeof(buf
));
304 memcpy(buf
+(0x8e0%256), trd_8e0
, 32);
306 dres
= disk_write_sector(flp
, 0, 9, buf
, 256);
307 if (dres
!= DISK_OK
) {
308 libfdcMsg(LIBFDC_MSG_ERROR
, "cannot write directory: %s", disk_strerror(dres
));
317 //**************************************************************************
321 //**************************************************************************
323 //==========================================================================
327 //==========================================================================
328 int flpCreateFileTRD (disk_t
*flp
, TRFile
*dsc
) {
329 if (flpDetectDiskType(flp
) != FLP_DISK_TYPE_TRDOS
) return FLPERR_SHIT
;
333 if (flpGetSectorData(flp
, 0, 9, fbuf
, 256) != FLPERR_OK
) return FLPERR_SHIT
;
334 dsc
->sec
= fbuf
[0xe1];
335 dsc
->trk
= fbuf
[0xe2];
337 if (files
> FLP_TRDOS_DIRCOUNT_MAX
) return FLPERR_MANYFILES
;
340 freesec
= fbuf
[0xe5]+(fbuf
[0xe6]<<8);
341 if (freesec
< dsc
->slen
) return FLPERR_NOSPACE
;
342 freesec
-= dsc
->slen
;
343 fbuf
[0xe5] = (freesec
&0xff);
344 fbuf
[0xe6] = ((freesec
&0xff00)>>8);
345 fbuf
[0xe1] += (dsc
->slen
&0x0f);
346 fbuf
[0xe2] += ((dsc
->slen
&0xf0)>>4);
347 if (fbuf
[0xe1] > 0x0f) {
351 if (flpPutSectorData(flp
, 0, 9, fbuf
, 256) != FLPERR_OK
) return FLPERR_SHIT
;
352 freesec
= ((files
&0xf0)>>4)+1;
353 if (flpGetSectorData(flp
, 0, freesec
, fbuf
, 256) != FLPERR_OK
) return FLPERR_SHIT
;
354 memmove(fbuf
+(((files
-1)&0x0f)<<4), dsc
, 16);
355 flpPutSectorData(flp
, 0, freesec
, fbuf
, 256);
361 //==========================================================================
365 //==========================================================================
366 int flpDeleteFileTRD (disk_t
*flp
, int num
) {
367 if (num
< 0 || num
>= FLP_TRDOS_DIRCOUNT_MAX
) return FLPERR_SHIT
;
368 if (flpDetectDiskType(flp
) != FLP_DISK_TYPE_TRDOS
) return FLPERR_SHIT
;
370 if (flpGetSectorData(flp
, 0, 9, fbuf
, 256) != FLPERR_OK
) return FLPERR_SHIT
;
371 uint8_t files
= fbuf
[0xe4];
372 if (files
== 0 || num
>= files
) return FLPERR_MANYFILES
;
373 const int sec
= ((num
&0xf0)>>4)+1; // sector
374 const int pos
= ((num
&0x0f)<<4); // file number inside sector
375 uint8_t markByte
= 0x01;
376 if (num
== files
-1) {
377 --fbuf
[0xe4]; // decrement number of files
378 if (flpPutSectorData(flp
, 0, 9, fbuf
, 256) != FLPERR_OK
) return FLPERR_NOSPACE
;
379 // load directory sector
380 if (flpGetSectorData(flp
, 0, sec
, fbuf
, 256) != FLPERR_OK
) return FLPERR_NOSPACE
;
381 // this file is no more, and shorten the directory too
384 // load directory sector
385 if (flpGetSectorData(flp
, 0, sec
, fbuf
, 256) != FLPERR_OK
) return FLPERR_NOSPACE
;
386 if (fbuf
[pos
] != markByte
) {
387 fbuf
[pos
] = markByte
;
388 if (flpPutSectorData(flp
, 0, sec
, fbuf
, 256) != FLPERR_OK
) return FLPERR_NOSPACE
;
394 //==========================================================================
398 //==========================================================================
399 int flpGetDirEntryTRD (disk_t
*flp
, TRFile
*dst
, int num
) {
402 if (flpDetectDiskType(flp
) != FLP_DISK_TYPE_TRDOS
) return FLPERR_SHIT
;
403 if (num
< 0 || num
> FLP_TRDOS_DIRCOUNT_MAX
) return FLPERR_MANYFILES
;
404 sec
= ((num
&0xf0)>>4); // sector
405 pos
= ((num
&0x0f)<<4); // file number inside sector
406 if (flpGetSectorData(flp
, 0, sec
+1, fbuf
, 256) != FLPERR_OK
) return FLPERR_NOSPACE
;
407 if (dst
!= NULL
) memmove(dst
, fbuf
+pos
, 16);
412 //==========================================================================
414 // flpGetDirectoryTRD
416 //==========================================================================
417 int flpGetDirectoryTRD (disk_t
*flp
, TRFile
*dst
) {
419 if (flpDetectDiskType(flp
) == FLP_DISK_TYPE_TRDOS
) {
420 uint8_t *dpt
= (uint8_t *)dst
;
422 for (int sc
= 1; sc
< 9; ++sc
) {
423 if (flpGetSectorData(flp
, 0, sc
, fbuf
, 256) != FLPERR_OK
) break;
424 const uint8_t *ptr
= fbuf
;
426 for (fc
= 0; fc
< 16; ++fc
) {
427 if (*ptr
== 0) break;
429 memmove(dpt
, ptr
, 16);
443 //**************************************************************************
447 //**************************************************************************
449 int flpIsBootFCBTRD (const TRFile
*fcb
) {
451 return (memcmp(fcb
->name
, "boot B", 9) == 0);
455 //==========================================================================
459 //==========================================================================
460 int flpHasBootTRD (disk_t
*flp
) {
461 if (flpDetectDiskType(flp
) == FLP_DISK_TYPE_TRDOS
) {
463 int catSize
= flpGetDirectoryTRD(flp
, cat
);
464 for (int i
= 0; i
< catSize
; ++i
) {
465 if (flpIsBootFCBTRD(&cat
[i
])) return i
;
472 //==========================================================================
476 //==========================================================================
477 int flpSetBootTRD (disk_t
*flp
, FILE *fl
, int replace
) {
478 if (flpDetectDiskType(flp
) == FLP_DISK_TYPE_TRDOS
) {
479 int idx
= flpHasBootTRD(flp
);
481 if (!replace
) return FLPERR_OK
;
482 if (flpDeleteFileTRD(flp
, idx
) != FLPERR_OK
) return FLPERR_SHIT
;
484 return flpLoadHoBeta(flp
, fl
);
490 //==========================================================================
492 // flpFindFirstBasicTRD
494 //==========================================================================
495 int flpFindFirstBasicTRD (disk_t
*flp
, TRFile
*dst
, int ffirst
) {
496 if (flpDetectDiskType(flp
) != FLP_DISK_TYPE_TRDOS
) return FLPERR_SHIT
;
497 if (ffirst
< 0) ffirst
= 0;
499 for (; ffirst
< FLP_TRDOS_DIRCOUNT_MAX
; ++ffirst
) {
500 if (flpGetDirEntryTRD(flp
, &fcb
, ffirst
) != 0) break;
501 if (fcb
.name
[0] == 0) break; // end of directory
502 if (fcb
.name
[0] == 1) continue; // deleted
503 if (fcb
.ext
!= 'B') continue;
506 for (unsigned f
= 0; f
< 8; ++f
) {
507 const uint8_t ch
= (unsigned)(fcb
.name
[f
]&0xffU
);
508 if (ch
< 32 || ch
== '"') { nameok
= 0; break; }
509 if (ch
!= 32) seennonspc
= 1;
511 if (!nameok
|| !seennonspc
) continue;
512 if (dst
) memcpy(dst
, &fcb
, sizeof(TRFile
));
519 //==========================================================================
523 //==========================================================================
524 static int isGoodBasicName (const TRFile
*fcb
) {
526 if (fcb
->ext
!= 'B') return 0;
527 if (fcb
->name
[0] <= 32 || fcb
->name
[0] >= 127) return 0;
528 for (int f
= 0; f
< 8; ++f
) {
529 if (fcb
->name
[f
] >= 'A' && fcb
->name
[f
] <= 'Z') return 1;
530 //if (fcb->name[f] >= 'a' && fcb->name[f] <= 'z') return 1;
531 if (fcb
->name
[f
] >= '0' && fcb
->name
[f
] <= '9') continue;
532 if (fcb
->name
[f
] == ' ') continue;
539 //==========================================================================
541 // flpHasAnyNonBootBasicNonBootTRD
543 //==========================================================================
544 int flpHasAnyNonBootBasicNonBootTRD (disk_t
*flp
, TRFile
*dst
) {
545 if (flpDetectDiskType(flp
) != FLP_DISK_TYPE_TRDOS
) return 0;
546 // check if we have any non-boot basic file
548 int seenAllBasic
= 0;
551 fidx
= flpFindFirstBasicTRD(flp
, &fcb
, fidx
);
554 if (flpIsBootFCBTRD(&fcb
)) return 0;
555 if (!isGoodBasicName(&fcb
)) {
556 if (fcb
.ext
== 'B') ++seenAllBasic
;
559 if (dst
) memcpy(dst
, &fcb
, sizeof(TRFile
));
562 return (seenAllBasic
== 1);
566 //==========================================================================
568 // flpSetBootSimpleTRD
570 //==========================================================================
571 int flpSetBootSimpleTRD (disk_t
*flp
) {
572 if (flpDetectDiskType(flp
) != FLP_DISK_TYPE_TRDOS
) return FLPERR_SHIT
;
574 // check if we have any non-boot basic file
578 int seenAllBasic
= 0;
581 fidx
= flpFindFirstBasicTRD(flp
, &fcb
, fidx
);
583 if (flpIsBootFCBTRD(&fcb
)) return FLPERR_SHIT
; // nothing to do
584 //fprintf(stderr, "...: <%.8s : %c> : %d\n", fcb.name, fcb.ext, isGoodBasicName(&fcb));
585 if (fcb
.ext
== 'B') ++seenAllBasic
;
586 if (isGoodBasicName(&fcb
)) {
587 if (seenBasic
) return FLPERR_SHIT
; // too many basic files
589 memcpy(&fcbbas
, &fcb
, sizeof(fcbbas
));
594 if (!(seenBasic
|| seenAllBasic
== 1)) return FLPERR_SHIT
;
597 if (seenAllBasic
!= 1) return FLPERR_SHIT
; // just in case
601 fidx
= flpFindFirstBasicTRD(flp
, &fcb
, fidx
);
603 if (fcb
.ext
== 'B') {
604 memcpy(&fcbbas
, &fcb
, sizeof(fcbbas
));
609 if (fidx
< 0) return FLPERR_SHIT
;
612 // create simple autorun boot:
613 // 10 RANDOMIZE USR VAL "15619":REM RUN "file"
616 memset(secBuf
, 0, sizeof(secBuf
));
620 secBuf
[scpos
++] = 10;
621 // line size (will be fixed later)
625 secBuf
[scpos
++] = 0xf9U
; // RANDOMIZE
626 secBuf
[scpos
++] = 0xc0U
; // USR
627 secBuf
[scpos
++] = 0xb0U
; // VAL
628 secBuf
[scpos
++] = '"';
629 secBuf
[scpos
++] = '1';
630 secBuf
[scpos
++] = '5';
631 secBuf
[scpos
++] = '6';
632 secBuf
[scpos
++] = '1';
633 secBuf
[scpos
++] = '9';
634 secBuf
[scpos
++] = '"';
635 secBuf
[scpos
++] = ':';
636 secBuf
[scpos
++] = 0xeaU
; // REM
637 secBuf
[scpos
++] = ':';
638 secBuf
[scpos
++] = 0xf7U
; // RUN // 0xefU; // LOAD
639 secBuf
[scpos
++] = '"';
642 while (nlen
> 0 && fcbbas
.name
[nlen
-1] == ' ') --nlen
;
643 if (nlen
== 0) return FLPERR_SHIT
; // just in case
644 memcpy(secBuf
+scpos
, fcbbas
.name
, nlen
); scpos
+= nlen
;
645 secBuf
[scpos
++] = '"';
647 secBuf
[scpos
++] = 13;
649 secBuf
[2] = (scpos
-4)&0xffU
;
650 secBuf
[3] = ((scpos
-4)>>8)&0xffU
;
652 secBuf
[scpos
++] = 0x80;
653 secBuf
[scpos
++] = 0xaa;
655 secBuf
[scpos
++] = 10;
658 memcpy(fcbbas
.name
, "boot ", 8);
660 // last 4 bytes are not in length
661 fcbbas
.lst
= (scpos
-4)&0xff;
662 fcbbas
.hst
= ((scpos
-4)>>8)&0xff;
663 fcbbas
.llen
= (scpos
-4)&0xff;
664 fcbbas
.hlen
= ((scpos
-4)>>8)&0xff;
665 fcbbas
.slen
= 1; // one sector
666 fcbbas
.sec
= fcbbas
.trk
= 0; // will be set in `flpCreateFileTRD()`
668 if (flpCreateFileTRD(flp
, &fcbbas
) != FLPERR_OK
) return FLPERR_SHIT
;
669 if (flpPutSectorData(flp
, fcbbas
.trk
, fcbbas
.sec
+1, secBuf
, 256) != FLPERR_OK
) return FLPERR_SHIT
;
676 //==========================================================================
682 //==========================================================================
683 int flpRemoveBootTRD (disk_t
*flp
) {
684 if (flpDetectDiskType(flp
) != FLP_DISK_TYPE_TRDOS
) return FLPERR_SHIT
;
689 fidx
= flpFindFirstBasicTRD(flp
, &fcb
, fidx
);
691 if (flpIsBootFCBTRD(&fcb
)) {
692 if (flpDeleteFileTRD(flp
, fidx
) != FLPERR_OK
) return FLPERR_SHIT
;
696 return FLPERR_SHIT
; //TODO