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, &sector, &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, &sector, &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, &sector);
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, &sector);
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, &sector);
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, &sector_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