1 // license:GPL-2.0+
2 // copyright-holders:Jonathan Edwards
3 /****************************************************************************
4
5 bml3.c
6
7 Hitachi bml3 disk images
8
9 By Jonathan Edwards, based on rsdos.c (both use Microsoft BASIC)
10
11 ****************************************************************************/
12
13 /* Supported Hitachi floppy formats are:
14 - 3" or 5"1/4 single density, single-sided: 40 tracks, 16 sectors/track, 128 bytes/sector
15 - (used with MP-1805 floppy disk controller card)
16 - 5"1/4 double density, double-sided: 40 tracks, 16 sectors/track, 256 bytes/sector
17 - (first track on first head may be single density)
18 - (used with MP-1802 floppy disk controller card)
19 */
20
21 #include <cstdio>
22 #include <cstring>
23 #include <cstdlib>
24 #include "imgtool.h"
25 #include "iflopimg.h"
26
27 #define MAX_SECTOR_SIZE 256
28
29 struct bml3_diskinfo
30 {
31 uint16_t sector_size; /* 128 or 256 */
32 uint8_t heads; /* 1 or 2 */
33 uint8_t fat_start_sector; /* in cylinder 20, start sector of FAT */
34 uint8_t fat_start_offset; /* start byte of FAT in sector */
35 uint8_t fat_sectors; /* the number of sectors in the FAT */
36 uint8_t dirent_start_sector; /* in cylinder 20, start sector of directory entries */
37 uint8_t granule_sectors; /* how many sectors per granule */
38 uint8_t first_granule_cylinder; /* the number of the first cylinder with granule numbers assigned */
39 uint8_t variant; /* 0 - older version, uses EOF to terminate files, 1 - newer version, stores file length */
40 };
41
42 /* this structure mirrors the structure of a directory entry on disk */
43 struct bml3_dirent
44 {
45 char fname[8];
46 char fext[3];
47 uint8_t ftype;
48 uint8_t asciiflag;
49 uint8_t first_granule;
50 uint16_t lastsectorbytes;
51 // TODO there are some 'unused' bytes here that are sometimes used to store a timestamp, maybe support this?
52 };
53
54 struct bml3_direnum
55 {
56 int index;
57 int eof;
58 };
59
60 #define MAX_GRANULEMAP_SIZE 256
61
62 struct granule_list_t {
63 uint8_t granules[MAX_GRANULEMAP_SIZE];
64 uint8_t granule_count;
65 uint8_t last_granule_sectors;
66 };
67
68 #define BML3_OPTIONS_FTYPE 'T'
69 #define BML3_OPTIONS_ASCII 'M'
70
71 static imgtoolerr_t bml3_diskimage_deletefile(imgtool::partition &partition, const char *fname);
72
73
74
75 /*********************************************************************
76 Imgtool module code
77 *********************************************************************/
78
bml3_get_diskinfo(imgtool::image & image)79 static bml3_diskinfo *bml3_get_diskinfo(imgtool::image &image)
80 {
81 return (bml3_diskinfo *) imgtool_floppy_extrabytes(image);
82 }
83
84
85
max_dirents(imgtool::image & image)86 static int max_dirents(imgtool::image &image)
87 {
88 bml3_diskinfo *info = bml3_get_diskinfo(image);
89 return (16 * info->heads + 1 - info->dirent_start_sector)*(info->sector_size/32);
90 }
91
92
93
dirent_location(imgtool::image & image,int index_loc,uint8_t * head,uint8_t * track,uint8_t * sector,uint8_t * offset)94 static void dirent_location(imgtool::image &image, int index_loc, uint8_t *head, uint8_t *track, uint8_t *sector, uint8_t *offset)
95 {
96 bml3_diskinfo *info = bml3_get_diskinfo(image);
97 *track = 20;
98 *sector = info->dirent_start_sector + index_loc / (info->sector_size / 32);
99 *head = 0;
100 if (*sector > 16) {
101 // wrap to second head
102 *sector -= 16;
103 (*head)++;
104 }
105 *offset = index_loc % (info->sector_size/32) * 32;
106 }
107
108
109
get_bml3_dirent(imgtool::image & f,int index_loc,struct bml3_dirent * ent)110 static floperr_t get_bml3_dirent(imgtool::image &f, int index_loc, struct bml3_dirent *ent)
111 {
112 floperr_t err;
113 uint8_t head, track, sector, offset;
114 uint8_t buf[32];
115 bml3_diskinfo *info = bml3_get_diskinfo(f);
116 dirent_location(f, index_loc, &head, &track, §or, &offset);
117 err = floppy_read_sector(imgtool_floppy(f), head, track, sector, offset, (void *) buf, sizeof(buf));
118 memset(ent, 0, sizeof(*ent));
119 switch (info->variant) {
120 case 0:
121 memcpy(&ent->fname, &buf[0], 8);
122 ent->ftype = buf[11];
123 ent->asciiflag = buf[12];
124 ent->first_granule = buf[14];
125 break;
126 case 1:
127 memcpy(&ent->fname, &buf[0], 8);
128 memcpy(&ent->fext, &buf[8], 3);
129 ent->ftype = buf[11];
130 ent->asciiflag = buf[12];
131 ent->first_granule = buf[13];
132 ent->lastsectorbytes = (buf[14] << 8) | buf[15];
133 break;
134 default:
135 return FLOPPY_ERROR_INVALIDIMAGE;
136 }
137 return err;
138 }
139
140
141
put_bml3_dirent(imgtool::image & f,int index_loc,const struct bml3_dirent * ent)142 static floperr_t put_bml3_dirent(imgtool::image &f, int index_loc, const struct bml3_dirent *ent)
143 {
144 floperr_t err;
145 uint8_t head, track, sector, offset;
146 uint8_t buf[32];
147 bml3_diskinfo *info = bml3_get_diskinfo(f);
148 if (index_loc >= max_dirents(f))
149 return (floperr_t)IMGTOOLERR_FILENOTFOUND;
150 dirent_location(f, index_loc, &head, &track, §or, &offset);
151 memset(buf, 0, sizeof(buf));
152 switch (info->variant) {
153 case 0:
154 memcpy(&buf[0], &ent->fname, 8);
155 buf[11] = ent->ftype;
156 buf[12] = ent->asciiflag;
157 buf[14] = ent->first_granule;
158 break;
159 case 1:
160 memcpy(&buf[0], &ent->fname, 8);
161 memcpy(&buf[8], &ent->fext, 3);
162 buf[11] = ent->ftype;
163 buf[12] = ent->asciiflag;
164 buf[13] = ent->first_granule;
165 buf[14] = ent->lastsectorbytes >> 8;
166 buf[15] = ent->lastsectorbytes & 0xff;
167 break;
168 default:
169 return FLOPPY_ERROR_INVALIDIMAGE;
170 }
171 err = floppy_write_sector(imgtool_floppy(f), head, track, sector, offset, (void *) buf, sizeof(buf), 0); /* TODO: pass ddam argument from imgtool */
172 return err;
173 }
174
175
176
177 /* fnamebuf must have at least 13 bytes */
get_dirent_fname(char * fnamebuf,const struct bml3_dirent * ent)178 static void get_dirent_fname(char *fnamebuf, const struct bml3_dirent *ent)
179 {
180 char *s;
181
182 memset(fnamebuf, 0, 13);
183 memcpy(fnamebuf, ent->fname, sizeof(ent->fname));
184 rtrim(fnamebuf);
185 s = fnamebuf + strlen(fnamebuf);
186 *(s++) = '.';
187 memcpy(s, ent->fext, sizeof(ent->fext));
188 rtrim(s);
189
190 /* If no extension, remove period */
191 if (*s == '\0')
192 s[-1] = '\0';
193 }
194
195
196
lookup_bml3_file(imgtool::image & f,const char * fname,struct bml3_dirent * ent,int * position)197 static imgtoolerr_t lookup_bml3_file(imgtool::image &f, const char *fname, struct bml3_dirent *ent, int *position)
198 {
199 int i;
200 floperr_t ferr;
201 char fnamebuf[13];
202
203 i = 0;
204 fnamebuf[0] = '\0';
205
206 do
207 {
208 do
209 {
210 ferr = get_bml3_dirent(f, i++, ent);
211 if (ferr)
212 return imgtool_floppy_error(ferr);
213 }
214 while(ent->fname[0] == '\0');
215
216
217 if (ent->fname[0] != -1)
218 get_dirent_fname(fnamebuf, ent);
219 }
220 while((ent->fname[0] != -1) && core_stricmp(fnamebuf, fname));
221
222 if (ent->fname[0] == -1)
223 return IMGTOOLERR_FILENOTFOUND;
224
225 if (position)
226 *position = i - 1;
227 return (imgtoolerr_t)0;
228 }
229
230
231
get_granule_count(imgtool::image & img)232 static uint8_t get_granule_count(imgtool::image &img)
233 {
234 // uint16_t tracks;
235 uint16_t disk_granules;
236 bml3_diskinfo *info = bml3_get_diskinfo(img);
237
238 // This always returns 82 for D88, so not quite right
239 // tracks = floppy_get_tracks_per_disk(imgtool_floppy(img));
240
241 // The number of granules is primarily constrained by the disk capacity.
242 disk_granules = (40 - 1 - info->first_granule_cylinder) * info->heads * (16 / info->granule_sectors);
243 // Also, granule numbers from 0xC0 upwards are reserved for terminating a granule chain
244 return (uint8_t)((disk_granules < 0xC0) ? disk_granules : 0xC0);
245 }
246
247 /* granule_map must be an array of MAX_GRANULEMAP_SIZE bytes */
get_granule_map(imgtool::image & img,uint8_t * granule_map,uint8_t * granule_count)248 static floperr_t get_granule_map(imgtool::image &img, uint8_t *granule_map, uint8_t *granule_count)
249 {
250 bml3_diskinfo *info = bml3_get_diskinfo(img);
251 uint8_t count;
252
253 count = get_granule_count(img);
254 if (granule_count)
255 *granule_count = count;
256
257 // The first byte of the granule map sector is ignored (and expected to be 0)
258 return floppy_read_sector(imgtool_floppy(img), 0, 20, info->fat_start_sector, info->fat_start_offset, granule_map, count);
259 }
260
261
262
put_granule_map(imgtool::image & img,const uint8_t * granule_map,uint8_t granule_count)263 static floperr_t put_granule_map(imgtool::image &img, const uint8_t *granule_map, uint8_t granule_count)
264 {
265 bml3_diskinfo *info = bml3_get_diskinfo(img);
266 return floppy_write_sector(imgtool_floppy(img), 0, 20, info->fat_start_sector, info->fat_start_offset, granule_map, granule_count, 0); /* TODO: pass ddam argument from imgtool */
267 }
268
269
270
271
granule_location(imgtool::image & image,uint8_t granule,uint8_t * head,uint8_t * track,uint8_t * sector)272 static void granule_location(imgtool::image &image, uint8_t granule, uint8_t *head, uint8_t *track, uint8_t *sector)
273 {
274 bml3_diskinfo *info = bml3_get_diskinfo(image);
275 uint16_t abs_track = granule * info->granule_sectors / 16;
276 *head = abs_track % info->heads;
277 *track = abs_track / info->heads + info->first_granule_cylinder;
278 // skip filesystem cylinder
279 if (*track >= 20)
280 (*track)++;
281 *sector = granule * info->granule_sectors % 16 + 1;
282 }
283
284
285
transfer_granule(imgtool::image & img,uint8_t granule,int length,imgtool::stream & f,imgtoolerr_t (* proc)(imgtool::image &,int,int,int,int,size_t,imgtool::stream &))286 static imgtoolerr_t transfer_granule(imgtool::image &img, uint8_t granule, int length, imgtool::stream &f, imgtoolerr_t (*proc)(imgtool::image &, int, int, int, int, size_t, imgtool::stream &))
287 {
288 imgtoolerr_t err = IMGTOOLERR_SUCCESS;
289 uint8_t head, track, sector;
290 granule_location(img, granule, &head, &track, §or);
291 if (length > 0)
292 err = proc(img, head, track, sector, 0, length, f);
293 return err;
294 }
295
296
transfer_from_granule(imgtool::image & img,uint8_t granule,int length,imgtool::stream & destf)297 static imgtoolerr_t transfer_from_granule(imgtool::image &img, uint8_t granule, int length, imgtool::stream &destf)
298 {
299 return transfer_granule(img, granule, length, destf, imgtool_floppy_read_sector_to_stream);
300 }
301
302
303
transfer_to_granule(imgtool::image & img,uint8_t granule,int length,imgtool::stream & sourcef)304 static imgtoolerr_t transfer_to_granule(imgtool::image &img, uint8_t granule, int length, imgtool::stream &sourcef)
305 {
306 return transfer_granule(img, granule, length, sourcef, imgtool_floppy_write_sector_from_stream);
307 }
308
309
310
read_granule(imgtool::image & img,uint8_t granule,int offset,int length,uint8_t * buf)311 static floperr_t read_granule(imgtool::image &img, uint8_t granule, int offset, int length, uint8_t *buf)
312 {
313 uint8_t head, track, sector;
314 granule_location(img, granule, &head, &track, §or);
315 return floppy_read_sector(imgtool_floppy(img), head, track, sector, offset, buf, length);
316 }
317
318
319
write_granule(imgtool::image & img,uint8_t granule,int offset,int length,const uint8_t * buf)320 static floperr_t write_granule(imgtool::image &img, uint8_t granule, int offset, int length, const uint8_t *buf)
321 {
322 uint8_t head, track, sector;
323 granule_location(img, granule, &head, &track, §or);
324 return floppy_write_sector(imgtool_floppy(img), head, track, sector, offset, buf, length, 0); /* TODO: pass ddam argument from imgtool */
325 }
326
327
328
list_granules(struct bml3_dirent * ent,imgtool::image & img,struct granule_list_t * granule_list)329 static imgtoolerr_t list_granules(struct bml3_dirent *ent, imgtool::image &img, struct granule_list_t *granule_list)
330 {
331 floperr_t ferr;
332 uint8_t max_granules;
333 uint8_t granule;
334 uint8_t usedmap[MAX_GRANULEMAP_SIZE]; /* Used to detect infinite loops */
335 uint8_t granule_map[MAX_GRANULEMAP_SIZE];
336 bml3_diskinfo *info = bml3_get_diskinfo(img);
337
338 ferr = get_granule_map(img, granule_map, &max_granules);
339 if (ferr)
340 return imgtool_floppy_error(ferr);
341
342 memset(usedmap, 0, max_granules);
343
344 granule = ent->first_granule;
345 granule_list->granule_count = 0;
346
347 while(!usedmap[granule] && granule < max_granules)
348 {
349 usedmap[granule] = 1;
350 granule_list->granules[granule_list->granule_count++] = granule;
351 granule = granule_map[granule];
352 }
353
354 granule_list->last_granule_sectors = granule - 0xc0;
355 if (info->variant == 0) {
356 // add final incomplete sector
357 granule_list->last_granule_sectors++;
358 }
359
360 // A value of zero (variant 1) and max (variant 0) seem to indicate a file open for writing.
361 // Strictly speaking this means the image is corrupt, although a real system will happily read
362 // garbage from the file.
363 if (granule_list->last_granule_sectors > info->granule_sectors)
364 return IMGTOOLERR_CORRUPTIMAGE;
365
366 return IMGTOOLERR_SUCCESS;
367 }
368
369
370
get_file_size(struct bml3_dirent * ent,imgtool::image & img,const struct granule_list_t * granule_list,size_t * size)371 static imgtoolerr_t get_file_size(struct bml3_dirent *ent, imgtool::image &img, const struct granule_list_t *granule_list, size_t *size)
372 {
373 floperr_t ferr;
374 size_t last_sector_bytes = 0;
375 bml3_diskinfo *info = bml3_get_diskinfo(img);
376
377 // TODO are these special cases valid, or maybe indicate a corrupt image?
378 if (granule_list->granule_count == 0) {
379 *size = 0;
380 return IMGTOOLERR_SUCCESS;
381 }
382 else if (granule_list->last_granule_sectors == 0) {
383 *size = info->sector_size * ((granule_list->granule_count - 1) * info->granule_sectors);
384 return IMGTOOLERR_SUCCESS;
385 }
386
387 // determine size excluding final sector
388 *size = info->sector_size * ((granule_list->granule_count - 1) * info->granule_sectors + granule_list->last_granule_sectors - 1);
389
390 // determine bytes used in final sector
391 switch (info->variant) {
392 case 0:
393 // look for EOF (ASCII SUB) and trailing NULs in final sector
394 {
395 uint8_t buf[MAX_SECTOR_SIZE];
396 ferr = read_granule(img, granule_list->granules[granule_list->granule_count-1], info->sector_size * (granule_list->last_granule_sectors - 1), info->sector_size, buf);
397 if (ferr)
398 return imgtool_floppy_error(ferr);
399 for (last_sector_bytes = info->sector_size - 1; ; last_sector_bytes--) {
400 if (buf[last_sector_bytes] != 0)
401 break;
402 if (last_sector_bytes == 0)
403 break;
404 }
405 if (buf[last_sector_bytes] != 0x1a) {
406 last_sector_bytes++;
407 }
408 }
409 break;
410 case 1:
411 last_sector_bytes = ent->lastsectorbytes;
412 break;
413 }
414
415 // TODO is it valid for last_sector_bytes == 0?
416 if (last_sector_bytes > info->sector_size) {
417 return IMGTOOLERR_CORRUPTIMAGE;
418 }
419 *size += last_sector_bytes;
420 return IMGTOOLERR_SUCCESS;
421 }
422
423
424
process_bml3_file(struct bml3_dirent * ent,imgtool::image & img,imgtool::stream * destf,size_t * size)425 static imgtoolerr_t process_bml3_file(struct bml3_dirent *ent, imgtool::image &img, imgtool::stream *destf, size_t *size)
426 {
427 imgtoolerr_t err;
428 size_t remaining_size, granule_size;
429 bml3_diskinfo *info = bml3_get_diskinfo(img);
430 struct granule_list_t granule_list;
431 granule_list.granule_count = 0;
432
433 err = list_granules(ent, img, &granule_list);
434 if (err)
435 return err;
436 err = get_file_size(ent, img, &granule_list, size);
437 if (err)
438 return err;
439
440 if (destf) {
441 remaining_size = *size;
442 granule_size = info->granule_sectors * info->sector_size;
443
444 for (int c = 0; c < granule_list.granule_count; c++) {
445 if (granule_size >= remaining_size)
446 granule_size = remaining_size;
447 transfer_from_granule(img, granule_list.granules[c], granule_size, *destf);
448 remaining_size -= granule_size;
449 }
450 }
451 return IMGTOOLERR_SUCCESS;
452 }
453
454
455
456 /* create a new directory entry with a specified name */
prepare_dirent(uint8_t variant,struct bml3_dirent * ent,const char * fname)457 static imgtoolerr_t prepare_dirent(uint8_t variant, struct bml3_dirent *ent, const char *fname)
458 {
459 const char *fname_end;
460 const char *fname_ext;
461 int fname_ext_len;
462
463 memset(ent, '\0', sizeof(*ent));
464 memset(ent->fname, ' ', sizeof(ent->fname));
465 memset(ent->fext, ' ', sizeof(ent->fext));
466
467 fname_end = strchr(fname, '.');
468 if (fname_end)
469 fname_ext = fname_end + 1;
470 else
471 fname_end = fname_ext = fname + strlen(fname);
472
473 fname_ext_len = strlen(fname_ext);
474
475 switch (variant) {
476 case 0:
477 /* 8-character max filename */
478 if (((fname_end - fname) > 8) || (fname_ext_len > 0))
479 return IMGTOOLERR_BADFILENAME;
480 break;
481 case 1:
482 /*8.3 filename */
483 if (((fname_end - fname) > 8) || (fname_ext_len > 3))
484 return IMGTOOLERR_BADFILENAME;
485 break;
486 default:
487 return IMGTOOLERR_CORRUPTIMAGE;
488 }
489
490 memcpy(ent->fname, fname, fname_end - fname);
491 memcpy(ent->fext, fname_ext, fname_ext_len);
492
493 /* By default, set as a type 2 binary file */
494 ent->ftype = 2;
495 ent->asciiflag = 0;
496 return IMGTOOLERR_SUCCESS;
497 }
498
499
500
bml3_diskimage_open(imgtool::image & image,imgtool::stream::ptr && dummy)501 static imgtoolerr_t bml3_diskimage_open(imgtool::image &image, imgtool::stream::ptr &&dummy)
502 {
503 // imgtoolerr_t err;
504 floperr_t ferr;
505 bml3_diskinfo *info = bml3_get_diskinfo(image);
506 floppy_image_legacy *floppy = imgtool_floppy(image);
507 const struct FloppyCallbacks *callbacks = floppy_callbacks(floppy);
508
509 // probe disk geometry to guess format
510 int heads_per_disk = callbacks->get_heads_per_disk(floppy);
511 uint32_t sector_length;
512 ferr = callbacks->get_sector_length(floppy, 0, 20, 1, §or_length);
513 if (ferr)
514 return imgtool_floppy_error(ferr);
515 int sectors_per_track = callbacks->get_sectors_per_track(floppy, 0, 20);
516
517 if (heads_per_disk == 2 && sector_length == 128 && sectors_per_track == 16) {
518 // single-sided, single-density
519 info->sector_size = 128;
520 info->heads = 1;
521 info->fat_start_sector = 1;
522 info->fat_start_offset = 5;
523 info->fat_sectors = 2;
524 info->dirent_start_sector = 7;
525 info->granule_sectors = 4;
526 info->first_granule_cylinder = 0;
527 info->variant = 0;
528 }
529 else if (heads_per_disk == 2 && sector_length == 256 && sectors_per_track == 16) {
530 // double-sided, double-density
531 info->sector_size = 256;
532 info->heads = 2;
533 info->fat_start_sector = 2;
534 info->fat_start_offset = 1;
535 info->fat_sectors = 1;
536 info->dirent_start_sector = 5;
537 info->granule_sectors = 8;
538 info->first_granule_cylinder = 1;
539 info->variant = 1;
540 }
541 else {
542 // invalid or unsupported format
543 return IMGTOOLERR_CORRUPTIMAGE;
544 }
545
546 return IMGTOOLERR_SUCCESS;
547 }
548
549
550
bml3_diskimage_nextenum(imgtool::directory & enumeration,imgtool_dirent & ent)551 static imgtoolerr_t bml3_diskimage_nextenum(imgtool::directory &enumeration, imgtool_dirent &ent)
552 {
553 floperr_t ferr;
554 imgtoolerr_t err;
555 size_t filesize;
556 struct bml3_direnum *rsenum;
557 struct bml3_dirent rsent;
558 char fname[13];
559 imgtool::image &image(enumeration.image());
560
561 rsenum = (struct bml3_direnum *) enumeration.extra_bytes();
562
563 /* Did we hit the end of file before? */
564 if (rsenum->eof)
565 goto eof;
566
567 do
568 {
569 if (rsenum->index >= max_dirents(image))
570 goto eof;
571
572 ferr = get_bml3_dirent(image, rsenum->index++, &rsent);
573 if (ferr)
574 return imgtool_floppy_error(ferr);
575 }
576 while(rsent.fname[0] == '\0');
577
578 /* Now are we at the eof point? */
579 if (rsent.fname[0] == -1)
580 {
581 rsenum->eof = 1;
582 eof:
583 ent.eof = 1;
584 }
585 else
586 {
587 /* Not the end of file */
588 err = process_bml3_file(&rsent, image, nullptr, &filesize);
589 if (err)
590 return err;
591
592 if (filesize == ((size_t) -1))
593 {
594 /* corrupt! */
595 ent.filesize = 0;
596 ent.corrupt = 1;
597 }
598 else
599 {
600 ent.filesize = filesize;
601 ent.corrupt = 0;
602 }
603 ent.eof = 0;
604
605 get_dirent_fname(fname, &rsent);
606
607 snprintf(ent.filename, ARRAY_LENGTH(ent.filename), "%s", fname);
608 snprintf(ent.attr, ARRAY_LENGTH(ent.attr), "%d %c", (int) rsent.ftype, (char) (rsent.asciiflag + 'B'));
609 }
610 return IMGTOOLERR_SUCCESS;
611 }
612
613
614
bml3_diskimage_freespace(imgtool::partition & partition,uint64_t * size)615 static imgtoolerr_t bml3_diskimage_freespace(imgtool::partition &partition, uint64_t *size)
616 {
617 floperr_t ferr;
618 uint8_t i;
619 size_t s = 0;
620 uint8_t granule_count;
621 uint8_t granule_map[MAX_GRANULEMAP_SIZE];
622 imgtool::image &image(partition.image());
623 bml3_diskinfo *info = bml3_get_diskinfo(image);
624
625 ferr = get_granule_map(image, granule_map, &granule_count);
626 if (ferr)
627 return imgtool_floppy_error(ferr);
628
629 for (i = 0; i < granule_count; i++)
630 {
631 if (granule_map[i] == 0xff)
632 s += (info->granule_sectors * info->sector_size);
633 }
634 *size = s;
635 return (imgtoolerr_t)FLOPPY_ERROR_SUCCESS;
636 }
637
638
639
delete_entry(imgtool::image & img,struct bml3_dirent * ent,int pos)640 static imgtoolerr_t delete_entry(imgtool::image &img, struct bml3_dirent *ent, int pos)
641 {
642 floperr_t ferr;
643 unsigned char g, i;
644 uint8_t granule_count;
645 uint8_t granule_map[MAX_GRANULEMAP_SIZE];
646
647 /* Write a NUL in the filename, marking it deleted */
648 ent->fname[0] = 0;
649 ferr = put_bml3_dirent(img, pos, ent);
650 if (ferr)
651 return imgtool_floppy_error(ferr);
652
653 ferr = get_granule_map(img, granule_map, &granule_count);
654 if (ferr)
655 return imgtool_floppy_error(ferr);
656
657 /* Now free up the granules */
658 g = ent->first_granule;
659 while (g < granule_count)
660 {
661 i = granule_map[g];
662 granule_map[g] = 0xff;
663 g = i;
664 }
665
666 ferr = put_granule_map(img, granule_map, granule_count);
667 if (ferr)
668 return imgtool_floppy_error(ferr);
669
670 return IMGTOOLERR_SUCCESS;
671 }
672
673
674
bml3_diskimage_readfile(imgtool::partition & partition,const char * fname,const char * fork,imgtool::stream & destf)675 static imgtoolerr_t bml3_diskimage_readfile(imgtool::partition &partition, const char *fname, const char *fork, imgtool::stream &destf)
676 {
677 imgtoolerr_t err;
678 struct bml3_dirent ent;
679 size_t size;
680 imgtool::image &img(partition.image());
681
682 err = lookup_bml3_file(img, fname, &ent, nullptr);
683 if (err)
684 return err;
685
686 err = process_bml3_file(&ent, img, &destf, &size);
687 if (err)
688 return err;
689
690 if (size == (size_t) -1)
691 return IMGTOOLERR_CORRUPTIMAGE;
692
693 return (imgtoolerr_t)0;
694 }
695
696
697
bml3_diskimage_writefile(imgtool::partition & partition,const char * fname,const char * fork,imgtool::stream & sourcef,util::option_resolution * writeoptions)698 static imgtoolerr_t bml3_diskimage_writefile(imgtool::partition &partition, const char *fname, const char *fork, imgtool::stream &sourcef, util::option_resolution *writeoptions)
699 {
700 floperr_t ferr;
701 imgtoolerr_t err;
702 imgtool::image &img(partition.image());
703 bml3_diskinfo *info = bml3_get_diskinfo(img);
704 struct bml3_dirent ent, ent2;
705 size_t i;
706 uint64_t sz, read_sz;
707 uint64_t freespace = 0;
708 unsigned char *gptr;
709 uint8_t granule_count;
710 uint8_t granule_map[MAX_GRANULEMAP_SIZE];
711 uint8_t eof_buf[MAX_SECTOR_SIZE];
712
713 // one-time setup of eof_buf
714 memset(eof_buf, 0, sizeof(eof_buf));
715 eof_buf[0] = 0x1A;
716
717 /* can we write to this image? */
718 if (floppy_is_read_only(imgtool_floppy(img)))
719 return IMGTOOLERR_READONLY;
720
721 err = bml3_diskimage_freespace(partition, &freespace);
722 if (err)
723 return err;
724
725 /* is there enough space? */
726 sz = read_sz = sourcef.size();
727 if (info->variant == 0) {
728 // also need to write EOF
729 sz++;
730 }
731 if (sz > freespace)
732 return IMGTOOLERR_NOSPACE;
733
734 /* setup our directory entry */
735 err = prepare_dirent(info->variant, &ent, fname);
736 if (err)
737 return err;
738
739 ent.ftype = writeoptions->lookup_int(BML3_OPTIONS_FTYPE);
740 ent.asciiflag = uint8_t(writeoptions->lookup_int(BML3_OPTIONS_ASCII)) - 1;
741 gptr = &ent.first_granule;
742
743 ferr = get_granule_map(img, granule_map, &granule_count);
744 if (ferr)
745 return imgtool_floppy_error(ferr);
746
747 unsigned char g = 0x00;
748 uint32_t granule_bytes = info->granule_sectors * info->sector_size;
749
750 do
751 {
752 while (granule_map[g] != 0xff)
753 {
754 g++;
755 if ((g >= granule_count) || (g == 0))
756 return IMGTOOLERR_UNEXPECTED; /* We should have already verified that there is enough space */
757 }
758 *gptr = g;
759 gptr = &granule_map[g];
760
761
762 i = std::min(read_sz, uint64_t(granule_bytes));
763 if (i > 0) {
764 err = transfer_to_granule(img, g, i, sourcef);
765 if (err)
766 return err;
767 read_sz -= i;
768 sz -= i;
769 }
770 if (i < info->granule_sectors * info->sector_size && sz > 0) {
771 // write EOF and trailing NULs in the final sector
772 ferr = write_granule(img, g, i, (info->granule_sectors * info->sector_size - i - 1) % info->sector_size + 1, eof_buf);
773 if (ferr)
774 return imgtool_floppy_error(ferr);
775 sz--;
776 i++;
777 }
778
779 /* Go to next granule */
780 g++;
781 }
782 while(sz > 0);
783
784 /* Now that we are done with the file, we need to specify the final entry
785 * in the file allocation table
786 */
787 *gptr = 0xc0 + ((i + info->sector_size-1) / info->sector_size) - (info->variant == 0 ? 1 : 0);
788 ent.lastsectorbytes = (i - 1) % info->sector_size + 1;
789
790 /* delete file if it already exists */
791 err = bml3_diskimage_deletefile(partition, fname);
792 if (err && err != IMGTOOLERR_FILENOTFOUND)
793 return err;
794
795 /* Now we need to find an empty directory entry */
796 i = -1;
797 do
798 {
799 ferr = get_bml3_dirent(img, ++i, &ent2);
800 if (ferr)
801 return imgtool_floppy_error(ferr);
802 }
803 while(ent2.fname[0] != '\0' && ent2.fname[0] != -1);
804
805 ferr = put_bml3_dirent(img, i, &ent);
806 if (ferr)
807 return imgtool_floppy_error(ferr);
808
809 /* write the granule map back out */
810 ferr = put_granule_map(img, granule_map, granule_count);
811 if (ferr)
812 return imgtool_floppy_error(ferr);
813
814 return IMGTOOLERR_SUCCESS;
815 }
816
817
818
bml3_diskimage_deletefile(imgtool::partition & partition,const char * fname)819 static imgtoolerr_t bml3_diskimage_deletefile(imgtool::partition &partition, const char *fname)
820 {
821 imgtoolerr_t err;
822 imgtool::image &image(partition.image());
823 int pos = 0;
824 struct bml3_dirent ent;
825
826 err = lookup_bml3_file(image, fname, &ent, &pos);
827 if (err)
828 return err;
829
830 return delete_entry(image, &ent, pos);
831 }
832
833
834
bml3_diskimage_suggesttransfer(imgtool::partition & partition,const char * fname,imgtool_transfer_suggestion * suggestions,size_t suggestions_length)835 static imgtoolerr_t bml3_diskimage_suggesttransfer(imgtool::partition &partition, const char *fname, imgtool_transfer_suggestion *suggestions, size_t suggestions_length)
836 {
837 imgtoolerr_t err;
838 imgtool::image &image(partition.image());
839 struct bml3_dirent ent;
840 int pos;
841
842 if (fname)
843 {
844 err = lookup_bml3_file(image, fname, &ent, &pos);
845 if (err)
846 return err;
847
848 if (ent.asciiflag == 0xFF)
849 {
850 /* ASCII file */
851 suggestions[0].viability = SUGGESTION_RECOMMENDED;
852 suggestions[0].filter = filter_eoln_getinfo;
853 suggestions[1].viability = SUGGESTION_POSSIBLE;
854 suggestions[1].filter = NULL;
855 }
856 else if (ent.ftype == 0)
857 {
858 /* tokenized BASIC file */
859 suggestions[0].viability = SUGGESTION_RECOMMENDED;
860 suggestions[0].filter = NULL;
861 suggestions[1].viability = SUGGESTION_POSSIBLE;
862 suggestions[1].filter = filter_bml3bas_getinfo;
863 }
864 }
865 else
866 {
867 suggestions[0].viability = SUGGESTION_RECOMMENDED;
868 suggestions[0].filter = NULL;
869 suggestions[1].viability = SUGGESTION_POSSIBLE;
870 suggestions[1].filter = filter_eoln_getinfo;
871 suggestions[2].viability = SUGGESTION_POSSIBLE;
872 suggestions[2].filter = filter_bml3bas_getinfo;
873 }
874
875 return IMGTOOLERR_SUCCESS;
876 }
877
878
879
880 /*********************************************************************
881 Imgtool module declaration
882 *********************************************************************/
883
884 OPTION_GUIDE_START( bml3_writefile_optionguide )
885 OPTION_ENUM_START( BML3_OPTIONS_FTYPE, "ftype", "File type" )
886 OPTION_ENUM( 0, "basic", "Basic" )
887 OPTION_ENUM( 1, "data", "Data" )
888 OPTION_ENUM( 2, "binary", "Binary" )
889 OPTION_ENUM( 3, "assembler", "Assembler Source" )
890 OPTION_ENUM_END
891 OPTION_ENUM_START( BML3_OPTIONS_ASCII, "ascii", "Ascii flag" )
892 OPTION_ENUM( 0, "ascii", "Ascii" )
893 OPTION_ENUM( 1, "binary", "Binary" )
894 OPTION_ENUM_END
895 OPTION_GUIDE_END
896
897
898
bml3_get_info(const imgtool_class * imgclass,uint32_t state,union imgtoolinfo * info)899 void bml3_get_info(const imgtool_class *imgclass, uint32_t state, union imgtoolinfo *info)
900 {
901 switch(state)
902 {
903 /* --- the following bits of info are returned as 64-bit signed integers --- */
904 case IMGTOOLINFO_INT_PREFER_UCASE: info->i = 1; break;
905 case IMGTOOLINFO_INT_IMAGE_EXTRA_BYTES: info->i = sizeof(bml3_diskinfo); break;
906 case IMGTOOLINFO_INT_DIRECTORY_EXTRA_BYTES: info->i = sizeof(struct bml3_direnum); break;
907
908 /* --- the following bits of info are returned as NULL-terminated strings --- */
909 case IMGTOOLINFO_STR_NAME: strcpy(info->s = imgtool_temp_str(), "bml3"); break;
910 case IMGTOOLINFO_STR_DESCRIPTION: strcpy(info->s = imgtool_temp_str(), "Basic Master Level 3 format"); break;
911 case IMGTOOLINFO_STR_FILE: strcpy(info->s = imgtool_temp_str(), __FILE__); break;
912 case IMGTOOLINFO_STR_EOLN: strcpy(info->s = imgtool_temp_str(), "\r"); break;
913 case IMGTOOLINFO_STR_WRITEFILE_OPTSPEC: strcpy(info->s = imgtool_temp_str(), "T0-[2]-3;M0-[1]"); break;
914
915 /* --- the following bits of info are returned as pointers to data or functions --- */
916 case IMGTOOLINFO_PTR_MAKE_CLASS: info->make_class = imgtool_floppy_make_class; break;
917 case IMGTOOLINFO_PTR_FLOPPY_OPEN: info->open = bml3_diskimage_open; break;
918 case IMGTOOLINFO_PTR_NEXT_ENUM: info->next_enum = bml3_diskimage_nextenum; break;
919 case IMGTOOLINFO_PTR_FREE_SPACE: info->free_space = bml3_diskimage_freespace; break;
920 case IMGTOOLINFO_PTR_READ_FILE: info->read_file = bml3_diskimage_readfile; break;
921 case IMGTOOLINFO_PTR_WRITE_FILE: info->write_file = bml3_diskimage_writefile; break;
922 case IMGTOOLINFO_PTR_DELETE_FILE: info->delete_file = bml3_diskimage_deletefile; break;
923 case IMGTOOLINFO_PTR_SUGGEST_TRANSFER: info->suggest_transfer = bml3_diskimage_suggesttransfer; break;
924 case IMGTOOLINFO_PTR_WRITEFILE_OPTGUIDE: info->writefile_optguide = &bml3_writefile_optionguide; break;
925 case IMGTOOLINFO_PTR_FLOPPY_FORMAT: info->p = (void *) floppyoptions_default; break;
926 }
927 }
928