// license:BSD-3-Clause // copyright-holders:Raphael Nabet /* Handlers for ti99 disk images We support the following types of image: * floppy disks ("DSK format") in v9t9, pc99 and old mess image format * hard disks ("WIN format") in MAME harddisk format (256-byte sectors only, i.e. no SCSI) Files are extracted in TIFILES format. There is a compile-time option to extract text files in flat format instead, but I need to re-implement it properly (using filters comes to mind). Raphael Nabet, 2002-2004 TODO: - finish and test hd support ***test sibling FDR support*** - check allocation bitmap against corruption when an image is opened */ #include "imgtool.h" #include "harddisk.h" #include "imghd.h" #include "opresolv.h" #include #include #include #include #include /* Concepts shared by both disk structures: ---------------------------------------- In both formats, the entire disk surface is split into physical records, each 256-byte-long. For best efficiency, the sector size is usually 256 bytes so that one sector is equivalent to a physical record (this is true with floppies and HFDC hard disks). If the sector size is not 256 bytes (e.g. 512 bytes with SCSI hard disks, possibly 128 bytes on a TI-99 simulator running on TI-990), sectors are grouped or split to form 256-byte physical records. Physical records are numbered from 0, with the first starting on sector 0 track 0. To avoid confusion with physical records in files, such physical records will be called "absolute physical records", abbreviated as "absolute physrecs" or "aphysrecs". Disk space is allocated in units referred to as AUs. Each AU represents an integral number of 256-byte physical records. The number of physrecs per AU varies depending on the disk format and size. AUs are numbered from 0, with the first starting with absolute physrec 0. Absolute physrec 0 contains a 256-byte record with volume information, which is called "Volume Information Block", abbreviated as "vib". To keep track of which areas on the disk are allocated and which are free, disk managers maintain a bit map of allocated AUs (called "allocation bitmap", abbreviated as "abm"). Each bit in the allocation bitmap represents an AU: if a bit is 0, the AU is free; if a bit is 1, the AU is allocated (or possibly bad if the disk manager can track bad sectors). Where on the disk the abm is located depends on the disk structure (see structure-specific description for details). Files are implemented as a succession of 256-byte physical records. To avoid confusion with absolute physical records, these physical records will be called "file physical records", abbreviated as "file physrecs" or "fphysrecs". Programs do normally not access file physical records directly. They may call high-level file routines that enable to create either fixed-length logical records of any size from 1 through 255, or variable-length records of any size from 0 through 254: logical records are grouped by file managers into 256-byte physical records. Some disk managers (HFDC and SCSI) allow programs to create fixed-length records larger than 255 bytes, too, but few programs use this possibility. Additionally, program files can be created: program files do not implement any logical record, and can be seen as a flat byte stream, not unlike files under MSDOS, UNIX and the C standard library. (Unfortunately, the API for program files lacks flexibility, and most programs that need to process a flat byte stream will use fixed-size records of 128 bytes.) Fixed-length records (reclen < 256) are grouped like this: fphysrec 0 fphysrec 1 _______________ _______________ |R0 |R1 |R2 |X| |R3 |R4 |R5 |X| --------------- --------------- \_/ \_/ unused unused (size < reclen) (size < reclen) Fixed-length records (reclen > 256) are grouped like this: fphysrec 0 fphysrec 1 fphysrec 2 fphysrec 3 ... _________________ _________________ _________________ _________________ | R0 (#0-255) | | R0 (#256-511) | |R0 (#512-end)|X| | R1 (#0-255) | ... ----------------- ----------------- ----------------- ----------------- \_/ unused Variable length records are grouped like this: fphysrec 0: byte: ------------------------------ 0 |l0 = length of record 0 data| |----------------------------| 1 | | | | : | record 0 data | | | l0 | | |----------------------------| l0+1 |l1 = length of record 1 data| |----------------------------| l0+2 | | | | : | record 1 data | | | l0+l1+1 | | |----------------------------| : : : : : : |----------------------------| n |lm = length of record m data| |----------------------------| n+1 | | | | : | record m data | | | n+lm | | |----------------------------| n+lm+1 |>ff: EOR mark | |----------------------------| n+lm+2 | | | | : | unused | : |(we should have size < lm+1)| | | 255 | | ------------------------------ fphysrec 1: byte: ------------------------------ 0 |lm+1=length of record 0 data| |----------------------------| 1 | | | | : | record m+1 data | | | lm+1 | | |----------------------------| : : : : : : I think the EOR mark is not required if all 256 bytes of a physical record are used by logical records. All files are associated with a "file descriptor record" ("fdr") that holds file information (name, format, length) and points to the data AUs. The WIN disk structure also supports sibling FDRs, in case a file is so fragmented that all the data pointers cannot fit in one FDR; the DSK disk structure does not implement any such feature, and you may be unable to append data to an existing file if it is too fragmented, even though the disk is not full. DSK disk structure: ------------------- The DSK disk structure is used for floppy disks, and a few RAMDISKs as well. It supports 1600 AUs and 16 MBytes at most (the 16 MByte value is a theoretical maximum, as I doubt anyone has ever created so big a DSK volume). The disk sector size is always 256 bytes (the only potential exceptions are the SCSI floppy disk units and the TI-99 simulator on TI-990, but I don't know anything about either). For some reason, the number of physrecs per AU is always a power of 2. Originally, AUs always were one physical record each. However, since the allocation bitmap cannot support more than 1600 AUs, disks larger than 400kbytes need to have several physical records per AU. Note that relatively few disk controllers implement AUs that span multiple physical records (IIRC, these are Myarc's HFDC, SCSI DSR with floppy disk interface, and some upgraded Myarc and Corcomp DD floppy controllers). The allocation bitmap is located in the VIB. It is 200 bytes long, and supports 1600 AUs at most. Directories are implemented with a "File Descriptor Index Record" (FDIR) for each directory. The FDIR is array of 0 through 128 words, containing the aphysrec address of each fdr in the directory, sorted by ascending file name, terminated with a 0. Note that, while we should be prepared to read images images with 128 entries, I think (not 100% sure) that we should write no more than 127 for compatibility with some existing disk managers. Originally, the DSK structure only supported one directory level (i.e. the root directory level). The FDIR record for the root directory is always located in aphysrec 1. Moreover, Myarc extended the DSK structure to support up to 3 subdirectories in the root directory (note that there is no support for more subdirs, or for subdirs located in subdirs). To do so, they used an unused field of the VIB to hold the 10-char name of each directory and the aphysrec address of the associated FDIR record. aphysrec 0: Volume Information Block (VIB): see below aphysrec 1: FDIR for root directory Remaining AUs are used for fdr and data (and subdirectory FDIR if applicable). There is one FDIR record per directory; the FDIR points to the FDR for each file in the directory. The FDR (File Descriptor Record) contains the file information (name, format, length, pointers to data physrecs/AUs, etc). WIN disk structure: ------------------- The WIN disk structure is used for hard disks. It supports 65535 AUs and 256 MBytes at most. The disk sector size is 256 bytes on HFDC, 512 bytes on SCSI. 512-byte sectors are split into 256-byte physical records. We may have from 1 through 16 physrecs per AUs. Since we cannot have more than 65535 AUs, we need to have several physical records per AU in disks larger than 16 Mbytes. Contrary to the DSK disk structure, the WIN disk structure supports hierarchic subdirectories, with a limit of 114 subdirectories and 127 files per directory. Each directory is associated with both a "Directory Descriptor Record" (DDR) and a "File Descriptor Index Record" (FDIR). The only exception is the root directory, which uses the VIB instead of a DDR (the VIB includes all the important fields of the DDR). The DDR contains some directory info (name, number of files and subdirectories directly enclosed in the directory, AU address of the FDIR of the parent directory, etc), the AU address of the associated FDIR, and the AU address of the DDR of up to 114 subdirectories, sorted by ascending directory name. The WIN FDIR is similar to, yet different from, the DSK FDIR: it contains up to 127 (vs. 128) AU address (vs. aphysrec address) of each fdr in the directory, sorted by ascending file name. Additionally, it includes the AU address of the associated DDR. When a file has more than 54 data fragments, the disk manager creates sibling FDRs that hold additional fragments. (Sibling FDRs are called offspring FDRs in most existing documentation, but I prefer "sibling FDRs" as "offspring FDRs" wrongly suggests a directory-like hierarchy. Incidentally, I cannot really figure out why they chose to duplicate the FDR rather than create a file extension record, but there must be a reason.) Note that sibling FDRs usually fill unused physrecs in the AU allocated for the eldest FDR, and a new AU is allocated for new sibling FDRs only when the first AU is full. aphysrec 0: Volume Information Block (VIB): see below aphysrec 1-n (where n = 1+SUP(number_of_AUs/2048)): Volume bitmap Remaining AUs are used for ddr, fdir, fdr and data. */ /* Since string length is encoded with a byte, the maximum length of a string is 255. Since we need to add a device name (1 char minimum, typically 4 chars) and a '.' separator, we chose a practical value of 250 (though few applications will accept paths of such length). */ #define MAX_PATH_LEN 253 #define MAX_SAFE_PATH_LEN 250 #define MAX_DIR_LEVEL 125 /* We need to put a recursion limit to avoid endless recursion hazard */ #if 0 #pragma mark MISCELLANEOUS UTILITIES #endif /* Miscellaneous utilities that are used to handle TI data types */ struct UINT16BE { uint8_t bytes[2]; }; struct UINT16LE { uint8_t bytes[2]; }; static inline uint16_t get_UINT16BE(UINT16BE word) { return (word.bytes[0] << 8) | word.bytes[1]; } static inline void set_UINT16BE(UINT16BE *word, uint16_t data) { word->bytes[0] = (data >> 8) & 0xff; word->bytes[1] = data & 0xff; } static inline uint16_t get_UINT16LE(UINT16LE word) { return word.bytes[0] | (word.bytes[1] << 8); } static inline void set_UINT16LE(UINT16LE *word, uint16_t data) { word->bytes[0] = data & 0xff; word->bytes[1] = (data >> 8) & 0xff; } /* Convert a C string to a 10-character file name (padded with spaces if necessary) dst (O): destination 10-character array src (I): source C string */ static void str_to_fname(char dst[10], const char *src) { int i; i = 0; /* copy 10 characters at most */ if (src) while ((i<10) && (src[i]!='\0')) { dst[i] = src[i]; i++; } /* pad with spaces */ while (i<10) { dst[i] = ' '; i++; } } /* Convert a 10-character file name to a C string (removing trailing spaces if necessary) dst (O): destination C string src (I): source 10-character array n (I): length of dst buffer (string may be truncated if less than 11) */ static void fname_to_str(char *dst, const char src[10], int n) { int i; int last_nonspace; /* copy 10 characters at most */ if (--n > 10) n = 10; /* copy filename */ i = 0; last_nonspace = -1; while (i MAX_PATH_LEN) || strchr(fpath, ' ')) return 1; /* check that each path element has an acceptable length */ element_start = fpath; do { /* find next path element */ element_end = strchr(element_start, '.'); element_len = element_end ? (element_end - element_start) : strlen(element_start); if ((element_len > 10) || (element_len == 0)) return 1; /* iterate */ if (element_end) element_start = element_end+1; else element_start = NULL; } while (element_start); return 0; } #if 0 #pragma mark - #pragma mark LEVEL 1 DISK ROUTINES #endif /* Level 1 deals with accessing the disk hardware (in our case, the raw disk image). Higher level routines will only know the disk as a succession of 256-byte-long physical records addressed by a linear address. */ /* Disk geometry */ struct ti99_geometry { int secspertrack; int cylinders; int heads; }; /* Physical sector address */ struct ti99_sector_address { int sector; int cylinder; int side; }; /* Time stamp (used in fdr, and WIN VIB/DDR) */ struct ti99_date_time { uint8_t time_MSB, time_LSB;/* 0-4: hour, 5-10: minutes, 11-15: seconds/2 */ uint8_t date_MSB, date_LSB;/* 0-6: year, 7-10: month, 11-15: day */ }; /* Subdirectory descriptor (HFDC only) The HFDC supports up to 3 subdirectories. */ struct dsk_subdir { char name[10]; /* subdirectory name (10 characters, pad with spaces) */ UINT16BE fdir_aphysrec; /* aphysrec address of fdir record for this subdirectory */ }; /* DSK VIB record Most fields in this record are only relevant to level 2 routines, but level 1 routines need the disk geometry information extracted from the VIB. */ struct dsk_vib { char name[10]; /* disk volume name (10 characters, pad with spaces) */ UINT16BE totphysrecs; /* total number of physrecs on disk (usually 360, */ /* 720, 640, 1280 or 1440). (TI originally */ /* said it should be the total number of AUs, */ /* but, in practice, DSRs that supports disks */ /* over 400kbytes (e.g. HFDC) write the total */ /* number of physrecs.) */ uint8_t secspertrack; /* sectors per track (usually 9 (FM SD), */ /* 16 or 18 (MFM DD), or 36 (MFM HD) */ uint8_t id[3]; /* 'DSK' */ uint8_t protection; /* 'P' if disk is protected, ' ' otherwise. */ uint8_t cylinders; /* tracks per side (usually 40) */ uint8_t heads; /* sides (1 or 2) */ uint8_t density; /* density: 1 (FM SD), 2 (MFM DD), or 3 (MFM HD) */ dsk_subdir subdir[3]; /* descriptor for up to 3 subdirectories (HFDC only) */ /* reserved by TI */ uint8_t abm[200]; /* allocation bitmap: there is one bit per AU. */ /* A binary 1 in a bit position indicates that */ /* the allocation unit associated with that */ /* bit has been allocated. */ /* Bits corresponding to system reserved AUs, */ /* non-existent AUs, and bad AUs, are set to one, too. */ /* (AU 0 is associated to LSBit of byte 0, */ /* AU 7 to MSBit of byte 0, AU 8 to LSBit */ /* of byte 1, etc.) */ }; enum ti99_img_format { if_mess, if_v9t9, if_pc99_fm, if_pc99_mfm, if_harddisk }; /* level-1 disk image descriptor */ struct ti99_lvl1_imgref { ti99_img_format img_format; /* tells the image format */ imgtool::stream *file_handle; /* imgtool file handle */ struct mess_hard_disk_file harddisk_handle; /* MAME harddisk handle (harddisk format) */ ti99_geometry geometry; /* geometry */ unsigned pc99_track_len; /* unformatted track length (pc99 format) */ uint32_t *pc99_data_offset_array; /* offset for each sector (pc99 format) */ }; /* calculate CRC for data address marks or sector data crc (I/O): current CRC value to be updated (initialize to 0xffff before calling this function for the first time) value: new byte of data to update the CRC with */ static void calc_crc(uint16_t *crc, uint8_t value) { uint8_t l, h; l = value ^ (*crc >> 8); *crc = (*crc & 0xff) | (l << 8); l >>= 4; l ^= (*crc >> 8); *crc <<= 8; *crc = (*crc & 0xff00) | l; l = (l << 4) | (l >> 4); h = l; l = (l << 2) | (l >> 6); l &= 0x1f; *crc = *crc ^ (l << 8); l = h & 0xf0; *crc = *crc ^ (l << 8); l = (h << 1) | (h >> 7); l &= 0xe0; *crc = *crc ^ l; } /* Parse a PC99 disk image file_handle (I/O): imgtool file handle fm_format (I): if true, the image is in FM format, otherwise it is in MFM format pass (I): 0 for first pass, 1 for second pass vib (O): buffer where the vib should be stored (first pass) geometry (O): disk image geometry (second pass) data_offset_array (O): array of data offset to generate (second pass) out_track_len (O): track length is returned there Return imgtool error code */ #define MAX_TRACK_LEN 6872 #define DATA_OFFSET_NONE 0xffffffff static int parse_pc99_image(imgtool::stream &file_handle, int fm_format, int pass, dsk_vib *vib, const ti99_geometry *geometry, uint32_t *data_offset_array, unsigned *out_track_len) { int track_len, num_tracks; /* length of a track in bytes, and number of tracks */ int phys_track; int expected_cylinder, expected_head; int track_start_pos, track_pos; uint8_t c; uint8_t cylinder, head, sector; int seclen; uint8_t crc1, crc2; uint16_t crc; long data_offset; uint8_t track_buf[MAX_TRACK_LEN]; int i; if (fm_format) track_len = 3253; else track_len = 6872; if (out_track_len) *out_track_len = track_len; if (file_handle.size() % track_len) return IMGTOOLERR_CORRUPTIMAGE; num_tracks = file_handle.size() / track_len; if (num_tracks <= 0) return IMGTOOLERR_CORRUPTIMAGE; /* we only support 40-track-per-side images */ if ((num_tracks != 40) && (num_tracks != 80)) return IMGTOOLERR_UNIMPLEMENTED; if (pass == 1) { /* initialize offset map */ for (head = 0; head < geometry->heads; head++) for (cylinder = 0; cylinder < geometry->cylinders; cylinder++) for (sector = 0; sector < geometry->secspertrack; sector++) data_offset_array[(head*geometry->cylinders + cylinder)*geometry->secspertrack + sector] = DATA_OFFSET_NONE; } /* rewind to start of file */ file_handle.seek(0, SEEK_SET); /* pass 0 only looks for vib in track 0; pass 1 scans every track */ for (phys_track=0; phys_track < ((pass == 1) ? num_tracks : 1); phys_track++) { if (file_handle.read(track_buf, track_len) != track_len) return IMGTOOLERR_READERROR; /* we only support 40-track-per-side images */ expected_cylinder = phys_track % 40; expected_head = phys_track / 40; track_start_pos = 0; while (track_start_pos < track_len) { if (fm_format) { do { c = track_buf[track_start_pos]; track_start_pos++; } while ((c != 0xfe) && (track_start_pos < track_len)); if (c != 0xfe) break; track_pos = track_start_pos; crc = 0xffff; calc_crc(& crc, c); } else { do { c = track_buf[track_start_pos]; track_start_pos++; } while ((c != 0xa1) && (track_start_pos < track_len)); if (c != 0xa1) break; track_pos = track_start_pos; c = track_buf[track_pos]; track_pos++; if (track_pos == track_len) track_pos = 0; if (c != 0xa1) continue; c = track_buf[track_pos]; track_pos++; if (track_pos == track_len) track_pos = 0; if (c != 0xa1) continue; crc = 0xffff; calc_crc(& crc, c); c = track_buf[track_pos]; track_pos++; if (track_pos == track_len) track_pos = 0; if (c != 0xfe) continue; } c = track_buf[track_pos]; track_pos++; if (track_pos == track_len) track_pos = 0; cylinder = c; calc_crc(& crc, c); c = track_buf[track_pos]; track_pos++; if (track_pos == track_len) track_pos = 0; head = c; calc_crc(& crc, c); c = track_buf[track_pos]; track_pos++; if (track_pos == track_len) track_pos = 0; sector = c; calc_crc(& crc, c); c = track_buf[track_pos]; track_pos++; if (track_pos == track_len) track_pos = 0; seclen = 128 << c; calc_crc(& crc, c); c = track_buf[track_pos]; track_pos++; if (track_pos == track_len) track_pos = 0; crc1 = c; calc_crc(& crc, c); c = track_buf[track_pos]; track_pos++; if (track_pos == track_len) track_pos = 0; crc2 = c; calc_crc(& crc, c); #if 0 /* CRC seems to be completely hosed */ if (crc) printf("aargh!"); #endif if ((seclen != 256) || (crc1 != 0xf7) || (crc2 != 0xf7) || (cylinder != expected_cylinder) || (head != expected_head) || ((pass == 1) && ((cylinder >= geometry->cylinders) || (head >= geometry->heads) || (sector >= geometry->secspertrack)))) continue; c = track_buf[track_pos]; track_pos++; if (track_pos == track_len) track_pos = 0; while (c == (fm_format ? 0xff : 0x4e)) { c = track_buf[track_pos]; track_pos++; if (track_pos == track_len) track_pos = 0; } while (c == 0x00) { c = track_buf[track_pos]; track_pos++; if (track_pos == track_len) track_pos = 0; } if (fm_format) { if (c != 0xfb) continue; crc = 0xffff; calc_crc(& crc, c); } else { if (c != 0xa1) continue; c = track_buf[track_pos]; track_pos++; if (track_pos == track_len) track_pos = 0; if (c != 0xa1) continue; c = track_buf[track_pos]; track_pos++; if (track_pos == track_len) track_pos = 0; if (c != 0xa1) continue; crc = 0xffff; calc_crc(& crc, c); c = track_buf[track_pos]; track_pos++; if (track_pos == track_len) track_pos = 0; if (c != 0xfb) continue; } data_offset = track_pos; for (i=0; icylinders + cylinder)*geometry->secspertrack + sector] != DATA_OFFSET_NONE) /* error: duplicate sector */ return IMGTOOLERR_CORRUPTIMAGE; data_offset_array[(head*geometry->cylinders + cylinder)*geometry->secspertrack + sector] = data_offset; break; } } } if (pass == 0) return IMGTOOLERR_CORRUPTIMAGE; if (pass == 1) { /* check offset map */ for (head = 0; head < geometry->heads; head++) for (cylinder = 0; cylinder < geometry->cylinders; cylinder++) for (sector = 0; sector < geometry->secspertrack; sector++) if (data_offset_array[(head*geometry->cylinders + cylinder)*geometry->secspertrack + sector] == DATA_OFFSET_NONE) /* error: missing sector */ return IMGTOOLERR_CORRUPTIMAGE; } return 0; } /* Read the volume information block (aphysrec 0) assuming no geometry information. (Called when an image is opened to figure out the geometry information.) file_handle (I/O): imgtool file handle img_format (I): image format (MESS, V9T9 or PC99) dest (O): pointer to 256-byte destination buffer Return imgtool error code */ static int read_image_vib_no_geometry(imgtool::stream &file_handle, ti99_img_format img_format, dsk_vib *dest) { int reply; switch (img_format) { case if_mess: case if_v9t9: /* seek to sector */ reply = file_handle.seek(0, SEEK_SET); if (reply) return IMGTOOLERR_READERROR; /* read it */ reply = file_handle.read(dest, 256); if (reply != 256) return IMGTOOLERR_READERROR; return 0; case if_pc99_fm: case if_pc99_mfm: return parse_pc99_image(file_handle, img_format == if_pc99_fm, 0, dest, NULL, NULL, NULL); case if_harddisk: /* not implemented, because we don't need it */ break; } return IMGTOOLERR_UNIMPLEMENTED; } /* Open a disk image at level 1 file_handle (I/O): imgtool file handle img_format (I): image format (MESS, V9T9, PC99, or MAME harddisk) l1_img (O): level-1 image handle vib (O): buffer where the vib should be stored (floppy images only) Return imgtool error code */ static imgtoolerr_t open_image_lvl1(imgtool::stream::ptr &&file_handle, ti99_img_format img_format, ti99_lvl1_imgref *l1_img, dsk_vib *vib) { imgtoolerr_t err; int reply; uint16_t totphysrecs; l1_img->img_format = img_format; if (img_format == if_harddisk) { err = imghd_open(*file_handle, &l1_img->harddisk_handle); if (err) return err; const auto &info = imghd_get_header(&l1_img->harddisk_handle); l1_img->geometry.cylinders = info.cylinders; l1_img->geometry.heads = info.heads; l1_img->geometry.secspertrack = info.sectors; if (info.sectorbytes != 256) { imghd_close(&l1_img->harddisk_handle); return IMGTOOLERR_CORRUPTIMAGE; /* TODO: support 512-byte sectors */ } #if 0 /* read vib */ reply = read_absolute_physrec(l1_img, 0, vib); if (reply) return reply; #endif } else { /* read vib */ reply = read_image_vib_no_geometry(*file_handle, img_format, vib); if (reply) return (imgtoolerr_t)reply; /* extract geometry information */ totphysrecs = get_UINT16BE(vib->totphysrecs); l1_img->geometry.secspertrack = vib->secspertrack; if (l1_img->geometry.secspertrack == 0) /* Some images might be like this, because the original SSSD TI controller always assumes 9. */ l1_img->geometry.secspertrack = 9; l1_img->geometry.cylinders = vib->cylinders; if (l1_img->geometry.cylinders == 0) /* Some images are like this, because the original SSSD TI controller always assumes 40. */ l1_img->geometry.cylinders = 40; l1_img->geometry.heads = vib->heads; if (l1_img->geometry.heads == 0) /* Some images are like this, because the original SSSD TI controller always assumes that tracks beyond 40 are on side 2. */ l1_img->geometry.heads = totphysrecs / (l1_img->geometry.secspertrack * l1_img->geometry.cylinders); /* check information */ if ((totphysrecs != (l1_img->geometry.secspertrack * l1_img->geometry.cylinders * l1_img->geometry.heads)) || (totphysrecs < 2) || memcmp(vib->id, "DSK", 3) || (! strchr(" P", vib->protection)) || (((img_format == if_mess) || (img_format == if_v9t9)) && (file_handle->size() != totphysrecs*256U))) return (imgtoolerr_t)IMGTOOLERR_CORRUPTIMAGE; if ((img_format == if_pc99_fm) || (img_format == if_pc99_mfm)) { l1_img->pc99_data_offset_array = (uint32_t*)malloc(sizeof(*l1_img->pc99_data_offset_array)*totphysrecs); if (! l1_img->pc99_data_offset_array) return IMGTOOLERR_OUTOFMEMORY; reply = parse_pc99_image(*file_handle, img_format == if_pc99_fm, 1, NULL, & l1_img->geometry, l1_img->pc99_data_offset_array, &l1_img->pc99_track_len); if (reply) { free(l1_img->pc99_data_offset_array); return (imgtoolerr_t)reply; } } } l1_img->file_handle = file_handle.release(); // we can only do this when we're sure we're successful return (imgtoolerr_t)0; } /* Close a disk image at level 1 l1_img (I/O): level-1 image handle Return imgtool error code */ static void close_image_lvl1(ti99_lvl1_imgref *l1_img) { if (l1_img->img_format == if_harddisk) { imghd_close(&l1_img->harddisk_handle); } delete l1_img->file_handle; if ((l1_img->img_format == if_pc99_fm) || (l1_img->img_format == if_pc99_mfm)) free(l1_img->pc99_data_offset_array); } /* Convert physical sector address to offset in disk image (old MESS and V9T9 formats only) l1_img (I/O): level-1 image handle address (I): physical sector address Return offset in image */ static inline int sector_address_to_image_offset(const ti99_lvl1_imgref *l1_img, const ti99_sector_address *address) { int offset = 0; switch (l1_img->img_format) { case if_mess: /* old MESS format */ offset = (((address->cylinder*l1_img->geometry.heads) + address->side)*l1_img->geometry.secspertrack + address->sector)*256; break; case if_v9t9: /* V9T9 format */ if (address->side & 1) /* on side 1, tracks are stored in the reverse order */ offset = (((address->side*l1_img->geometry.cylinders) + (l1_img->geometry.cylinders-1 - address->cylinder))*l1_img->geometry.secspertrack + address->sector)*256; else offset = (((address->side*l1_img->geometry.cylinders) + address->cylinder)*l1_img->geometry.secspertrack + address->sector)*256; break; case if_pc99_fm: case if_pc99_mfm: /* pc99 format */ case if_harddisk: /* harddisk format */ assert(1); /* not implemented */ break; } return offset; } /* Read one 256-byte sector from a disk image l1_img (I/O): level-1 image handle address (I): physical sector address dest (O): pointer to 256-byte destination buffer Return non-zero on error */ static int read_sector(ti99_lvl1_imgref *l1_img, const ti99_sector_address *address, void *dest) { int reply; uint32_t track_len, track_offset, sector_offset; switch (l1_img->img_format) { case if_mess: /* old MESS format */ case if_v9t9: /* V9T9 format */ /* seek to sector */ reply = l1_img->file_handle->seek(sector_address_to_image_offset(l1_img, address), SEEK_SET); if (reply) return 1; /* read it */ reply = l1_img->file_handle->read(dest, 256); if (reply != 256) return 1; break; case if_pc99_fm: case if_pc99_mfm: /* pc99 format */ track_len = l1_img->pc99_track_len; track_offset = (address->side*l1_img->geometry.cylinders + address->cylinder)*track_len; sector_offset = l1_img->pc99_data_offset_array[(address->side*l1_img->geometry.cylinders + address->cylinder)*l1_img->geometry.secspertrack + address->sector]; if ((sector_offset + 256) <= track_len) { /* seek to sector */ reply = l1_img->file_handle->seek(track_offset+sector_offset, SEEK_SET); if (reply) return 1; /* read it */ reply = l1_img->file_handle->read(dest, 256); if (reply != 256) return 1; } else { /* seek to sector */ reply = l1_img->file_handle->seek(track_offset+sector_offset, SEEK_SET); if (reply) return 1; /* read first chunk (until end of track) */ reply = l1_img->file_handle->read((uint8_t *)dest, track_len-sector_offset); if (reply != track_len-sector_offset) return 1; /* wrap to start of track */ reply = l1_img->file_handle->seek(track_offset, SEEK_SET); if (reply) return 1; /* read remnant of sector */ reply = l1_img->file_handle->read((uint8_t *)dest + (track_len-sector_offset), 256-(track_len-sector_offset)); if (reply != 256-(track_len-sector_offset)) return 1; } break; case if_harddisk: /* not implemented */ assert(1); /*return imghd_read(l1_img->harddisk_handle, ((address->cylinder*l1_img->geometry.heads) + address->side)*l1_img->geometry.secspertrack + address->sector, 1, dest) != 1;*/ break; } return 0; } /* Write one 256-byte sector to a disk image l1_img (I/O): level-1 image handle address (I): physical sector address src (I): pointer to 256-byte source buffer Return non-zero on error */ static int write_sector(ti99_lvl1_imgref *l1_img, const ti99_sector_address *address, const void *src) { int reply; uint32_t track_len, track_offset, sector_offset; switch (l1_img->img_format) { case if_mess: /* old MESS format */ case if_v9t9: /* V9T9 format */ /* seek to sector */ reply = l1_img->file_handle->seek(sector_address_to_image_offset(l1_img, address), SEEK_SET); if (reply) return 1; /* write it */ reply = l1_img->file_handle->write(src, 256); if (reply != 256) return 1; break; case if_pc99_fm: case if_pc99_mfm: /* pc99 format */ track_len = l1_img->pc99_track_len; track_offset = (address->side*l1_img->geometry.cylinders + address->cylinder)*track_len; sector_offset = l1_img->pc99_data_offset_array[(address->side*l1_img->geometry.cylinders + address->cylinder)*l1_img->geometry.secspertrack + address->sector]; if ((sector_offset + 256) <= track_len) { /* seek to sector */ reply = l1_img->file_handle->seek(track_offset+sector_offset, SEEK_SET); if (reply) return 1; /* write it */ reply = l1_img->file_handle->write(src, 256); if (reply != 256) return 1; } else { /* seek to sector */ reply = l1_img->file_handle->seek(track_offset+sector_offset, SEEK_SET); if (reply) return 1; /* write first chunk (until end of track) */ reply = l1_img->file_handle->write((uint8_t *)src, track_len-sector_offset); if (reply != track_len-sector_offset) return 1; /* wrap to start of track */ reply = l1_img->file_handle->seek(track_offset, SEEK_SET); if (reply) return 1; /* write remnant of sector */ reply = l1_img->file_handle->write((uint8_t *)src + (track_len-sector_offset), 256-(track_len-sector_offset)); if (reply != 256-(track_len-sector_offset)) return 1; } break; case if_harddisk: /* not implemented */ assert(1); /*return imghd_write(l1_img->harddisk_handle, ((address->cylinder*l1_img->geometry.heads) + address->side)*l1_img->geometry.secspertrack + address->sector, 1, src) != 1;*/ break; } return 0; } /* Convert physical record address to sector address (DSK format) aphysrec (I): absolute physrec address geometry (I): disk image geometry address (O): physical sector address */ static void dsk_aphysrec_to_sector_address(int aphysrec, const ti99_geometry *geometry, ti99_sector_address *address) { address->sector = aphysrec % geometry->secspertrack; aphysrec /= geometry->secspertrack; address->cylinder = aphysrec % geometry->cylinders; address->side = aphysrec / geometry->cylinders; if (address->side & 1) /* on side 1, tracks are stored in the reverse order */ address->cylinder = geometry->cylinders-1 - address->cylinder; } /* Convert physical record address to sector address (WIN format for HFDC) Note that physical address translation makes sense for HFDC, but not SCSI. aphysrec (I): absolute physrec address geometry (I): disk image geometry address (O): physical sector address */ [[maybe_unused]] static void win_aphysrec_to_sector_address(int aphysrec, const ti99_geometry &geometry, ti99_sector_address &address) { address.sector = aphysrec % geometry.secspertrack; aphysrec /= geometry.secspertrack; address.side = aphysrec % geometry.heads; address.cylinder = aphysrec / geometry.heads; } /* Read one 256-byte physical record from a disk image l1_img (I/O): level-1 image handle aphysrec (I): absolute physrec address dest (O): pointer to 256-byte destination buffer Return non-zero on error */ static int read_absolute_physrec(ti99_lvl1_imgref *l1_img, unsigned aphysrec, void *dest) { ti99_sector_address address; if (l1_img->img_format == if_harddisk) { #if 0 win_aphysrec_to_sector_address(aphysrec, l1_img->geometry, address); return read_sector(l1_img, & address, dest); #endif return imghd_read(&l1_img->harddisk_handle, aphysrec, dest) != IMGTOOLERR_SUCCESS; } else { dsk_aphysrec_to_sector_address(aphysrec, & l1_img->geometry, & address); return read_sector(l1_img, & address, dest); } } /* Write one 256-byte physical record to a disk image l1_img (I/O): level-1 image handle aphysrec (I): absolute physrec address src (I): pointer to 256-byte source buffer Return non-zero on error */ static int write_absolute_physrec(ti99_lvl1_imgref *l1_img, unsigned aphysrec, const void *src) { ti99_sector_address address; if (l1_img->img_format == if_harddisk) { #if 0 win_aphysrec_to_sector_address(aphysrec, & l1_img->geometry, & address); return write_sector(l1_img, & address, dest); #endif return imghd_write(&l1_img->harddisk_handle, aphysrec, src) != IMGTOOLERR_SUCCESS; } else { dsk_aphysrec_to_sector_address(aphysrec, & l1_img->geometry, & address); return write_sector(l1_img, & address, src); } } #if 0 #pragma mark - #pragma mark LEVEL 2 DISK ROUTINES #endif /* Level 2 implements files as a succession of 256-byte-long physical records. Level 2 implements allocation bitmap (and AU), disk catalog, etc. */ /* WIN VIB/DDR record */ struct win_vib_ddr { char name[10]; /* disk volume name (10 characters, pad with spaces) */ UINT16BE totAUs; /* total number of AUs */ uint8_t secspertrack; /* HFDC: sectors per track (typically 32) */ /* SCSI: reserved */ union { struct { uint8_t id[3]; /* V1 VIB: 'WIN' */ } vib_v1; struct /* V2 VIB: extra params */ { uint8_t res_AUs; /* # AUs reserved for vib, bitmap, ddr, fdir and fdr, divided by 64 */ uint8_t step_spd; /* HFDC: step speed (0-7) */ /* SCSI: reserved */ uint8_t red_w_cur;/* HFDC: reduced write current cylinder, divided by 8 */ /* SCSI: reserved */ } vib_v2; struct { uint8_t id[3]; /* DDR: 'DIR' */ } ddr; } u; UINT16BE params; /* bits 0-3: sectors/AU - 1 */ /* HFDC: */ /* bits 4-7: # heads - 1 */ /* bit 8: V1: buffered head stepping flag */ /* V2: reserved */ /* bit 9-15: write precompensation track, divided by 16 */ /* SCSI: */ /* bits 4-15: reserved */ ti99_date_time creation;/* date and time of creation */ uint8_t num_files; /* number of files in directory */ uint8_t num_subdirs; /* number of subdirectories in directory */ UINT16BE fdir_AU; /* points to root directory fdir */ union { struct { UINT16BE dsk1_AU; /* HFDC: points to current dsk1 emulation image (0 if none) */ /* SCSI: reserved */ } vib; struct { UINT16BE parent_ddr_AU; /* points to parent directory DDR */ } ddr; } u2; UINT16BE subdir_AU[114];/* points to all subdirectory DDRs */ }; /* AU format */ struct ti99_AUformat { int totAUs; /* total number of AUs */ int physrecsperAU; /* number of 256-byte physical records per AU */ }; /* DSK directory reference: 0 for root, 1 for 1st subdir, 2 for 2nd subdir, 3 for 3rd subdir */ /*typedef int dir_ref;*/ /* catalog entry (used for in-memory catalog) */ struct dir_entry { uint16_t dir_ptr; /* DSK: unused */ /* WIN: AU address of the DDR for this directory */ char name[10]; /* name of this directory (copied from the VIB for DSK, DDR for WIN) */ }; struct file_entry { uint16_t fdr_ptr; /* DSK: aphysrec address of the FDR for this file */ /* WIN: AU address of the FDR for this file */ char name[10]; /* name of this file (copied from FDR) */ }; struct ti99_catalog { int num_subdirs; /* number of subdirectories */ int num_files; /* number of files */ dir_entry subdirs[114]; /* description of each subdir */ file_entry files[128]; /* description of each file */ }; /* level-2 disk image descriptor */ struct ti99_lvl2_imgref_dsk { uint16_t totphysrecs; /* total number of aphysrecs (extracted from vib record in aphysrec 0) */ ti99_catalog catalogs[4]; /* catalog of root directory and up to 3 subdirectories */ uint16_t fdir_aphysrec[4]; /* fdir aphysrec address for root directory and up to 3 subdirectories */ }; enum win_vib_t { win_vib_v1, win_vib_v2 }; struct ti99_lvl2_imgref_win { win_vib_t vib_version; /* version of the vib record in aphysrec 0 (see win_vib_ddr) */ }; enum l2i_t { L2I_DSK, L2I_WIN }; struct ti99_lvl2_imgref { ti99_lvl1_imgref l1_img;/* image format, imgtool image handle, image geometry */ ti99_AUformat AUformat; /* AU format */ int data_offset; /* In order to reduce seek times when searching the disk for a given file name, fdr (and ddr, and fdir) records are preferentially allocated in AUs n to data_offset, whereas data records are preferentially allocated in AUs starting at data_offset. */ /* With the DSK disk structure, n is always 2 (if 1 physrec per AU) or 1 (if 2 physrecs per AU or more), and data_offset is arbitrarily chosen as 34. */ /* With the WIN disk structure, n depends on the size of the volume bitmap, which itself depends on the number of AUs on disk (we always have n <= 33), and data_offset is read from the vib record (except with the obsolete v1 VIB, where we use a default value of 64). */ char vol_name[10]; /* cached volume name (extracted from vib record in aphysrec 0) */ uint8_t abm[8192]; /* allocation bitmap */ l2i_t type; /* structure format */ union { ti99_lvl2_imgref_dsk dsk; ti99_lvl2_imgref_win win; }; /* structure-specific info */ }; /* file flags found in fdr (and tifiles) */ enum { fdr99_f_program = 0x01, /* set for program files */ fdr99_f_int = 0x02, /* set for binary files */ fdr99_f_wp = 0x08, /* set if file is write-protected */ /*fdr99_f_backup = 0x10,*/ /* set if file has been modified since last backup */ /*fdr99_f_dskimg = 0x20,*/ /* set if file is a DSK image (HFDC HD only) */ fdr99_f_var = 0x80 /* set if file uses variable-length records*/ }; /* DSK FDR record */ struct dsk_fdr { char name[10]; /* file name (10 characters, pad with spaces) */ UINT16BE xreclen; /* extended record len: if record len is >= 256, */ /* reclen is set to 0 and the actual reclen is */ /* stored here (Myarc HFDC only). TI reserved */ /* this field for data chain pointer extension, */ /* but this was never implemented. */ uint8_t flags; /* file status flags (see enum above) */ uint8_t recsperphysrec; /* logical records per physrec */ /* ignored for variable length record files and */ /* program files */ UINT16BE fphysrecs; /* file length in physrecs */ /* Note that the HFDC defines this field as the */ /* number of allocated physrecs in the cluster */ /* chain (i.e. rounded on the next AU */ /* boundary), so level-3 routines should use */ /* the fixrecs field instead to determine the */ /* logical length of field. IIRC, the HFDC */ /* implementation is regarded as a bug because */ /* program files do not define the fixrecs field */ /* field, so program field saved by the HFDC */ /* DSR may be larger than they should. */ uint8_t eof; /* EOF offset in last physrec for variable length */ /* record files and program files (0->256) */ uint8_t reclen; /* logical record size in bytes ([1,255] 0->256) */ /* Maximum allowable record size for variable */ /* length record files. Reserved for program */ /* files (set to 0). Set to 0 if reclen >=256 */ /* (HFDC only). */ UINT16LE fixrecs; /* file length in logical records */ /* For variable length record files, number of */ /* 256-byte records actually used. */ ti99_date_time creation;/* date and time of creation (HFDC and BwG only; */ /* reserved in TI) */ ti99_date_time update; /* date and time of last write to file (HFDC and */ /* BwG only; reserved in TI) */ uint8_t clusters[76][3]; /* data cluster table: 0 through 76 entries (3 */ /* bytes each), one entry for each file cluster. */ /* 12 bits: address of first AU of cluster */ /* 12 bits: offset of last 256-byte record in cluster */ }; /* WIN FDR record */ struct win_fdr { char name[10]; /* file name (10 characters, pad with spaces) */ UINT16BE xreclen; /* extended record len: if record len is >= 256, */ /* reclen is set to 0 and the actual reclen is */ /* stored here (Myarc HFDC only). TI reserved */ /* this field for data chain pointer extension, */ /* but this was never implemented. */ uint8_t flags; /* file status flags (see enum above) */ uint8_t recsperphysrec; /* logical records per physrec */ /* ignored for variable length record files and */ /* program files */ UINT16BE fphysrecs_LSW; /* eldest FDR: file length in physrecs */ /* Note that the HFDC defines this field as the */ /* number of allocated physrecs in the cluster */ /* chain (i.e. rounded on the next AU */ /* boundary), so level-3 routines should use */ /* the fixrecs field instead to determine the */ /* logical length of field. IIRC, the HFDC */ /* implementation is regarded as a bug because */ /* program files do not define the fixrecs field */ /* field, so program field saved by the HFDC */ /* DSR may be larger than they should. */ /* other sibling FDRs: index of the first file */ /* physrec in this particular sibling FDR */ uint8_t eof; /* EOF offset in last physrec for variable length */ /* record files and program files (0->256)*/ uint8_t reclen; /* logical record size in bytes ([1,255]) */ /* Maximum allowable record size for variable */ /* length record files. Reserved for program */ /* files (set to 0). Set to 0 if reclen >=256 */ /* (HFDC only). */ UINT16LE fixrecs_LSW; /* file length in logical records */ /* For variable length record files, number of */ /* 256-byte records actually used. */ ti99_date_time creation;/* date and time of creation */ ti99_date_time update; /* date and time of last write to file */ char id[2]; /* 'FI' */ UINT16BE prevsibFDR_AU; /* address of the AU where previous sibling FDR is */ /* (see also xinfo_LSB) */ UINT16BE nextsibFDR_AU; /* address of the AU where next sibling FDR is */ /* (see also xinfo_LSB) */ UINT16BE sibFDR_AUlen; /* total number of data AUs allocated in this particular sibling FDR */ UINT16BE parent_FDIR_AU;/* FDIR the file is listed in */ uint8_t xinfo_MSB; /* extended information (MSByte) */ /* bits 0-3: MSN of fphysrecs */ /* bits 4-7: MSN of fixrecs */ uint8_t xinfo_LSB; /* extended information (LSByte) */ /* bits 8-11: physrec offset within AU for */ /* previous sibling FDR (see prevsibFDR_AU) */ /* bits 12-15: physrec offset within AU for */ /* next sibling FDR (see nextsibFDR_AU) */ UINT16BE clusters[54][2];/* data cluster table: 0 through 54 entries (4 */ /* bytes each), one entry for each file cluster. */ /* 16 bits: address of first AU of cluster */ /* 16 bits: address of last AU of cluster */ }; /* tifile header: stand-alone file */ struct tifile_header { char tifiles[8]; /* always '\7TIFILES' */ UINT16BE fphysrecs; /* file length in physrecs */ uint8_t flags; /* see enum above */ uint8_t recsperphysrec; /* records per physrec */ uint8_t eof; /* current position of eof in last physrec (0->255)*/ uint8_t reclen; /* bytes per record ([1,255] 0->256) */ UINT16BE fixrecs; /* file length in records */ uint8_t res[128-16]; /* reserved */ /* * variant a: */ /* 112 chars: 0xCA53 repeated 56 times */ /* * variant b: */ /* 4 chars: unknown */ /* 108 chars: 0xCA53 repeated 54 times */ /* * variant c: */ /* 10 chars: original TI file name filed with spaces */ /* 102 chars: spaces */ /* * variant d: */ /* 10 chars: original TI file name filed with spaces */ /* 2 chars: CR+LF */ /* 98 chars: spaces */ /* 2 chars: CR+LF */ /* * variant e: */ /* 10 chars: original TI file name */ /* 4 bytes: unknown */ /* 4 bytes: time & date of creation */ /* 4 bytes: time & date of last update */ /* 90 chars: spaces */ /* * variant f: */ /* 6 bytes: 'MYTERM' */ /* 4 bytes: time & date of creation */ /* 4 bytes: time & date of last update */ /* 2 bytes: unknown (always >0000) */ /* 96 chars: 0xCA53 repeated 56 times */ }; /* level-2 file descriptor */ struct ti99_lvl2_fileref_dsk { struct ti99_lvl2_imgref *l2_img; int fdr_aphysrec; dsk_fdr fdr; }; struct ti99_lvl2_fileref_win { struct ti99_lvl2_imgref *l2_img; unsigned fphysrecs; /* copy of field in the eldest FDR */ unsigned eldestfdr_aphysrec; /* aphysrec address of the eldest FDR */ unsigned curfdr_aphysrec; /* aphysrec address of the currently open sibling FDR */ win_fdr curfdr; /* buffer with currently open sibling FDR */ }; struct ti99_lvl2_fileref_tifiles { imgtool::stream *file_handle; tifile_header hdr; }; enum l2f_type_t { L2F_DSK, L2F_WIN, L2F_TIFILES }; struct ti99_lvl2_fileref { l2f_type_t type; union { ti99_lvl2_fileref_dsk dsk; ti99_lvl2_fileref_win win; ti99_lvl2_fileref_tifiles tifiles; }; }; static struct ti99_lvl2_imgref *get_lvl2_imgref(imgtool::image &image) { return (struct ti99_lvl2_imgref *) image.extra_bytes(); } /* Compare two (possibly empty) catalog entry for qsort */ static int cat_file_compare_qsort(const void *p1, const void *p2) { const file_entry *entry1 = (const file_entry *)p1; const file_entry *entry2 = (const file_entry *)p2; if ((entry1->fdr_ptr == 0) && (entry2->fdr_ptr == 0)) return 0; else if (entry1->fdr_ptr == 0) return +1; else if (entry2->fdr_ptr == 0) return -1; else return memcmp(entry1->name, entry2->name, 10); } static int cat_dir_compare_qsort(const void *p1, const void *p2) { const dir_entry *entry1 = (const dir_entry *)p1; const dir_entry *entry2 = (const dir_entry *)p2; if ((entry1->dir_ptr == 0) && (entry2->dir_ptr == 0)) return 0; else if (entry1->dir_ptr == 0) return +1; else if (entry2->dir_ptr == 0) return -1; else return memcmp(entry1->name, entry2->name, 10); } /* Read a directory catalog from disk image l2_img: image reference aphysrec: physical record address of the FDIR dest: pointer to the destination buffer where the catalog should be written Return an error code if there was an error, 0 otherwise. */ static int dsk_read_catalog(struct ti99_lvl2_imgref *l2_img, int aphysrec, ti99_catalog *dest) { int totphysrecs = l2_img->dsk.totphysrecs; UINT16BE fdir_buf[128]; dsk_fdr fdr; int i; int reply; /* Read FDIR record */ reply = read_absolute_physrec(& l2_img->l1_img, aphysrec, fdir_buf); if (reply) return IMGTOOLERR_READERROR; /* Copy FDIR info to catalog structure */ for (i=0; i<128; i++) dest->files[i].fdr_ptr = get_UINT16BE(fdir_buf[i]); /* Check FDIR pointers and check and extract file names from DDRs */ for (i=0; i<128; i++) { if (dest->files[i].fdr_ptr >= totphysrecs) { return IMGTOOLERR_CORRUPTIMAGE; } else if (dest->files[i].fdr_ptr) { reply = read_absolute_physrec(& l2_img->l1_img, dest->files[i].fdr_ptr, &fdr); if (reply) return IMGTOOLERR_READERROR; /* check and copy file name */ if (check_fname(fdr.name)) return IMGTOOLERR_CORRUPTIMAGE; memcpy(dest->files[i].name, fdr.name, 10); } } /* Check catalog */ for (i=0; i<127; i++) { if (((! dest->files[i].fdr_ptr) && dest->files[i+1].fdr_ptr) || ((dest->files[i].fdr_ptr && dest->files[i+1].fdr_ptr) && (memcmp(dest->files[i].name, dest->files[i+1].name, 10) >= 0))) { /* if the catalog is not sorted, we repair it */ qsort(dest->files, std::size(dest->files), sizeof(dest->files[0]), cat_file_compare_qsort); break; } } /* Set file count */ for (i=0; (i<128) && (dest->files[i].fdr_ptr != 0); i++) ; dest->num_files = i; /* Set subdir count to 0 (subdirs are loaded elsewhere) */ dest->num_subdirs = 0; return 0; } /* Read a directory catalog from disk image l2_img: image reference DDR_AU: AU address of the VIB/DDR dest: pointer to the destination buffer where the catalog should be written Return an error code if there was an error, 0 otherwise. */ static int win_read_catalog(struct ti99_lvl2_imgref *l2_img, int DDR_AU, ti99_catalog *dest) { win_vib_ddr ddr_buf; UINT16BE fdir_buf[128]; win_fdr fdr_buf; int i; int reply; /* Read DDR record */ reply = read_absolute_physrec(& l2_img->l1_img, DDR_AU*l2_img->AUformat.physrecsperAU, &ddr_buf); if (reply) return IMGTOOLERR_READERROR; /* sanity checks */ if ((ddr_buf.num_files > 127) || (ddr_buf.num_subdirs > 114) || (get_UINT16BE(ddr_buf.fdir_AU) > l2_img->AUformat.totAUs)) return IMGTOOLERR_CORRUPTIMAGE; /* set file count and subdir count */ dest->num_files = ddr_buf.num_files; dest->num_subdirs = ddr_buf.num_subdirs; /* Copy DDR info to catalog structure */ for (i=0; isubdirs[i].dir_ptr = get_UINT16BE(ddr_buf.subdir_AU[i]); /* Read FDIR record */ reply = read_absolute_physrec(& l2_img->l1_img, get_UINT16BE(ddr_buf.fdir_AU)*l2_img->AUformat.physrecsperAU, fdir_buf); if (reply) return IMGTOOLERR_READERROR; /* Copy FDIR info to catalog structure */ for (i=0; inum_files; i++) dest->files[i].fdr_ptr = get_UINT16BE(fdir_buf[i]); /* Check DDR pointers and check and extract file names from FDRs */ for (i=0; inum_subdirs; i++) { if (dest->subdirs[i].dir_ptr >= l2_img->AUformat.totAUs) { return IMGTOOLERR_CORRUPTIMAGE; } else if (dest->subdirs[i].dir_ptr) { reply = read_absolute_physrec(& l2_img->l1_img, dest->subdirs[i].dir_ptr*l2_img->AUformat.physrecsperAU, &ddr_buf); if (reply) return IMGTOOLERR_READERROR; /* check and copy file name */ if (check_fname(ddr_buf.name)) return IMGTOOLERR_CORRUPTIMAGE; memcpy(dest->subdirs[i].name, ddr_buf.name, 10); } } /* Check FDIR pointers and check and extract file names from FDRs */ for (i=0; inum_files; i++) { if (dest->files[i].fdr_ptr >= l2_img->AUformat.totAUs) { return IMGTOOLERR_CORRUPTIMAGE; } else if (dest->files[i].fdr_ptr) { reply = read_absolute_physrec(& l2_img->l1_img, dest->files[i].fdr_ptr*l2_img->AUformat.physrecsperAU, &fdr_buf); if (reply) return IMGTOOLERR_READERROR; /* check and copy file name */ if (check_fname(fdr_buf.name)) return IMGTOOLERR_CORRUPTIMAGE; memcpy(dest->files[i].name, fdr_buf.name, 10); } } /* Check catalog */ /* Check subdir order */ for (i=0; inum_subdirs-1; i++) { if (((! dest->subdirs[i].dir_ptr) && dest->subdirs[i+1].dir_ptr) || ((dest->subdirs[i].dir_ptr && dest->subdirs[i+1].dir_ptr) && (memcmp(dest->subdirs[i].name, dest->subdirs[i+1].name, 10) >= 0))) { /* if the subdir catalog is not sorted, we repair it */ qsort(dest->subdirs, dest->num_subdirs, sizeof(dest->subdirs[0]), cat_dir_compare_qsort); break; } } /* Fix subdir count */ while (dest->num_subdirs && (dest->subdirs[dest->num_subdirs-1].dir_ptr == 0)) dest->num_subdirs--; /* Check file order */ for (i=0; inum_files-1; i++) { if (((! dest->files[i].fdr_ptr) && dest->files[i+1].fdr_ptr) || ((dest->files[i].fdr_ptr && dest->files[i+1].fdr_ptr) && (memcmp(dest->files[i].name, dest->files[i+1].name, 10) >= 0))) { /* if the file catalog is not sorted, we repair it */ qsort(dest->files, dest->num_files, sizeof(dest->files[0]), cat_file_compare_qsort); break; } } /* Fix file count */ while (dest->num_files && (dest->files[dest->num_files-1].fdr_ptr == 0)) dest->num_files--; return 0; } /* Search for a file path on a floppy image l2_img: image reference fpath: path of the file to search parent_ref_valid: set to true if either the file was found or the file was not found but its parent dir was parent_ref: reference to parent dir (0 for root) out_is_dir: true if element is a directory catalog_index: on output, index of file catalog entry (may be NULL) */ static int dsk_find_catalog_entry(struct ti99_lvl2_imgref *l2_img, const char *fpath, int *parent_ref_valid, int *parent_ref, int *out_is_dir, int *catalog_index) { int i; const ti99_catalog *cur_catalog; const char *element_start, *element_end; int element_len; char element[10]; int is_dir = false; cur_catalog = & l2_img->dsk.catalogs[0]; if (parent_ref_valid) (* parent_ref_valid) = false; if (parent_ref) *parent_ref = 0; element_start = fpath; do { /* find next path element */ element_end = strchr(element_start, '.'); element_len = element_end ? (element_end - element_start) : strlen(element_start); if ((element_len > 10) || (element_len == 0)) return IMGTOOLERR_BADFILENAME; /* last path element */ if ((!element_end) && parent_ref_valid) (* parent_ref_valid) = true; /* generate file name */ memcpy(element, element_start, element_len); memset(element+element_len, ' ', 10-element_len); /* search entry in subdirectories */ for (i = 0; inum_subdirs; i++) { if (! memcmp(element, cur_catalog->subdirs[i].name, 10)) { is_dir = true; break; } } /* if it failed, search entry in files */ if (i == cur_catalog->num_subdirs) { for (i = 0; inum_files; i++) { if (! memcmp(element, cur_catalog->files[i].name, 10)) { is_dir = false; break; } } /* exit if not found */ if (i == cur_catalog->num_files) { return IMGTOOLERR_FILENOTFOUND; } } /* iterate */ if (element_end) { element_start = element_end+1; if (! is_dir) /* this is not a directory */ return IMGTOOLERR_BADFILENAME; /* initialize cur_catalog */ cur_catalog = & l2_img->dsk.catalogs[i+1]; if (parent_ref) *parent_ref = i+1; } else element_start = NULL; } while (element_start); if (out_is_dir) *out_is_dir = is_dir; if (catalog_index) *catalog_index = i; return 0; } /* Search for a file path on a harddisk image l2_img: image reference fpath: path of the file to search parent_ref_valid: set to true if either the file was found or the file was not found but its parent dir was parent_ddr_AU: parent DDR AU address (0 for root) parent_catalog: catalog of parent dir (cannot be NULL) out_is_dir: true if element is a directory catalog_index: on output, index of file catalog entry (may be NULL) */ static int win_find_catalog_entry(struct ti99_lvl2_imgref *l2_img, const char *fpath, int *parent_ref_valid, int *parent_ddr_AU, ti99_catalog *parent_catalog, int *out_is_dir, int *catalog_index) { int i; const char *element_start, *element_end; int element_len; char element[10]; int is_dir = false; int errorcode; if (parent_ref_valid) (* parent_ref_valid) = false; if (parent_ddr_AU) *parent_ddr_AU = 0; errorcode = win_read_catalog(l2_img, 0, parent_catalog); if (errorcode) return errorcode; element_start = fpath; do { /* find next path element */ element_end = strchr(element_start, '.'); element_len = element_end ? (element_end - element_start) : strlen(element_start); if ((element_len > 10) || (element_len == 0)) return IMGTOOLERR_BADFILENAME; /* last path element */ if ((!element_end) && parent_ref_valid) (* parent_ref_valid) = true; /* generate file name */ memcpy(element, element_start, element_len); memset(element+element_len, ' ', 10-element_len); /* search entry in subdirectories */ for (i = 0; inum_subdirs; i++) { if (! memcmp(element, parent_catalog->subdirs[i].name, 10)) { is_dir = true; break; } } /* if it failed, search entry in files */ if (i == parent_catalog->num_subdirs) { for (i = 0; inum_files; i++) { if (! memcmp(element, parent_catalog->files[i].name, 10)) { is_dir = false; break; } } /* exit if not found */ if (i == parent_catalog->num_files) { return IMGTOOLERR_FILENOTFOUND; } } /* iterate */ if (element_end) { element_start = element_end+1; if (! is_dir) /* this is not a directory */ return IMGTOOLERR_BADFILENAME; if (parent_ddr_AU) *parent_ddr_AU = parent_catalog->subdirs[i].dir_ptr; errorcode = win_read_catalog(l2_img, parent_catalog->subdirs[i].dir_ptr, parent_catalog); if (errorcode) return errorcode; } else element_start = NULL; } while (element_start); if (out_is_dir) *out_is_dir = is_dir; if (catalog_index) *catalog_index = i; return 0; } /* Allocate one AU on disk, for use as a fdr record l2_img: image reference fdr_AU: on output, address of allocated AU */ static int alloc_fdr_AU(struct ti99_lvl2_imgref *l2_img, unsigned *fdr_AU) { int totAUs = l2_img->AUformat.totAUs; int i; for (i=0; iabm[i >> 3] & (1 << (i & 7)))) { *fdr_AU = i; l2_img->abm[i >> 3] |= 1 << (i & 7); return 0; } } return IMGTOOLERR_NOSPACE; } static inline int get_dsk_fdr_cluster_baseAU(struct ti99_lvl2_imgref *l2_img, dsk_fdr *fdr, int cluster_index) { int reply; /* read base AU/physrec for this cluster */ reply = ((fdr->clusters[cluster_index][1] & 0xf) << 8) | fdr->clusters[cluster_index][0]; /* convert to AU address */ if (l2_img->AUformat.physrecsperAU <= 2) reply /= l2_img->AUformat.physrecsperAU; return reply; } static inline int get_dsk_fdr_cluster_baseaphysrec(struct ti99_lvl2_imgref *l2_img, dsk_fdr *fdr, int cluster_index) { int reply; /* read base AU/physrec for this cluster */ reply = ((fdr->clusters[cluster_index][1] & 0xf) << 8) | fdr->clusters[cluster_index][0]; /* convert to physrec address */ if (l2_img->AUformat.physrecsperAU > 2) reply *= l2_img->AUformat.physrecsperAU; return reply; } static inline int get_dsk_fdr_cluster_lastfphysrec(dsk_fdr *fdr, int cluster_index) { return (fdr->clusters[cluster_index][2] << 4) | (fdr->clusters[cluster_index][1] >> 4); } static inline void set_dsk_fdr_cluster_lastfphysrec(dsk_fdr *fdr, int cluster_index, int data) { fdr->clusters[cluster_index][1] = (fdr->clusters[cluster_index][1] & 0x0f) | (data << 4); fdr->clusters[cluster_index][2] = data >> 4; } static inline void set_dsk_fdr_cluster(struct ti99_lvl2_imgref *l2_img, dsk_fdr *fdr, int cluster_index, int baseAU, int lastfphysrec) { /* convert AU address to FDR value */ if (l2_img->AUformat.physrecsperAU <= 2) baseAU *= l2_img->AUformat.physrecsperAU; /* write cluster entry */ fdr->clusters[cluster_index][0] = baseAU; fdr->clusters[cluster_index][1] = ((baseAU >> 8) & 0x0f) | (lastfphysrec << 4); fdr->clusters[cluster_index][2] = lastfphysrec >> 4; } static inline unsigned get_win_fdr_fphysrecs(win_fdr *fdr) { return (((unsigned) fdr->xinfo_MSB << 12) & 0xf0000) | get_UINT16BE(fdr->fphysrecs_LSW); } static inline void set_win_fdr_fphysrecs(win_fdr *fdr, unsigned data) { fdr->xinfo_MSB = (fdr->xinfo_MSB & 0x0f) | ((data >> 12) & 0xf0); set_UINT16BE(&fdr->fphysrecs_LSW, data & 0xffff); } static inline unsigned get_win_fdr_fixrecs(win_fdr *fdr) { return (((unsigned) fdr->xinfo_MSB << 16) & 0xf0000) | get_UINT16LE(fdr->fixrecs_LSW); } static inline void set_win_fdr_fixrecs(win_fdr *fdr, unsigned data) { fdr->xinfo_MSB = (fdr->xinfo_MSB & 0xf0) | ((data >> 16) & 0x0f); set_UINT16LE(&fdr->fixrecs_LSW, data & 0xffff); } static inline unsigned get_win_fdr_prevsibFDR_aphysrec(struct ti99_lvl2_imgref *l2_img, win_fdr *fdr) { unsigned prevsibFDR_AU = get_UINT16BE(fdr->prevsibFDR_AU); return prevsibFDR_AU ? (prevsibFDR_AU * l2_img->AUformat.physrecsperAU + ((fdr->xinfo_LSB >> 4) & 0xf)) : 0; } static inline unsigned get_win_fdr_nextsibFDR_aphysrec(struct ti99_lvl2_imgref *l2_img, win_fdr *fdr) { unsigned nextsibFDR_AU = get_UINT16BE(fdr->nextsibFDR_AU); return nextsibFDR_AU ? (nextsibFDR_AU * l2_img->AUformat.physrecsperAU + (fdr->xinfo_LSB & 0xf)) : 0; } static inline unsigned get_win_fdr_cursibFDR_basefphysrec(win_fdr *fdr) { return get_UINT16BE(fdr->prevsibFDR_AU) ? get_win_fdr_fphysrecs(fdr) : 0; } /* Advance to next sibling FDR */ static int win_goto_next_sibFDR(ti99_lvl2_fileref_win *win_file) { if (get_UINT16BE(win_file->curfdr.nextsibFDR_AU) == 0) return IMGTOOLERR_UNEXPECTED; win_file->curfdr_aphysrec = get_win_fdr_nextsibFDR_aphysrec(win_file->l2_img, &win_file->curfdr); if (read_absolute_physrec(& win_file->l2_img->l1_img, win_file->curfdr_aphysrec, &win_file->curfdr)) return IMGTOOLERR_READERROR; return 0; } /* Back to previous sibling FDR */ static int win_goto_prev_sibFDR(ti99_lvl2_fileref_win *win_file) { if (get_UINT16BE(win_file->curfdr.prevsibFDR_AU) == 0) return IMGTOOLERR_UNEXPECTED; win_file->curfdr_aphysrec = get_win_fdr_prevsibFDR_aphysrec(win_file->l2_img, &win_file->curfdr); if (read_absolute_physrec(& win_file->l2_img->l1_img, win_file->curfdr_aphysrec, &win_file->curfdr)) return IMGTOOLERR_READERROR; return 0; } /* Append a new sibling FDR at the end of the sibling FDR list, and open it. You must have gone to the end of the list. */ static int win_alloc_sibFDR(ti99_lvl2_fileref_win *win_file) { unsigned oldfdr_AU, oldfdr_physrecinAU; unsigned newfdr_AU, newfdr_physrecinAU; int allocated = false; int errorcode; unsigned cursibFDR_basefphysrec; if (get_UINT16BE(win_file->curfdr.nextsibFDR_AU)) return IMGTOOLERR_UNEXPECTED; oldfdr_AU = win_file->curfdr_aphysrec / win_file->l2_img->AUformat.physrecsperAU; oldfdr_physrecinAU = win_file->curfdr_aphysrec % win_file->l2_img->AUformat.physrecsperAU; if (oldfdr_physrecinAU != (win_file->l2_img->AUformat.physrecsperAU - 1)) { /* current AU is not full */ newfdr_AU = oldfdr_AU; newfdr_physrecinAU = oldfdr_physrecinAU + 1; } else { /* current AU is full: allocate another */ errorcode = alloc_fdr_AU(win_file->l2_img, &newfdr_AU); if (errorcode) return errorcode; newfdr_physrecinAU = 0; allocated = true; } set_UINT16BE(&win_file->curfdr.nextsibFDR_AU, newfdr_AU); win_file->curfdr.xinfo_LSB = (win_file->curfdr.xinfo_LSB & 0xf0) | newfdr_physrecinAU; /* save current sibling FDR */ if (write_absolute_physrec(& win_file->l2_img->l1_img, win_file->curfdr_aphysrec, &win_file->curfdr)) { /* clear pointer */ set_UINT16BE(&win_file->curfdr.nextsibFDR_AU, 0); win_file->curfdr.xinfo_LSB = win_file->curfdr.xinfo_LSB & 0xf0; if (allocated) /* free AU */ win_file->l2_img->abm[newfdr_AU >> 3] |= 1 << (newfdr_AU & 7); return IMGTOOLERR_WRITEERROR; } /* now update in-memory structure to describe new sibling FDR */ cursibFDR_basefphysrec = get_win_fdr_cursibFDR_basefphysrec(&win_file->curfdr) + get_UINT16BE(win_file->curfdr.sibFDR_AUlen) * win_file->l2_img->AUformat.physrecsperAU; set_UINT16BE(&win_file->curfdr.nextsibFDR_AU, 0); set_UINT16BE(&win_file->curfdr.prevsibFDR_AU, oldfdr_AU); win_file->curfdr.xinfo_LSB = oldfdr_physrecinAU << 4; win_file->curfdr_aphysrec = newfdr_AU * win_file->l2_img->AUformat.physrecsperAU + newfdr_physrecinAU; set_win_fdr_fphysrecs(&win_file->curfdr, cursibFDR_basefphysrec); set_UINT16BE(&win_file->curfdr.sibFDR_AUlen, 0); memset(win_file->curfdr.clusters, 0, sizeof(win_file->curfdr.clusters)); return 0; } /* Extend a file with nb_alloc_physrecs extra physrecs dsk_file: file reference nb_alloc_physrecs: number of physical records to allocate */ static int dsk_alloc_file_physrecs(ti99_lvl2_fileref_dsk *dsk_file, int nb_alloc_physrecs) { int totAUs = dsk_file->l2_img->AUformat.totAUs; int free_physrecs; int fphysrecs; int i; int cluster_index; int last_sec, p_last_sec = 0; int cur_block_seclen; int cluster_baseAU, cluster_AUlen; int first_best_block_baseAU = 0, first_best_block_seclen; int second_best_block_baseAU = 0, second_best_block_seclen; int search_start; /* compute free space */ free_physrecs = 0; for (i=0; il2_img->abm[i >> 3] & (1 << (i & 7)))) free_physrecs += dsk_file->l2_img->AUformat.physrecsperAU; } /* check we have enough free space */ if (free_physrecs < nb_alloc_physrecs) return IMGTOOLERR_NOSPACE; /* current number of data physrecs in file */ fphysrecs = get_UINT16BE(dsk_file->fdr.fphysrecs); if (fphysrecs == 0) { /* cluster array must be empty */ cluster_index = 0; } else { /* try to extend last block */ last_sec = -1; for (cluster_index=0; cluster_index<76; cluster_index++) { p_last_sec = last_sec; last_sec = get_dsk_fdr_cluster_lastfphysrec(&dsk_file->fdr, cluster_index); if (last_sec >= (fphysrecs-1)) break; } if (cluster_index == 76) /* that sucks */ return IMGTOOLERR_CORRUPTIMAGE; if (last_sec > (fphysrecs-1)) { /* some extra space has already been allocated */ cur_block_seclen = last_sec - (fphysrecs-1); if (cur_block_seclen > nb_alloc_physrecs) cur_block_seclen = nb_alloc_physrecs; fphysrecs += cur_block_seclen; set_UINT16BE(& dsk_file->fdr.fphysrecs, fphysrecs); nb_alloc_physrecs -= cur_block_seclen; if (! nb_alloc_physrecs) return 0; /* done */ } /* round up to next AU boundary */ last_sec = last_sec + dsk_file->l2_img->AUformat.physrecsperAU - (last_sec % dsk_file->l2_img->AUformat.physrecsperAU) - 1; if (last_sec > (fphysrecs-1)) { /* some extra space has already been allocated */ cur_block_seclen = last_sec - (fphysrecs-1); if (cur_block_seclen > nb_alloc_physrecs) cur_block_seclen = nb_alloc_physrecs; fphysrecs += cur_block_seclen; set_UINT16BE(& dsk_file->fdr.fphysrecs, fphysrecs); set_dsk_fdr_cluster_lastfphysrec(&dsk_file->fdr, cluster_index, fphysrecs-1); nb_alloc_physrecs -= cur_block_seclen; if (! nb_alloc_physrecs) return 0; /* done */ } /* read base AU address for this cluster */ cluster_baseAU = get_dsk_fdr_cluster_baseAU(dsk_file->l2_img, &dsk_file->fdr, cluster_index); /* point past cluster end */ cluster_baseAU += (last_sec-p_last_sec/*+file->l2_img->AUformat.physrecsperAU-1*/) / dsk_file->l2_img->AUformat.physrecsperAU; /* count free physrecs after last block */ cur_block_seclen = 0; for (i=cluster_baseAU; (! (dsk_file->l2_img->abm[i >> 3] & (1 << (i & 7)))) && (cur_block_seclen < nb_alloc_physrecs) && (i < totAUs); i++) cur_block_seclen += dsk_file->l2_img->AUformat.physrecsperAU; if (cur_block_seclen) { /* extend last block */ if (cur_block_seclen > nb_alloc_physrecs) cur_block_seclen = nb_alloc_physrecs; fphysrecs += cur_block_seclen; set_UINT16BE(& dsk_file->fdr.fphysrecs, fphysrecs); last_sec += cur_block_seclen; nb_alloc_physrecs -= cur_block_seclen; set_dsk_fdr_cluster_lastfphysrec(&dsk_file->fdr, cluster_index, last_sec); cluster_AUlen = (cur_block_seclen + dsk_file->l2_img->AUformat.physrecsperAU - 1) / dsk_file->l2_img->AUformat.physrecsperAU; for (i=0; il2_img->abm[(i+cluster_baseAU) >> 3] |= 1 << ((i+cluster_baseAU) & 7); if (! nb_alloc_physrecs) return 0; /* done */ } cluster_index++; if (cluster_index == 76) /* that sucks */ return IMGTOOLERR_NOSPACE; } search_start = dsk_file->l2_img->data_offset; /* initially, search for free space only in data space */ while (nb_alloc_physrecs) { /* find smallest data block at least nb_alloc_physrecs in length, and largest data block less than nb_alloc_physrecs in length */ first_best_block_seclen = INT_MAX; second_best_block_seclen = 0; for (i=search_start; il2_img->abm[i >> 3] & (1 << (i & 7)))) { /* found one free block */ /* compute its length */ cluster_baseAU = i; cur_block_seclen = 0; while ((il2_img->abm[i >> 3] & (1 << (i & 7))))) { cur_block_seclen += dsk_file->l2_img->AUformat.physrecsperAU; i++; } /* compare to previous best and second-best blocks */ if ((cur_block_seclen < first_best_block_seclen) && (cur_block_seclen >= nb_alloc_physrecs)) { first_best_block_baseAU = cluster_baseAU; first_best_block_seclen = cur_block_seclen; if (cur_block_seclen == nb_alloc_physrecs) /* no need to search further */ break; } else if ((cur_block_seclen > second_best_block_seclen) && (cur_block_seclen < nb_alloc_physrecs)) { second_best_block_baseAU = cluster_baseAU; second_best_block_seclen = cur_block_seclen; } } } if (first_best_block_seclen != INT_MAX) { /* found one contiguous block which can hold it all */ fphysrecs += nb_alloc_physrecs; set_UINT16BE(& dsk_file->fdr.fphysrecs, fphysrecs); set_dsk_fdr_cluster(dsk_file->l2_img, &dsk_file->fdr, cluster_index, first_best_block_baseAU, fphysrecs-1); cluster_AUlen = (nb_alloc_physrecs + dsk_file->l2_img->AUformat.physrecsperAU - 1) / dsk_file->l2_img->AUformat.physrecsperAU; for (i=0; il2_img->abm[(i+first_best_block_baseAU) >> 3] |= 1 << ((i+first_best_block_baseAU) & 7); nb_alloc_physrecs = 0; } else if (second_best_block_seclen != 0) { /* jeez, we need to fragment it. We use the largest smaller block to limit fragmentation. */ fphysrecs += second_best_block_seclen; set_UINT16BE(& dsk_file->fdr.fphysrecs, fphysrecs); set_dsk_fdr_cluster(dsk_file->l2_img, &dsk_file->fdr, cluster_index, second_best_block_baseAU, fphysrecs-1); cluster_AUlen = (second_best_block_seclen + dsk_file->l2_img->AUformat.physrecsperAU - 1) / dsk_file->l2_img->AUformat.physrecsperAU; for (i=0; il2_img->abm[(i+second_best_block_baseAU) >> 3] |= 1 << ((i+second_best_block_baseAU) & 7); nb_alloc_physrecs -= second_best_block_seclen; cluster_index++; if (cluster_index == 76) /* that sucks */ return IMGTOOLERR_NOSPACE; } else if (search_start != 0) { /* we did not find any free physrec in the data section of the disk */ search_start = 0; /* time to fall back to fdr space */ } else return IMGTOOLERR_NOSPACE; /* This should never happen, as we pre-check that there is enough free space */ } return 0; } /* Extend a file with nb_alloc_physrecs extra physrecs win_file: file reference nb_alloc_physrecs: number of physical records to allocate */ static int win_alloc_file_physrecs(ti99_lvl2_fileref_win *win_file, int nb_alloc_physrecs) { int totAUs = win_file->l2_img->AUformat.totAUs; int free_physrecs; int fphysrecs; int i; int cluster_index; int num_fphysrec; int cur_block_seclen; int cluster_baseAU, cluster_AUlen; int first_best_block_baseAU = 0, first_best_block_seclen; int second_best_block_baseAU = 0, second_best_block_seclen; int search_start; int errorcode; /* compute free space */ free_physrecs = 0; for (i=0; il2_img->abm[i >> 3] & (1 << (i & 7)))) free_physrecs += win_file->l2_img->AUformat.physrecsperAU; } /* check we have enough free space */ if (free_physrecs < nb_alloc_physrecs) return IMGTOOLERR_NOSPACE; /* move to last sibling non-empty FDR */ while ((get_UINT16BE(win_file->curfdr.nextsibFDR_AU) != 0) && (get_UINT16BE(win_file->curfdr.clusters[53][0]) != 0)) { errorcode = win_goto_next_sibFDR(win_file); if (errorcode) return errorcode; } if ((get_UINT16BE(win_file->curfdr.clusters[0][0]) == 0) && (get_UINT16BE(win_file->curfdr.prevsibFDR_AU) != 0)) { /* this is annoying: we have found a sibling FDR filled with 0s: rewind to last non-empty sibling if applicable */ errorcode = win_goto_prev_sibFDR(win_file); if (errorcode) return errorcode; } /* current number of data physrecs in file */ fphysrecs = win_file->fphysrecs; /* current number of allocated physrecs */ num_fphysrec = get_win_fdr_cursibFDR_basefphysrec(&win_file->curfdr) + get_UINT16BE(win_file->curfdr.sibFDR_AUlen) * win_file->l2_img->AUformat.physrecsperAU; if (num_fphysrec > fphysrecs) { /* some extra space has already been allocated */ cur_block_seclen = num_fphysrec - fphysrecs; if (cur_block_seclen > nb_alloc_physrecs) cur_block_seclen = nb_alloc_physrecs; fphysrecs += cur_block_seclen; win_file->fphysrecs = fphysrecs; /* TODO: update eldest FDR fphysrecs field */ /*set_win_fdr_fphysrecs(& win_file->rootfdr.fphysrecs, fphysrecs);*/ nb_alloc_physrecs -= cur_block_seclen; if (! nb_alloc_physrecs) return 0; /* done */ } /* find last non-empty cluster */ for (cluster_index=0; (cluster_index<54) && (get_UINT16BE(win_file->curfdr.clusters[cluster_index][0]) != 0); cluster_index++) ; /* if we are dealing with an empty file, we will have (cluster_index == 0)... */ if (cluster_index != 0) { cluster_index--; /* try to extend last cluster */ /* point past cluster end */ cluster_baseAU = get_UINT16BE(win_file->curfdr.clusters[cluster_index][1]) + 1; /* count free physrecs after last block */ cur_block_seclen = 0; for (i=cluster_baseAU; (! (win_file->l2_img->abm[i >> 3] & (1 << (i & 7)))) && (cur_block_seclen < nb_alloc_physrecs) && (i < totAUs); i++) cur_block_seclen += win_file->l2_img->AUformat.physrecsperAU; if (cur_block_seclen) { /* extend last block */ if (cur_block_seclen > nb_alloc_physrecs) cur_block_seclen = nb_alloc_physrecs; fphysrecs += cur_block_seclen; cluster_AUlen = (cur_block_seclen + win_file->l2_img->AUformat.physrecsperAU - 1) / win_file->l2_img->AUformat.physrecsperAU; win_file->fphysrecs = fphysrecs; /* TODO: update eldest FDR fphysrecs field */ /*set_win_fdr_fphysrecs(& win_file->rootfdr.fphysrecs, fphysrecs);*/ set_UINT16BE(&win_file->curfdr.sibFDR_AUlen, get_UINT16BE(win_file->curfdr.sibFDR_AUlen)+cluster_AUlen); set_UINT16BE(&win_file->curfdr.clusters[cluster_index][1], get_UINT16BE(win_file->curfdr.clusters[cluster_index][1])+cluster_AUlen); for (i=0; il2_img->abm[(i+cluster_baseAU) >> 3] |= 1 << ((i+cluster_baseAU) & 7); nb_alloc_physrecs -= cur_block_seclen; if (! nb_alloc_physrecs) return 0; /* done */ } /* now point to first free entry in cluster table */ cluster_index++; } search_start = win_file->l2_img->data_offset; /* initially, search for free space only in data space */ while (nb_alloc_physrecs) { /* find smallest data block at least nb_alloc_physrecs in length, and largest data block less than nb_alloc_physrecs in length */ first_best_block_seclen = INT_MAX; second_best_block_seclen = 0; for (i=search_start; il2_img->abm[i >> 3] & (1 << (i & 7)))) { /* found one free block */ /* compute its length */ cluster_baseAU = i; cur_block_seclen = 0; while ((il2_img->abm[i >> 3] & (1 << (i & 7))))) { cur_block_seclen += win_file->l2_img->AUformat.physrecsperAU; i++; } /* compare to previous best and second-best blocks */ if ((cur_block_seclen < first_best_block_seclen) && (cur_block_seclen >= nb_alloc_physrecs)) { first_best_block_baseAU = cluster_baseAU; first_best_block_seclen = cur_block_seclen; if (cur_block_seclen == nb_alloc_physrecs) /* no need to search further */ break; } else if ((cur_block_seclen > second_best_block_seclen) && (cur_block_seclen < nb_alloc_physrecs)) { second_best_block_baseAU = cluster_baseAU; second_best_block_seclen = cur_block_seclen; } } } if ((first_best_block_seclen != INT_MAX) || (second_best_block_seclen != 0)) { if (cluster_index == 54) { /* end of cluster list: go to next sibling FDR */ if (write_absolute_physrec(& win_file->l2_img->l1_img, win_file->curfdr_aphysrec, &win_file->curfdr)) return IMGTOOLERR_WRITEERROR; if (get_UINT16BE(win_file->curfdr.nextsibFDR_AU) != 0) { /* read next sibling FDR */ errorcode = win_goto_next_sibFDR(win_file); if (errorcode) return errorcode; } else { /* allocate new sibling FDR */ errorcode = win_alloc_sibFDR(win_file); if (errorcode) return errorcode; } cluster_index = 0; } } if (first_best_block_seclen != INT_MAX) { /* found one contiguous block which can hold it all */ fphysrecs += nb_alloc_physrecs; cluster_AUlen = (nb_alloc_physrecs + win_file->l2_img->AUformat.physrecsperAU - 1) / win_file->l2_img->AUformat.physrecsperAU; win_file->fphysrecs = fphysrecs; /* TODO: update eldest FDR fphysrecs field */ /*set_win_fdr_fphysrecs(& win_file->rootfdr.fphysrecs, fphysrecs);*/ set_UINT16BE(&win_file->curfdr.sibFDR_AUlen, get_UINT16BE(win_file->curfdr.sibFDR_AUlen)+cluster_AUlen); set_UINT16BE(&win_file->curfdr.clusters[cluster_index][0], first_best_block_baseAU); set_UINT16BE(&win_file->curfdr.clusters[cluster_index][1], first_best_block_baseAU+cluster_AUlen-1); for (i=0; il2_img->abm[(i+first_best_block_baseAU) >> 3] |= 1 << ((i+first_best_block_baseAU) & 7); nb_alloc_physrecs = 0; } else if (second_best_block_seclen != 0) { /* jeez, we need to fragment it. We use the largest smaller block to limit fragmentation. */ fphysrecs += second_best_block_seclen; cluster_AUlen = (second_best_block_seclen + win_file->l2_img->AUformat.physrecsperAU - 1) / win_file->l2_img->AUformat.physrecsperAU; win_file->fphysrecs = fphysrecs; /* TODO: update eldest FDR fphysrecs field */ /*set_win_fdr_fphysrecs(& win_file->rootfdr.fphysrecs, fphysrecs);*/ set_UINT16BE(&win_file->curfdr.sibFDR_AUlen, get_UINT16BE(win_file->curfdr.sibFDR_AUlen)+cluster_AUlen); set_UINT16BE(&win_file->curfdr.clusters[cluster_index][0], second_best_block_baseAU); set_UINT16BE(&win_file->curfdr.clusters[cluster_index][1], second_best_block_baseAU+cluster_AUlen-1); for (i=0; il2_img->abm[(i+second_best_block_baseAU) >> 3] |= 1 << ((i+second_best_block_baseAU) & 7); nb_alloc_physrecs -= second_best_block_seclen; /* now point to first free entry in cluster table */ cluster_index++; } else if (search_start != 0) { /* we did not find any free physrec in the data section of the disk */ search_start = 0; /* time to fall back to fdr space */ } else return IMGTOOLERR_NOSPACE; /* This should never happen, as we pre-check that there is enough free space */ } return 0; } /* Allocate a new (empty) file */ static int new_file_lvl2_dsk(struct ti99_lvl2_imgref *l2_img, int parent_ref, char filename[10], struct ti99_lvl2_fileref *l2_file) { ti99_catalog *catalog = &l2_img->dsk.catalogs[parent_ref]; unsigned fdr_AU, fdr_aphysrec; int catalog_index, i; int reply = 0; int errorcode; if (catalog->num_files >= 127) /* if num_files == 128, catalog is full */ /* if num_files == 127, catalog is not full, but we don't want to write a 128th entry for compatibility with some existing DSRs that detect the end of the FDIR array with a 0 entry (and do not check that the index has not reached 128) */ return IMGTOOLERR_NOSPACE; /* find insertion point in catalog */ for (i=0; (i < catalog->num_files) && ((reply = memcmp(catalog->files[i].name, filename, 10)) < 0); i++) ; if ((inum_files) && (reply == 0)) /* file already exists */ return IMGTOOLERR_BADFILENAME; else { /* otherwise insert new entry */ catalog_index = i; errorcode = alloc_fdr_AU(l2_img, &fdr_AU); if (errorcode) return errorcode; fdr_aphysrec = fdr_AU * l2_img->AUformat.physrecsperAU; /* shift catalog entries until the insertion point */ for (i=catalog->num_files; i>catalog_index; i--) catalog->files[i] = catalog->files[i-1]; /* write new catalog entry */ catalog->files[catalog_index].fdr_ptr = fdr_aphysrec; memcpy(catalog->files[catalog_index].name, filename, 10); /* update catalog len */ catalog->num_files++; } /* set up file handle */ l2_file->type = L2F_DSK; l2_file->dsk.l2_img = l2_img; l2_file->dsk.fdr_aphysrec = fdr_aphysrec; memset(&l2_file->dsk.fdr, 0, sizeof(l2_file->dsk.fdr)); memcpy(l2_file->dsk.fdr.name, filename, 10); return 0; } /* Allocate a new (empty) file */ static int new_file_lvl2_win(struct ti99_lvl2_imgref *l2_img, ti99_catalog *parent_catalog, char filename[10], struct ti99_lvl2_fileref *l2_file) { unsigned fdr_AU; int catalog_index, i; int reply = 0; int errorcode; if (parent_catalog->num_files >= 127) /* if num_files == 127, catalog is full */ return IMGTOOLERR_NOSPACE; /* find insertion point in catalog */ for (i=0; (i < parent_catalog->num_files) && ((reply = memcmp(parent_catalog->files[i].name, filename, 10)) < 0); i++) ; if ((inum_files) && (reply == 0)) /* file already exists */ return IMGTOOLERR_BADFILENAME; else { /* otherwise insert new entry */ catalog_index = i; errorcode = alloc_fdr_AU(l2_img, &fdr_AU); if (errorcode) return errorcode; /* shift catalog entries until the insertion point */ for (i=parent_catalog->num_files; i>catalog_index; i--) parent_catalog->files[i] = parent_catalog->files[i-1]; /* write new catalog entry */ parent_catalog->files[catalog_index].fdr_ptr = fdr_AU; memcpy(parent_catalog->files[catalog_index].name, filename, 10); /* update catalog len */ parent_catalog->num_files++; } /* set up file handle */ l2_file->type = L2F_WIN; l2_file->win.l2_img = l2_img; l2_file->win.fphysrecs = 0; l2_file->win.eldestfdr_aphysrec = fdr_AU * l2_img->AUformat.physrecsperAU; l2_file->win.curfdr_aphysrec = l2_file->win.eldestfdr_aphysrec; memset(&l2_file->win.curfdr, 0, sizeof(l2_file->win.curfdr)); memcpy(l2_file->win.curfdr.name, filename, 10); return 0; } /* Allocate a new (empty) file */ static int new_file_lvl2_tifiles(imgtool::stream &file_handle, struct ti99_lvl2_fileref *l2_file) { /* set up file handle */ l2_file->type = L2F_TIFILES; l2_file->tifiles.file_handle = &file_handle; memset(&l2_file->tifiles.hdr, 0, sizeof(l2_file->tifiles.hdr)); l2_file->tifiles.hdr.tifiles[0] = '\7'; l2_file->tifiles.hdr.tifiles[1] = 'T'; l2_file->tifiles.hdr.tifiles[2] = 'I'; l2_file->tifiles.hdr.tifiles[3] = 'F'; l2_file->tifiles.hdr.tifiles[4] = 'I'; l2_file->tifiles.hdr.tifiles[5] = 'L'; l2_file->tifiles.hdr.tifiles[6] = 'E'; l2_file->tifiles.hdr.tifiles[7] = 'S'; return 0; } /* Open an existing file on a floppy image l2_img: level 2 image the file is located on fpath: access path to the file file: set up if open is successful */ static int open_file_lvl2_dsk(struct ti99_lvl2_imgref *l2_img, const char *fpath, struct ti99_lvl2_fileref *l2_file) { int parent_ref, is_dir, catalog_index; int reply; reply = dsk_find_catalog_entry(l2_img, fpath, NULL, &parent_ref, &is_dir, &catalog_index); if (reply) return reply; if (is_dir) /* this is not a file */ return IMGTOOLERR_BADFILENAME; l2_file->type = L2F_DSK; l2_file->dsk.l2_img = l2_img; l2_file->dsk.fdr_aphysrec = l2_img->dsk.catalogs[parent_ref].files[catalog_index].fdr_ptr; if (read_absolute_physrec(& l2_img->l1_img, l2_file->dsk.fdr_aphysrec, &l2_file->dsk.fdr)) return IMGTOOLERR_READERROR; return 0; } /* Open an existing file on a harddisk image l2_img: level 2 image the file is located on fpath: access path to the file file: set up if open is successful */ static int open_file_lvl2_win(struct ti99_lvl2_imgref *l2_img, const char *fpath, struct ti99_lvl2_fileref *l2_file) { int parent_ref, is_dir, catalog_index; ti99_catalog catalog; int reply; reply = win_find_catalog_entry(l2_img, fpath, NULL, &parent_ref, &catalog, &is_dir, &catalog_index); if (reply) return reply; if (is_dir) /* this is not a file */ return IMGTOOLERR_BADFILENAME; l2_file->type = L2F_WIN; l2_file->win.l2_img = l2_img; l2_file->win.eldestfdr_aphysrec = catalog.files[catalog_index].fdr_ptr * l2_img->AUformat.physrecsperAU; l2_file->win.curfdr_aphysrec = l2_file->win.eldestfdr_aphysrec; if (read_absolute_physrec(& l2_img->l1_img, l2_file->win.curfdr_aphysrec, &l2_file->win.curfdr)) return IMGTOOLERR_READERROR; l2_file->win.fphysrecs = get_win_fdr_fphysrecs(&l2_file->win.curfdr); /* check integrity of FDR sibling chain */ /* note that as we check that the back chain is consistent with the forward chain, we will also detect any cycle in the sibling chain, so we do not need to check against them explicitely */ if (get_UINT16BE(l2_file->win.curfdr.prevsibFDR_AU) != 0) return IMGTOOLERR_CORRUPTIMAGE; { int i, pastendoflist_flag; unsigned cur_fphysrec, sibFDR_AUlen; win_fdr *cur_fdr, fdr_buf; unsigned curfdr_aphysrec, prevfdr_aphysrec; cur_fphysrec = 0; pastendoflist_flag = 0; cur_fdr = &l2_file->win.curfdr; curfdr_aphysrec = l2_file->win.eldestfdr_aphysrec; while (1) { sibFDR_AUlen = 0; i=0; if (! pastendoflist_flag) { /* compute number of allocated AUs and check number of AUs */ for (; i<54; i++) { if (get_UINT16BE(cur_fdr->clusters[i][0]) == 0) { pastendoflist_flag = true; break; } sibFDR_AUlen += get_UINT16BE(cur_fdr->clusters[i][1]) - get_UINT16BE(cur_fdr->clusters[i][0]) + 1; } } /* clear remainder of cluster table */ for (; i<54; i++) { #if 0 set_UINT16BE(&cur_fdr->clusters[i][0], 0); set_UINT16BE(&cur_fdr->clusters[i][1], 0); #endif if ((get_UINT16BE(cur_fdr->clusters[i][0]) != 0) || (get_UINT16BE(cur_fdr->clusters[i][1]) != 0)) return IMGTOOLERR_CORRUPTIMAGE; } /* check sibFDR_AUlen field */ if (get_UINT16BE(cur_fdr->sibFDR_AUlen) != sibFDR_AUlen) return IMGTOOLERR_CORRUPTIMAGE; /* update current file physrec position to point to end of sibling FDR */ cur_fphysrec += sibFDR_AUlen * l2_file->win.l2_img->AUformat.physrecsperAU; /* exit loop if end of sibling chain */ if (! get_UINT16BE(cur_fdr->nextsibFDR_AU)) break; /* otherwise read next FDR */ if (get_UINT16BE(cur_fdr->nextsibFDR_AU) >= l2_file->win.l2_img->AUformat.totAUs) return IMGTOOLERR_CORRUPTIMAGE; prevfdr_aphysrec = curfdr_aphysrec; curfdr_aphysrec = get_win_fdr_nextsibFDR_aphysrec(l2_file->win.l2_img, cur_fdr); if (read_absolute_physrec(& l2_file->win.l2_img->l1_img, curfdr_aphysrec, &fdr_buf)) return IMGTOOLERR_READERROR; cur_fdr = &fdr_buf; /* check that back chaining is consistent with forward chaining */ if (get_win_fdr_prevsibFDR_aphysrec(l2_file->win.l2_img, &fdr_buf) != prevfdr_aphysrec) return IMGTOOLERR_CORRUPTIMAGE; /* check fphysrecs field */ if (get_win_fdr_fphysrecs(&fdr_buf) != cur_fphysrec) return IMGTOOLERR_CORRUPTIMAGE; /* check consistency of informative fields (name, record format, flags, etc) */ if (memcmp(fdr_buf.name, l2_file->win.curfdr.name, 10) || (get_UINT16BE(fdr_buf.xreclen) != get_UINT16BE(l2_file->win.curfdr.xreclen)) || (fdr_buf.flags != l2_file->win.curfdr.flags) || (fdr_buf.recsperphysrec != l2_file->win.curfdr.recsperphysrec) || (fdr_buf.eof != l2_file->win.curfdr.eof) || (fdr_buf.reclen != l2_file->win.curfdr.reclen) || (get_UINT16LE(fdr_buf.fixrecs_LSW) != get_UINT16LE(l2_file->win.curfdr.fixrecs_LSW)) || memcmp(&fdr_buf.creation, &l2_file->win.curfdr.creation, 4) || memcmp(&fdr_buf.update, &l2_file->win.curfdr.update, 4) /*|| memcmp(fdr_buf.id, l2_file->win.curfdr.id, 2)*/ || (get_UINT16BE(fdr_buf.parent_FDIR_AU) != get_UINT16BE(l2_file->win.curfdr.parent_FDIR_AU)) || ((fdr_buf.xinfo_MSB & 0xf) != (l2_file->win.curfdr.xinfo_MSB & 0xf))) return IMGTOOLERR_CORRUPTIMAGE; } if (cur_fphysrec < l2_file->win.fphysrecs) return IMGTOOLERR_CORRUPTIMAGE; } return 0; } /* Open an existing file in TIFILES format */ static int open_file_lvl2_tifiles(imgtool::stream &file_handle, struct ti99_lvl2_fileref *l2_file) { /* set up file handle */ l2_file->type = L2F_TIFILES; l2_file->tifiles.file_handle = &file_handle; /* seek to header */ if (l2_file->tifiles.file_handle->seek(0, SEEK_SET)) return IMGTOOLERR_READERROR; /* read it */ if (l2_file->tifiles.file_handle->read(&l2_file->tifiles.hdr, sizeof(l2_file->tifiles.hdr)) != sizeof(l2_file->tifiles.hdr)) return IMGTOOLERR_READERROR; return 0; } /* compute the aphysrec address for a given file physical record (fphysrec) l2_img: image where the file is located */ static int dsk_fphysrec_to_aphysrec(ti99_lvl2_fileref_dsk *dsk_file, unsigned fphysrec, unsigned *aphysrec) { int cluster_index; int cluster_firstfphysrec, cluster_lastfphysrec; int cluster_baseaphysrec; /* check parameter */ if (fphysrec >= get_UINT16BE(dsk_file->fdr.fphysrecs)) return IMGTOOLERR_UNEXPECTED; /* search for the cluster in the data chain pointers array */ cluster_firstfphysrec = 0; for (cluster_index=0; cluster_index<76; cluster_index++) { /* read curent file block table entry */ cluster_lastfphysrec = get_dsk_fdr_cluster_lastfphysrec(&dsk_file->fdr, cluster_index); if (cluster_lastfphysrec >= fphysrec) break; cluster_firstfphysrec = cluster_lastfphysrec+1; } if (cluster_index == 76) /* if not found, the file is corrupt */ return IMGTOOLERR_CORRUPTIMAGE; /* read base aphysrec address for this cluster */ cluster_baseaphysrec = get_dsk_fdr_cluster_baseaphysrec(dsk_file->l2_img, &dsk_file->fdr, cluster_index); /* return absolute physrec address */ *aphysrec = cluster_baseaphysrec + (fphysrec - cluster_firstfphysrec); return 0; } /* compute the aphysrec address for a given file physical record (fphysrec) l2_img: image where the file is located */ static int win_fphysrec_to_aphysrec(ti99_lvl2_fileref_win *win_file, unsigned fphysrec, unsigned *aphysrec) { int cluster_index; int cluster_firstfphysrec, cluster_lastfphysrec; int cluster_baseaphysrec; int errorcode; /* check parameter */ if (fphysrec >= win_file->fphysrecs) return IMGTOOLERR_UNEXPECTED; /* look for correct sibling */ if (fphysrec < get_win_fdr_cursibFDR_basefphysrec(& win_file->curfdr)) { while (fphysrec < get_win_fdr_cursibFDR_basefphysrec(& win_file->curfdr)) { if (get_UINT16BE(win_file->curfdr.prevsibFDR_AU) == 0) return IMGTOOLERR_CORRUPTIMAGE; errorcode = win_goto_prev_sibFDR(win_file); if (errorcode) return errorcode; } } else /*if (fphysrec >= get_win_fdr_cursibFDR_basefphysrec(& dsk_file->curfdr))*/ { while (fphysrec >= (get_win_fdr_cursibFDR_basefphysrec(& win_file->curfdr) + get_UINT16BE(win_file->curfdr.sibFDR_AUlen) * win_file->l2_img->AUformat.physrecsperAU)) { if (get_UINT16BE(win_file->curfdr.nextsibFDR_AU) == 0) return IMGTOOLERR_CORRUPTIMAGE; errorcode = win_goto_next_sibFDR(win_file); if (errorcode) return errorcode; } } /* search for the cluster in the data chain pointers array */ cluster_firstfphysrec = get_win_fdr_cursibFDR_basefphysrec(& win_file->curfdr); for (cluster_index = 0; cluster_index<54; cluster_index++) { cluster_lastfphysrec = cluster_firstfphysrec + (get_UINT16BE(win_file->curfdr.clusters[cluster_index][1]) - get_UINT16BE(win_file->curfdr.clusters[cluster_index][0]) + 1) * win_file->l2_img->AUformat.physrecsperAU; if (fphysrec < cluster_lastfphysrec) break; cluster_firstfphysrec = cluster_lastfphysrec; } if (cluster_index == 54) return IMGTOOLERR_CORRUPTIMAGE; /* read base aphysrec address for this cluster */ cluster_baseaphysrec = get_UINT16BE(win_file->curfdr.clusters[cluster_index][0]) * win_file->l2_img->AUformat.physrecsperAU; /* return absolute physrec address */ *aphysrec = cluster_baseaphysrec + (fphysrec - cluster_firstfphysrec); return 0; } /* read a 256-byte physical record from a file */ static int read_file_physrec(struct ti99_lvl2_fileref *l2_file, unsigned fphysrec, void *dest) { int errorcode; unsigned aphysrec; switch (l2_file->type) { case L2F_DSK: /* compute absolute physrec address */ errorcode = dsk_fphysrec_to_aphysrec(&l2_file->dsk, fphysrec, &aphysrec); if (errorcode) return errorcode; /* read physrec */ if (read_absolute_physrec(& l2_file->dsk.l2_img->l1_img, aphysrec, dest)) return IMGTOOLERR_READERROR; break; case L2F_WIN: /* compute absolute physrec address */ errorcode = win_fphysrec_to_aphysrec(&l2_file->win, fphysrec, &aphysrec); if (errorcode) return errorcode; /* read physrec */ if (read_absolute_physrec(& l2_file->win.l2_img->l1_img, aphysrec, dest)) return IMGTOOLERR_READERROR; break; case L2F_TIFILES: /* seek to physrec */ if (l2_file->tifiles.file_handle->seek(128+256*fphysrec, SEEK_SET)) return IMGTOOLERR_READERROR; /* read it */ if (l2_file->tifiles.file_handle->read(dest, 256) != 256) return IMGTOOLERR_READERROR; break; } return 0; } /* read a 256-byte physical record from a file */ static int write_file_physrec(struct ti99_lvl2_fileref *l2_file, unsigned fphysrec, const void *src) { int errorcode; unsigned aphysrec; switch (l2_file->type) { case L2F_DSK: /* compute absolute physrec address */ errorcode = dsk_fphysrec_to_aphysrec(&l2_file->dsk, fphysrec, &aphysrec); if (errorcode) return errorcode; /* write physrec */ if (write_absolute_physrec(& l2_file->dsk.l2_img->l1_img, aphysrec, src)) return IMGTOOLERR_WRITEERROR; break; case L2F_WIN: /* compute absolute physrec address */ errorcode = win_fphysrec_to_aphysrec(&l2_file->win, fphysrec, &aphysrec); if (errorcode) return errorcode; /* write physrec */ if (write_absolute_physrec(& l2_file->win.l2_img->l1_img, aphysrec, src)) return IMGTOOLERR_WRITEERROR; break; case L2F_TIFILES: /* seek to physrec */ if (l2_file->tifiles.file_handle->seek(128+256*fphysrec, SEEK_SET)) return IMGTOOLERR_WRITEERROR; /* write it */ if (l2_file->tifiles.file_handle->write(src, 256) != 256) return IMGTOOLERR_WRITEERROR; break; } return 0; } /* Write a field in every fdr record associated to a file */ [[maybe_unused]] static int set_win_fdr_field(struct ti99_lvl2_fileref *l2_file, size_t offset, size_t size, void *data) { win_fdr fdr_buf; unsigned fdr_aphysrec; int errorcode = 0; for (fdr_aphysrec = l2_file->win.eldestfdr_aphysrec; fdr_aphysrec && ((errorcode = (read_absolute_physrec(&l2_file->win.l2_img->l1_img, fdr_aphysrec, &fdr_buf) ? IMGTOOLERR_READERROR : 0)) == 0); fdr_aphysrec = get_win_fdr_nextsibFDR_aphysrec(l2_file->win.l2_img, &fdr_buf)) { memcpy(((uint8_t *) &fdr_buf) + offset, data, size); if (write_absolute_physrec(&l2_file->win.l2_img->l1_img, fdr_aphysrec, &fdr_buf)) { errorcode = IMGTOOLERR_WRITEERROR; break; } } return errorcode; } static uint8_t get_file_flags(struct ti99_lvl2_fileref *l2_file) { int reply = 0; switch (l2_file->type) { case L2F_DSK: reply = l2_file->dsk.fdr.flags; break; case L2F_WIN: reply = l2_file->win.curfdr.flags; break; case L2F_TIFILES: reply = l2_file->tifiles.hdr.flags; break; } return reply; } static void set_file_flags(struct ti99_lvl2_fileref *l2_file, uint8_t data) { switch (l2_file->type) { case L2F_DSK: l2_file->dsk.fdr.flags = data; break; case L2F_WIN: l2_file->win.curfdr.flags = data; break; case L2F_TIFILES: l2_file->tifiles.hdr.flags = data; break; } } static uint8_t get_file_recsperphysrec(struct ti99_lvl2_fileref *l2_file) { int reply = 0; switch (l2_file->type) { case L2F_DSK: reply = l2_file->dsk.fdr.recsperphysrec; break; case L2F_WIN: reply = l2_file->win.curfdr.recsperphysrec; break; case L2F_TIFILES: reply = l2_file->tifiles.hdr.recsperphysrec; break; } return reply; } static void set_file_recsperphysrec(struct ti99_lvl2_fileref *l2_file, uint8_t data) { switch (l2_file->type) { case L2F_DSK: l2_file->dsk.fdr.recsperphysrec = data; break; case L2F_WIN: l2_file->win.curfdr.recsperphysrec = data; break; case L2F_TIFILES: l2_file->tifiles.hdr.recsperphysrec = data; break; } } static unsigned get_file_fphysrecs(struct ti99_lvl2_fileref *l2_file) { int reply = 0; switch (l2_file->type) { case L2F_DSK: reply = get_UINT16BE(l2_file->dsk.fdr.fphysrecs); break; case L2F_WIN: reply = l2_file->win.fphysrecs; break; case L2F_TIFILES: reply = get_UINT16BE(l2_file->tifiles.hdr.fphysrecs); break; } return reply; } static int set_file_fphysrecs(struct ti99_lvl2_fileref *l2_file, unsigned data) { switch (l2_file->type) { case L2F_DSK: if (data >= 65536) return IMGTOOLERR_UNIMPLEMENTED; set_UINT16BE(&l2_file->dsk.fdr.fphysrecs, data); break; case L2F_WIN: l2_file->win.fphysrecs = data; break; case L2F_TIFILES: if (data >= 65536) return IMGTOOLERR_UNIMPLEMENTED; set_UINT16BE(&l2_file->tifiles.hdr.fphysrecs, data); break; } return 0; } static uint8_t get_file_eof(struct ti99_lvl2_fileref *l2_file) { int reply = 0; switch (l2_file->type) { case L2F_DSK: reply = l2_file->dsk.fdr.eof; break; case L2F_WIN: reply = l2_file->win.curfdr.eof; break; case L2F_TIFILES: reply = l2_file->tifiles.hdr.eof; break; } return reply; } static void set_file_eof(struct ti99_lvl2_fileref *l2_file, uint8_t data) { switch (l2_file->type) { case L2F_DSK: l2_file->dsk.fdr.eof = data; break; case L2F_WIN: l2_file->win.curfdr.eof = data; break; case L2F_TIFILES: l2_file->tifiles.hdr.eof = data; break; } } static uint16_t get_file_reclen(struct ti99_lvl2_fileref *l2_file) { int reply = 0; switch (l2_file->type) { case L2F_DSK: reply = l2_file->dsk.fdr.reclen; if ((reply == 0) && (! (l2_file->dsk.fdr.flags & (fdr99_f_program /*| fdr99_f_var*/)))) reply = get_UINT16BE(l2_file->dsk.fdr.xreclen); break; case L2F_WIN: reply = l2_file->win.curfdr.reclen; if ((reply == 0) && (! (l2_file->win.curfdr.flags & (fdr99_f_program /*| fdr99_f_var*/)))) reply = get_UINT16BE(l2_file->win.curfdr.xreclen); break; case L2F_TIFILES: reply = l2_file->tifiles.hdr.reclen; break; } return reply; } static int set_file_reclen(struct ti99_lvl2_fileref *l2_file, uint16_t data) { switch (l2_file->type) { case L2F_DSK: if (data < 256) { l2_file->dsk.fdr.reclen = data; set_UINT16BE(&l2_file->dsk.fdr.xreclen, 0); } else { l2_file->dsk.fdr.reclen = 0; set_UINT16BE(&l2_file->dsk.fdr.xreclen, data); } break; case L2F_WIN: if (data < 256) { l2_file->win.curfdr.reclen = data; set_UINT16BE(&l2_file->win.curfdr.xreclen, 0); } else { l2_file->win.curfdr.reclen = 0; set_UINT16BE(&l2_file->win.curfdr.xreclen, data); } break; case L2F_TIFILES: if (data >= 256) return IMGTOOLERR_UNIMPLEMENTED; l2_file->tifiles.hdr.reclen = data; break; } return 0; } static unsigned get_file_fixrecs(struct ti99_lvl2_fileref *l2_file) { int reply = 0; switch (l2_file->type) { case L2F_DSK: reply = get_UINT16LE(l2_file->dsk.fdr.fixrecs); break; case L2F_WIN: reply = get_win_fdr_fixrecs(&l2_file->win.curfdr); break; case L2F_TIFILES: reply = get_UINT16BE(l2_file->tifiles.hdr.fixrecs); break; } return reply; } static int set_file_fixrecs(struct ti99_lvl2_fileref *l2_file, unsigned data) { switch (l2_file->type) { case L2F_DSK: if (data >= 65536) return IMGTOOLERR_UNIMPLEMENTED; set_UINT16LE(&l2_file->dsk.fdr.fixrecs, data); break; case L2F_WIN: set_win_fdr_fixrecs(&l2_file->win.curfdr, data); break; case L2F_TIFILES: if (data >= 65536) return IMGTOOLERR_UNIMPLEMENTED; set_UINT16BE(&l2_file->tifiles.hdr.fixrecs, data); break; } return 0; } static void get_file_creation_date(struct ti99_lvl2_fileref *l2_file, ti99_date_time *reply) { switch (l2_file->type) { case L2F_DSK: *reply = l2_file->dsk.fdr.creation; break; case L2F_WIN: *reply = l2_file->win.curfdr.creation; break; case L2F_TIFILES: memset(reply, 0, sizeof(*reply)); break; } } static void set_file_creation_date(struct ti99_lvl2_fileref *l2_file, ti99_date_time data) { switch (l2_file->type) { case L2F_DSK: l2_file->dsk.fdr.creation = data; break; case L2F_WIN: l2_file->win.curfdr.creation = data; break; case L2F_TIFILES: break; } } static void get_file_update_date(struct ti99_lvl2_fileref *l2_file, ti99_date_time *reply) { switch (l2_file->type) { case L2F_DSK: *reply = l2_file->dsk.fdr.update; break; case L2F_WIN: *reply = l2_file->win.curfdr.update; break; case L2F_TIFILES: memset(reply, 0, sizeof(*reply)); break; } } static void set_file_update_date(struct ti99_lvl2_fileref *l2_file, ti99_date_time data) { switch (l2_file->type) { case L2F_DSK: l2_file->dsk.fdr.update = data; break; case L2F_WIN: l2_file->win.curfdr.update = data; break; case L2F_TIFILES: break; } } [[maybe_unused]] static void current_date_time(ti99_date_time *reply) { /* All these functions should be ANSI */ time_t cur_time = time(nullptr); struct tm expanded_time = *localtime(& cur_time); reply->time_MSB = (expanded_time.tm_hour << 3) | (expanded_time.tm_min >> 3); reply->time_LSB = (expanded_time.tm_min << 5) | (expanded_time.tm_sec >> 1); reply->date_MSB = ((expanded_time.tm_year % 100) << 1) | ((expanded_time.tm_mon+1) >> 3); reply->date_LSB = ((expanded_time.tm_mon+1) << 5) | expanded_time.tm_mday; } #if 0 #pragma mark - #pragma mark LEVEL 3 DISK ROUTINES #endif /* Level 3 implements files as a succession of logical records. There are three types of files: * program files that are not implemented at level 3 (no logical record) * files with fixed-size records (random-access files) * files with variable-size records (sequential-access) */ struct ti99_lvl3_fileref { ti99_lvl2_fileref l2_file; int cur_log_rec; int cur_phys_rec; int cur_pos_in_phys_rec; }; /* Open a file on level 3. To open a file on level 3, you must open (or create) the file on level 2, then pass the file reference to open_file_lvl3. */ [[maybe_unused]] static int open_file_lvl3(ti99_lvl3_fileref *l3_file) { l3_file->cur_log_rec = 0; l3_file->cur_phys_rec = 0; l3_file->cur_pos_in_phys_rec = 0; /*if ()*/ return 0; } /* Test a file for EOF */ static int is_eof(ti99_lvl3_fileref *l3_file) { int flags = get_file_flags(&l3_file->l2_file); int fphysrecs = get_file_fphysrecs(&l3_file->l2_file); int fdr_eof = get_file_eof(&l3_file->l2_file); if (flags & fdr99_f_var) { return (l3_file->cur_phys_rec >= fphysrecs); } else { return ((l3_file->cur_phys_rec >= fphysrecs) || ((l3_file->cur_phys_rec == (fphysrecs-1)) && (l3_file->cur_pos_in_phys_rec >= (fdr_eof ? fdr_eof : 256)))); } } /* Read next record from a file */ [[maybe_unused]] static int read_next_record(ti99_lvl3_fileref *l3_file, void *dest, int *out_reclen) { int errorcode; uint8_t physrec_buf[256]; int reclen; int flags = get_file_flags(&l3_file->l2_file); int fphysrecs = get_file_fphysrecs(&l3_file->l2_file); int fdr_eof = get_file_eof(&l3_file->l2_file); if (flags & fdr99_f_program) { /* program files have no level-3 record */ return IMGTOOLERR_UNEXPECTED; } else if (flags & fdr99_f_var) { /* variable-length records */ if (is_eof(l3_file)) return IMGTOOLERR_UNEXPECTED; errorcode = read_file_physrec(&l3_file->l2_file, l3_file->cur_phys_rec, physrec_buf); if (errorcode) return errorcode; /* read reclen */ reclen = physrec_buf[l3_file->cur_pos_in_phys_rec]; /* check integrity */ if ((reclen == 0xff) || (reclen > get_file_reclen(&l3_file->l2_file)) || ((l3_file->cur_pos_in_phys_rec + 1 + reclen) > 256)) return IMGTOOLERR_CORRUPTIMAGE; /* copy to buffer */ memcpy(dest, physrec_buf + l3_file->cur_pos_in_phys_rec + 1, reclen); l3_file->cur_pos_in_phys_rec += reclen + 1; /* skip to next physrec if needed */ if ((l3_file->cur_pos_in_phys_rec == 256) || (physrec_buf[l3_file->cur_pos_in_phys_rec] == 0xff)) { l3_file->cur_pos_in_phys_rec = 0; l3_file->cur_phys_rec++; } (* out_reclen) = reclen; } else { /* fixed len records */ reclen = get_file_reclen(&l3_file->l2_file); if (is_eof(l3_file)) return IMGTOOLERR_UNEXPECTED; if ((l3_file->cur_pos_in_phys_rec + reclen) > 256) { l3_file->cur_pos_in_phys_rec = 0; l3_file->cur_phys_rec++; } if ((l3_file->cur_phys_rec >= fphysrecs) || ((l3_file->cur_phys_rec == (fphysrecs-1)) && ((l3_file->cur_pos_in_phys_rec + reclen) >= (fdr_eof ? fdr_eof : 256)))) return IMGTOOLERR_CORRUPTIMAGE; errorcode = read_file_physrec(&l3_file->l2_file, l3_file->cur_phys_rec, physrec_buf); if (errorcode) return errorcode; memcpy(dest, physrec_buf + l3_file->cur_pos_in_phys_rec, reclen); l3_file->cur_pos_in_phys_rec += reclen; if (l3_file->cur_pos_in_phys_rec == 256) { l3_file->cur_pos_in_phys_rec = 0; l3_file->cur_phys_rec++; } (* out_reclen) = reclen; } return 0; } #if 0 #pragma mark - #pragma mark IMGTOOL MODULE IMPLEMENTATION #endif /* ti99 catalog iterator, used when imgtool reads the catalog */ struct dsk_iterator { struct ti99_lvl2_imgref *image; int level; int listing_subdirs; /* true if we are listing subdirectories at current level */ int index[2]; /* current index in the disk catalog */ ti99_catalog *cur_catalog; /* current catalog */ }; struct win_iterator { struct ti99_lvl2_imgref *image; int level; int listing_subdirs; /* true if we are listing subdirectories at current level */ int index[MAX_DIR_LEVEL]; /* current index in the disk catalog */ ti99_catalog catalog[MAX_DIR_LEVEL]; /* current catalog */ }; static imgtoolerr_t dsk_image_init_mess(imgtool::image &image, imgtool::stream::ptr &&stream); static imgtoolerr_t dsk_image_init_v9t9(imgtool::image &image, imgtool::stream::ptr &&stream); static imgtoolerr_t dsk_image_init_pc99_fm(imgtool::image &image, imgtool::stream::ptr &&stream); static imgtoolerr_t dsk_image_init_pc99_mfm(imgtool::image &image, imgtool::stream::ptr &&stream); static imgtoolerr_t win_image_init(imgtool::image &image, imgtool::stream::ptr &&stream); static void ti99_image_exit(imgtool::image &img); static void ti99_image_info(imgtool::image &img, std::ostream &stream); static imgtoolerr_t dsk_image_beginenum(imgtool::directory &enumeration, const char *path); static imgtoolerr_t dsk_image_nextenum(imgtool::directory &enumeration, imgtool_dirent &ent); static imgtoolerr_t win_image_beginenum(imgtool::directory &enumeration, const char *path); static imgtoolerr_t win_image_nextenum(imgtool::directory &enumeration, imgtool_dirent &ent); static imgtoolerr_t ti99_image_freespace(imgtool::partition &partition, uint64_t *size); static imgtoolerr_t ti99_image_readfile(imgtool::partition &partition, const char *fpath, const char *fork, imgtool::stream &destf); static imgtoolerr_t ti99_image_writefile(imgtool::partition &partition, const char *fpath, const char *fork, imgtool::stream &sourcef, util::option_resolution *writeoptions); static imgtoolerr_t dsk_image_deletefile(imgtool::partition &partition, const char *fpath); static imgtoolerr_t win_image_deletefile(imgtool::partition &partition, const char *fpath); static imgtoolerr_t dsk_image_create_mess(imgtool::image &image, imgtool::stream::ptr &&stream, util::option_resolution *createoptions); static imgtoolerr_t dsk_image_create_v9t9(imgtool::image &image, imgtool::stream::ptr &&stream, util::option_resolution *createoptions); enum { dsk_createopts_volname = 'A', dsk_createopts_sides = 'B', dsk_createopts_tracks = 'C', dsk_createopts_sectors = 'D', dsk_createopts_protection = 'E', dsk_createopts_density = 'F' }; OPTION_GUIDE_START( dsk_create_optionguide ) OPTION_STRING(dsk_createopts_volname, "label", "Volume name" ) OPTION_INT(dsk_createopts_sides, "sides", "Sides" ) OPTION_INT(dsk_createopts_tracks, "tracks", "Tracks" ) OPTION_INT(dsk_createopts_sectors, "sectors", "Sectors (1->9 for SD, 1->18 for DD, 1->36 for HD)" ) OPTION_INT(dsk_createopts_protection, "protection", "Protection (0 for normal, 1 for protected)" ) OPTION_ENUM_START(dsk_createopts_density, "density", "Density" ) OPTION_ENUM( 0, "Auto", "Auto" ) OPTION_ENUM( 1, "SD", "Single Density" ) OPTION_ENUM( 2, "DD", "Double Density" ) OPTION_ENUM( 3, "HD", "High Density" ) OPTION_ENUM_END OPTION_GUIDE_END #define dsk_create_optionspecs "B1-[2];C1-[40]-80;D1-[18]-36;E[0]-1;F[0]-3" static void ti99_getinfo(const imgtool_class *imgclass, uint32_t state, union imgtoolinfo *info) { switch(state) { case IMGTOOLINFO_INT_IMAGE_EXTRA_BYTES: info->i = sizeof(ti99_lvl2_imgref); break; case IMGTOOLINFO_INT_DIRECTORY_EXTRA_BYTES: info->i = sizeof(dsk_iterator); break; case IMGTOOLINFO_STR_EOLN: strcpy(info->s = imgtool_temp_str(), "\r"); break; case IMGTOOLINFO_PTR_CLOSE: info->close = ti99_image_exit; break; case IMGTOOLINFO_PTR_INFO: info->info = ti99_image_info; break; case IMGTOOLINFO_PTR_FREE_SPACE: info->free_space = ti99_image_freespace; break; case IMGTOOLINFO_PTR_READ_FILE: info->read_file = ti99_image_readfile; break; case IMGTOOLINFO_PTR_WRITE_FILE: info->write_file = ti99_image_writefile; break; } } static void ti99_dsk_getinfo(const imgtool_class *imgclass, uint32_t state, union imgtoolinfo *info) { switch(state) { case IMGTOOLINFO_STR_FILE_EXTENSIONS: strcpy(info->s = imgtool_temp_str(), "dsk"); break; case IMGTOOLINFO_PTR_BEGIN_ENUM: info->begin_enum = dsk_image_beginenum; break; case IMGTOOLINFO_PTR_NEXT_ENUM: info->next_enum = dsk_image_nextenum; break; case IMGTOOLINFO_PTR_DELETE_FILE: info->delete_file = dsk_image_deletefile; break; default: ti99_getinfo(imgclass, state, info); } } void ti99_old_get_info(const imgtool_class *imgclass, uint32_t state, union imgtoolinfo *info) { switch(state) { case IMGTOOLINFO_STR_NAME: strcpy(info->s = imgtool_temp_str(), "ti99_old"); break; case IMGTOOLINFO_STR_DESCRIPTION: strcpy(info->s = imgtool_temp_str(), "TI99 Diskette (old MESS format)"); break; case IMGTOOLINFO_PTR_OPEN: info->open = dsk_image_init_mess; break; case IMGTOOLINFO_PTR_CREATE: info->create = dsk_image_create_mess; break; case IMGTOOLINFO_PTR_CREATEIMAGE_OPTGUIDE: info->createimage_optguide = &dsk_create_optionguide; break; case IMGTOOLINFO_STR_CREATEIMAGE_OPTSPEC: strcpy(info->s = imgtool_temp_str(), dsk_create_optionspecs); break; default: ti99_dsk_getinfo(imgclass, state, info); break; } } void ti99_v9t9_get_info(const imgtool_class *imgclass, uint32_t state, union imgtoolinfo *info) { switch(state) { case IMGTOOLINFO_STR_NAME: strcpy(info->s = imgtool_temp_str(), "v9t9"); break; case IMGTOOLINFO_STR_DESCRIPTION: strcpy(info->s = imgtool_temp_str(), "TI99 Diskette (V9T9 format)"); break; case IMGTOOLINFO_PTR_OPEN: info->open = dsk_image_init_v9t9; break; case IMGTOOLINFO_PTR_CREATE: info->create = dsk_image_create_v9t9; break; case IMGTOOLINFO_PTR_CREATEIMAGE_OPTGUIDE: info->createimage_optguide = &dsk_create_optionguide; break; case IMGTOOLINFO_STR_CREATEIMAGE_OPTSPEC: strcpy(info->s = imgtool_temp_str(), dsk_create_optionspecs); break; default: ti99_dsk_getinfo(imgclass, state, info); break; } } void ti99_pc99fm_get_info(const imgtool_class *imgclass, uint32_t state, union imgtoolinfo *info) { switch(state) { case IMGTOOLINFO_STR_NAME: strcpy(info->s = imgtool_temp_str(), "pc99fm"); break; case IMGTOOLINFO_STR_DESCRIPTION: strcpy(info->s = imgtool_temp_str(), "TI99 Diskette (PC99 FM format)"); break; case IMGTOOLINFO_PTR_OPEN: info->open = dsk_image_init_pc99_fm; break; case IMGTOOLINFO_PTR_CREATE: /* info->create = dsk_image_create_pc99fm; */ break; default: ti99_dsk_getinfo(imgclass, state, info); break; } } void ti99_pc99mfm_get_info(const imgtool_class *imgclass, uint32_t state, union imgtoolinfo *info) { switch(state) { case IMGTOOLINFO_STR_NAME: strcpy(info->s = imgtool_temp_str(), "pc99mfm"); break; case IMGTOOLINFO_STR_DESCRIPTION: strcpy(info->s = imgtool_temp_str(), "TI99 Diskette (PC99 MFM format)"); break; case IMGTOOLINFO_PTR_OPEN: info->open = dsk_image_init_pc99_mfm; break; case IMGTOOLINFO_PTR_CREATE: /* info->create = dsk_image_create_pc99mfm; */ break; default: ti99_dsk_getinfo(imgclass, state, info); break; } } void ti99_ti99hd_get_info(const imgtool_class *imgclass, uint32_t state, union imgtoolinfo *info) { switch(state) { case IMGTOOLINFO_STR_NAME: strcpy(info->s = imgtool_temp_str(), "ti99hd"); break; case IMGTOOLINFO_STR_DESCRIPTION: strcpy(info->s = imgtool_temp_str(), "TI99 Harddisk"); break; case IMGTOOLINFO_PTR_OPEN: info->open = win_image_init; break; case IMGTOOLINFO_PTR_CREATE: /* info->create = hd_image_create; */ break; case IMGTOOLINFO_STR_FILE_EXTENSIONS: strcpy(info->s = imgtool_temp_str(), "hd"); break; case IMGTOOLINFO_PTR_BEGIN_ENUM: info->begin_enum = win_image_beginenum; break; case IMGTOOLINFO_PTR_NEXT_ENUM: info->next_enum = win_image_nextenum; break; case IMGTOOLINFO_PTR_DELETE_FILE: info->delete_file = win_image_deletefile; break; default: ti99_getinfo(imgclass, state, info); } } /* Open a file as a ti99_image (common code). */ static imgtoolerr_t dsk_image_init(imgtool::image &img, imgtool::stream::ptr &&stream, ti99_img_format img_format) { struct ti99_lvl2_imgref *image = get_lvl2_imgref(img); dsk_vib vib; imgtoolerr_t reply; int totphysrecs; unsigned fdir_aphysrec; int i; /* open disk image at level 1 */ reply = open_image_lvl1(std::move(stream), img_format, &image->l1_img, &vib); if (reply) return reply; /* open disk image at level 2 */ image->type = L2I_DSK; /* @BN@ */ /* Copy in the allocation bit map! */ memcpy(image->abm, vib.abm, 200); /* first compute AU size and number of AUs */ totphysrecs = get_UINT16BE(vib.totphysrecs); image->AUformat.physrecsperAU = (totphysrecs + 1599) / 1600; /* round to next larger power of 2 */ for (i = 1; i < image->AUformat.physrecsperAU; i <<= 1) ; image->AUformat.physrecsperAU = i; image->AUformat.totAUs = totphysrecs / image->AUformat.physrecsperAU; /* extract number of physrecs */ image->dsk.totphysrecs = get_UINT16BE(vib.totphysrecs); /* read and check main volume catalog */ reply = (imgtoolerr_t)dsk_read_catalog(image, 1, &image->dsk.catalogs[0]); if (reply) return reply; image->dsk.fdir_aphysrec[0] = 1; /* read and check subdirectory catalogs */ /* Note that the reserved areas used for HFDC subdirs may be used for other purposes by other FDRs, so, if we get any error, we will assume there is no subdir after all... */ image->dsk.catalogs[0].num_subdirs = 0; for (i=0; i<3; i++) { fdir_aphysrec = get_UINT16BE(vib.subdir[i].fdir_aphysrec); if ((! memcmp(vib.subdir[i].name, "\0\0\0\0\0\0\0\0\0\0", 10)) || (! memcmp(vib.subdir[i].name, " ", 10))) { /* name is empty: fine with us unless there is a fdir pointer */ if (fdir_aphysrec != 0) { image->dsk.catalogs[0].num_subdirs = 0; break; } } else if (check_fname(vib.subdir[i].name)) { /* name is invalid: this is not an HFDC format floppy */ image->dsk.catalogs[0].num_subdirs = 0; break; } else { /* there is a non-empty name */ if ((fdir_aphysrec == 0) || (fdir_aphysrec >= totphysrecs)) { /* error: fdir pointer is invalid or NULL */ image->dsk.catalogs[0].num_subdirs = 0; break; } /* fill in descriptor fields */ image->dsk.fdir_aphysrec[image->dsk.catalogs[0].num_subdirs+1] = fdir_aphysrec; /*image->dsk.catalogs[0].subdirs[image->dsk.catalogs[0].num_subdirs].dir_ptr = fdir_aphysrec;*/ memcpy(image->dsk.catalogs[0].subdirs[image->dsk.catalogs[0].num_subdirs].name, vib.subdir[i].name, 10); reply = (imgtoolerr_t) dsk_read_catalog(image, fdir_aphysrec, &image->dsk.catalogs[image->dsk.catalogs[0].num_subdirs+1]); if (reply) { /* error: invalid fdir */ image->dsk.catalogs[0].num_subdirs = 0; break; } /* found valid subdirectory: increment subdir count */ image->dsk.catalogs[0].num_subdirs++; } } /* extract volume name */ memcpy(image->vol_name, vib.name, 10); /* initialize default data_offset */ image->data_offset = 32+2; return (imgtoolerr_t)0; } /* Open a file as a ti99_image (MESS format). */ static imgtoolerr_t dsk_image_init_mess(imgtool::image &image, imgtool::stream::ptr &&stream) { return dsk_image_init(image, std::move(stream), if_mess); } /* Open a file as a ti99_image (V9T9 format). */ static imgtoolerr_t dsk_image_init_v9t9(imgtool::image &image, imgtool::stream::ptr &&stream) { return dsk_image_init(image, std::move(stream), if_v9t9); } /* Open a file as a ti99_image (PC99 FM format). */ static imgtoolerr_t dsk_image_init_pc99_fm(imgtool::image &image, imgtool::stream::ptr &&stream) { return dsk_image_init(image, std::move(stream), if_pc99_fm); } /* Open a file as a ti99_image (PC99 MFM format). */ static imgtoolerr_t dsk_image_init_pc99_mfm(imgtool::image &image, imgtool::stream::ptr &&stream) { return dsk_image_init(image, std::move(stream), if_pc99_mfm); } /* Open a file as a ti99_image (harddisk format). */ static imgtoolerr_t win_image_init(imgtool::image &img, imgtool::stream::ptr &&stream) { struct ti99_lvl2_imgref *image = get_lvl2_imgref(img); win_vib_ddr vib; int reply; int i; /* open disk image at level 1 */ reply = open_image_lvl1(std::move(stream), if_harddisk, & image->l1_img, NULL); if (reply) return (imgtoolerr_t)reply; /* open disk image at level 2 */ image->type = L2I_WIN; /* read VIB */ reply = read_absolute_physrec(&image->l1_img, 0, &vib); if (reply) return (imgtoolerr_t)reply; /* guess VIB version */ image->win.vib_version = memcmp(vib.u.vib_v1.id, "WIN", 3) ? win_vib_v2 : win_vib_v1; /* extract AU size and number of AUs */ image->AUformat.physrecsperAU = ((get_UINT16BE(vib.params) >> 12) & 0xf) + 1; image->AUformat.totAUs = get_UINT16BE(vib.totAUs); /* extract volume name */ memcpy(image->vol_name, vib.name, 10); /* extract data_offset */ switch (image->win.vib_version) { case win_vib_v1: image->data_offset = 64; break; case win_vib_v2: image->data_offset = vib.u.vib_v2.res_AUs * 64; break; } /* read allocation bitmap (aphysrecs 1 through n, n<=33) */ for (i=0; i < (image->AUformat.totAUs+2047)/2048; i++) { reply = read_absolute_physrec(&image->l1_img, i+1, image->abm+i*256); if (reply) return (imgtoolerr_t)reply; } return (imgtoolerr_t)0; } /* close a ti99_image */ static void ti99_image_exit(imgtool::image &img) { struct ti99_lvl2_imgref *image = get_lvl2_imgref(img); close_image_lvl1(&image->l1_img); } /* get basic information on a ti99_image Currently returns the volume name */ static void ti99_image_info(imgtool::image &img, std::ostream &stream) { struct ti99_lvl2_imgref *image = get_lvl2_imgref(img); char vol_name[11]; fname_to_str(vol_name, image->vol_name, 11); stream << vol_name; } /* Open the disk catalog for enumeration */ static imgtoolerr_t dsk_image_beginenum(imgtool::directory &enumeration, const char *path) { struct ti99_lvl2_imgref *image = get_lvl2_imgref(enumeration.image()); dsk_iterator *iter = (dsk_iterator *) enumeration.extra_bytes(); iter->image = image; iter->level = 0; iter->listing_subdirs = 1; iter->index[0] = 0; iter->cur_catalog = &iter->image->dsk.catalogs[0]; return (imgtoolerr_t)0; } /* Enumerate disk catalog next entry */ static imgtoolerr_t dsk_image_nextenum(imgtool::directory &enumeration, imgtool_dirent &ent) { dsk_iterator *iter = (dsk_iterator*) enumeration.extra_bytes(); dsk_fdr fdr; int reply; unsigned fdr_aphysrec; ent.corrupt = 0; ent.eof = 0; /* iterate through catalogs to next file or dir entry */ while ((iter->level >= 0) && (iter->index[iter->level] >= (iter->listing_subdirs ? iter->cur_catalog->num_subdirs : iter->cur_catalog->num_files))) { if (iter->listing_subdirs) { iter->listing_subdirs = 0; iter->index[iter->level] = 0; } else { iter->listing_subdirs = 1; if (! iter->level) iter->level = -1; else { iter->level = 0; iter->index[0]++; iter->cur_catalog = &iter->image->dsk.catalogs[0]; } } } if (iter->level < 0) { ent.eof = 1; } else { if (iter->listing_subdirs) { fname_to_str(ent.filename, iter->image->dsk.catalogs[0].subdirs[iter->index[iter->level]].name, std::size(ent.filename)); /* set type of DIR */ snprintf(ent.attr, std::size(ent.attr), "DIR"); /* len in physrecs */ /* @BN@ return length in bytes */ /* ent.filesize = 1; */ ent.filesize = 256; /* recurse subdirectory */ iter->listing_subdirs = 0; /* no need to list subdirs as only the root dir has subdirs in DSK format */ iter->level = 1; iter->cur_catalog = &iter->image->dsk.catalogs[iter->index[0]+1]; iter->index[iter->level] = 0; } else { fdr_aphysrec = iter->cur_catalog->files[iter->index[iter->level]].fdr_ptr; reply = read_absolute_physrec(& iter->image->l1_img, fdr_aphysrec, & fdr); if (reply) return IMGTOOLERR_READERROR; #if 0 fname_to_str(ent.filename, fdr.name, std::size(ent.filename)); #else { char buf[11]; ent.filename[0] = '\0'; if (iter->level) { fname_to_str(ent.filename, iter->image->dsk.catalogs[0].subdirs[iter->index[0]].name, std::size(ent.filename)); strncat(ent.filename, ".", std::size(ent.filename) - 1); } fname_to_str(buf, fdr.name, 11); strncat(ent.filename, buf, std::size(ent.filename) - 1); } #endif /* parse flags */ if (fdr.flags & fdr99_f_program) snprintf(ent.attr, std::size(ent.attr), "PGM%s", (fdr.flags & fdr99_f_wp) ? " R/O" : ""); else snprintf(ent.attr, std::size(ent.attr), "%c/%c %d%s", (fdr.flags & fdr99_f_int) ? 'I' : 'D', (fdr.flags & fdr99_f_var) ? 'V' : 'F', fdr.reclen, (fdr.flags & fdr99_f_wp) ? " R/O" : ""); /* len in physrecs */ /* @BN@ return length in bytes */ /* ent.filesize = get_UINT16BE(fdr.fphysrecs); */ ent.filesize = (get_UINT16BE(fdr.fphysrecs)+1)*256; iter->index[iter->level]++; } } return (imgtoolerr_t)0; } /* Open the disk catalog for enumeration */ static imgtoolerr_t win_image_beginenum(imgtool::directory &enumeration, const char *path) { struct ti99_lvl2_imgref *image = get_lvl2_imgref(enumeration.image()); win_iterator *iter = (win_iterator *) enumeration.extra_bytes(); imgtoolerr_t errorcode; iter->image = image; iter->level = 0; iter->listing_subdirs = 1; iter->index[0] = 0; errorcode = (imgtoolerr_t)win_read_catalog(image, 0, &iter->catalog[0]); if (errorcode) return errorcode; return (imgtoolerr_t)0; } /* Enumerate disk catalog next entry */ static imgtoolerr_t win_image_nextenum(imgtool::directory &enumeration, imgtool_dirent &ent) { win_iterator *iter = (win_iterator *) enumeration.extra_bytes(); unsigned fdr_aphysrec; win_fdr fdr; int reply; int i; ent.corrupt = 0; ent.eof = 0; /* iterate through catalogs to next file or dir entry */ while ((iter->level >= 0) && (iter->index[iter->level] >= (iter->listing_subdirs ? iter->catalog[iter->level].num_subdirs : iter->catalog[iter->level].num_files))) { if (iter->listing_subdirs) { iter->listing_subdirs = 0; iter->index[iter->level] = 0; } else { iter->listing_subdirs = 1; iter->level--; iter->index[iter->level]++; } } if (iter->level < 0) { ent.eof = 1; } else { if (iter->listing_subdirs) { #if 0 fname_to_str(ent.filename, iter->catalog[iter->level].subdirs[iter->index[iter->level]].name, std::size(ent.filename)); #else { char buf[11]; ent.filename[0] = '\0'; for (i=0; ilevel; i++) { fname_to_str(buf, iter->catalog[i].subdirs[iter->index[i]].name, 11); strncat(ent.filename, buf, std::size(ent.filename) - 1); strncat(ent.filename, ".", std::size(ent.filename) - 1); } fname_to_str(buf, iter->catalog[iter->level].subdirs[iter->index[iter->level]].name, 11); strncat(ent.filename, buf, std::size(ent.filename) - 1); } #endif /* set type of DIR */ snprintf(ent.attr, std::size(ent.attr), "DIR"); /* len in physrecs */ /* @BN@ return length in bytes */ /* ent.filesize = 2; */ ent.filesize = 512; /* recurse subdirectory */ /*iter->listing_subdirs = 1;*/ iter->level++; iter->index[iter->level] = 0; reply = win_read_catalog(iter->image, iter->catalog[iter->level-1].subdirs[iter->index[iter->level-1]].dir_ptr, &iter->catalog[iter->level]); if (reply) { ent.corrupt = 1; return (imgtoolerr_t)reply; } } else { fdr_aphysrec = iter->catalog[iter->level].files[iter->index[iter->level]].fdr_ptr*iter->image->AUformat.physrecsperAU; reply = read_absolute_physrec(& iter->image->l1_img, fdr_aphysrec, & fdr); if (reply) return IMGTOOLERR_READERROR; #if 0 fname_to_str(ent.filename, iter->catalog[iter->level].files[iter->index[iter->level]].name, std::size(ent.filename)); #else { char buf[11]; ent.filename[0] = '\0'; for (i=0; ilevel; i++) { fname_to_str(buf, iter->catalog[i].subdirs[iter->index[i]].name, 11); strncat(ent.filename, buf, std::size(ent.filename) - 1); strncat(ent.filename, ".", std::size(ent.filename) - 1); } fname_to_str(buf, iter->catalog[iter->level].files[iter->index[iter->level]].name, 11); strncat(ent.filename, buf, std::size(ent.filename) - 1); } #endif /* parse flags */ if (fdr.flags & fdr99_f_program) snprintf(ent.attr, std::size(ent.attr), "PGM%s", (fdr.flags & fdr99_f_wp) ? " R/O" : ""); else snprintf(ent.attr, std::size(ent.attr), "%c/%c %d%s", (fdr.flags & fdr99_f_int) ? 'I' : 'D', (fdr.flags & fdr99_f_var) ? 'V' : 'F', fdr.reclen, (fdr.flags & fdr99_f_wp) ? " R/O" : ""); /* len in physrecs */ /* @BN@ return length in bytes */ /* ent.filesize = get_win_fdr_fphysrecs(&fdr); */ ent.filesize = (get_win_fdr_fphysrecs(&fdr)+1)*256; iter->index[iter->level]++; } } return (imgtoolerr_t)0; } /* Compute free space on disk image (in AUs) */ static imgtoolerr_t ti99_image_freespace(imgtool::partition &partition, uint64_t *size) { imgtool::image &img(partition.image()); struct ti99_lvl2_imgref *image = get_lvl2_imgref(img); size_t freeAUs; int i; freeAUs = 0; for (i=0; iAUformat.totAUs; i++) { if (! (image->abm[i >> 3] & (1 << (i & 7)))) freeAUs++; } /* @BN@ return free space in bytes */ /* *size = freeAUs; */ *size = freeAUs*256; return IMGTOOLERR_SUCCESS; } /* Extract a file from a ti99_image. The file is saved in tifile format. */ static imgtoolerr_t ti99_image_readfile(imgtool::partition &partition, const char *fpath, const char *fork, imgtool::stream &destf) { imgtool::image &img(partition.image()); #if 1 /* extract data as TIFILES */ struct ti99_lvl2_imgref *image = get_lvl2_imgref(img); ti99_lvl2_fileref src_file; ti99_lvl2_fileref dst_file; ti99_date_time date_time; int fphysrecs; int i; uint8_t buf[256]; imgtoolerr_t errorcode; if (check_fpath(fpath)) return IMGTOOLERR_BADFILENAME; /* open file on TI image */ switch (image->type) { case L2I_DSK: errorcode = (imgtoolerr_t)open_file_lvl2_dsk(image, fpath, &src_file); break; case L2I_WIN: errorcode = (imgtoolerr_t)open_file_lvl2_win(image, fpath, &src_file); break; default: errorcode = (imgtoolerr_t)-1; break; } if (errorcode) return errorcode; errorcode = (imgtoolerr_t)new_file_lvl2_tifiles(destf, &dst_file); if (errorcode) return errorcode; /* write TIFILE header */ /* set up parameters */ set_file_flags(&dst_file, get_file_flags(&src_file)); set_file_recsperphysrec(&dst_file, get_file_recsperphysrec(&src_file)); errorcode = (imgtoolerr_t)set_file_fphysrecs(&dst_file, get_file_fphysrecs(&src_file)); if (errorcode) return errorcode; set_file_eof(&dst_file, get_file_eof(&src_file)); errorcode = (imgtoolerr_t)set_file_reclen(&dst_file, get_file_reclen(&src_file)); if (errorcode) return errorcode; errorcode = (imgtoolerr_t)set_file_fixrecs(&dst_file, get_file_fixrecs(&src_file)); if (errorcode) return errorcode; get_file_creation_date(&src_file, &date_time); #if 0 if ((date_time.time_MSB == 0) || (date_time.time_LSB == 0) || (date_time.date_MSB == 0) || (date_time.date_LSB == 0)) current_date_time(&date_time); #endif set_file_creation_date(&dst_file, date_time); get_file_update_date(&src_file, &date_time); #if 0 if ((date_time.time_MSB == 0) || (date_time.time_LSB == 0) || (date_time.date_MSB == 0) || (date_time.date_LSB == 0)) current_date_time(&date_time); #endif set_file_update_date(&dst_file, date_time); if (destf.write(& dst_file.tifiles.hdr, 128) != 128) return (imgtoolerr_t)IMGTOOLERR_WRITEERROR; /* copy data to TIFILE */ fphysrecs = get_file_fphysrecs(&src_file); for (i=0; itype) { case L2I_DSK: errorcode = open_file_lvl2_dsk(image, fpath, &src_file.l2_file); break; case L2I_WIN: errorcode = open_file_lvl2_win(image, fpath, &src_file.l2_file); break; } if (errorcode) return errorcode; errorcode = open_file_lvl3(& src_file); if (errorcode) return errorcode; /* write text data */ while (! is_eof(& src_file)) { errorcode = read_next_record(& src_file, buf, & reclen); if (errorcode) return errorcode; if (destf.write(buf, reclen) != reclen) return IMGTOOLERR_WRITEERROR; if (destf.write(&lineend, 1) != 1) return IMGTOOLERR_WRITEERROR; } return 0; #endif } /* Add a file to a ti99_image. The file must be in tifile format. */ static imgtoolerr_t ti99_image_writefile(imgtool::partition &partition, const char *fpath, const char *fork, imgtool::stream &sourcef, util::option_resolution *writeoptions) { imgtool::image &img(partition.image()); struct ti99_lvl2_imgref *image = get_lvl2_imgref(img); const char *filename; char ti_fname[10]; ti99_lvl2_fileref src_file; ti99_lvl2_fileref dst_file; ti99_date_time date_time; int i; int fphysrecs; uint8_t buf[256]; imgtoolerr_t errorcode; int parent_ref_valid, parent_ref = 0; ti99_catalog *catalog, catalog_buf = {0, }; (void) writeoptions; if (check_fpath(fpath)) return IMGTOOLERR_BADFILENAME; switch (image->type) { case L2I_DSK: errorcode = (imgtoolerr_t)dsk_find_catalog_entry(image, fpath, &parent_ref_valid, &parent_ref, NULL, NULL); if (errorcode == 0) /* file already exists: causes an error for now */ return (imgtoolerr_t)IMGTOOLERR_UNEXPECTED; else if ((! parent_ref_valid) || (errorcode != IMGTOOLERR_FILENOTFOUND)) return (imgtoolerr_t)errorcode; break; case L2I_WIN: errorcode = (imgtoolerr_t)win_find_catalog_entry(image, fpath, &parent_ref_valid, &parent_ref, &catalog_buf, NULL, NULL); if (errorcode == 0) /* file already exists: causes an error for now */ return (imgtoolerr_t)IMGTOOLERR_UNEXPECTED; else if ((! parent_ref_valid) || (errorcode != IMGTOOLERR_FILENOTFOUND)) return (imgtoolerr_t)errorcode; break; } errorcode =(imgtoolerr_t) open_file_lvl2_tifiles(sourcef, & src_file); if (errorcode) return (imgtoolerr_t)errorcode; /* create new file */ filename = strrchr(fpath, '.'); if (filename) filename++; else filename = fpath; str_to_fname(ti_fname, filename); switch (image->type) { case L2I_DSK: errorcode = (imgtoolerr_t)new_file_lvl2_dsk(image, parent_ref, ti_fname, &dst_file); if (errorcode) return (imgtoolerr_t)errorcode; break; case L2I_WIN: errorcode = (imgtoolerr_t)new_file_lvl2_win(image, &catalog_buf, ti_fname, &dst_file); if (errorcode) return (imgtoolerr_t)errorcode; break; } /* set up parameters */ set_file_flags(&dst_file, get_file_flags(&src_file)); set_file_recsperphysrec(&dst_file, get_file_recsperphysrec(&src_file)); errorcode = (imgtoolerr_t)set_file_fphysrecs(&dst_file, /*get_file_fphysrecs(&src_file)*/0); if (errorcode) return (imgtoolerr_t)errorcode; set_file_eof(&dst_file, get_file_eof(&src_file)); errorcode = (imgtoolerr_t)set_file_reclen(&dst_file, get_file_reclen(&src_file)); if (errorcode) return (imgtoolerr_t)errorcode; errorcode = (imgtoolerr_t)set_file_fixrecs(&dst_file, get_file_fixrecs(&src_file)); if (errorcode) return (imgtoolerr_t)errorcode; get_file_creation_date(&src_file, &date_time); #if 0 if ((date_time.time_MSB == 0) || (date_time.time_LSB == 0) || (date_time.date_MSB == 0) || (date_time.date_LSB == 0)) current_date_time(&date_time); #endif set_file_creation_date(&dst_file, date_time); get_file_update_date(&src_file, &date_time); #if 0 if ((date_time.time_MSB == 0) || (date_time.time_LSB == 0) || (date_time.date_MSB == 0) || (date_time.date_LSB == 0)) current_date_time(&date_time); #endif set_file_update_date(&dst_file, date_time); /* alloc data physrecs */ fphysrecs = get_file_fphysrecs(&src_file); switch (dst_file.type) { case L2F_DSK: errorcode = (imgtoolerr_t)dsk_alloc_file_physrecs(&dst_file.dsk, fphysrecs); if (errorcode) return (imgtoolerr_t)errorcode; break; case L2F_WIN: errorcode = (imgtoolerr_t)win_alloc_file_physrecs(&dst_file.win, fphysrecs); if (errorcode) return (imgtoolerr_t)errorcode; break; case L2F_TIFILES: break; } /* copy data */ for (i=0; itype) { case L2I_DSK: if (write_absolute_physrec(& image->l1_img, dst_file.dsk.fdr_aphysrec, &dst_file.dsk.fdr)) return (imgtoolerr_t)IMGTOOLERR_WRITEERROR; break; case L2I_WIN: /* save fphysrecs field as well */ if (dst_file.win.curfdr_aphysrec == dst_file.win.eldestfdr_aphysrec) set_win_fdr_fphysrecs(&dst_file.win.curfdr, dst_file.win.fphysrecs); if (write_absolute_physrec(& image->l1_img, dst_file.win.curfdr_aphysrec, &dst_file.win.curfdr)) return (imgtoolerr_t)IMGTOOLERR_WRITEERROR; if (dst_file.win.curfdr_aphysrec != dst_file.win.eldestfdr_aphysrec) { dst_file.win.curfdr_aphysrec = dst_file.win.eldestfdr_aphysrec; if (read_absolute_physrec(& image->l1_img, dst_file.win.curfdr_aphysrec, &dst_file.win.curfdr)) return (imgtoolerr_t)IMGTOOLERR_WRITEERROR; set_win_fdr_fphysrecs(&dst_file.win.curfdr, dst_file.win.fphysrecs); if (write_absolute_physrec(& image->l1_img, dst_file.win.curfdr_aphysrec, &dst_file.win.curfdr)) return (imgtoolerr_t)IMGTOOLERR_WRITEERROR; } break; } /* update catalog */ switch (image->type) { case L2I_DSK: catalog = &image->dsk.catalogs[parent_ref]; for (i=0; i<128; i++) { buf[2*i] = catalog->files[i].fdr_ptr >> 8; buf[2*i+1] = catalog->files[i].fdr_ptr & 0xff; } if (write_absolute_physrec(& image->l1_img, image->dsk.fdir_aphysrec[parent_ref], buf)) return (imgtoolerr_t)IMGTOOLERR_WRITEERROR; break; case L2I_WIN: { win_vib_ddr parent_ddr; int fdir_AU; /* update VIB/DDR and get FDIR AU address */ if (read_absolute_physrec(& image->l1_img, parent_ref * image->AUformat.physrecsperAU, &parent_ddr)) return (imgtoolerr_t)IMGTOOLERR_READERROR; parent_ddr.num_files = catalog_buf.num_files; if (write_absolute_physrec(& image->l1_img, parent_ref * image->AUformat.physrecsperAU, &parent_ddr)) return (imgtoolerr_t)IMGTOOLERR_WRITEERROR; fdir_AU = get_UINT16BE(parent_ddr.fdir_AU); /* generate FDIR and save it */ for (i=0; i<127; i++) { buf[2*i] = catalog_buf.files[i].fdr_ptr >> 8; buf[2*i+1] = catalog_buf.files[i].fdr_ptr & 0xff; } buf[254] = parent_ref >> 8; buf[255] = parent_ref & 0xff; if (write_absolute_physrec(& image->l1_img, fdir_AU * image->AUformat.physrecsperAU, buf)) return (imgtoolerr_t)IMGTOOLERR_WRITEERROR; } break; } /* update bitmap */ switch (image->type) { case L2I_DSK: { dsk_vib vib; if (read_absolute_physrec(& image->l1_img, 0, &vib)) return (imgtoolerr_t)IMGTOOLERR_READERROR; memcpy(vib.abm, image->abm, 200); if (write_absolute_physrec(& image->l1_img, 0, &vib)) return (imgtoolerr_t)IMGTOOLERR_WRITEERROR; } break; case L2I_WIN: /* save allocation bitmap (aphysrecs 1 through n, n<=33) */ for (i=0; i < (image->AUformat.totAUs+2047)/2048; i++) if (write_absolute_physrec(&image->l1_img, i+1, image->abm+i*256)) return (imgtoolerr_t)IMGTOOLERR_WRITEERROR; break; } return (imgtoolerr_t)0; } /* Delete a file from a ti99_image. */ static imgtoolerr_t dsk_image_deletefile(imgtool::partition &partition, const char *fpath) { imgtool::image &img(partition.image()); struct ti99_lvl2_imgref *image = get_lvl2_imgref(img); dsk_fdr fdr; int i, cluster_index; unsigned cur_AU, cluster_lastfphysrec; // int fphysrecs; int parent_ref, is_dir, catalog_index; imgtoolerr_t errorcode; uint8_t buf[256]; ti99_catalog *catalog; if (check_fpath(fpath)) return IMGTOOLERR_BADFILENAME; errorcode = (imgtoolerr_t)dsk_find_catalog_entry(image, fpath, NULL, &parent_ref, &is_dir, &catalog_index); if (errorcode) return errorcode; if (is_dir) { catalog = &image->dsk.catalogs[catalog_index+1]; if ((catalog->num_files != 0) || (catalog->num_subdirs != 0)) return IMGTOOLERR_UNIMPLEMENTED; catalog = &image->dsk.catalogs[0]; /* free fdir AU */ cur_AU = image->dsk.fdir_aphysrec[catalog_index+1] / image->AUformat.physrecsperAU; image->abm[cur_AU >> 3] &= ~ (1 << (cur_AU & 7)); /* delete catalog entry */ for (i=catalog_index; i<2; i++) { catalog->subdirs[i] = catalog->subdirs[i+1]; image->dsk.fdir_aphysrec[i+1] = image->dsk.fdir_aphysrec[i+2]; } memset(catalog->subdirs[2].name, 0, 10); catalog->subdirs[2].dir_ptr = 0; image->dsk.fdir_aphysrec[3] = 0; catalog->num_subdirs--; /* update directory and bitmap in vib */ { dsk_vib vib; if (read_absolute_physrec(& image->l1_img, 0, &vib)) return IMGTOOLERR_READERROR; for (i=0; i<3; i++) { memcpy(vib.subdir[i].name, catalog->subdirs[i].name, 10); set_UINT16BE(&vib.subdir[i].fdir_aphysrec, image->dsk.fdir_aphysrec[i+1]); } memcpy(vib.abm, image->abm, 200); if (write_absolute_physrec(& image->l1_img, 0, &vib)) return IMGTOOLERR_WRITEERROR; } } else { catalog = &image->dsk.catalogs[parent_ref]; if (read_absolute_physrec(& image->l1_img, catalog->files[catalog_index].fdr_ptr, &fdr)) return IMGTOOLERR_READERROR; /* free data AUs */ // fphysrecs = get_UINT16BE(fdr.fphysrecs); i = 0; cluster_index = 0; while (cluster_index < 76) { cur_AU = get_dsk_fdr_cluster_baseAU(image, & fdr, cluster_index); if (cur_AU == 0) /* end of cluster list */ break; cluster_lastfphysrec = get_dsk_fdr_cluster_lastfphysrec(& fdr, cluster_index); while (i<=cluster_lastfphysrec) { /* the condition below is an error, but it should not prevent us from deleting the file */ if (cur_AU >= image->AUformat.totAUs) /*return IMGTOOLERR_CORRUPTIMAGE;*/ break; image->abm[cur_AU >> 3] &= ~ (1 << (cur_AU & 7)); i += image->AUformat.physrecsperAU; cur_AU++; } cluster_index++; } /* the condition below is an error, but it should not prevent us from deleting the file */ #if 0 if (ifiles[catalog_index].fdr_ptr / image->AUformat.physrecsperAU; image->abm[cur_AU >> 3] &= ~ (1 << (cur_AU & 7)); /* delete catalog entry */ for (i=catalog_index; i<127; i++) catalog->files[i] = catalog->files[i+1]; catalog->files[127].fdr_ptr = 0; catalog->num_files--; /* update parent fdir */ for (i=0; i<128; i++) { buf[2*i] = catalog->files[i].fdr_ptr >> 8; buf[2*i+1] = catalog->files[i].fdr_ptr & 0xff; } if (write_absolute_physrec(& image->l1_img, image->dsk.fdir_aphysrec[parent_ref], buf)) return IMGTOOLERR_WRITEERROR; /* update bitmap */ { dsk_vib vib; if (read_absolute_physrec(& image->l1_img, 0, &vib)) return IMGTOOLERR_READERROR; memcpy(vib.abm, image->abm, 200); if (write_absolute_physrec(& image->l1_img, 0, &vib)) return IMGTOOLERR_WRITEERROR; } } return (imgtoolerr_t)0; } static imgtoolerr_t win_image_deletefile(imgtool::partition &partition, const char *fpath) { imgtool::image &img(partition.image()); struct ti99_lvl2_imgref *image = get_lvl2_imgref(img); int parent_ddr_AU, is_dir, catalog_index; win_fdr fdr; int i; unsigned cur_AU, end_AU; unsigned curfdr_aphysrec; unsigned cursibFDR_index, endsibFDR_index = 0; int errorcode; uint8_t buf[256]; ti99_catalog catalog; if (check_fpath(fpath)) return (imgtoolerr_t)IMGTOOLERR_BADFILENAME; errorcode = win_find_catalog_entry(image, fpath, NULL, &parent_ddr_AU, &catalog, &is_dir, &catalog_index); if (errorcode) return (imgtoolerr_t)errorcode; if (is_dir) { unsigned DDR_AU; win_vib_ddr ddr_buf; /* Read DDR record */ DDR_AU = catalog.subdirs[catalog_index].dir_ptr; if (read_absolute_physrec(& image->l1_img, DDR_AU*image->AUformat.physrecsperAU, &ddr_buf)) return (imgtoolerr_t)IMGTOOLERR_READERROR; /* read FDIR AU pointer */ cur_AU = get_UINT16BE(ddr_buf.fdir_AU); /* sanity checks */ if ((ddr_buf.num_files > 127) || (ddr_buf.num_subdirs > 114) || (cur_AU > image->AUformat.totAUs)) return (imgtoolerr_t)IMGTOOLERR_CORRUPTIMAGE; if ((ddr_buf.num_files != 0) || (ddr_buf.num_subdirs != 0)) return (imgtoolerr_t)IMGTOOLERR_UNIMPLEMENTED; /* free fdir AU */ image->abm[cur_AU >> 3] &= ~ (1 << (cur_AU & 7)); /* free ddr AU */ image->abm[DDR_AU >> 3] &= ~ (1 << (DDR_AU & 7)); /* delete parent catalog entry */ for (i=catalog_index; i<113; i++) catalog.subdirs[i] = catalog.subdirs[i+1]; catalog.subdirs[113].dir_ptr = 0; catalog.num_subdirs--; /* update parent VIB/DDR */ if (read_absolute_physrec(& image->l1_img, parent_ddr_AU * image->AUformat.physrecsperAU, &ddr_buf)) return (imgtoolerr_t)IMGTOOLERR_READERROR; ddr_buf.num_subdirs = catalog.num_subdirs; for (i=0; i<114; i++) set_UINT16BE(&ddr_buf.subdir_AU[i], catalog.subdirs[i].dir_ptr); if (write_absolute_physrec(& image->l1_img, parent_ddr_AU * image->AUformat.physrecsperAU, &ddr_buf)) return (imgtoolerr_t)IMGTOOLERR_WRITEERROR; /* save allocation bitmap (aphysrecs 1 through n, n<=33) */ for (i=0; i < (image->AUformat.totAUs+2047)/2048; i++) if (write_absolute_physrec(&image->l1_img, i+1, image->abm+i*256)) return (imgtoolerr_t)IMGTOOLERR_WRITEERROR; } else { /* check integrity of FDR sibling chain, and go to last sibling */ /* note that as we check that the back chain is consistent with the forward chain, we will also detect any cycle in the sibling chain, so we do not need to check against them explicitly */ { int pastendoflist_flag; unsigned prevfdr_aphysrec; pastendoflist_flag = 0; cursibFDR_index = 0; curfdr_aphysrec = catalog.files[catalog_index].fdr_ptr * image->AUformat.physrecsperAU; if (read_absolute_physrec(& image->l1_img, curfdr_aphysrec, &fdr)) return (imgtoolerr_t)IMGTOOLERR_READERROR; if (get_UINT16BE(fdr.prevsibFDR_AU) != 0) return (imgtoolerr_t)IMGTOOLERR_CORRUPTIMAGE; while (1) { if (! pastendoflist_flag) { /* look for end of list */ for (i=0; i<54; i++) { if (get_UINT16BE(fdr.clusters[i][0]) == 0) { endsibFDR_index = cursibFDR_index; pastendoflist_flag = true; break; } } } /* exit loop if end of sibling chain */ if (! get_UINT16BE(fdr.nextsibFDR_AU)) break; /* otherwise read next FDR */ if (get_UINT16BE(fdr.nextsibFDR_AU) >= image->AUformat.totAUs) return (imgtoolerr_t)IMGTOOLERR_CORRUPTIMAGE; prevfdr_aphysrec = curfdr_aphysrec; curfdr_aphysrec = get_win_fdr_nextsibFDR_aphysrec(image, &fdr); if (read_absolute_physrec(& image->l1_img, curfdr_aphysrec, &fdr)) return (imgtoolerr_t)IMGTOOLERR_READERROR; cursibFDR_index++; /* check that back chaining is consistent with forward chaining */ if (get_win_fdr_prevsibFDR_aphysrec(image, &fdr) != prevfdr_aphysrec) return (imgtoolerr_t)IMGTOOLERR_CORRUPTIMAGE; } if (! pastendoflist_flag) endsibFDR_index = cursibFDR_index; } while (1) { if (cursibFDR_index <= endsibFDR_index) { for (i = 0; i < 54; i++) { cur_AU = get_UINT16BE(fdr.clusters[i][0]); if (cur_AU == 0) /* end of cluster list */ break; end_AU = get_UINT16BE(fdr.clusters[i][1]); while (cur_AU<=end_AU) { /* the condition below is an error, but it should not prevent us from deleting the file */ if (cur_AU >= image->AUformat.totAUs) /*return IMGTOOLERR_CORRUPTIMAGE;*/ break; image->abm[cur_AU >> 3] &= ~ (1 << (cur_AU & 7)); cur_AU++; } } } /* free fdr AU if applicable */ if ((curfdr_aphysrec % image->AUformat.physrecsperAU) == 0) { cur_AU = curfdr_aphysrec / image->AUformat.physrecsperAU; image->abm[cur_AU >> 3] &= ~ (1 << (cur_AU & 7)); } if (cursibFDR_index == 0) break; curfdr_aphysrec = get_win_fdr_prevsibFDR_aphysrec(image, &fdr); if (read_absolute_physrec(& image->l1_img, curfdr_aphysrec, &fdr)) return (imgtoolerr_t)IMGTOOLERR_READERROR; cursibFDR_index--; } /* delete catalog entry */ for (i=catalog_index; i<127; i++) catalog.files[i] = catalog.files[i+1]; catalog.files[127].fdr_ptr = 0; catalog.num_files--; /* update VIB/DDR and get FDIR AU address */ { win_vib_ddr parent_ddr; if (read_absolute_physrec(& image->l1_img, parent_ddr_AU * image->AUformat.physrecsperAU, &parent_ddr)) return (imgtoolerr_t)IMGTOOLERR_READERROR; parent_ddr.num_files = catalog.num_files; if (write_absolute_physrec(& image->l1_img, parent_ddr_AU * image->AUformat.physrecsperAU, &parent_ddr)) return (imgtoolerr_t)IMGTOOLERR_WRITEERROR; cur_AU = get_UINT16BE(parent_ddr.fdir_AU); } /* generate FDIR and save it */ for (i=0; i<127; i++) { buf[2*i] = catalog.files[i].fdr_ptr >> 8; buf[2*i+1] = catalog.files[i].fdr_ptr & 0xff; } buf[254] = parent_ddr_AU >> 8; buf[255] = parent_ddr_AU & 0xff; if (write_absolute_physrec(& image->l1_img, cur_AU * image->AUformat.physrecsperAU, buf)) return (imgtoolerr_t)IMGTOOLERR_WRITEERROR; /* save allocation bitmap (aphysrecs 1 through n, n<=33) */ for (i=0; i < (image->AUformat.totAUs+2047)/2048; i++) if (write_absolute_physrec(&image->l1_img, i+1, image->abm+i*256)) return (imgtoolerr_t)IMGTOOLERR_WRITEERROR; } return (imgtoolerr_t)0; } /* Create a blank ti99_image (common code). Supports MESS and V9T9 formats only */ static imgtoolerr_t dsk_image_create(imgtool::image &image, imgtool::stream::ptr &&stream, util::option_resolution *createoptions, ti99_img_format img_format) { const char *volname; int density; ti99_lvl1_imgref l1_img; int protected_var; int totphysrecs, physrecsperAU, totAUs; dsk_vib vib; uint8_t empty_sec[256]; int i; l1_img.img_format = img_format; l1_img.file_handle = stream.get(); // can't release here /* read options */ volname = createoptions->lookup_string(dsk_createopts_volname).c_str(); l1_img.geometry.heads = createoptions->lookup_int(dsk_createopts_sides); l1_img.geometry.cylinders = createoptions->lookup_int(dsk_createopts_tracks); l1_img.geometry.secspertrack = createoptions->lookup_int(dsk_createopts_sectors); protected_var = createoptions->lookup_int(dsk_createopts_protection); density = createoptions->lookup_int(dsk_createopts_density); /* NPW 03-DEC-2003 - Fixes a crash if volname is NULL */ if (!volname) volname = ""; totphysrecs = l1_img.geometry.secspertrack * l1_img.geometry.cylinders * l1_img.geometry.heads; physrecsperAU = (totphysrecs + 1599) / 1600; /* round to next larger power of 2 */ for (i = 1; i < physrecsperAU; i <<= 1) ; physrecsperAU = i; totAUs = totphysrecs / physrecsperAU; /* auto-density */ if (density == 0) { if (l1_img.geometry.secspertrack <= 9) density = 1; else if (l1_img.geometry.secspertrack <= 18) density = 2; else density = 3; } /* check volume name */ if (strchr(volname, '.') || strchr(volname, ' ') || (strlen(volname) > 10)) return /*IMGTOOLERR_BADFILENAME*/IMGTOOLERR_PARAMCORRUPT; /* FM disks can hold fewer sector per track than MFM disks */ if ((density == 1) && (l1_img.geometry.secspertrack > 9)) return IMGTOOLERR_PARAMTOOLARGE; /* DD disks can hold fewer sector per track than HD disks */ if ((density == 2) && (l1_img.geometry.secspertrack > 18)) return IMGTOOLERR_PARAMTOOLARGE; /* check total disk size */ if (totphysrecs < 2) return IMGTOOLERR_PARAMTOOSMALL; /* initialize vib in aphysrec 0 */ /* set volume name */ str_to_fname(vib.name, volname); /* set every header field */ set_UINT16BE(& vib.totphysrecs, totphysrecs); vib.secspertrack = l1_img.geometry.secspertrack; vib.id[0] = 'D'; vib.id[1] = 'S'; vib.id[2] = 'K'; vib.protection = protected_var ? 'P' : ' '; vib.cylinders = l1_img.geometry.cylinders; vib.heads = l1_img.geometry.heads; vib.density = density; for (i=0; i<3; i++) { memset(vib.subdir[i].name, '\0', 10); set_UINT16BE(& vib.subdir[i].fdir_aphysrec, 0); } /* clear bitmap */ for (i=0; i < (totAUs>>3); i++) vib.abm[i] = 0; if (totAUs & 7) { vib.abm[i] = 0xff << (totAUs & 7); i++; } for (; i < 200; i++) vib.abm[i] = 0xff; /* mark first 2 aphysrecs (vib and fdir) as used */ vib.abm[0] |= (physrecsperAU == 1) ? 3 : 1; /* write aphysrec 0 */ if (write_absolute_physrec(&l1_img, 0, &vib)) return IMGTOOLERR_WRITEERROR; /* now clear every other physrecs, including the FDIR record */ memset(empty_sec, 0, sizeof(empty_sec)); for (i=1; i