diff options
author | 2016-09-16 14:05:35 -0400 | |
---|---|---|
committer | 2016-09-16 14:05:35 -0400 | |
commit | 0ecea843d04bd199caaaa20ffbc797211a6e2731 (patch) | |
tree | d5a95dadfb94a2b2ae3d32358a4170a17f3dff78 | |
parent | d5ab804c2b72b139b8c5cbe09fe9344e9396860f (diff) | |
parent | e25e1b84b0731cd922068ea3f44326645641b424 (diff) |
Merge pull request #1398 from fulivi/hp9845_tools
Imgtool now supports HP9845B tape image
-rw-r--r-- | scripts/src/tools.lua | 1 | ||||
-rw-r--r-- | src/tools/imgtool/main.cpp | 2 | ||||
-rw-r--r-- | src/tools/imgtool/modules.cpp | 1 | ||||
-rw-r--r-- | src/tools/imgtool/modules/hp9845_tape.cpp | 1295 | ||||
-rw-r--r-- | src/tools/unidasm.cpp | 4 |
5 files changed, 1302 insertions, 1 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/main.cpp b/src/tools/imgtool/main.cpp index 0c7db16a1d3..bbc246a86fc 100644 --- a/src/tools/imgtool/main.cpp +++ b/src/tools/imgtool/main.cpp @@ -743,10 +743,10 @@ static void listoptions(const util::option_guide &opt_guide, const char *opt_spe fprintf(stdout, "Option Allowed values Description\n"); fprintf(stdout, "---------------- ------------------------------ -----------\n"); - std::stringstream description_buffer; for (auto iter = resolution.entries_begin(); iter != resolution.entries_end(); iter++) { const util::option_resolution::entry &entry = *iter; + std::stringstream description_buffer; std::string opt_name = string_format("--%s", entry.identifier()); const char *opt_desc = entry.display_name(); diff --git a/src/tools/imgtool/modules.cpp b/src/tools/imgtool/modules.cpp index 5d3acc7e31d..eeb50bdb1c1 100644 --- a/src/tools/imgtool/modules.cpp +++ b/src/tools/imgtool/modules.cpp @@ -101,5 +101,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..45ed0a7d332 --- /dev/null +++ b/src/tools/imgtool/modules/hp9845_tape.cpp @@ -0,0 +1,1295 @@ +// license:BSD-3-Clause +// copyright-holders:F. Ulivi +/********************************************************************* + + hp9845_tape.cpp + + HP-9845 tape format + + This imgtool module manipulates HTI files. These are image files + of the DC-100 tape cartridges that are simulated for the HP9845B + driver. + HP9845 filesystem for tapes has the following features: + * File names are 1 to 6 characters long. + * Case is significant in file names. + * There is no file "extension", file type is encoded separately + in file metadata. + * There are 8 file types. File type is encoded in 5 bits. + Only 8 out of the 32 possible values are valid. + * This module handles the file type as a fake file extension. + For example, a file named "TEST" having DATA type is get/put/shown + as "TEST.DATA". + * File type is deduced from host file extension when putting files + into image. File type can be overridden by the "ftype" option. + * Files are always stored in units of 256-byte physical records. + * An important metadata of files is WPR: Words Per Records. This + is a numeric value that sets the length of each logical record of + the file (in units of 16-bit words). It defaults to 128 (i.e. + logical and physical records are the same thing). It can be + set by the "wpr" option when putting files into the image. + * There is no fragmentation map in the filesystem: each file + always occupy a contiguous set of physical records. This fact + could prevent the putting of a file into an image when there + is no single block of free records big enough to hold the file + even though the total amount of free space would be sufficient. + + **** dir command **** + The format of the "attr" part of file listing is as follows: + %c '*' if file has the protection bit set, else ' ' + %02x Hexadecimal value of file type (00-1f) + %c '?' if file type is not valid, else ' ' + %4u Number of logical records + %4u WPR * 2 (i.e. bytes per logical record) + %3u First physical record of file + + **** get command **** + A file can be extracted from an image with or without an explicit + extension. If an extension is given, it must match the one corresponding + to file type. + + **** getall command **** + Files are extracted with their "fake" extension. + + **** put command **** + File type can be specified explicitly through the "ftype" option. + If this option is "auto" (the default), type is deduced from file + extension, if present. When extension is not given or it doesn't + match any known type, file type is set to "DATA". + WPR can be set through the "wpr" option. If it's 0 (the default), + WPR is set to 128. + + **** del command **** + File extension is ignored, if present. + +*********************************************************************/ +#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 CHARS_PER_FNAME 6 // Maximum characters in a filename +#define CHARS_PER_EXT 4 // Characters in file extension. Extension is encoded as file type, it's not actually stored in directory as characters. +#define CHARS_PER_FNAME_EXT (CHARS_PER_FNAME + 1 + CHARS_PER_EXT) // Characters in filename + extension +#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[ CHARS_PER_FNAME ]; // 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_sector(unsigned s_no , tape_word_t *s_data); + + bool get_dir_entry(unsigned idx , const dir_entry_t*& entry) const; + bool find_file(const char *filename , bool ignore_ext , unsigned& idx) const; + + void delete_dir_entry(unsigned idx); + + bool find_free_block(unsigned blocks , unsigned& first_s) const; + + bool alloc_new_file(unsigned blocks , dir_entry_t*& entry); + + 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); + + static void get_filename_and_ext(const dir_entry_t& ent , bool inc_ext , char *out , bool& qmark); + static void split_filename_and_ext(const char *filename , char *fname , char *ext); + +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); + 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 +{ + std::bitset<TOT_SECTORS> tmp(alloc_map); + + // Reserve sectors that cannot be allocated to files + for (unsigned i = 0; i < FIRST_FILE_SECTOR; i++) { + tmp[ i ] = true; + } + + return TOT_SECTORS - tmp.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_sector(unsigned s_no , tape_word_t *s_data) +{ + if (s_no < TOT_SECTORS && alloc_map[ s_no ]) { + memcpy(s_data , &img[ s_no ][ 0 ] , SECTOR_LEN); + return true; + } else { + return false; + } +} + +bool tape_image_t::get_dir_entry(unsigned idx , const dir_entry_t*& entry) const +{ + if (idx >= dir.size()) { + return false; + } else { + entry = &dir[ idx ]; + return true; + } +} + +bool tape_image_t::find_file(const char *filename , bool ignore_ext , unsigned& idx) const +{ + char fname[ CHARS_PER_FNAME_EXT + 1 ]; + char ext[ CHARS_PER_EXT + 1 ]; + + split_filename_and_ext(filename, fname, ext); + + bool has_ext = !ignore_ext && *ext != '\0'; + + if (has_ext) { + strcat(fname , "."); + strcat(fname , ext); + } + + for (auto i = dir.cbegin(); i < dir.cend(); i++) { + char full_fname[ CHARS_PER_FNAME_EXT + 1 ]; + bool qmark; + + get_filename_and_ext(*i, has_ext, full_fname, qmark); + + if (strcmp(fname , full_fname) == 0) { + idx = i - dir.cbegin(); + return true; + } + } + + return false; +} + +void tape_image_t::delete_dir_entry(unsigned idx) +{ + const dir_entry_t& ent = dir[ idx ]; + + // Release all sectors of file + for (unsigned i = ent.filepos; i < ent.filepos + ent.n_sects; i++) { + unset_sector(i); + } + + dir.erase(dir.begin() + idx); + dirty = true; +} + +bool tape_image_t::find_free_block(unsigned blocks , unsigned& first_s) const +{ + if (blocks >= (TOT_SECTORS - FIRST_FILE_SECTOR)) { + return false; + } + + std::bitset<TOT_SECTORS> scanner; + + for (unsigned i = FIRST_FILE_SECTOR; i < (FIRST_FILE_SECTOR + blocks); i++) { + scanner[ i ] = true; + } + + for (unsigned i = FIRST_FILE_SECTOR; i <= (TOT_SECTORS - blocks); i++) { + std::bitset<TOT_SECTORS> tmp_map(alloc_map & scanner); + if (tmp_map.none()) { + first_s = i; + return true; + } + scanner <<= 1; + } + + return false; +} + +bool tape_image_t::alloc_new_file(unsigned blocks , dir_entry_t*& entry) +{ + if (dir.size() >= MAX_DIR_ENTRIES) { + return false; + } + + dir_entry_t new_entry; + + memset(&new_entry.filename[ 0 ] , 0 , sizeof(new_entry.filename)); + new_entry.protection = 0; + new_entry.filetype = 0; + new_entry.n_recs = 0; + new_entry.wpr = 0; + + unsigned first_s; + + if (!find_free_block(blocks, first_s)) { + return false; + } + + new_entry.filepos = (UINT16)first_s; + new_entry.n_sects = blocks; + + dir.push_back(new_entry); + entry = &dir.back(); + dirty = true; + + return true; +} + +void tape_image_t::split_filename_and_ext(const char *filename , char *fname , char *ext) +{ + char *fname_fence = fname + CHARS_PER_FNAME; + + while (fname < fname_fence && *filename != '\0' && *filename != '.') { + *fname++ = *filename++; + } + + *fname = '\0'; + + while (*filename != '\0' && *filename != '.') { + filename++; + } + + if (*filename == '\0') { + *ext = '\0'; + } else { + filename++; + strncpy(ext , filename , CHARS_PER_EXT); + ext[ CHARS_PER_EXT ] = '\0'; + } +} + +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; +} + +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 +}; + +void tape_image_t::get_filename_and_ext(const dir_entry_t& ent , bool inc_ext , char *out , bool& qmark) +{ + strncpy(&out[ 0 ] , (const char*)&ent.filename[ 0 ] , CHARS_PER_FNAME); + out[ CHARS_PER_FNAME ] = '\0'; + + // Decode filetype + UINT8 type_low = ent.filetype & 7; + UINT8 type_hi = (ent.filetype >> 3) & 3; + + const char *filetype_str = filetype_attrs[ type_low ]; + + // Same logic used by hp9845b to add a question mark next to filetype + qmark = (type_low == DATA_FILETYPE && type_hi == 3) || + (type_low != DATA_FILETYPE && type_hi != 2); + + if (inc_ext) { + strcat(out , "."); + strcat(out , filetype_str); + } +} + +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 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); + + const dir_entry_t *entry = nullptr; + + if (!tape_image.get_dir_entry(ds->dir_idx, entry)) { + ent->eof = 1; + } else { + ds->dir_idx++; + + bool qmark; + + tape_image_t::get_filename_and_ext(*entry, true, ent->filename, qmark); + + // "filename" and "attr" fields try to look like the output of the "CAT" command + snprintf(ent->attr , sizeof(ent->attr) , "%c %02x%c %4u %4u %3u" , entry->protection ? '*' : ' ' , entry->filetype , qmark ? '?' : ' ' , 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); + + unsigned idx; + + if (!tape_image.find_file(filename , false , idx)) { + return IMGTOOLERR_FILENOTFOUND; + } + + const dir_entry_t *ent = nullptr; + + tape_image.get_dir_entry(idx, ent); + + unsigned sect_no = ent->filepos; + unsigned n_sects = ent->n_sects; + tape_word_t buff_w[ WORDS_PER_SECTOR ]; + UINT8 buff_b[ SECTOR_LEN ]; + + while (n_sects--) { + if (!tape_image.get_sector(sect_no++, &buff_w[ 0 ])) { + return IMGTOOLERR_READERROR; + } + for (unsigned i = 0; i < WORDS_PER_SECTOR; i++) { + tape_image_t::tape_word_to_bytes(buff_w[ i ], buff_b[ i * 2 ], buff_b[ i * 2 + 1 ]); + } + + stream_write(destf , buff_b , SECTOR_LEN); + } + + return IMGTOOLERR_SUCCESS; +} + +static imgtoolerr_t hp9845_tape_write_file(imgtool_partition *partition, const char *filename, const char *fork, imgtool_stream *sourcef, util::option_resolution *opts) +{ + tape_state_t& state = get_tape_state(imgtool_partition_image(partition)); + tape_image_t& tape_image = get_tape_image(state); + + unsigned idx; + + if (tape_image.find_file(filename , true , idx)) { + // When overwriting a file, delete its old version first + tape_image.delete_dir_entry(idx); + } + + unsigned blocks = (unsigned)((stream_size(sourcef) + SECTOR_LEN - 1) / SECTOR_LEN); + + if (!blocks) { + fprintf(stderr , "Null file, not writing..\n"); + return IMGTOOLERR_SUCCESS; + } + + dir_entry_t *ent = nullptr; + + if (!tape_image.alloc_new_file(blocks, ent)) { + return IMGTOOLERR_NOSPACE; + } + + unsigned s_no = ent->filepos; + + char fname[ CHARS_PER_FNAME + 1 ]; + char ext[ CHARS_PER_EXT + 1 ]; + + tape_image_t::split_filename_and_ext(filename, fname, ext); + + strncpy((char*)&ent->filename[ 0 ] , fname , CHARS_PER_FNAME); + + for (unsigned i = 0; i < blocks; i++) { + tape_word_t buff_w[ WORDS_PER_SECTOR ]; + UINT8 buff_b[ SECTOR_LEN ]; + + memset(&buff_b[ 0 ] , 0 , sizeof(buff_b)); + + if (stream_read(sourcef , buff_b , SECTOR_LEN) != SECTOR_LEN && i != (blocks - 1)) { + return IMGTOOLERR_READERROR; + } + for (unsigned j = 0; j < WORDS_PER_SECTOR; j++) { + tape_image_t::bytes_to_tape_word(buff_b[ 2 * j ], buff_b[ 2 * j + 1 ], buff_w[ j ]); + } + tape_image.set_sector(s_no, buff_w); + s_no++; + } + + int wpr = opts->lookup_int('W'); + if (wpr == 0) { + wpr = WORDS_PER_SECTOR; + } else if (wpr > (blocks * WORDS_PER_SECTOR)) { + fprintf(stderr , "WPR value too large, using %u\n" , WORDS_PER_SECTOR); + wpr = WORDS_PER_SECTOR; + } + ent->wpr = (UINT16)wpr; + + ent->n_recs = (UINT16)((blocks * WORDS_PER_SECTOR) / wpr); + + unsigned type_low; + + if (opts->lookup_int('T') == 0) { + // File type defaults to DATA if no extension is given or extension is invalid + type_low = DATA_FILETYPE; + for (unsigned i = 0; i < 8; i++) { + if (strcmp(filetype_attrs[ i ] , ext) == 0) { + type_low = i; + break; + } + } + } else { + type_low = opts->lookup_int('T') - 1; + } + + // See tape_image_t::get_filename_and_ext for the logic behind file type + if (type_low == DATA_FILETYPE) { + ent->filetype = (UINT8)type_low + (1U << 3); + } else { + ent->filetype = (UINT8)type_low + (2U << 3); + } + + return IMGTOOLERR_SUCCESS; +} + +static imgtoolerr_t hp9845_tape_delete_file(imgtool_partition *partition, const char *filename) +{ + tape_state_t& state = get_tape_state(imgtool_partition_image(partition)); + tape_image_t& tape_image = get_tape_image(state); + + unsigned idx; + + if (!tape_image.find_file(filename , true , idx)) { + return IMGTOOLERR_FILENOTFOUND; + } + + tape_image.delete_dir_entry(idx); + + return IMGTOOLERR_SUCCESS; +} + +#define HP9845_WRITEFILE_OPTSPEC "W[0]-65535;T[0]-8" + +OPTION_GUIDE_START(hp9845_write_optguide) + OPTION_INT('W' , "wpr" , "Words per record") + OPTION_ENUM_START('T' , "ftype" , "File type") + OPTION_ENUM(0 , "auto" , "Automatic (\"DATA\" or by extension)") + OPTION_ENUM(1 , "U" , "BKUP") + OPTION_ENUM(2 , "D" , "DATA") + OPTION_ENUM(3 , "P" , "PROG") + OPTION_ENUM(4 , "K" , "KEYS") + OPTION_ENUM(5 , "T" , "BDAT") + OPTION_ENUM(6 , "A" , "ALL") + OPTION_ENUM(7 , "B" , "BPRG") + OPTION_ENUM(8 , "O" , "OPRM") + OPTION_ENUM_END +OPTION_GUIDE_END + +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; + + case IMGTOOLINFO_PTR_WRITE_FILE: + info->write_file = hp9845_tape_write_file; + break; + + case IMGTOOLINFO_PTR_DELETE_FILE: + info->delete_file = hp9845_tape_delete_file; + break; + + case IMGTOOLINFO_PTR_WRITEFILE_OPTGUIDE: + info->writefile_optguide = &hp9845_write_optguide; + break; + + case IMGTOOLINFO_STR_WRITEFILE_OPTSPEC: + strcpy(info->s = imgtool_temp_str(), HP9845_WRITEFILE_OPTSPEC); + break; + } +} diff --git a/src/tools/unidasm.cpp b/src/tools/unidasm.cpp index ddcaf7ec389..96b6913b94c 100644 --- a/src/tools/unidasm.cpp +++ b/src/tools/unidasm.cpp @@ -102,6 +102,8 @@ CPU_DISASSEMBLE( hd6301 ); CPU_DISASSEMBLE( hd6309 ); CPU_DISASSEMBLE( hd63701 ); CPU_DISASSEMBLE( hmcs40 ); +CPU_DISASSEMBLE( hp_hybrid ); +CPU_DISASSEMBLE( hp_5061_3001 ); CPU_DISASSEMBLE( hyperstone_generic ); CPU_DISASSEMBLE( i4004 ); CPU_DISASSEMBLE( i8008 ); @@ -268,6 +270,8 @@ static const dasm_table_entry dasm_table[] = { "hd6309", _8bit, 0, CPU_DISASSEMBLE_NAME(hd6309) }, { "hd63701", _8bit, 0, CPU_DISASSEMBLE_NAME(hd63701) }, { "hmcs40", _16le, -1, CPU_DISASSEMBLE_NAME(hmcs40) }, + { "hp_hybrid", _16be, -1, CPU_DISASSEMBLE_NAME(hp_hybrid) }, + { "hp_5061_3001",_16be, -1, CPU_DISASSEMBLE_NAME(hp_5061_3001) }, { "hyperstone", _16be, 0, CPU_DISASSEMBLE_NAME(hyperstone_generic) }, { "i4004", _8bit, 0, CPU_DISASSEMBLE_NAME(i4004) }, { "i8008", _8bit, 0, CPU_DISASSEMBLE_NAME(i8008) }, |