diff options
Diffstat (limited to 'src/mame/tools/imgtool/modules/ti99.c')
-rw-r--r-- | src/mame/tools/imgtool/modules/ti99.c | 5360 |
1 files changed, 5360 insertions, 0 deletions
diff --git a/src/mame/tools/imgtool/modules/ti99.c b/src/mame/tools/imgtool/modules/ti99.c new file mode 100644 index 00000000000..220cbebdb27 --- /dev/null +++ b/src/mame/tools/imgtool/modules/ti99.c @@ -0,0 +1,5360 @@ +// 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 <limits.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> +#include "imgtool.h" +#include "harddisk.h" +#include "imghd.h" + +/* + 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 + theorical 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 bytes[2]; +}; + +struct UINT16LE +{ + UINT8 bytes[2]; +}; + +INLINE UINT16 get_UINT16BE(UINT16BE word) +{ + return (word.bytes[0] << 8) | word.bytes[1]; +} + +INLINE void set_UINT16BE(UINT16BE *word, UINT16 data) +{ + word->bytes[0] = (data >> 8) & 0xff; + word->bytes[1] = data & 0xff; +} + +INLINE UINT16 get_UINT16LE(UINT16LE word) +{ + return word.bytes[0] | (word.bytes[1] << 8); +} + +INLINE void set_UINT16LE(UINT16LE *word, UINT16 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<n) + { + dst[i] = src[i]; + if (src[i] != ' ') + last_nonspace = i; + i++; + } + + /* terminate with '\0' */ + dst[last_nonspace+1] = '\0'; +} + +/* + Check a 10-character file name. + + filename (I): 10-character array with file name + + Return non-zero if the file name is invalid. +*/ +static int check_fname(const char filename[10]) +{ + int i; + int space_found_flag; + + + /* check and copy file name */ + space_found_flag = FALSE; + for (i=0; i<10; i++) + { + switch (filename[i]) + { + case '.': + case '\0': + /* illegal characters */ + return 1; + case ' ': + /* illegal in a file name, but file names shorter than 10 + characters are padded with spaces */ + space_found_flag = TRUE; + break; + default: + /* all other characters are legal (though non-ASCII characters, + control characters and even lower-case characters are not + recommended) */ + if (space_found_flag) + return 1; + break; + } + } + + return 0; +} + +/* + Check a file path. + + fpath (I): C string with file path + + Return non-zero if the file path is invalid. +*/ +static int check_fpath(const char *fpath) +{ + const char *element_start, *element_end; + int element_len; + + + /* first check that the path is not too long and that there is no space + character */ + if ((strlen(fpath) > 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 time_MSB, time_LSB;/* 0-4: hour, 5-10: minutes, 11-15: seconds/2 */ + UINT8 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 secspertrack; /* sectors per track (usually 9 (FM SD), */ + /* 16 or 18 (MFM DD), or 36 (MFM HD) */ + UINT8 id[3]; /* 'DSK' */ + UINT8 protection; /* 'P' if disk is protected, ' ' otherwise. */ + UINT8 cylinders; /* tracks per side (usually 40) */ + UINT8 heads; /* sides (1 or 2) */ + UINT8 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 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-existant 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 *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 *crc, UINT8 value) +{ + UINT8 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 *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 c; + UINT8 cylinder, head, sector; + int seclen; + UINT8 crc1, crc2; + UINT16 crc; + long data_offset; + UINT8 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 (stream_size(file_handle) % track_len) + return IMGTOOLERR_CORRUPTIMAGE; + + num_tracks = stream_size(file_handle) / 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 */ + stream_seek(file_handle, 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 (stream_read(file_handle, 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; i<seclen; i++) + { + c = track_buf[track_pos]; + track_pos++; + if (track_pos == track_len) + track_pos = 0; + + 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 ((crc1 != 0xf7) || (crc2 != 0xf7)) + continue; + + switch (pass) + { + case 0: + if ((cylinder == 0) && (head == 0) && (sector == 0)) + { + /* return vib */ + if ((data_offset + 256) <= track_len) + memcpy(vib, track_buf + data_offset, 256); + else + { + memcpy((UINT8 *)vib, track_buf + data_offset, track_len-data_offset); + memcpy((UINT8 *)vib + (track_len-data_offset), track_buf, 256-(track_len-data_offset)); + } + return 0; + } + break; + + case 1: + /* set up offset map */ + if (data_offset_array[(head*geometry->cylinders + 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 = stream_seek(file_handle, 0, SEEK_SET); + if (reply) + return IMGTOOLERR_READERROR; + /* read it */ + reply = stream_read(file_handle, 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 *file_handle, ti99_img_format img_format, ti99_lvl1_imgref *l1_img, dsk_vib *vib) +{ + imgtoolerr_t err; + int reply; + UINT16 totphysrecs; + + + l1_img->img_format = img_format; + l1_img->file_handle = file_handle; + + if (img_format == if_harddisk) + { + const hard_disk_info *info; + + err = imghd_open(file_handle, &l1_img->harddisk_handle); + if (err) + return err; + + 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)) + && (stream_size(file_handle) != 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*)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; + } + } + } + + 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); + } + + stream_close(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 +*/ +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 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 = stream_seek(l1_img->file_handle, sector_address_to_image_offset(l1_img, address), SEEK_SET); + if (reply) + return 1; + /* read it */ + reply = stream_read(l1_img->file_handle, 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 = stream_seek(l1_img->file_handle, track_offset+sector_offset, SEEK_SET); + if (reply) + return 1; + /* read it */ + reply = stream_read(l1_img->file_handle, dest, 256); + if (reply != 256) + return 1; + } + else + { + /* seek to sector */ + reply = stream_seek(l1_img->file_handle, track_offset+sector_offset, SEEK_SET); + if (reply) + return 1; + /* read first chunk (until end of track) */ + reply = stream_read(l1_img->file_handle, (UINT8 *)dest, track_len-sector_offset); + if (reply != track_len-sector_offset) + return 1; + + /* wrap to start of track */ + reply = stream_seek(l1_img->file_handle, track_offset, SEEK_SET); + if (reply) + return 1; + /* read remnant of sector */ + reply = stream_read(l1_img->file_handle, (UINT8 *)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 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 = stream_seek(l1_img->file_handle, sector_address_to_image_offset(l1_img, address), SEEK_SET); + if (reply) + return 1; + /* write it */ + reply = stream_write(l1_img->file_handle, 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 = stream_seek(l1_img->file_handle, track_offset+sector_offset, SEEK_SET); + if (reply) + return 1; + /* write it */ + reply = stream_write(l1_img->file_handle, src, 256); + if (reply != 256) + return 1; + } + else + { + /* seek to sector */ + reply = stream_seek(l1_img->file_handle, track_offset+sector_offset, SEEK_SET); + if (reply) + return 1; + /* write first chunk (until end of track) */ + reply = stream_write(l1_img->file_handle, (UINT8 *)src, track_len-sector_offset); + if (reply != track_len-sector_offset) + return 1; + + /* wrap to start of track */ + reply = stream_seek(l1_img->file_handle, track_offset, SEEK_SET); + if (reply) + return 1; + /* write remnant of sector */ + reply = stream_write(l1_img->file_handle, (UINT8 *)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 +*/ +#ifdef UNUSED_FUNCTION +static void win_aphysrec_to_sector_address(int aphysrec, const ti99_geometry *geometry, ti99_sector_address *address) +{ + address.sector = aphysrec % l1_img->geometry.secspertrack; + aphysrec /= l1_img->geometry.secspertrack; + address.side = aphysrec % l1_img->geometry.heads; + address.cylinder = aphysrec / l1_img->geometry.heads; +} +#endif + +/* + 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 secspertrack; /* HFDC: sectors per track (typically 32) */ + /* SCSI: reserved */ + union + { + struct + { + UINT8 id[3]; /* V1 VIB: 'WIN' */ + } vib_v1; + + struct /* V2 VIB: extra params */ + { + UINT8 res_AUs; /* # AUs reserved for vib, bitmap, ddr, fdir and fdr, divided by 64 */ + UINT8 step_spd; /* HFDC: step speed (0-7) */ + /* SCSI: reserved */ + UINT8 red_w_cur;/* HFDC: reduced write current cylinder, divided by 8 */ + /* SCSI: reserved */ + } vib_v2; + + struct + { + UINT8 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 num_files; /* number of files in directory */ + UINT8 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 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 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 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 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 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 flags; /* file status flags (see enum above) */ + UINT8 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 whan they should. */ + UINT8 eof; /* EOF offset in last physrec for variable length */ + /* record files and program files (0->256)*/ + UINT8 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 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 flags; /* file status flags (see enum above) */ + UINT8 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 whan they should. */ + /* other sibling FDRs: index of the first file */ + /* physrec in this particular sibling FDR */ + UINT8 eof; /* EOF offset in last physrec for variable length */ + /* record files and program files (0->256)*/ + UINT8 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 xinfo_MSB; /* extended information (MSByte) */ + /* bits 0-3: MSN of fphysrecs */ + /* bits 4-7: MSN of fixrecs */ + UINT8 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 flags; /* see enum above */ + UINT8 recsperphysrec; /* records per physrec */ + UINT8 eof; /* current position of eof in last physrec (0->255)*/ + UINT8 reclen; /* bytes per record ([1,255] 0->256) */ + UINT16BE fixrecs; /* file length in records */ + UINT8 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; + }; +}; + +/* + 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, ARRAY_LENGTH(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; i<ddr_buf.num_subdirs; i++) + dest->subdirs[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; i<dest->num_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; i<dest->num_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; i<dest->num_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; i<dest->num_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; i<dest->num_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; i<cur_catalog->num_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; i<cur_catalog->num_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; i<parent_catalog->num_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; i<parent_catalog->num_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; i<totAUs; i++) + { + if (! (l2_img->abm[i >> 3] & (1 << (i & 7)))) + { + *fdr_AU = i; + l2_img->abm[i >> 3] |= 1 << (i & 7); + + return 0; + } + } + + return IMGTOOLERR_NOSPACE; +} + +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; +} + +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; +} + +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); +} + +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; +} + +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; +} + +INLINE unsigned get_win_fdr_fphysrecs(win_fdr *fdr) +{ + return (((unsigned) fdr->xinfo_MSB << 12) & 0xf0000) | get_UINT16BE(fdr->fphysrecs_LSW); +} + +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); +} + +INLINE unsigned get_win_fdr_fixrecs(win_fdr *fdr) +{ + return (((unsigned) fdr->xinfo_MSB << 16) & 0xf0000) | get_UINT16LE(fdr->fixrecs_LSW); +} + +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); +} + +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; +} + +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; +} + +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; i<totAUs; i++) + { + if (! (dsk_file->l2_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; i<cluster_AUlen; i++) + dsk_file->l2_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; i<totAUs; i++) + { + if (! (dsk_file->l2_img->abm[i >> 3] & (1 << (i & 7)))) + { /* found one free block */ + /* compute its length */ + cluster_baseAU = i; + cur_block_seclen = 0; + while ((i<totAUs) && (! (dsk_file->l2_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; i<cluster_AUlen; i++) + dsk_file->l2_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; i<cluster_AUlen; i++) + dsk_file->l2_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; i<totAUs; i++) + { + if (! (win_file->l2_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; i<cluster_AUlen; i++) + win_file->l2_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; i<totAUs; i++) + { + if (! (win_file->l2_img->abm[i >> 3] & (1 << (i & 7)))) + { /* found one free block */ + /* compute its length */ + cluster_baseAU = i; + cur_block_seclen = 0; + while ((i<totAUs) && (! (win_file->l2_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; i<cluster_AUlen; i++) + win_file->l2_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; i<cluster_AUlen; i++) + win_file->l2_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 ((i<catalog->num_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 ((i<parent_catalog->num_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 (stream_seek(l2_file->tifiles.file_handle, 0, SEEK_SET)) + return IMGTOOLERR_READERROR; + /* read it */ + if (stream_read(l2_file->tifiles.file_handle, &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 (stream_seek(l2_file->tifiles.file_handle, 128+256*fphysrec, SEEK_SET)) + return IMGTOOLERR_READERROR; + /* read it */ + if (stream_read(l2_file->tifiles.file_handle, 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 (stream_seek(l2_file->tifiles.file_handle, 128+256*fphysrec, SEEK_SET)) + return IMGTOOLERR_WRITEERROR; + /* write it */ + if (stream_write(l2_file->tifiles.file_handle, src, 256) != 256) + return IMGTOOLERR_WRITEERROR; + break; + } + + return 0; +} + +/* + Write a field in every fdr record associated to a file +*/ +#ifdef UNUSED_FUNCTION +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_physrec(l2_file->win.l2_img, &fdr_buf)) + { + memcpy(((UINT8 *) &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; +} +#endif + +static UINT8 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 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 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 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 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 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 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 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; + } +} + +#ifdef UNUSED_FUNCTION +static void current_date_time(ti99_date_time *reply) +{ + /* All these functions should be ANSI */ + time_t cur_time = time(NULL); + 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; +} +#endif + +#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; +}; + +#ifdef UNUSED_FUNCTION +/* + 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. +*/ +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 +*/ +static int read_next_record(ti99_lvl3_fileref *l3_file, void *dest, int *out_reclen) +{ + int errorcode; + UINT8 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; +} +#endif + +#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 *f); +static imgtoolerr_t dsk_image_init_v9t9(imgtool_image *image, imgtool_stream *f); +static imgtoolerr_t dsk_image_init_pc99_fm(imgtool_image *image, imgtool_stream *f); +static imgtoolerr_t dsk_image_init_pc99_mfm(imgtool_image *image, imgtool_stream *f); +static imgtoolerr_t win_image_init(imgtool_image *image, imgtool_stream *f); +static void ti99_image_exit(imgtool_image *img); +static void ti99_image_info(imgtool_image *img, char *string, size_t len); +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 *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, 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 *f, option_resolution *createoptions); +static imgtoolerr_t dsk_image_create_v9t9(imgtool_image *image, imgtool_stream *f, 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' +}; + +static 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 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 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 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 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 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 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 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 int dsk_image_init(imgtool_image *img, imgtool_stream *f, ti99_img_format img_format) +{ + struct ti99_lvl2_imgref *image = (struct ti99_lvl2_imgref *) imgtool_image_extra_bytes(img); + dsk_vib vib; + int reply; + int totphysrecs; + unsigned fdir_aphysrec; + int i; + + /* open disk image at level 1 */ + reply = open_image_lvl1(f, 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 = 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 = 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 0; +} + +/* + Open a file as a ti99_image (MESS format). +*/ +static imgtoolerr_t dsk_image_init_mess(imgtool_image *image, imgtool_stream *f) +{ + return (imgtoolerr_t)dsk_image_init(image, f, if_mess); +} + +/* + Open a file as a ti99_image (V9T9 format). +*/ +static imgtoolerr_t dsk_image_init_v9t9(imgtool_image *image, imgtool_stream *f) +{ + return (imgtoolerr_t)dsk_image_init(image, f, 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 *f) +{ + return (imgtoolerr_t)dsk_image_init(image, f, 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 *f) +{ + return (imgtoolerr_t)dsk_image_init(image, f, if_pc99_mfm); +} + +/* + Open a file as a ti99_image (harddisk format). +*/ +static imgtoolerr_t win_image_init(imgtool_image *img, imgtool_stream *f) +{ + struct ti99_lvl2_imgref *image = (struct ti99_lvl2_imgref *) imgtool_image_extra_bytes(img); + win_vib_ddr vib; + int reply; + int i; + + /* open disk image at level 1 */ + reply = open_image_lvl1(f, 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 = (struct ti99_lvl2_imgref *) imgtool_image_extra_bytes(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, char *string, size_t len) +{ + struct ti99_lvl2_imgref *image = (struct ti99_lvl2_imgref *) imgtool_image_extra_bytes(img); + char vol_name[11]; + + fname_to_str(vol_name, image->vol_name, 11); + + snprintf(string, len, "%s", 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 = (struct ti99_lvl2_imgref *) imgtool_image_extra_bytes(imgtool_directory_image(enumeration)); + dsk_iterator *iter = (dsk_iterator *) imgtool_directory_extrabytes(enumeration); + + 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*) imgtool_directory_extrabytes(enumeration); + 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, ARRAY_LENGTH(ent->filename)); + + /* set type of DIR */ + snprintf(ent->attr, ARRAY_LENGTH(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, ARRAY_LENGTH(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, ARRAY_LENGTH(ent->filename)); + strncat(ent->filename, ".", ARRAY_LENGTH(ent->filename) - 1); + } + fname_to_str(buf, fdr.name, 11); + strncat(ent->filename, buf, ARRAY_LENGTH(ent->filename) - 1); + } +#endif + /* parse flags */ + if (fdr.flags & fdr99_f_program) + snprintf(ent->attr, ARRAY_LENGTH(ent->attr), "PGM%s", + (fdr.flags & fdr99_f_wp) ? " R/O" : ""); + else + snprintf(ent->attr, ARRAY_LENGTH(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 = (struct ti99_lvl2_imgref *) imgtool_image_extra_bytes(imgtool_directory_image(enumeration)); + win_iterator *iter = (win_iterator *) imgtool_directory_extrabytes(enumeration); + 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 *) imgtool_directory_extrabytes(enumeration); + 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, ARRAY_LENGTH(ent->filename)); +#else + { + char buf[11]; + + ent->filename[0] = '\0'; + for (i=0; i<iter->level; i++) + { + fname_to_str(buf, iter->catalog[i].subdirs[iter->index[i]].name, 11); + strncat(ent->filename, buf, ARRAY_LENGTH(ent->filename) - 1); + strncat(ent->filename, ".", ARRAY_LENGTH(ent->filename) - 1); + } + fname_to_str(buf, iter->catalog[iter->level].subdirs[iter->index[iter->level]].name, 11); + strncat(ent->filename, buf, ARRAY_LENGTH(ent->filename) - 1); + } +#endif + + /* set type of DIR */ + snprintf(ent->attr, ARRAY_LENGTH(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, ARRAY_LENGTH(ent->filename)); +#else + { + char buf[11]; + + ent->filename[0] = '\0'; + for (i=0; i<iter->level; i++) + { + fname_to_str(buf, iter->catalog[i].subdirs[iter->index[i]].name, 11); + strncat(ent->filename, buf, ARRAY_LENGTH(ent->filename) - 1); + strncat(ent->filename, ".", ARRAY_LENGTH(ent->filename) - 1); + } + fname_to_str(buf, iter->catalog[iter->level].files[iter->index[iter->level]].name, 11); + strncat(ent->filename, buf, ARRAY_LENGTH(ent->filename) - 1); + } +#endif + /* parse flags */ + if (fdr.flags & fdr99_f_program) + snprintf(ent->attr, ARRAY_LENGTH(ent->attr), "PGM%s", + (fdr.flags & fdr99_f_wp) ? " R/O" : ""); + else + snprintf(ent->attr, ARRAY_LENGTH(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 *size) +{ + imgtool_image *img = imgtool_partition_image(partition); + struct ti99_lvl2_imgref *image = (struct ti99_lvl2_imgref *) imgtool_image_extra_bytes(img); + size_t freeAUs; + int i; + + freeAUs = 0; + for (i=0; i<image->AUformat.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 = imgtool_partition_image(partition); +#if 1 + + /* extract data as TIFILES */ + struct ti99_lvl2_imgref *image = (struct ti99_lvl2_imgref *) imgtool_image_extra_bytes(img); + ti99_lvl2_fileref src_file; + ti99_lvl2_fileref dst_file; + ti99_date_time date_time; + int fphysrecs; + int i; + UINT8 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 (stream_write(destf, & dst_file.tifiles.hdr, 128) != 128) + return (imgtoolerr_t)IMGTOOLERR_WRITEERROR; + + /* copy data to TIFILE */ + fphysrecs = get_file_fphysrecs(&src_file); + + for (i=0; i<fphysrecs; i++) + { + errorcode = (imgtoolerr_t)read_file_physrec(& src_file, i, buf); + if (errorcode) + return errorcode; + + errorcode = (imgtoolerr_t)write_file_physrec(& dst_file, i, buf); + if (errorcode) + return errorcode; + } + + return (imgtoolerr_t)0; + +#else + + struct ti99_lvl2_imgref *image = (struct ti99_lvl2_imgref *) imgtool_image_extra_bytes(img); + ti99_lvl3_fileref src_file; + UINT8 buf[256]; + int reclen; + char lineend = '\r'; + int errorcode; + + + if (check_fpath(fpath)) + return IMGTOOLERR_BADFILENAME; + + /* open file on TI image */ + switch (image->type) + { + 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 (stream_write(destf, buf, reclen) != reclen) + return IMGTOOLERR_WRITEERROR; + if (stream_write(destf, &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, option_resolution *writeoptions) +{ + imgtool_image *img = imgtool_partition_image(partition); + struct ti99_lvl2_imgref *image = (struct ti99_lvl2_imgref *) imgtool_image_extra_bytes(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 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; i<fphysrecs; i++) + { + if (stream_read(sourcef, buf, 256) != 256) + return (imgtoolerr_t)IMGTOOLERR_READERROR; + + errorcode = (imgtoolerr_t)write_file_physrec(& dst_file, i, buf); + if (errorcode) + return (imgtoolerr_t)errorcode; + } + + /* write fdr */ + switch (image->type) + { + 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 = imgtool_partition_image(partition); + struct ti99_lvl2_imgref *image = (struct ti99_lvl2_imgref *) imgtool_image_extra_bytes(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 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 (i<fphysrecs) + return IMGTOOLERR_CORRUPTIMAGE; +#endif + + /* free fdr AU */ + cur_AU = catalog->files[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 = imgtool_partition_image(partition); + struct ti99_lvl2_imgref *image = (struct ti99_lvl2_imgref *) imgtool_image_extra_bytes(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 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 explicitely */ + { + 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 *f, 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 empty_sec[256]; + + int i; + + l1_img.img_format = img_format; + l1_img.file_handle = f; + + /* read options */ + volname = option_resolution_lookup_string(createoptions, dsk_createopts_volname); + l1_img.geometry.heads = option_resolution_lookup_int(createoptions, dsk_createopts_sides); + l1_img.geometry.cylinders = option_resolution_lookup_int(createoptions, dsk_createopts_tracks); + l1_img.geometry.secspertrack = option_resolution_lookup_int(createoptions, dsk_createopts_sectors); + protected_var = option_resolution_lookup_int(createoptions, dsk_createopts_protection); + density = option_resolution_lookup_int(createoptions, 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<totphysrecs; i++) + if (write_absolute_physrec(& l1_img, i, empty_sec)) + return IMGTOOLERR_WRITEERROR; + + + return (imgtoolerr_t)dsk_image_init(image, f, img_format); +} + +/* + Create a blank ti99_image (MESS format). +*/ +static imgtoolerr_t dsk_image_create_mess(imgtool_image *image, imgtool_stream *f, option_resolution *createoptions) +{ + return dsk_image_create(image, f, createoptions, if_mess); +} + +/* + Create a blank ti99_image (V9T9 format). +*/ +static imgtoolerr_t dsk_image_create_v9t9(imgtool_image *image, imgtool_stream *f, option_resolution *createoptions) +{ + return dsk_image_create(image, f, createoptions, if_v9t9); +} |