libfusefdc: use "RUN" instead of "LOAD" in simple boot
[zymosis.git] / src / libfusefdc / diskfs_trdos.c
blobfd3f11b0512b1ef636530d5bff1ab676883c14ed
1 /*
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"
29 #include <string.h>
32 //==========================================================================
34 // flpIsValidDisk
36 //==========================================================================
37 int flpIsValidDisk (const disk_t *dsk) {
38 return
39 dsk && dsk->data &&
40 (dsk->sides == 1 || dsk->sides == 2) &&
41 (dsk->cylinders >= 40 && dsk->cylinders <= 83) &&
42 dsk->bpt >= 1024 /*&&
43 dsk->density*/;
47 //==========================================================================
49 // flpDetectDiskType
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 //==========================================================================
70 // flpGetSectorData
72 // <0: error
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;
80 int ssize = 0;
81 return (disk_read_sector(flp, tr, sc, buf, len, &ssize) == DISK_OK ? FLPERR_OK : FLPERR_SHIT);
85 //==========================================================================
87 // flpGetSectorDataEx
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;
94 int ssize = 0;
95 if (disk_read_sector(flp, tr, sc, buf, len, &ssize) != DISK_OK) return FLPERR_SHIT;
96 return ssize;
100 //==========================================================================
102 // flpGetSectorDataPtrRO
104 //==========================================================================
105 const uint8_t *flpGetSectorDataPtrRO (disk_t *flp, uint8_t tr, uint8_t sc) {
106 if (!flpIsValidDisk(flp)) return NULL;
107 int ssize = 0;
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;
119 int ssize = 0;
120 return disk_get_sector_data_ptr(flp, tr, sc, &ssize, NULL);
124 //==========================================================================
126 // flpPutSectorData
128 // <0: error
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 //==========================================================================
155 // flpIsDiskTRDOS
157 //==========================================================================
158 int flpIsDiskTRDOS (disk_t *flp) {
159 if (!flpIsValidDisk(flp)) return 0;
160 // trdos
161 uint8_t fbuf[0x100];
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 //==========================================================================
171 // calcHobSum
173 //==========================================================================
174 static uint16_t calcHobSum (const void *hdr) {
175 const uint8_t *buf = (const uint8_t *)hdr;
176 uint16_t res = 0;
177 for (unsigned int f = 0; f < 15; ++f) res += ((uint16_t)buf[f])*257+f;
178 return res;
182 //==========================================================================
184 // flpIsHoBetaBuf
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;
192 return 1;
196 //==========================================================================
198 // flpLoadHoBeta
200 //==========================================================================
201 int flpLoadHoBeta (disk_t *flp, FILE *fl) {
202 uint8_t secBuf[256];
203 TRFile nfle;
204 uint16_t csum;
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");
209 return FLPERR_SHIT;
211 if (fread(secBuf, 17, 1, fl) != 1) {
212 libfdcMsg(LIBFDC_MSG_ERROR, "cannot read HoBeta header");
213 return FLPERR_SHIT;
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);
219 return FLPERR_SHIT;
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");
225 return FLPERR_SHIT;
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");
230 return FLPERR_SHIT;
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");
234 return FLPERR_SHIT;
236 ++nfle.sec;
237 if (nfle.sec > 15) {
238 ++nfle.trk;
239 nfle.sec -= 16;
242 flp->dirty = 1;
243 return FLPERR_OK;
247 //==========================================================================
249 // flpSaveHoBeta
251 //==========================================================================
252 int flpSaveHoBeta (disk_t *flp, FILE *fl, int catidx) {
253 uint8_t buf[256]; // header/sector
254 uint16_t csum;
255 TRFile tfl;
256 uint8_t tr, sc;
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);
261 buf[13] = 0x00;
262 buf[14] = tfl.slen;
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;
267 tr = tfl.trk;
268 sc = tfl.sec;
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; }
274 return FLPERR_OK;
278 //==========================================================================
280 // flpFormatTRD
282 // format whole disk as 2x84x16x256 and init as TRDOS
284 //==========================================================================
285 int flpFormatTRD (disk_t *flp) {
286 if (!flp) return FLPERR_SHIT;
288 disk_close(flp);
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));
292 return FLPERR_SHIT;
295 if (disk_format(flp, 2, 80, 1, 16, 256, 2) != DISK_OK) return FLPERR_SHIT;
297 // first track
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,
303 uint8_t buf[256];
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));
310 return FLPERR_SHIT;
313 return FLPERR_OK;
318 //**************************************************************************
320 // TR-DOS utilities
322 //**************************************************************************
324 //==========================================================================
326 // flpCreateFileTRD
328 //==========================================================================
329 int flpCreateFileTRD (disk_t *flp, TRFile *dsc) {
330 if (flpDetectDiskType(flp) != FLP_DISK_TYPE_TRDOS) return FLPERR_SHIT;
331 uint8_t fbuf[0x100];
332 uint8_t files;
333 uint16_t freesec;
334 if (flpGetSectorData(flp, 0, 9, fbuf, 256) != FLPERR_OK) return FLPERR_SHIT;
335 dsc->sec = fbuf[0xe1];
336 dsc->trk = fbuf[0xe2];
337 files = fbuf[0xe4];
338 if (files > 127) return FLPERR_MANYFILES;
339 ++files;
340 fbuf[0xe4] = files;
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) {
349 fbuf[0xe1] -= 0x10;
350 ++fbuf[0xe2];
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);
357 flp->dirty = 1;
358 return FLPERR_OK;
362 //==========================================================================
364 // flpGetDirEntryTRD
366 //==========================================================================
367 int flpGetDirEntryTRD (disk_t *flp, TRFile *dst, int num) {
368 uint8_t fbuf[0x100];
369 int sec, pos;
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);
376 return FLPERR_OK;
380 //==========================================================================
382 // flpGetDirectoryTRD
384 //==========================================================================
385 int flpGetDirectoryTRD (disk_t *flp, TRFile *dst) {
386 int cnt = 0;
387 if (flpDetectDiskType(flp) == FLP_DISK_TYPE_TRDOS) {
388 uint8_t *dpt = (uint8_t *)dst;
389 uint8_t fbuf[0x100];
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;
393 unsigned fc;
394 for (fc = 0; fc < 16; ++fc) {
395 if (*ptr == 0) break;
396 if (dpt) {
397 memmove(dpt, ptr, 16);
398 dpt += 16;
400 ptr += 16;
401 ++cnt;
403 if (fc < 16) break;
406 return cnt;
411 //**************************************************************************
413 // 'boot' utilities
415 //**************************************************************************
417 int flpIsBootFCBTRD (const TRFile *fcb) {
418 if (!fcb) return 0;
419 return (memcmp(fcb->name, "boot B", 9) == 0);
423 //==========================================================================
425 // flpHasBootTRD
427 //==========================================================================
428 int flpHasBootTRD (disk_t *flp) {
429 if (flp != NULL && flpDetectDiskType(flp) == FLP_DISK_TYPE_TRDOS) {
430 TRFile cat[128];
431 int catSize = flpGetDirectoryTRD(flp, cat);
432 for (int i = 0; i < catSize; ++i) {
433 if (flpIsBootFCBTRD(&cat[i])) return i;
436 return FLPERR_SHIT;
440 //==========================================================================
442 // flpSetBootTRD
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);
448 if (idx >= 0) {
449 if (replace) return FLPERR_SHIT; //TODO
450 return FLPERR_OK;
452 return flpLoadHoBeta(flp, fl);
454 return FLPERR_SHIT;
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;
467 TRFile fcb;
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;
473 int nameok = 1;
474 int seennonspc = 0;
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));
482 return ffirst;
484 return FLPERR_SHIT;
488 //==========================================================================
490 // isGoodBasicName
492 //==========================================================================
493 static int isGoodBasicName (const TRFile *fcb) {
494 if (!fcb) return 0;
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;
502 return 0;
504 return 0;
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
517 TRFile fcb;
518 int fidx = 0;
519 for (;;) {
520 fidx = flpFindFirstBasicTRD(flp, &fcb, fidx);
521 if (fidx < 0) break;
522 ++fidx;
523 if (flpIsBootFCBTRD(&fcb)) return 0;
524 if (!isGoodBasicName(&fcb)) continue;
525 if (dst) memcpy(dst, &fcb, sizeof(TRFile));
526 return 1;
528 return 0;
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
541 TRFile fcbbas;
542 int fidx = 0;
543 int seenBasic = 0;
544 for (;;) {
545 TRFile fcb;
546 fidx = flpFindFirstBasicTRD(flp, &fcb, fidx);
547 if (fidx < 0) break;
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
552 seenBasic = 1;
553 memcpy(&fcbbas, &fcb, sizeof(fcbbas));
555 ++fidx;
557 if (!seenBasic) return FLPERR_SHIT;
559 // create simple autorun boot:
560 // 10 RANDOMIZE USR VAL "15619":REM RUN "file"
562 uint8_t secBuf[256];
563 memset(secBuf, 0, sizeof(secBuf));
564 size_t scpos = 0;
565 // line number
566 secBuf[scpos++] = 0;
567 secBuf[scpos++] = 10;
568 // line size (will be fixed later)
569 secBuf[scpos++] = 0;
570 secBuf[scpos++] = 0;
571 // line data
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++] = '"';
587 // put name
588 size_t nlen = 8;
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++] = '"';
593 // line end
594 secBuf[scpos++] = 13;
595 // fix line size
596 secBuf[2] = (scpos-4)&0xffU;
597 secBuf[3] = ((scpos-4)>>8)&0xffU;
598 // TR-DOS signature
599 secBuf[scpos++] = 0x80;
600 secBuf[scpos++] = 0xaa;
601 // start line
602 secBuf[scpos++] = 10;
603 secBuf[scpos++] = 0;
605 memcpy(fcbbas.name, "boot ", 8);
606 fcbbas.ext = 'B';
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;
618 flp->dirty = 1;
619 return FLPERR_OK;
623 //==========================================================================
625 // flpRemoveBootTRD
627 // TODO
629 //==========================================================================
630 int flpRemoveBootTRD (disk_t *flp) {
631 return FLPERR_SHIT; //TODO