diff options
-rw-r--r-- | scripts/src/tools.lua | 1 | ||||
-rw-r--r-- | src/tools/imgtool/modules.cpp | 1 | ||||
-rw-r--r-- | src/tools/imgtool/modules/hp9845_tape.cpp | 922 |
3 files changed, 924 insertions, 0 deletions
diff --git a/scripts/src/tools.lua b/scripts/src/tools.lua index 6832190f5d3..ed7355b228b 100644 --- a/scripts/src/tools.lua +++ b/scripts/src/tools.lua @@ -734,6 +734,7 @@ files { MAME_DIR .. "src/tools/imgtool/modules/psion.cpp", MAME_DIR .. "src/tools/imgtool/modules/bml3.cpp", MAME_DIR .. "src/tools/imgtool/modules/hp48.cpp", + MAME_DIR .. "src/tools/imgtool/modules/hp9845_tape.cpp", } configuration { "mingw*" or "vs*" } diff --git a/src/tools/imgtool/modules.cpp b/src/tools/imgtool/modules.cpp index 72f81b862b9..8119e86802a 100644 --- a/src/tools/imgtool/modules.cpp +++ b/src/tools/imgtool/modules.cpp @@ -115,5 +115,6 @@ MODULE(cybikoxt) MODULE(psion) MODULE(bml3) MODULE(hp48) +MODULE(hp9845_tape) #endif /* MODULES_RECURSIVE */ diff --git a/src/tools/imgtool/modules/hp9845_tape.cpp b/src/tools/imgtool/modules/hp9845_tape.cpp new file mode 100644 index 00000000000..b756eed352f --- /dev/null +++ b/src/tools/imgtool/modules/hp9845_tape.cpp @@ -0,0 +1,922 @@ +// license:BSD-3-Clause +// copyright-holders:F. Ulivi +/********************************************************************* + + hp9845_tape.cpp + + HP-9845 tape format + +*********************************************************************/ +#include <bitset> + +#include "imgtool.h" +#include "formats/imageutl.h" + +// Constants +#define SECTOR_LEN 256 // Bytes in a sector +#define WORDS_PER_SECTOR (SECTOR_LEN / 2) // 16-bit words in a sector payload +#define SECTORS_PER_TRACK 426 // Sectors in a track +#define TRACKS_NO 2 // Number of tracks +#define TOT_SECTORS (SECTORS_PER_TRACK * TRACKS_NO) // Total number of sectors +#define DIR_WORD_0 0x0500 // First word of directories +#define DIR_WORD_1 0xffff // Second word of directories +#define DIR_LAST_WORD 0xffff // Last word of directories +#define FIRST_DIR_SECTOR 1 // First directory sector +#define SECTORS_PER_DIR 2 // Sectors per copy of directory +#define MAX_DIR_ENTRIES 42 // And the answer is.... the maximum number of entries in the directory! +#define DIR_COPIES 2 // Count of directory copies +#define PAD_WORD 0xffff // Word value for padding +#define FIRST_FILE_SECTOR (FIRST_DIR_SECTOR + SECTORS_PER_DIR * DIR_COPIES) // First file sector +#define MAGIC 0x5441434f // Magic value at start of image file: "TACO" +#define ONE_INCH_POS (968 * 1024) // 1 inch of tape in tape_pos_t units +#define START_POS ((tape_pos_t)(72.25 * ONE_INCH_POS)) // Start position on each track +#define DZ_WORDS 350 // Words in deadzone +#define IRG_SIZE ONE_INCH_POS // Size of inter-record-gap: 1" +#define IFG_SIZE ((tape_pos_t)(2.5 * ONE_INCH_POS)) // Size of inter-file-gap: 2.5" +#define ZERO_BIT_LEN 619 // Length of "0" bits when encoded on tape +#define ONE_BIT_LEN 1083 // Length of "1" bits when encoded on tape +#define HDR_W0_ZERO_MASK 0x4000 // Mask of zero bits in word 0 of header +#define RES_FREE_FIELD 0x2000 // Mask of "reserved free field" bit +#define FILE_ID_BIT 0x8000 // Mask of "file identifier" bit +#define SECTOR_IN_USE 0x1800 // Mask of "empty record indicator" (== !sector in use indicator) +#define SIF_FILE_NO 1 // SIF file # +#define SIF_FILE_NO_MASK 0x07ff // Mask of SIF file # +#define SIF_FREE_FIELD 0 // SIF free field +#define SIF_FREE_FIELD_MASK 0xf000 // Mask of SIF free field +#define BYTES_AVAILABLE 0xff00 // "bytes available" field = 256 +#define BYTES_AVAILABLE_MASK 0xff00 // Mask of "bytes available" field +#define BYTES_USED 0x00ff // "bytes used" field = 256 +#define BYTES_USED_MASK 0x00ff // Mask of "bytes used" field +#define FORMAT_SECT_SIZE ((tape_pos_t)(2.85 * ONE_INCH_POS)) // Size of sectors including padding: 2.85" +#define PAD_WORD_LENGTH (17 * ONE_BIT_LEN) // Size of PAD_WORD on tape +#define PREAMBLE_WORD 0 // Value of preamble word +#define WORDS_PER_SECTOR_W_MARGIN 256 // Maximum number of words in a sector with a lot of margin (there are actually never more than about 160 words) +#define MIN_IRG_SIZE ((tape_pos_t)(0.066 * ONE_INCH_POS)) // Minimum size of IRG gaps: 0.066" + +// File types +#define BKUP_FILETYPE 0 +#define BKUP_ATTR_STR "BKUP" +#define DATA_FILETYPE 1 +#define DATA_ATTR_STR "DATA" +#define PROG_FILETYPE 2 +#define PROG_ATTR_STR "PROG" +#define KEYS_FILETYPE 3 +#define KEYS_ATTR_STR "KEYS" +#define BDAT_FILETYPE 4 +#define BDAT_ATTR_STR "BDAT" +#define ALL_FILETYPE 5 +#define ALL_ATTR_STR "ALL " +#define BPRG_FILETYPE 6 +#define BPRG_ATTR_STR "BPRG" +#define OPRM_FILETYPE 7 +#define OPRM_ATTR_STR "OPRM" + +// Words stored on tape +typedef UINT16 tape_word_t; + +// Tape position, 1 unit = 1 inch / (968 * 1024) +typedef INT32 tape_pos_t; + +/******************************************************************************** + * Directory entries + ********************************************************************************/ +typedef struct { + UINT8 filename[ 6 ];// Filename (left justified, 0 padded on the right) + bool protection; // File protection + UINT8 filetype; // File type (00-1f) + UINT16 filepos; // File position (# of 1st sector) + UINT16 n_recs; // Number of records + UINT16 wpr; // Word-per-record + unsigned n_sects; // Count of sectors +} dir_entry_t; + +/******************************************************************************** + * Tape image + ********************************************************************************/ +class tape_image_t { +public: + tape_image_t(void); + + bool is_dirty(void) const { return dirty; } + + void format_img(void); + + imgtoolerr_t load_from_file(imgtool_stream *stream); + imgtoolerr_t save_to_file(imgtool_stream *stream); + + unsigned free_sectors(void) const; + + void set_sector(unsigned s_no , const tape_word_t *s_data); + void unset_sector(unsigned s_no); + + bool get_dir_entry(unsigned idx , dir_entry_t& entry); + +private: + bool dirty; + // Tape image + tape_word_t img[ TOT_SECTORS ][ WORDS_PER_SECTOR ]; + // Map of sectors in use + std::bitset<TOT_SECTORS> alloc_map; + // Directory + std::vector<dir_entry_t> dir; + + static void wipe_sector(tape_word_t *s); + static void tape_word_to_bytes(tape_word_t w , UINT8& bh , UINT8& bl); + static void bytes_to_tape_word(UINT8 bh , UINT8 bl , tape_word_t& w); + void dump_dir_sect(const tape_word_t *dir_sect , unsigned dir_sect_idx); + void fill_and_dump_dir_sect(tape_word_t *dir_sect , unsigned& idx , unsigned& dir_sect_idx , tape_word_t w) ; + void encode_dir(void); + bool read_sector_words(unsigned& sect_no , unsigned& sect_idx , size_t word_no , tape_word_t *out) const; + static bool filename_char_check(UINT8 c); + static bool filename_check(const UINT8 *filename); + bool decode_dir(void); + static tape_pos_t word_length(tape_word_t w); + static tape_pos_t block_end_pos(tape_pos_t pos , const tape_word_t *block , unsigned block_len); + static bool save_word(imgtool_stream *stream , tape_pos_t& pos , tape_word_t w); + static bool save_words(imgtool_stream *stream , tape_pos_t& pos , const tape_word_t *block , unsigned block_len); + static tape_word_t checksum(const tape_word_t *block , unsigned block_len); +}; + +/******************************************************************************** + * Image state + ********************************************************************************/ +typedef struct { + imgtool_stream *stream; + tape_image_t *img; +} tape_state_t; + +/******************************************************************************** + * Directory enumeration + ********************************************************************************/ +typedef struct { + unsigned dir_idx; +} dir_state_t; + +/******************************************************************************** + * Internal functions + ********************************************************************************/ +tape_image_t::tape_image_t(void) + : dirty(false) +{ +} + +void tape_image_t::format_img(void) +{ + // Deallocate all sectors + alloc_map.reset(); + + // Create an empty directory + dir.clear(); + + dirty = true; +} + +imgtoolerr_t tape_image_t::load_from_file(imgtool_stream *stream) +{ + stream_seek(stream , 0 , SEEK_SET); + + UINT8 tmp[ 4 ]; + + if (stream_read(stream , tmp , 4) != 4) { + return IMGTOOLERR_READERROR; + } + + if (pick_integer_be(tmp , 0 , 4) != MAGIC) { + return IMGTOOLERR_CORRUPTIMAGE; + } + + unsigned exp_sector = 0; + // Loader state: + // 0 Wait for DZ + // 1 Wait for sector data + // 2 Wait for gap + unsigned state; + tape_pos_t end_pos = 0; + for (unsigned track = 0; track < TRACKS_NO; track++) { + state = 0; + + while (1) { + if (stream_read(stream , tmp , 4) != 4) { + return IMGTOOLERR_READERROR; + } + UINT32 words_no = pick_integer_le(tmp , 0 , 4); + if (words_no == (UINT32)-1) { + // Track ended + break; + } + if (stream_read(stream , tmp , 4) != 4) { + return IMGTOOLERR_READERROR; + } + tape_pos_t pos = pick_integer_le(tmp , 0 , 4); + tape_word_t buffer[ WORDS_PER_SECTOR_W_MARGIN ]; + for (unsigned i = 0; i < words_no; i++) { + if (stream_read(stream , tmp , 2) != 2) { + return IMGTOOLERR_READERROR; + } + if (i < WORDS_PER_SECTOR_W_MARGIN) { + buffer[ i ] = pick_integer_le(tmp , 0 , 2); + } + } + switch (state) { + case 0: + // Skip DZ + state = 1; + break; + + case 2: + if ((pos - end_pos) < MIN_IRG_SIZE) { + // Gap too short, discard data block + break; + } + // Intentional fall-through + + case 1: + if (words_no < 6 + WORDS_PER_SECTOR || buffer[ 0 ] != PREAMBLE_WORD || + buffer[ 4 ] != checksum(&buffer[ 1 ], 3) || + buffer[ 5 + WORDS_PER_SECTOR ] != checksum(&buffer[ 5 ], WORDS_PER_SECTOR)) { + return IMGTOOLERR_CORRUPTIMAGE; + } + // Check sector content + if (exp_sector != (buffer[ 2 ] & 0xfff)) { + return IMGTOOLERR_CORRUPTIMAGE; + } + if (((buffer[ 1 ] & FILE_ID_BIT) != 0 && exp_sector != 0) || + ((buffer[ 1 ] & FILE_ID_BIT) == 0 && exp_sector == 0)) { + return IMGTOOLERR_CORRUPTIMAGE; + } + if ((buffer[ 1 ] & (HDR_W0_ZERO_MASK | RES_FREE_FIELD | SIF_FILE_NO_MASK)) != (RES_FREE_FIELD | SIF_FILE_NO)) { + return IMGTOOLERR_CORRUPTIMAGE; + } + if ((buffer[ 2 ] & SIF_FREE_FIELD_MASK) != SIF_FREE_FIELD) { + return IMGTOOLERR_CORRUPTIMAGE; + } + bool in_use = (buffer[ 1 ] & SECTOR_IN_USE) != 0; + if ((buffer[ 3 ] & BYTES_AVAILABLE_MASK) != BYTES_AVAILABLE || + (in_use && (buffer[ 3 ] & BYTES_USED_MASK) != BYTES_USED) || + (!in_use && (buffer[ 3 ] & BYTES_USED_MASK) != 0)) { + return IMGTOOLERR_CORRUPTIMAGE; + } + if (in_use) { + set_sector(exp_sector, &buffer[ 5 ]); + } else { + unset_sector(exp_sector); + } + exp_sector++; + state = 2; + break; + } + end_pos = block_end_pos(pos, &buffer[ 0 ], words_no); + } + } + + if (exp_sector != TOT_SECTORS) { + return IMGTOOLERR_CORRUPTIMAGE; + } + + if (!decode_dir()) { + return IMGTOOLERR_CORRUPTDIR; + } + + dirty = false; + + return IMGTOOLERR_SUCCESS; +} + +tape_pos_t tape_image_t::word_length(tape_word_t w) +{ + unsigned zeros , ones; + + // pop count of w + ones = (w & 0x5555) + ((w >> 1) & 0x5555); + ones = (ones & 0x3333) + ((ones >> 2) & 0x3333); + ones = (ones & 0x0f0f) + ((ones >> 4) & 0x0f0f); + ones = (ones & 0x00ff) + ((ones >> 8) & 0x00ff); + + zeros = 16 - ones; + + return zeros * ZERO_BIT_LEN + (ones + 1) * ONE_BIT_LEN; +} + +tape_pos_t tape_image_t::block_end_pos(tape_pos_t pos , const tape_word_t *block , unsigned block_len) +{ + while (block_len--) { + pos += word_length(*block++); + } + return pos; +} + +bool tape_image_t::save_word(imgtool_stream *stream , tape_pos_t& pos , tape_word_t w) +{ + UINT8 tmp[ 2 ]; + + place_integer_le(tmp , 0 , 2 , w); + if (stream_write(stream , tmp , 2) != 2) { + return false; + } + + pos += word_length(w); + + return true; +} + +bool tape_image_t::save_words(imgtool_stream *stream , tape_pos_t& pos , const tape_word_t *block , unsigned block_len) +{ + UINT8 tmp[ 4 ]; + + // Number of words (including preamble) + place_integer_le(tmp , 0 , 4 , block_len + 1); + if (stream_write(stream , tmp , 4) != 4) { + return false; + } + // Start position + place_integer_le(tmp , 0 , 4 , pos); + if (stream_write(stream , tmp , 4) != 4) { + return false; + } + // Preamble + if (!save_word(stream , pos , PREAMBLE_WORD)) { + return false; + } + // Words + for (unsigned i = 0; i < block_len; i++) { + if (!save_word(stream, pos, block[ i ])) { + return false; + } + } + + return true; +} + +tape_word_t tape_image_t::checksum(const tape_word_t *block , unsigned block_len) +{ + tape_word_t csum = 0; + for (unsigned i = 0; i < block_len; i++) { + csum += *block++; + } + return csum & 0xffff; +} + +imgtoolerr_t tape_image_t::save_to_file(imgtool_stream *stream) +{ + // Encode copies of directory into sectors + encode_dir(); + + // Store sectors + stream_seek(stream , 0 , SEEK_SET); + + UINT8 tmp[ 4 ]; + + place_integer_be(tmp , 0 , 4 , MAGIC); + if (stream_write(stream , tmp , 4) != 4) { + return IMGTOOLERR_WRITEERROR; + } + + tape_pos_t pos = START_POS; + + for (unsigned i = 0; i < TOT_SECTORS; i++) { + if (i == TOT_SECTORS / 2) { + // Track 0 -> 1 + place_integer_le(tmp , 0 , 4 , (UINT32)-1); + if (stream_write(stream , tmp , 4) != 4) { + return IMGTOOLERR_WRITEERROR; + } + } + if (i == 0 || i == TOT_SECTORS / 2) { + // Start of either track + pos = START_POS; + // Deadzone + 1" of gap + tape_word_t deadzone[ DZ_WORDS ]; + for (unsigned j = 0; j < DZ_WORDS; j++) { + deadzone[ j ] = PAD_WORD; + } + if (!save_words(stream, pos, deadzone, DZ_WORDS)) { + return IMGTOOLERR_WRITEERROR; + } + + pos += IRG_SIZE; + } + bool in_use = alloc_map[ i ]; + // Sector header + tape_word_t sector[ WORDS_PER_SECTOR_W_MARGIN ]; + + // Header word 0: file identifier bit, reserved free-field bit, empty record indicator & file # + sector[ 0 ] = RES_FREE_FIELD | SIF_FILE_NO; + if (i == 0) { + sector[ 0 ] |= FILE_ID_BIT; + } + if (in_use) { + sector[ 0 ] |= SECTOR_IN_USE; + } + // Header word 1: free-field & sector # + sector[ 1 ] = SIF_FREE_FIELD | i; + // Header word 2: bytes available & bytes used + sector[ 2 ] = BYTES_AVAILABLE; + if (in_use) { + sector[ 2 ] |= BYTES_USED; + } + // Checksum of header + sector[ 3 ] = checksum(§or[ 0 ] , 3); + // Sector payload + if (in_use) { + memcpy(§or[ 4 ] , &img[ i ][ 0 ] , SECTOR_LEN); + } else { + for (unsigned j = 4; j < (4 + WORDS_PER_SECTOR); j++) { + sector[ j ] = PAD_WORD; + } + } + // Checksum of payload + sector[ 4 + WORDS_PER_SECTOR ] = checksum(§or[ 4 ] , WORDS_PER_SECTOR); + // Pad sector up to FORMAT_SECT_SIZE + tape_pos_t sect_size = 0; + for (unsigned j = 0; j < 5 + WORDS_PER_SECTOR; j++) { + sect_size += word_length(sector[ j ]); + } + unsigned padding_words = (FORMAT_SECT_SIZE - sect_size) / PAD_WORD_LENGTH; + for (unsigned j = 5 + WORDS_PER_SECTOR; j < 5 + WORDS_PER_SECTOR + padding_words; j++) { + sector[ j ] = PAD_WORD; + } + if (!save_words(stream, pos, sector, 5 + WORDS_PER_SECTOR + padding_words)) { + return IMGTOOLERR_WRITEERROR; + } + // Gap between sectors + if (i == 0) { + pos += IFG_SIZE; + } else { + pos += IRG_SIZE; + } + } + + place_integer_le(tmp , 0 , 4 , (UINT32)-1); + if (stream_write(stream , tmp , 4) != 4) { + return IMGTOOLERR_WRITEERROR; + } + + return IMGTOOLERR_SUCCESS; +} + +unsigned tape_image_t::free_sectors(void) const +{ + return TOT_SECTORS - alloc_map.count(); +} + +void tape_image_t::set_sector(unsigned s_no , const tape_word_t *s_data) +{ + if (s_no < TOT_SECTORS) { + memcpy(&img[ s_no ][ 0 ] , s_data , SECTOR_LEN); + alloc_map.set(s_no); + dirty = true; + } +} + +void tape_image_t::unset_sector(unsigned s_no) +{ + if (s_no < TOT_SECTORS) { + alloc_map.reset(s_no); + dirty = true; + } +} + +bool tape_image_t::get_dir_entry(unsigned idx , dir_entry_t& entry) +{ + if (idx >= dir.size()) { + return false; + } else { + entry = dir[ idx ]; + return true; + } +} + +void tape_image_t::wipe_sector(tape_word_t *s) +{ + for (unsigned i = 0; i < WORDS_PER_SECTOR; i++) { + s[ i ] = PAD_WORD; + } +} + +void tape_image_t::tape_word_to_bytes(tape_word_t w , UINT8& bh , UINT8& bl) +{ + bh = (UINT8)(w >> 8); + bl = (UINT8)(w & 0xff); +} + +void tape_image_t::bytes_to_tape_word(UINT8 bh , UINT8 bl , tape_word_t& w) +{ + w = ((tape_word_t)bh << 8) | ((tape_word_t)bl); +} + +void tape_image_t::dump_dir_sect(const tape_word_t *dir_sect , unsigned dir_sect_idx) +{ + for (unsigned i = 0; i < DIR_COPIES; i++) { + set_sector(FIRST_DIR_SECTOR + i * SECTORS_PER_DIR + dir_sect_idx, dir_sect); + } +} + +void tape_image_t::fill_and_dump_dir_sect(tape_word_t *dir_sect , unsigned& idx , unsigned& dir_sect_idx , tape_word_t w) +{ + // Dump sector once it's full + if (idx >= WORDS_PER_SECTOR) { + dump_dir_sect(dir_sect, dir_sect_idx); + wipe_sector(dir_sect); + idx = 0; + dir_sect_idx++; + } + dir_sect[ idx++ ] = w; +} + +void tape_image_t::encode_dir(void) +{ + tape_word_t dir_sect[ WORDS_PER_SECTOR ]; + + wipe_sector(dir_sect); + + unsigned idx = 0; + unsigned dir_sect_idx = 0; + + fill_and_dump_dir_sect(dir_sect, idx, dir_sect_idx, DIR_WORD_0); + fill_and_dump_dir_sect(dir_sect, idx, dir_sect_idx, DIR_WORD_1); + + for (const dir_entry_t& ent : dir) { + tape_word_t tmp; + + // Filename + bytes_to_tape_word(ent.filename[ 0 ], ent.filename[ 1 ], tmp); + fill_and_dump_dir_sect(dir_sect, idx, dir_sect_idx, tmp); + bytes_to_tape_word(ent.filename[ 2 ], ent.filename[ 3 ], tmp); + fill_and_dump_dir_sect(dir_sect, idx, dir_sect_idx, tmp); + bytes_to_tape_word(ent.filename[ 4 ], ent.filename[ 5 ], tmp); + fill_and_dump_dir_sect(dir_sect, idx, dir_sect_idx, tmp); + // Protection, file type & file position + tmp = ((tape_word_t)ent.filetype << 10) | (tape_word_t)ent.filepos; + if (ent.protection) { + tmp |= 0x8000; + } + fill_and_dump_dir_sect(dir_sect, idx, dir_sect_idx, tmp); + // File size (# of records) + fill_and_dump_dir_sect(dir_sect, idx, dir_sect_idx, ent.n_recs); + // Words per record + fill_and_dump_dir_sect(dir_sect, idx, dir_sect_idx, ent.wpr); + } + + // Terminator + fill_and_dump_dir_sect(dir_sect, idx, dir_sect_idx, DIR_LAST_WORD); + + // Dump last partial sector + dump_dir_sect(dir_sect, dir_sect_idx); + + // Unset unused sectors + for (unsigned i = dir_sect_idx + 1; i < SECTORS_PER_DIR; i++) { + for (unsigned j = 0; j < DIR_COPIES; j++) { + unset_sector(FIRST_DIR_SECTOR + i + j * SECTORS_PER_DIR); + } + } +} + +bool tape_image_t::read_sector_words(unsigned& sect_no , unsigned& sect_idx , size_t word_no , tape_word_t *out) const +{ + while (word_no > 0) { + if (sect_idx >= WORDS_PER_SECTOR) { + sect_idx = 0; + sect_no++; + if (sect_no >= TOT_SECTORS || !alloc_map[ sect_no ]) { + return false; + } + } + *out++ = img[ sect_no ][ sect_idx ]; + sect_idx++; + word_no--; + } + + return true; +} + +bool tape_image_t::filename_char_check(UINT8 c) +{ + // Colons and quotation marks are forbidden in file names + return 0x20 < c && c < 0x7f && c != ':' && c != '"'; +} + +bool tape_image_t::filename_check(const UINT8 *filename) +{ + bool ended = false; + + for (unsigned i = 0; i < 6; i++) { + UINT8 c = *filename++; + + if (ended) { + if (c != 0) { + return false; + } + } else if (c == 0) { + ended = true; + } else if (!filename_char_check(c)) { + return false; + } + } + + return true; +} + +bool tape_image_t::decode_dir(void) +{ + unsigned sect_no = FIRST_DIR_SECTOR - 1; + unsigned sect_idx = SECTOR_LEN; + + dir.clear(); + + tape_word_t tmp; + + if (!read_sector_words(sect_no, sect_idx, 1, &tmp)) { + return false; + } + if (tmp != DIR_WORD_0) { + return false; + } + if (!read_sector_words(sect_no, sect_idx, 1, &tmp)) { + return false; + } + if (tmp != DIR_WORD_1) { + return false; + } + + // This is to check for overlapping files + std::bitset<TOT_SECTORS> sect_in_use; + + while (1) { + if (!read_sector_words(sect_no, sect_idx, 1, &tmp)) { + return false; + } + if (tmp == DIR_LAST_WORD) { + // End of directory + break; + } + + if (dir.size() >= MAX_DIR_ENTRIES) { + // Too many entries + return false; + } + + dir_entry_t new_entry; + + // Filename + tape_word_to_bytes(tmp, new_entry.filename[ 0 ], new_entry.filename[ 1 ]); + if (!read_sector_words(sect_no, sect_idx, 1, &tmp)) { + return false; + } + tape_word_to_bytes(tmp, new_entry.filename[ 2 ], new_entry.filename[ 3 ]); + if (!read_sector_words(sect_no, sect_idx, 1, &tmp)) { + return false; + } + tape_word_to_bytes(tmp, new_entry.filename[ 4 ], new_entry.filename[ 5 ]); + if (!filename_check(new_entry.filename)) { + return false; + } + + // Protection, file type & file position + if (!read_sector_words(sect_no, sect_idx, 1, &tmp)) { + return false; + } + new_entry.protection = (tmp & 0x8000) != 0; + new_entry.filetype = ((tmp >> 10) & 0x1f); + new_entry.filepos = tmp & 0x3ff; + if (new_entry.filepos < FIRST_FILE_SECTOR || new_entry.filepos >= TOT_SECTORS) { + return false; + } + + // File size (# of records) + if (!read_sector_words(sect_no, sect_idx, 1, &tmp)) { + return false; + } + new_entry.n_recs = tmp; + + // Words per record + if (!read_sector_words(sect_no, sect_idx, 1, &tmp)) { + return false; + } + new_entry.wpr = tmp; + if (new_entry.wpr < 1) { + return false; + } + + new_entry.n_sects = ((unsigned)new_entry.wpr * new_entry.n_recs * 2 + SECTOR_LEN - 1) / SECTOR_LEN; + if (new_entry.n_sects < 1 || (new_entry.n_sects + new_entry.filepos) > TOT_SECTORS) { + return false; + } + + for (unsigned i = new_entry.filepos; i < new_entry.n_sects + new_entry.filepos; i++) { + if (sect_in_use[ i ]) { + return false; + } + sect_in_use[ i ] = true; + } + + dir.push_back(new_entry); + } + + // Check for inconsistency between alloc_map & sect_in_use + for (unsigned i = 0; i < FIRST_FILE_SECTOR; i++) { + sect_in_use[ i ] = alloc_map[ i ]; + } + + std::bitset<TOT_SECTORS> tmp_map(~alloc_map & sect_in_use); + if (tmp_map.any()) { + // There is at least 1 sector that is in use by a file but it's empty/unallocated + return false; + } + + alloc_map = sect_in_use; + + return true; +} + +static tape_state_t& get_tape_state(imgtool_image *img) +{ + tape_state_t *ts = (tape_state_t*)imgtool_image_extra_bytes(img); + + return *ts; +} + +static tape_image_t& get_tape_image(tape_state_t& ts) +{ + if (ts.img == nullptr) { + ts.img = global_alloc(tape_image_t); + } + + return *(ts.img); +} + +/******************************************************************************** + * Imgtool functions + ********************************************************************************/ +static imgtoolerr_t hp9845_tape_open(imgtool_image *image, imgtool_stream *stream) +{ + tape_state_t& state = get_tape_state(image); + + state.stream = stream; + + tape_image_t& tape_image = get_tape_image(state); + + return tape_image.load_from_file(stream); +} + +static imgtoolerr_t hp9845_tape_create(imgtool_image *image, imgtool_stream *stream, util::option_resolution *opts) +{ + tape_state_t& state = get_tape_state(image); + + state.stream = stream; + + tape_image_t& tape_image = get_tape_image(state); + + tape_image.format_img(); + + return IMGTOOLERR_SUCCESS; +} + +static void hp9845_tape_close(imgtool_image *image) +{ + tape_state_t& state = get_tape_state(image); + tape_image_t& tape_image = get_tape_image(state); + + if (tape_image.is_dirty()) { + (void)tape_image.save_to_file(state.stream); + } + + stream_close(state.stream); + + // Free tape_image + global_free(&tape_image); +} + +static imgtoolerr_t hp9845_tape_begin_enum (imgtool_directory *enumeration, const char *path) +{ + dir_state_t *ds = (dir_state_t*)imgtool_directory_extrabytes(enumeration); + + ds->dir_idx = 0; + + return IMGTOOLERR_SUCCESS; +} + +static const char *const filetype_attrs[] = { + BKUP_ATTR_STR, // 0 + DATA_ATTR_STR, // 1 + PROG_ATTR_STR, // 2 + KEYS_ATTR_STR, // 3 + BDAT_ATTR_STR, // 4 + ALL_ATTR_STR, // 5 + BPRG_ATTR_STR, // 6 + OPRM_ATTR_STR // 7 +}; + +static imgtoolerr_t hp9845_tape_next_enum (imgtool_directory *enumeration, imgtool_dirent *ent) +{ + tape_state_t& state = get_tape_state(imgtool_directory_image(enumeration)); + tape_image_t& tape_image = get_tape_image(state); + dir_state_t *ds = (dir_state_t*)imgtool_directory_extrabytes(enumeration); + + dir_entry_t entry; + + if (!tape_image.get_dir_entry(ds->dir_idx, entry)) { + ent->eof = 1; + } else { + ds->dir_idx++; + + UINT8 tmp_fn[ 7 ]; + + memcpy(&tmp_fn[ 0 ] , &entry.filename[ 0 ] , 6); + tmp_fn[ 6 ] = '\0'; + + // Decode filetype + UINT8 type_low = entry.filetype & 7; + UINT8 type_hi = (entry.filetype >> 3) & 3; + + const char *filetype_str = filetype_attrs[ type_low ]; + + // Same logic used by hp9845b to add a question mark next to filetype + bool qmark = (type_low == DATA_FILETYPE && type_hi == 3) || + (type_low != DATA_FILETYPE && type_hi != 2); + + // "filename" and "attr" fields try to look like the output of the "CAT" command + snprintf(ent->filename , sizeof(ent->filename) , "%-6s %c %s%c" , tmp_fn , entry.protection ? '*' : ' ' , filetype_str , qmark ? '?' : ' '); + + snprintf(ent->attr , sizeof(ent->attr) , "%4u %4u %3u" , entry.n_recs , entry.wpr * 2 , entry.filepos); + + ent->filesize = entry.n_sects * SECTOR_LEN; + } + return IMGTOOLERR_SUCCESS; +} + +static imgtoolerr_t hp9845_tape_free_space(imgtool_partition *partition, UINT64 *size) +{ + tape_state_t& state = get_tape_state(imgtool_partition_image(partition)); + tape_image_t& tape_image = get_tape_image(state); + + *size = tape_image.free_sectors() * SECTOR_LEN; + + return IMGTOOLERR_SUCCESS; +} + +static imgtoolerr_t hp9845_tape_read_file(imgtool_partition *partition, const char *filename, const char *fork, imgtool_stream *destf) +{ + tape_state_t& state = get_tape_state(imgtool_partition_image(partition)); + tape_image_t& tape_image = get_tape_image(state); + + // TODO: + return IMGTOOLERR_UNIMPLEMENTED; +} + +void hp9845_tape_get_info(const imgtool_class *imgclass, UINT32 state, union imgtoolinfo *info) +{ + switch (state) { + case IMGTOOLINFO_STR_NAME: + strcpy(info->s = imgtool_temp_str(), "hp9845_tape"); + break; + + case IMGTOOLINFO_STR_DESCRIPTION: + strcpy(info->s = imgtool_temp_str(), "HP9845 tape image"); + break; + + case IMGTOOLINFO_STR_FILE: + strcpy(info->s = imgtool_temp_str(), __FILE__); + break; + + case IMGTOOLINFO_STR_FILE_EXTENSIONS: + strcpy(info->s = imgtool_temp_str(), "hti"); + break; + + case IMGTOOLINFO_INT_IMAGE_EXTRA_BYTES: + info->i = sizeof(tape_state_t); + break; + + case IMGTOOLINFO_INT_DIRECTORY_EXTRA_BYTES: + info->i = sizeof(dir_state_t); + break; + + case IMGTOOLINFO_PTR_OPEN: + info->open = hp9845_tape_open; + break; + + case IMGTOOLINFO_PTR_CREATE: + info->create = hp9845_tape_create; + break; + + case IMGTOOLINFO_PTR_CLOSE: + info->close = hp9845_tape_close; + break; + + case IMGTOOLINFO_PTR_BEGIN_ENUM: + info->begin_enum = hp9845_tape_begin_enum; + break; + + case IMGTOOLINFO_PTR_NEXT_ENUM: + info->next_enum = hp9845_tape_next_enum; + break; + + case IMGTOOLINFO_PTR_FREE_SPACE: + info->free_space = hp9845_tape_free_space; + break; + + case IMGTOOLINFO_PTR_READ_FILE: + info->read_file = hp9845_tape_read_file; + break; + } +} + |