summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author R. Belmont <rb6502@users.noreply.github.com>2016-09-16 14:05:35 -0400
committer GitHub <noreply@github.com>2016-09-16 14:05:35 -0400
commit0ecea843d04bd199caaaa20ffbc797211a6e2731 (patch)
treed5a95dadfb94a2b2ae3d32358a4170a17f3dff78
parentd5ab804c2b72b139b8c5cbe09fe9344e9396860f (diff)
parente25e1b84b0731cd922068ea3f44326645641b424 (diff)
Merge pull request #1398 from fulivi/hp9845_tools
Imgtool now supports HP9845B tape image
-rw-r--r--scripts/src/tools.lua1
-rw-r--r--src/tools/imgtool/main.cpp2
-rw-r--r--src/tools/imgtool/modules.cpp1
-rw-r--r--src/tools/imgtool/modules/hp9845_tape.cpp1295
-rw-r--r--src/tools/unidasm.cpp4
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(&sector[ 0 ] , 3);
+ // Sector payload
+ if (in_use) {
+ memcpy(&sector[ 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(&sector[ 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) },