summaryrefslogtreecommitdiffstatshomepage
path: root/src/lib
diff options
context:
space:
mode:
author fulivi <fulivi@users.noreply.github.com>2023-05-14 22:24:48 +0200
committer GitHub <noreply@github.com>2023-05-15 06:24:48 +1000
commit57b08b837418c9f17ec2cd91247dea9bc0a44446 (patch)
tree67e4d0f61263703b59d19668afa6ff936151d567 /src/lib
parentfdcd90da80120a6623cf3a8c1cef1c07eb398f3f (diff)
formats/fs_hp98x5.cpp: Added filesystem handlers for HP9825, HP9831 and HP9845. (#11175)
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/formats/all.cpp9
-rw-r--r--src/lib/formats/fs_hp98x5.cpp1545
-rw-r--r--src/lib/formats/fs_hp98x5.h88
-rw-r--r--src/lib/formats/hpi_dsk.cpp7
-rw-r--r--src/lib/formats/hpi_dsk.h13
5 files changed, 1649 insertions, 13 deletions
diff --git a/src/lib/formats/all.cpp b/src/lib/formats/all.cpp
index 9519b323686..efc78e53911 100644
--- a/src/lib/formats/all.cpp
+++ b/src/lib/formats/all.cpp
@@ -744,6 +744,10 @@
#include "fs_isis.h"
#endif
+#ifdef HAS_FORMATS_FS_HP98X5
+#include "fs_hp98x5.h"
+#endif
+
void mame_formats_full_list(mame_formats_enumerator &en)
{
en.category("Generic");
@@ -902,6 +906,11 @@ void mame_formats_full_list(mame_formats_enumerator &en)
#ifdef HAS_FORMATS_FS_HPLIF
en.add(fs::HPLIF); // fs_lif.h
#endif
+#ifdef HAS_FORMATS_FS_HP98X5
+ en.add(fs::HP9825);
+ en.add(fs::HP9831);
+ en.add(fs::HP9845);
+#endif
en.category("Applix");
#ifdef HAS_FORMATS_APPLIX_DSK
diff --git a/src/lib/formats/fs_hp98x5.cpp b/src/lib/formats/fs_hp98x5.cpp
new file mode 100644
index 00000000000..bc06e1fa0a9
--- /dev/null
+++ b/src/lib/formats/fs_hp98x5.cpp
@@ -0,0 +1,1545 @@
+// license:BSD-3-Clause
+// copyright-holders: F. Ulivi
+/*********************************************************************
+
+ fs_hp98x5.cpp
+
+ HP 9825, 9831 & 9845 filesystem handling
+
+ HP9825,HP9831 and HP9845 have very similar filesystems for disk
+ storage (storage on tape is an entirely different story, each
+ system has its own format).
+ The main reference for 9845 FS comes from A. Kueckes page at
+ https://www.hp9845.net/9845/projects/hpdir
+ For what concerns the 9825 FS, I mainly reverse engineered it
+ from assembly sources of mass storage ROMs.
+ File names in 98x5 filesystems have no extension. In this driver
+ I'm using a virtual extensions to provide a more user-friendly
+ encoding of the numeric file type,
+ I've added the "TXT" extension to handle the very common string-only
+ DATA files. This kind of DATA files emulates text files on
+ 9825/45 systems. When reading or writing TXT files, ASCII text is
+ automatically translated to/from DATA files (see also
+ src/tools/imgtool/modules/hp9845_tape.cpp for the same function
+ on DC100 tapes).
+ The general form of file name is:
+ name[[.&bpr].ext]
+ Where "name" is the filename itself (1 to 6 characters), "bpr" is
+ the optional record size and "ext" is the virtual extension.
+ BPR applies to 9845 only. It encodes the record size attribute
+ of files (in bytes, 4 <= bpr <= 32766, even values only).
+ BPR and ext are optional when reading a file. If specified they
+ have to match file metadata, though.
+ Extension is required when file is being created. It's also
+ required for DATA<->TXT translation (see above).
+ BPR is optional when creating a file, it defaults to 256 bytes
+ when missing.
+ This form of file names comes from hpdir tool (see URL above).
+ Examples:
+ - Create a text file having 1024 bytes-per-record:
+ "FILE.&1024.TXT"
+ - Read a data file as text:
+ "FILE.TXT"
+ - Read/write an existing program file:
+ "FILE[.PROG]"
+
+ This driver generates the spare track when formatting and updates
+ it when writing. If the main directory or availability table
+ are corrupted, though, no attempt is made to use their 2nd copy
+ from spare track.
+
+ NB: This driver doesn't add the so-called "bootstraps" when
+ formatting a HP9825 disk. For this reason the created image
+ cannot be used with HP98217 mass storage ROM which relies on
+ bootstraps. Pre-formatted images (such as the one available at
+ http://www.hpmuseum.net/software/9825_discs.zip) should be
+ used with this ROM. Bootstraps, when present, are completely
+ ignored by this driver.
+
+*********************************************************************/
+
+#include "fs_hp98x5.h"
+#include "hpi_dsk.h"
+#include "strformat.h"
+#include <bitset>
+#include <map>
+
+using namespace fs;
+using namespace std::literals;
+
+// Constants
+constexpr unsigned SECTORS = hpi_format::HPI_SECTORS;
+constexpr unsigned SECTOR_SIZE = hpi_format::HPI_SECTOR_SIZE;
+constexpr unsigned DS_SECTORS = hpi_format::HPI_TRACKS * hpi_format::HPI_HEADS * SECTORS;
+constexpr unsigned DS_IMAGE_SIZE = DS_SECTORS * SECTOR_SIZE;
+constexpr unsigned SS_SECTORS = hpi_format::HPI_9885_TRACKS * SECTORS;
+constexpr unsigned SS_IMAGE_SIZE = SS_SECTORS * SECTOR_SIZE;
+
+// +--------+
+// | HP9825 |
+// | HP9831 |
+// | HP9845 |
+// +--------+
+namespace fs {
+ const hp9825_image HP9825;
+ const hp9831_image HP9831;
+ const hp9845_image HP9845;
+}
+
+// +-------------+
+// | hp98x5_impl |
+// +-------------+
+namespace {
+ class hp98x5_impl : public filesystem_t {
+ public:
+ enum fs_type {
+ FS_TYPE_9825,
+ FS_TYPE_9831,
+ FS_TYPE_9845,
+ FS_COUNT
+ };
+
+ hp98x5_impl(fsblk_t& blockdev, fs_type type);
+ virtual ~hp98x5_impl() = default;
+
+ virtual meta_data volume_metadata() override;
+
+ virtual std::pair<err_t, meta_data> metadata(const std::vector<std::string> &path) override;
+
+ virtual std::pair<err_t, std::vector<dir_entry>> directory_contents(const std::vector<std::string> &path) override;
+
+ virtual err_t file_create(const std::vector<std::string> &path, const meta_data &meta) override;
+
+ virtual std::pair<err_t, std::vector<u8>> file_read(const std::vector<std::string> &path) override;
+
+ virtual err_t file_write(const std::vector<std::string> &path, const std::vector<u8> &data) override;
+
+ virtual err_t format(const meta_data &meta) override;
+
+ private:
+ // Map of used/free sectors
+ using sect_map = std::bitset<DS_SECTORS>;
+ // 0-based LBAs
+ using lba_t = int;
+
+ // Position of system record
+ static constexpr lba_t SYS_REC_LBA = 0;
+ // Size of dir. entries
+ static constexpr unsigned DIR_ENTRY_SIZE = 16;
+ // Unspecified file type
+ static constexpr u8 NO_FILE_TYPE = 0xff;
+ // Special file type: DATA file as a text file
+ static constexpr u8 TEXT_TYPE = 0xfe;
+ // DATA file type on 9825/9831
+ static constexpr u8 DATA_TYPE_9825 = 1;
+ // DATA file type on 9845
+ static constexpr u8 DATA_TYPE_9845 = 8;
+ // Record size of DATA files on 9825
+ static constexpr unsigned BPR_9825 = 256;
+ // Default record size
+ static constexpr unsigned DEF_BPR = 256;
+ // Special extension for text files
+ inline static const std::string_view TEXT_EXT = "TXT"sv;
+ // DATA record types
+ static constexpr u16 REC_TYPE_EOR = 0x1e; // End-of-record
+ static constexpr u16 REC_TYPE_EOF = 0x3e; // End-of-file
+ static constexpr u16 REC_TYPE_FULLSTR = 0x3c; // A whole (un-split) string
+ static constexpr u16 REC_TYPE_1STSTR = 0x1c; // First part of a string
+ static constexpr u16 REC_TYPE_MIDSTR = 0x0c; // Middle part(s) of a string
+ static constexpr u16 REC_TYPE_ENDSTR = 0x2c; // Last part of a string
+ // Sys flag value for DATA files (HP9845)
+ static constexpr u8 SYS_FLAG_DATA = 1;
+ // Sys flag value for non-DATA files (HP9845)
+ static constexpr u8 SYS_FLAG_NON_DATA = 6;
+ // Spare track when formatting
+ static constexpr unsigned FMT_SPARE_TRACK = 1;
+ // Directory size when formatting (sectors)
+ static constexpr unsigned FMT_DIR_SIZE = 22;
+ // Av. table size when formatting (sectors)
+ static constexpr unsigned FMT_MAP_SIZE = 6;
+ // # of system tracks when formatting
+ static constexpr unsigned FMT_SYS_TRACKS = 2;
+ // # of data tracks when formatting
+ static constexpr unsigned FMT_DATA_TRACKS_SS = 65;
+ static constexpr unsigned FMT_DATA_TRACKS_DS = 148;
+
+ // Directory entries
+ struct entry {
+ entry()
+ : m_in_use(false)
+ , m_name{}
+ , m_type_code{0}
+ , m_size{0}
+ , m_sectors{0}
+ , m_bpr{0}
+ , m_1st_sect{0}
+ , m_extra_w{0}
+ , m_attrs{0}
+ { }
+
+ // In use?
+ bool m_in_use;
+ // Filename
+ std::string m_name;
+ // Type code
+ u8 m_type_code;
+ // Size (bytes)
+ unsigned m_size;
+ // Size in sectors
+ unsigned m_sectors;
+ // Record size (bytes-per-record)
+ unsigned m_bpr;
+ // First sector of data
+ lba_t m_1st_sect;
+ // Extra word
+ u16 m_extra_w;
+ // Various attributes
+ u8 m_attrs;
+ };
+
+ using dir_t = std::vector<entry>;
+
+ err_t parse_filename(const std::string& name, std::string& basename, int& bpr, u8& file_type) const;
+ bool validate_filename(const std::string& s) const;
+ void ensure_dir_loaded();
+ std::pair<dir_t, sect_map> decode_dir() const;
+ std::vector<u8> encode_dir() const;
+ bool is_data_file(u8 type_code) const;
+ void store_dir_map();
+ static dir_t::iterator scan_dir(dir_t& dir, const std::string& name);
+ dir_t::iterator find_file(const std::string& basename, int bpr, u8 file_type);
+ meta_data get_metadata(const entry& e);
+ sect_map no_data_map() const;
+ sect_map decode_map() const;
+ std::vector<u8> encode_map() const;
+ void set_not_in_use(lba_t first, unsigned sectors);
+ bool allocate(unsigned sectors, lba_t& first);
+ bool is_valid_char(u8 c) const;
+ std::string dec_name(std::vector<u8>::const_iterator begin, std::vector<u8>::const_iterator end) const;
+ static u16 r16b(std::vector<u8>::const_iterator it);
+ template<class T> static void w16b(T it, u16 w);
+ std::vector<u8> get_sector_range(lba_t first, unsigned size) const;
+ void store_sector_range(lba_t first, const std::vector<u8>& data);
+ static void check_av_data(const std::vector<u8>& in, const std::vector<u8>::const_iterator& it, int min_av);
+ static u16 get_u16(const std::vector<u8>& in, std::vector<u8>::const_iterator& it);
+ static std::vector<u8> convert_data_2_txt(const std::vector<u8>& in, unsigned bpr);
+ static void split_string_n_dump(const std::vector<u8>& in, std::vector<u8>& out, unsigned bpr);
+ static std::vector<u8> convert_txt_2_data(const std::vector<u8>& in, unsigned bpr);
+
+ // FS type
+ const fs_type m_fs_type;
+ // Number of tracks in image
+ unsigned m_img_tracks;
+ // Total tracks
+ unsigned m_tot_tracks;
+ // System tracks
+ unsigned m_sys_tracks;
+ // Data tracks
+ unsigned m_data_tracks;
+ // Directory start
+ lba_t m_dir_start;
+ // Size of directory
+ unsigned m_dir_size;
+ // Start of av. table
+ lba_t m_av_start;
+ // Size of av. table
+ unsigned m_av_size;
+ // Start of data part
+ lba_t m_data_start;
+ // End of data part
+ lba_t m_data_end;
+ // Start of spare part
+ lba_t m_spare_start;
+ // Has dir. been loaded?
+ bool m_dir_loaded;
+ // Directory
+ dir_t m_dir;
+ // Sectors in use
+ sect_map m_in_use;
+
+ // Interleave factor when formatting
+ // Index into array is fs_type
+ static const std::array<u16, FS_COUNT> m_fmt_interleave;
+
+ // Mapping of file types in each FS type
+ // Index into array is fs_type
+ // Key is type code
+ // Value is virtual file extension
+ const std::array<std::map<u8, std::string_view>, FS_COUNT> m_fs_type_maps;
+ };
+}
+
+hp98x5_impl::hp98x5_impl(fsblk_t& blockdev, fs_type type)
+ : filesystem_t(blockdev , SECTOR_SIZE)
+ , m_fs_type(type)
+ , m_dir_loaded(false)
+ , m_fs_type_maps({
+ // ********
+ // * 9825 *
+ // ********
+ std::map<u8, std::string_view>{
+ { 1, "DATA"sv },
+ { 2, "PROG"sv },
+ { 3, "KEYS"sv },
+ { 5, "ALL"sv },
+ { 6, "BPRG"sv }
+ },
+ // ********
+ // * 9831 *
+ // ********
+ std::map<u8, std::string_view>{
+ { 1, "DATA"sv },
+ { 9, "DATA"sv },
+ { 18, "PROG"sv },
+ { 19, "KEYS"sv },
+ { 20, "NULL"sv },
+ { 21, "MEMORY"sv },
+ { 22, "BINARY"sv },
+ { 23, "86DATA"sv }
+ },
+ // ********
+ // * 9845 *
+ // ********
+ std::map<u8, std::string_view>{
+ { 0, "BKUP"sv },
+ { 8, "DATA"sv },
+ { 16,"PROG"sv },
+ { 24,"KEYS"sv },
+ { 32,"BDAT"sv },
+ { 40,"ALL"sv },
+ { 48,"BPRG"sv },
+ { 56,"OPRM"sv },
+ { 60,"ASMB"sv }
+ }})
+{
+}
+
+meta_data hp98x5_impl::volume_metadata()
+{
+ meta_data res;
+ return res;
+}
+
+std::pair<err_t, meta_data> hp98x5_impl::metadata(const std::vector<std::string> &path)
+{
+ if (path.size() != 1) {
+ return std::make_pair(ERR_NOT_FOUND, meta_data{});
+ }
+
+ std::string basename;
+ int bpr;
+ u8 file_type;
+
+ auto err = parse_filename(path.front(), basename, bpr, file_type);
+ if (err != ERR_OK) {
+ return std::make_pair(ERR_NOT_FOUND, meta_data{});
+ }
+
+ ensure_dir_loaded();
+ auto it = scan_dir(m_dir, basename);
+ if (it == m_dir.end()) {
+ return std::make_pair(ERR_NOT_FOUND, meta_data{});
+ }
+
+ auto meta = get_metadata(*it);
+ return std::make_pair(ERR_OK, std::move(meta));
+}
+
+std::pair<err_t, std::vector<dir_entry>> hp98x5_impl::directory_contents(const std::vector<std::string> &path)
+{
+ if (path.empty()) {
+ ensure_dir_loaded();
+ // Copy all dir_entry(s) out of m_dir
+ std::vector<dir_entry> dir_entries;
+ for (const auto& e : m_dir) {
+ if (e.m_in_use) {
+ auto meta = get_metadata(e);
+ dir_entries.emplace_back(dir_entry_type::file, std::move(meta));
+ }
+ }
+ return std::make_pair(ERR_OK, dir_entries);
+ } else {
+ return std::make_pair(ERR_NOT_FOUND, std::vector<dir_entry>{});
+ }
+}
+
+err_t hp98x5_impl::file_create(const std::vector<std::string> &path, const meta_data &meta)
+{
+ if (!path.empty()) {
+ return ERR_INVALID;
+ }
+
+ auto name = meta.get_string(meta_name::name);
+
+ std::string basename;
+ int bpr;
+ u8 file_type;
+
+ // Parse & validate filename
+ // Extension must be specified
+ auto err = parse_filename(name, basename, bpr, file_type);
+ if (err != ERR_OK || file_type == NO_FILE_TYPE) {
+ return ERR_INVALID;
+ }
+ if (bpr < 0) {
+ // Use default bpr when not specified
+ bpr = DEF_BPR;
+ }
+
+ // Check that file doesn't exist
+ ensure_dir_loaded();
+ auto it = scan_dir(m_dir, basename);
+ if (it != m_dir.end()) {
+ return ERR_INVALID;
+ }
+
+ // Find a free entry in directory
+ auto it2 = std::find_if(m_dir.begin(), m_dir.end(), [](const entry& e) { return !e.m_in_use; });
+ if (it2 == m_dir.end()) {
+ // Add a new entry at the end of directory
+ if (m_dir.size() < m_dir_size * SECTOR_SIZE / DIR_ENTRY_SIZE) {
+ m_dir.emplace_back(entry());
+ it2 = m_dir.end() - 1;
+ } else {
+ return ERR_NO_SPACE;
+ }
+ }
+
+ // Fill dir entry
+ it2->m_in_use = true;
+ it2->m_name = basename;
+ it2->m_type_code = (file_type != TEXT_TYPE) ? file_type : (m_fs_type != FS_TYPE_9845 ? DATA_TYPE_9825 : DATA_TYPE_9845);
+ it2->m_size = 0;
+ it2->m_sectors = 0;
+ it2->m_bpr = bpr > 0 ? unsigned(bpr) : DEF_BPR;
+ it2->m_1st_sect = 0;
+ it2->m_extra_w = 0;
+ it2->m_attrs = m_fs_type != FS_TYPE_9845 ? 0 : (it2->m_type_code == DATA_TYPE_9845 ? SYS_FLAG_DATA : SYS_FLAG_NON_DATA);
+
+ store_dir_map();
+
+ return ERR_OK;
+}
+
+std::pair<err_t, std::vector<u8>> hp98x5_impl::file_read(const std::vector<std::string> &path)
+{
+ if (path.size() != 1) {
+ return std::make_pair(ERR_NOT_FOUND, std::vector<u8>{});
+ }
+ std::string basename;
+ int bpr;
+ u8 file_type;
+
+ auto err = parse_filename(path.front(), basename, bpr, file_type);
+
+ if (err == ERR_OK) {
+ auto it = find_file(basename, bpr, file_type);
+ if (it == m_dir.end()) {
+ err = ERR_NOT_FOUND;
+ } else {
+ auto file_data = get_sector_range(it->m_1st_sect, it->m_sectors);
+ if (file_type == TEXT_TYPE) {
+ file_data = convert_data_2_txt(file_data, it->m_bpr);
+ } else {
+ file_data.resize(it->m_size);
+ }
+ return std::make_pair(ERR_OK, std::move(file_data));
+ }
+ }
+ return std::make_pair(err, std::vector<u8>{});
+}
+
+err_t hp98x5_impl::file_write(const std::vector<std::string> &path, const std::vector<u8> &data)
+{
+ if (path.size() != 1) {
+ return ERR_NOT_FOUND;
+ }
+
+ std::string basename;
+ int bpr;
+ u8 file_type;
+
+ auto err = parse_filename(path.front(), basename, bpr, file_type);
+ if (err != ERR_OK) {
+ return err;
+ }
+ // Check that file already exists
+ auto it = find_file(basename, bpr, file_type);
+ if (it == m_dir.end()) {
+ return ERR_NOT_FOUND;
+ }
+
+ // De-allocate current blocks
+ if (it->m_sectors) {
+ set_not_in_use(it->m_1st_sect, it->m_sectors);
+ }
+
+ const std::vector<u8> *data_ptr = &data;
+ std::vector<u8> new_data;
+
+ if (file_type == TEXT_TYPE) {
+ new_data = convert_txt_2_data(data, it->m_bpr);
+ data_ptr = &new_data;
+ }
+
+ // Allocate space
+ it->m_sectors = (data_ptr->size() + SECTOR_SIZE - 1) / SECTOR_SIZE;
+ if (!allocate(it->m_sectors, it->m_1st_sect)) {
+ return ERR_NO_SPACE;
+ }
+
+ // Store file content
+ store_sector_range(it->m_1st_sect, *data_ptr);
+ it->m_size = data_ptr->size();
+
+ // Update directory & map
+ store_dir_map();
+
+ return ERR_OK;
+}
+
+err_t hp98x5_impl::format(const meta_data &meta)
+{
+ bool is_ds = m_blockdev.block_count() == DS_SECTORS;
+
+ // Compose system record
+ std::vector<u8> sys_rec;
+ auto out_ins = std::back_inserter(sys_rec);
+
+ // System ID
+ w16b(out_ins, 0);
+ // Sectors per track
+ w16b(out_ins, SECTORS);
+ // Total tracks
+ m_data_tracks = is_ds ? FMT_DATA_TRACKS_DS : FMT_DATA_TRACKS_SS;
+ m_sys_tracks = FMT_SYS_TRACKS;
+ m_tot_tracks = m_data_tracks + m_sys_tracks;
+ m_data_start = m_sys_tracks * SECTORS;
+ m_data_end = m_tot_tracks * SECTORS;
+ w16b(out_ins, m_tot_tracks);
+ // Spare track
+ m_spare_start = FMT_SPARE_TRACK * SECTORS;
+ w16b(out_ins, FMT_SPARE_TRACK);
+ // First sector of directory
+ m_dir_start = 1;
+ m_dir_size = FMT_DIR_SIZE;
+ w16b(out_ins, m_dir_start);
+ // First sector of availability table
+ m_av_start = m_dir_start + m_dir_size;
+ m_av_size = FMT_MAP_SIZE;
+ w16b(out_ins, m_av_start);
+ // First sector after availability table
+ w16b(out_ins, m_av_start + m_av_size);
+ // Count of system tracks
+ w16b(out_ins, m_sys_tracks);
+ // Count of data tracks
+ w16b(out_ins, m_data_tracks);
+ // Interleave factor
+ w16b(out_ins, m_fmt_interleave[ m_fs_type ]);
+
+ m_blockdev.get(0).copy(0, sys_rec.data(), sys_rec.size());
+
+ // 9825 & 9831 have a backup copy of system record in spare track but 9845 hasn't, go figure
+ if (m_fs_type != FS_TYPE_9845) {
+ m_blockdev.get(m_spare_start).copy(0, sys_rec.data(), sys_rec.size());
+ }
+
+ m_dir.clear();
+ m_dir_loaded = true;
+ m_in_use = no_data_map();
+
+ store_dir_map();
+
+ return ERR_OK;
+}
+
+err_t hp98x5_impl::parse_filename(const std::string& name, std::string& basename, int& bpr, u8& file_type) const
+{
+ // General form of filenames:
+ // basename[[.&bpr].ext]
+ // where "bpr" is the optional byte-per-record attribute of file (this part can only be
+ // specified for 9845 filesystems)
+ // and "ext" is the virtual file extension
+
+ file_type = NO_FILE_TYPE;
+ bpr = -1;
+
+ auto p1 = name.find_first_of('.');
+
+ if (p1 != std::string::npos) {
+ std::string::size_type p2 = std::string::npos;
+
+ if (m_fs_type == FS_TYPE_9845) {
+ p2 = name.find_first_of('.', p1 + 1);
+ }
+
+ if (p2 != std::string::npos) {
+ // [p1+1 p2) should have "&bpr"
+ if (name[ p1 + 1 ] != '&') {
+ return ERR_INVALID;
+ }
+ if (!std::all_of(name.cbegin() + p1 + 2, name.cbegin() + p2, [](char c) { return c >= '0' && c <= '9'; })) {
+ return ERR_INVALID;
+ }
+ try {
+ bpr = std::stoul(std::string{name, p1 + 2, p2 - p1 - 2});
+ } catch (...) {
+ return ERR_INVALID;
+ }
+ if (bpr < 4 || bpr > 32767 || (bpr & 1) != 0) {
+ return ERR_INVALID;
+ }
+ } else {
+ p2 = p1;
+ }
+
+ std::string ext{ name, p2 + 1, std::string::npos };
+ if (ext == TEXT_EXT) {
+ file_type = TEXT_TYPE;
+ } else {
+ for (const auto& e : m_fs_type_maps[ m_fs_type ]) {
+ if (e.second == ext) {
+ file_type = e.first;
+ break;
+ }
+ }
+ if (file_type == NO_FILE_TYPE) {
+ return ERR_INVALID;
+ }
+ }
+ }
+
+ // [0 p1) has "basename"
+ basename = std::string(name, 0, p1);
+ if (validate_filename(basename)) {
+ return ERR_OK;
+ } else {
+ return ERR_INVALID;
+ }
+}
+
+bool hp98x5_impl::validate_filename(const std::string& s) const
+{
+ if (s.size() < 1 || s.size() > 6) {
+ return false;
+ }
+
+ // 9831 files must not have '*' at beginning
+ if (m_fs_type == FS_TYPE_9831 && s.front() == '*') {
+ return false;
+ }
+
+ auto it = std::find_if_not(s.cbegin(), s.cend(), [this](char c) { return is_valid_char(u8(c)); });
+ return it == s.cend();
+}
+
+void hp98x5_impl::ensure_dir_loaded()
+{
+ if (m_dir_loaded) {
+ return;
+ }
+
+ // Get system record
+ auto sys_rec = m_blockdev.get(SYS_REC_LBA);
+
+ // Layout of system record
+ // Word # Content
+ // ================
+ // 0 System ID
+ // 1 Sectors per track
+ // 2 Total tracks
+ // 3 Track of spare system info
+ // 4 First sector of directory
+ // 5 First sector of availability table
+ // 6 First sector after availability table
+ // 7 Count of system tracks
+ // 8 Count of data tracks
+ // 9 Interleave factor
+ // 10..127 N/U
+ u16 sects_per_track = sys_rec.r16b(2);
+ u16 tot_tracks = sys_rec.r16b(4);
+ u16 spare_track = sys_rec.r16b(6);
+ u16 dir_start = sys_rec.r16b(8);
+ u16 av_start = sys_rec.r16b(10);
+ u16 av_end = sys_rec.r16b(12);
+ u16 sys_tracks = sys_rec.r16b(14);
+ u16 data_tracks = sys_rec.r16b(16);
+
+ m_img_tracks = m_blockdev.block_count() / SECTORS;
+
+ m_tot_tracks = tot_tracks;
+ m_sys_tracks = sys_tracks;
+ m_data_tracks = data_tracks;
+
+ m_dir_start = lba_t(dir_start);
+ m_dir_size = av_start - dir_start;
+ m_av_start = lba_t(av_start);
+ m_av_size = av_end - av_start;
+
+ m_data_start = sys_tracks * SECTORS;
+ m_data_end = (sys_tracks + data_tracks) * SECTORS;
+
+ m_spare_start = spare_track * SECTORS;
+
+ // Consistency checks
+ if (sects_per_track != SECTORS) {
+ throw std::runtime_error(util::string_format("Invalid sectors per track (%u)", sects_per_track));
+ }
+ if (dir_start == 0 ||
+ av_start <= dir_start ||
+ av_end <= av_start) {
+ throw std::runtime_error(util::string_format("Invalid dir/av. table pointers (%u,%u,%u)", dir_start, av_start, av_end));
+ }
+ lba_t spare_end = m_spare_start + av_end;
+ if (m_spare_start < av_end ||
+ spare_end > m_data_start) {
+ throw std::runtime_error(util::string_format("Invalid spare info position (%d,%d)", m_spare_start, spare_end));
+ }
+ if (tot_tracks > m_img_tracks) {
+ throw std::runtime_error(util::string_format("Invalid total track number (%u,%u)", tot_tracks, m_img_tracks));
+ }
+ if (tot_tracks < (data_tracks + sys_tracks) ||
+ data_tracks < 1 ||
+ sys_tracks < 2) {
+ throw std::runtime_error(util::string_format("Invalid sys/data tracks (%u,%u,%u)", sys_tracks, data_tracks, tot_tracks));
+ }
+
+ auto&& [decoded_dir, decoded_map] = decode_dir();
+ m_in_use = decode_map();
+
+ if (m_in_use != decoded_map) {
+ fprintf(stderr, "WARNING: Incorrect map\n");
+ }
+
+ m_dir = std::move(decoded_dir);
+
+ m_dir_loaded = true;
+}
+
+std::pair<hp98x5_impl::dir_t, hp98x5_impl::sect_map> hp98x5_impl::decode_dir() const
+{
+ dir_t dir;
+ sect_map in_use = no_data_map();
+
+ auto enc_dir = get_sector_range(m_dir_start, m_dir_size);
+
+ for (auto it = enc_dir.cbegin(); it != enc_dir.cend(); it += DIR_ENTRY_SIZE) {
+ auto w1 = r16b(it + 2);
+ if (w1 == 0xffff) {
+ // Directory ends
+ break;
+ }
+
+ entry e;
+
+ auto w0 = r16b(it);
+ if (w0 != 0) {
+ e.m_in_use = true;
+ // +0..+5: File name
+ e.m_name = dec_name(it, it + 6);
+ if (scan_dir(dir, e.m_name) != dir.end()) {
+ throw std::runtime_error(util::string_format("Duplicated file name (%s)", e.m_name));
+ }
+ // +6..+7: 1st sector
+ lba_t first_sect = r16b(it + 6);
+ // +8..+9: Number of sectors
+ unsigned sectors = r16b(it + 8);
+ if (first_sect < m_data_start || (first_sect + sectors) >= m_data_end || sectors == 0) {
+ throw std::runtime_error(util::string_format("Invalid file allocation (%d,%u)", first_sect, sectors));
+ }
+ e.m_1st_sect = first_sect;
+ e.m_sectors = sectors;
+ // Mark used sectors
+ for (lba_t i = first_sect; i < (first_sect + sectors); i++) {
+ in_use.set(i);
+ }
+ if (m_fs_type == FS_TYPE_9825 || m_fs_type == FS_TYPE_9831) {
+ // ******** ********
+ // * 9825 * * 9831 *
+ // ******** ********
+
+ // +12..+13: File type & secure flag
+ auto w6 = r16b(it + 12);
+ // b15..b10 = File type
+ e.m_type_code = u8(w6 >> 10);
+ // b9 = secured file (9825 only)
+ if (m_fs_type == FS_TYPE_9825) {
+ e.m_attrs = u8((w6 >> 9) & 1);
+ } else {
+ e.m_attrs = 0;
+ }
+ // +14..+15: Extra word
+ e.m_extra_w = r16b(it + 14);
+
+ // DATA files have a fixed record size
+ e.m_bpr = BPR_9825;
+
+ if (!is_data_file(e.m_type_code)) {
+ // File type != DATA
+ // +10..+11: Size of file in words - 1
+ auto w5 = r16b(it + 10);
+ e.m_size = (w5 + 1) * 2;
+ unsigned exp_sectors = (e.m_size + SECTOR_SIZE - 1) / SECTOR_SIZE;
+ if (exp_sectors != sectors) {
+ throw std::runtime_error(util::string_format("Mismatching number of sectors (%u,%u)", exp_sectors, sectors));
+ }
+ } else {
+ // File type == DATA
+ e.m_size = e.m_sectors * SECTOR_SIZE;
+ }
+ } else {
+ // ********
+ // * 9845 *
+ // ********
+
+ // +10..+11: Words per record
+ auto w5 = r16b(it + 10);
+ if (w5 < 2 || w5 > 16383) {
+ throw std::runtime_error(util::string_format("Invalid size of records (%u)", w5));
+ }
+ e.m_bpr = w5 * 2;
+
+ // +12..+13: File type & sys. flags
+ auto w6 = r16b(it + 12);
+ // b12..b7 = File type
+ e.m_type_code = u8((w6 >> 7) & 0x3f);
+ // b15..b13 = System flags
+ e.m_attrs = u8(w6 >> 13);
+
+ // +14..+15: Protection
+ e.m_extra_w = r16b(it + 14);
+
+ e.m_size = e.m_sectors * SECTOR_SIZE;
+ }
+ }
+
+ dir.emplace_back(std::move(e));
+ }
+
+ return std::make_pair(std::move(dir), std::move(in_use));
+}
+
+std::vector<u8> hp98x5_impl::encode_dir() const
+{
+ std::vector<u8> res;
+ auto out_ins = std::back_inserter(res);
+
+ for (const auto& e : m_dir) {
+ if (e.m_in_use) {
+ // Filename
+ for (auto c : e.m_name) {
+ *out_ins = u8(c);
+ }
+ if (e.m_name.size() < 6) {
+ std::fill_n(out_ins, 6 - e.m_name.size(), 0);
+ }
+ // 1st sector
+ w16b(out_ins, e.m_1st_sect);
+ // Number of sectors
+ w16b(out_ins, e.m_sectors);
+ if (m_fs_type == FS_TYPE_9825 || m_fs_type == FS_TYPE_9831) {
+ // ******** ********
+ // * 9825 * * 9831 *
+ // ******** ********
+
+ // Size of file in words - 1 (any file type but DATA)
+ if (!is_data_file(e.m_type_code)) {
+ w16b(out_ins, e.m_size / 2 - 1);
+ } else {
+ w16b(out_ins, 0);
+ }
+
+ // File type & secure flag
+ u16 w6 = u16(e.m_type_code) << 10;
+ if (e.m_attrs) {
+ w6 |= 1U << 9;
+ }
+ w16b(out_ins, w6);
+
+ // Extra word
+ w16b(out_ins, e.m_extra_w);
+ } else {
+ // ********
+ // * 9845 *
+ // ********
+
+ // Words per record
+ w16b(out_ins, e.m_bpr / 2);
+
+ // File type & sys. flags
+ u16 w6 = (u16(e.m_attrs) << 13) | (u16(e.m_type_code) << 7);
+ w16b(out_ins, w6);
+
+ // Protection
+ w16b(out_ins, e.m_extra_w);
+ }
+ } else {
+ // Empty entry
+ std::fill_n(out_ins, DIR_ENTRY_SIZE, 0);
+ }
+ }
+
+ // Insert dir. terminator (9845 only)
+ unsigned dir_len = m_dir_size * SECTOR_SIZE;
+ if (m_fs_type == FS_TYPE_9845 && dir_len - res.size() >= DIR_ENTRY_SIZE) {
+ w16b(out_ins, 0);
+ w16b(out_ins, 0xffff);
+ w16b(out_ins, 0);
+ w16b(out_ins, 0);
+ w16b(out_ins, 0);
+ w16b(out_ins, 0);
+ w16b(out_ins, 0);
+ w16b(out_ins, 0);
+ }
+
+ // Pad to end
+ if (res.size() < dir_len) {
+ std::fill_n(out_ins, dir_len - res.size(), 0);
+ }
+
+ return res;
+}
+
+bool hp98x5_impl::is_data_file(u8 type_code) const
+{
+ auto mp = m_fs_type_maps[ m_fs_type ];
+ auto it = mp.find(type_code);
+ return it != mp.end() && it->second == "DATA"sv;
+}
+
+void hp98x5_impl::store_dir_map()
+{
+ // Store directory (2 copies)
+ auto dir_content = encode_dir();
+ store_sector_range(m_dir_start, dir_content);
+ store_sector_range(m_dir_start + m_spare_start, dir_content);
+ // Store map (2 copies)
+ auto map_content = encode_map();
+ store_sector_range(m_av_start, map_content);
+ store_sector_range(m_av_start + m_spare_start, map_content);
+}
+
+hp98x5_impl::dir_t::iterator hp98x5_impl::scan_dir(dir_t& dir, const std::string& name)
+{
+ return std::find_if(dir.begin(), dir.end(), [&name](const entry& e){ return e.m_in_use && name == e.m_name; });
+}
+
+hp98x5_impl::dir_t::iterator hp98x5_impl::find_file(const std::string& basename, int bpr, u8 file_type)
+{
+ ensure_dir_loaded();
+ auto it = scan_dir(m_dir, basename);
+ if (it != m_dir.end() &&
+ (file_type == NO_FILE_TYPE ||
+ (file_type == TEXT_TYPE && is_data_file(it->m_type_code)) ||
+ file_type == it->m_type_code) &&
+ (bpr < 0 || bpr == it->m_bpr)) {
+ return it;
+ } else {
+ return m_dir.end();
+ }
+}
+
+meta_data hp98x5_impl::get_metadata(const entry& e)
+{
+ meta_data meta;
+ meta.set(meta_name::name, e.m_name);
+
+ auto mp = m_fs_type_maps[ m_fs_type ];
+ auto it = mp.find(e.m_type_code);
+ if (it != mp.end()) {
+ meta.set(meta_name::file_type , it->second);
+ } else {
+ meta.set(meta_name::file_type , util::string_format("?%02x", e.m_type_code));
+ }
+
+ if (m_fs_type == FS_TYPE_9825) {
+ // ********
+ // * 9825 *
+ // ********
+ if (e.m_attrs) {
+ // "Secured" program
+ meta.set(meta_name::attributes, "S");
+ }
+ } else if (m_fs_type == FS_TYPE_9845) {
+ // ********
+ // * 9845 *
+ // ********
+ if (e.m_bpr != DEF_BPR) {
+ // System flags & record size
+ meta.set(meta_name::attributes, util::string_format("S=%u R=%u", e.m_attrs, e.m_bpr));
+ } else {
+ // System flags
+ meta.set(meta_name::attributes, util::string_format("S=%u", e.m_attrs));
+ }
+ }
+
+ meta.set(meta_name::length, e.m_size);
+
+ return meta;
+}
+
+hp98x5_impl::sect_map hp98x5_impl::no_data_map() const
+{
+ sect_map res;
+
+ // First mark every sector as used...
+ res.set();
+
+ // ...then carve out the data part of disk
+ for (auto i = m_data_start; i < m_data_end; i++) {
+ res.reset(i);
+ }
+
+ return res;
+}
+
+hp98x5_impl::sect_map hp98x5_impl::decode_map() const
+{
+ sect_map res;
+
+ // First mark every sector as used...
+ res.set();
+
+ // ..then scan availability table and mark free sectors
+ auto enc_av = get_sector_range(m_av_start, m_av_size);
+ for (auto it = enc_av.cbegin(); it != enc_av.cend(); it += 4) {
+ lba_t start = r16b(it);
+ unsigned size = r16b(it + 2);
+ if (start == 0xffff) {
+ // table ends
+ break;
+ } else if (size == 0) {
+ // Null entry, skip
+ continue;
+ }
+ if (start < m_data_start ||
+ start + size > m_data_end) {
+ throw std::runtime_error(util::string_format("Invalid entry in av. table (%d,%u)", start, size));
+ }
+ for (lba_t k = start; k < start + size; k++) {
+ if (!res[ k ]) {
+ throw std::runtime_error(util::string_format("Sector %d marked free twice", k));
+ } else {
+ res.reset(k);
+ }
+ }
+ }
+
+ return res;
+}
+
+std::vector<u8> hp98x5_impl::encode_map() const
+{
+ std::vector<u8> res(m_av_size * SECTOR_SIZE);
+
+ auto it = res.begin();
+
+ bool in_free_block = false;
+
+ for (auto i = m_data_start; i < m_data_end; i++) {
+ if (m_in_use[ i ]) {
+ in_free_block = false;
+ } else if (in_free_block) {
+ it -= 2;
+ w16b(it, r16b(it) + 1);
+ it += 2;
+ } else {
+ in_free_block = true;
+ if (res.end() - it < 4) {
+ throw std::runtime_error("Availability table too fragmented");
+ }
+ w16b(it, u16(i));
+ it += 2;
+ w16b(it, 1);
+ it += 2;
+ }
+ }
+
+ if (m_fs_type == FS_TYPE_9845 && res.end() - it >= 4) {
+ w16b(it, 0xffffU);
+ }
+
+ return res;
+}
+
+void hp98x5_impl::set_not_in_use(lba_t first, unsigned sectors)
+{
+ for (lba_t i = first; i < (first + sectors); i++) {
+ m_in_use.reset(i);
+ }
+}
+
+bool hp98x5_impl::allocate(unsigned sectors, lba_t& first)
+{
+ if (sectors > (m_data_end - m_data_start)) {
+ return false;
+ }
+
+ sect_map scanner;
+ for (lba_t i = m_data_start; i < (m_data_start + sectors); i++) {
+ scanner.set(i);
+ }
+
+ for (lba_t i = m_data_start; i <= (m_data_end - sectors); i++) {
+ sect_map tmp = m_in_use & scanner;
+ if (tmp.none()) {
+ m_in_use |= scanner;
+ first = i;
+ return true;
+ }
+ scanner <<= 1;
+ }
+
+ return false;
+}
+
+bool hp98x5_impl::is_valid_char(u8 c) const
+{
+ if (c <= 0x20 || c >= 0x7f || c == '"' || c == ':') {
+ return false;
+ } else if (m_fs_type == FS_TYPE_9825) {
+ return c != ',' && c != ';';
+ } else if (m_fs_type == FS_TYPE_9831) {
+ return c != ',';
+ } else {
+ return true;
+ }
+}
+
+std::string hp98x5_impl::dec_name(std::vector<u8>::const_iterator begin, std::vector<u8>::const_iterator end) const
+{
+ std::string res;
+ bool ended = false;
+ for (auto i = begin; i < end; i++) {
+ auto c = *i;
+ if (ended) {
+ if (c != 0) {
+ throw std::runtime_error("Invalid name in dir. entry");
+ }
+ } else {
+ if (c == 0) {
+ ended = true;
+ } else if (is_valid_char(c)) {
+ res.push_back(char(c));
+ } else {
+ throw std::runtime_error("Invalid name in dir. entry");
+ }
+ }
+ }
+
+ return res;
+}
+
+u16 hp98x5_impl::r16b(std::vector<u8>::const_iterator it)
+{
+ u16 w = (u16(it[ 0 ]) << 8) | it[ 1 ];
+ return w;
+}
+
+template<class T> void hp98x5_impl::w16b(T it, u16 w)
+{
+ *it++ = u8(w >> 8);
+ *it = u8(w & 0xff);
+}
+
+std::vector<u8> hp98x5_impl::get_sector_range(lba_t first, unsigned size) const
+{
+ std::vector<u8> res;
+
+ res.resize(size * SECTOR_SIZE);
+
+ u8 *ptr = res.data();
+
+ for (lba_t idx = first; idx < first + size; idx++) {
+ auto data_sect = m_blockdev.get(u32(idx));
+ memcpy(ptr, data_sect.rodata(), SECTOR_SIZE);
+ ptr += SECTOR_SIZE;
+ }
+ return res;
+}
+
+void hp98x5_impl::store_sector_range(lba_t first, const std::vector<u8>& data)
+{
+ const u8 *ptr = data.data();
+ u32 to_go = u32(data.size());
+ unsigned sects = (data.size() + SECTOR_SIZE - 1) / SECTOR_SIZE;
+ for (lba_t idx = first; idx < first + sects; idx++) {
+ u32 count = std::min<u32>(to_go, SECTOR_SIZE);
+ auto blk = m_blockdev.get(u32(idx));
+ blk.copy(0, ptr, count);
+ ptr += count;
+ to_go -= count;
+ }
+}
+
+void hp98x5_impl::check_av_data(const std::vector<u8>& in, const std::vector<u8>::const_iterator& it, int min_av)
+{
+ if (in.cend() - it < min_av) {
+ throw std::runtime_error("DATA file too short");
+ }
+}
+
+u16 hp98x5_impl::get_u16(const std::vector<u8>& in, std::vector<u8>::const_iterator& it)
+{
+ check_av_data(in, it, 2);
+ auto tmp = r16b(it);
+ it += 2;
+ return tmp;
+}
+
+std::vector<u8> hp98x5_impl::convert_data_2_txt(const std::vector<u8>& in, unsigned bpr)
+{
+ auto it = in.cbegin();
+ std::vector<u8> accum;
+
+ u16 rec_type;
+ unsigned accum_len = 0;
+
+ do {
+ // Get record type
+ rec_type = get_u16(in, it);
+ switch (rec_type) {
+ case REC_TYPE_EOR:
+ // End of record: just skip it
+ break;
+
+ case REC_TYPE_FULLSTR:
+ // A string in a single piece
+ case REC_TYPE_1STSTR:
+ // First piece of a split string
+ case REC_TYPE_MIDSTR:
+ // Mid piece(s) of a split string
+ case REC_TYPE_ENDSTR: {
+ // Closing piece of a split string
+ if (((rec_type == REC_TYPE_FULLSTR || rec_type == REC_TYPE_1STSTR) && accum_len > 0) ||
+ ((rec_type == REC_TYPE_MIDSTR || rec_type == REC_TYPE_ENDSTR) && accum_len == 0)) {
+ throw std::runtime_error("Wrong sequence of string pieces");
+ }
+
+ unsigned tmp_len = (unsigned)get_u16(in, it);
+
+ if (rec_type == REC_TYPE_FULLSTR || rec_type == REC_TYPE_1STSTR) {
+ accum_len = tmp_len;
+ } else if (tmp_len != accum_len) {
+ throw std::runtime_error("Wrong length of string piece");
+ }
+
+ unsigned len_to_eor = bpr - (it - in.cbegin()) % bpr;
+ unsigned rec_len;
+ if (rec_type == REC_TYPE_FULLSTR || rec_type == REC_TYPE_ENDSTR) {
+ if (accum_len > len_to_eor) {
+ throw std::runtime_error("Wrong length of string piece");
+ }
+ rec_len = accum_len;
+ } else {
+ if (accum_len <= len_to_eor) {
+ throw std::runtime_error("Wrong length of string piece");
+ }
+ rec_len = len_to_eor;
+ }
+ check_av_data(in, it, rec_len);
+ // Sanitize and copy string
+ for (unsigned i = 0; i < rec_len; i++) {
+ u8 c = *it++;
+ if (c < 0x20 || c >= 0x7f) {
+ c = ' ';
+ }
+ accum.push_back(c);
+ }
+ if (rec_type == REC_TYPE_FULLSTR || rec_type == REC_TYPE_ENDSTR) {
+ // Record ended, add a \n to output
+ accum.push_back('\n');
+ }
+ if (rec_len & 1) {
+ // Keep length of string pieces even
+ it++;
+ }
+ accum_len -= rec_len;
+ break;
+ }
+
+ case REC_TYPE_EOF:
+ // End of file
+ break;
+
+ default:
+ throw std::runtime_error(util::string_format("Unknown record type (%04x)", rec_type));
+ }
+ } while (rec_type != REC_TYPE_EOF);
+
+ return accum;
+}
+
+void hp98x5_impl::split_string_n_dump(const std::vector<u8>& in, std::vector<u8>& out, unsigned bpr)
+{
+ auto it = in.cbegin();
+ auto out_ins = std::back_inserter(out);
+ u16 rec_type = REC_TYPE_1STSTR;
+
+ while (1) {
+ unsigned free_len = bpr - (out.size() % bpr);
+ if (free_len <= 4) {
+ // Not enough free space at end of current record: fill with EORs
+ while (free_len) {
+ w16b(out_ins, REC_TYPE_EOR);
+ free_len -= 2;
+ }
+ } else {
+ unsigned s_len = in.cend() - it;
+ unsigned s_part_len = std::min(free_len - 4 , s_len);
+ if (s_part_len == s_len) {
+ // Free space to EOR enough for what's left of string
+ break;
+ }
+ w16b(out_ins, rec_type);
+ w16b(out_ins, u16(s_len));
+ std::copy(it, it + s_part_len, out_ins);
+ it += s_part_len;
+ rec_type = REC_TYPE_MIDSTR;
+ }
+ }
+
+ w16b(out_ins, rec_type == REC_TYPE_MIDSTR ? REC_TYPE_ENDSTR : REC_TYPE_FULLSTR);
+ w16b(out_ins, u16(in.cend() - it));
+ std::copy(it, in.cend(), out_ins);
+ if (in.size() & 1) {
+ *out_ins = 0;
+ }
+}
+
+std::vector<u8> hp98x5_impl::convert_txt_2_data(const std::vector<u8>& in, unsigned bpr)
+{
+ std::vector<u8> res;
+
+ std::vector<u8> accum;
+ std::vector<u8> trailer;
+
+ // Split "in" into lines of text discarding trailing whitespace on each one
+ for (auto c : in) {
+ if (c == '\n') {
+ if (!accum.empty()) {
+ split_string_n_dump(accum, res, bpr);
+ }
+ accum.clear();
+ trailer.clear();
+ } else if (c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\f' || c == '\v') {
+ trailer.push_back(c);
+ } else {
+ if (!trailer.empty()) {
+ accum.insert(accum.end(), trailer.cbegin(), trailer.cend());
+ trailer.clear();
+ }
+ accum.push_back(c);
+ }
+ }
+
+ // Fill free space of last record with EOFs
+ auto out_ins = std::back_inserter(res);
+ for (unsigned pad_len = bpr - (res.size() % bpr); pad_len; pad_len -= 2) {
+ w16b(out_ins, REC_TYPE_EOF);
+ }
+
+ return res;
+}
+
+const std::array<u16, hp98x5_impl::FS_COUNT> hp98x5_impl::m_fmt_interleave = {
+ // ********
+ // * 9825 *
+ // ********
+ 2,
+ // ********
+ // * 9831 *
+ // ********
+ 0,
+ // ********
+ // * 9845 *
+ // ********
+ 7
+};
+
+// +--------------+
+// | hp9825_image |
+// +--------------+
+const char *hp9825_image::name() const
+{
+ return "hp9825";
+}
+
+const char *hp9825_image::description() const
+{
+ return "HP9825 FS";
+}
+
+void hp9825_image::enumerate_f(floppy_enumerator &fe) const
+{
+ fe.add(FLOPPY_HPI_FORMAT, floppy_image::FF_8, floppy_image::SSDD, SS_IMAGE_SIZE, "hp9825_ss", "HP9825 SS");
+ fe.add(FLOPPY_HPI_FORMAT, floppy_image::FF_8, floppy_image::DSDD, DS_IMAGE_SIZE, "hp9825_ds", "HP9825 DS");
+}
+
+bool hp9825_image::can_format() const
+{
+ return true;
+}
+
+bool hp9825_image::can_read() const
+{
+ return true;
+}
+
+bool hp9825_image::can_write() const
+{
+ return true;
+}
+
+bool hp9825_image::has_rsrc() const
+{
+ return false;
+}
+
+std::vector<meta_description> hp9825_image::volume_meta_description() const
+{
+ std::vector<meta_description> res;
+
+ return res;
+}
+
+std::vector<meta_description> hp9825_image::file_meta_description() const
+{
+ std::vector<meta_description> res;
+
+ res.emplace_back(meta_description(meta_name::name, "Empty", false, [](const meta_value &m) { return m.as_string().size() <= 6; }, "File name, up to 6 characters"));
+ res.emplace_back(meta_description(meta_name::length, 0, true, nullptr, "File size"));
+ res.emplace_back(meta_description(meta_name::file_type, "", true, nullptr, "File type"));
+ res.emplace_back(meta_description(meta_name::attributes, "", true, nullptr, "File attributes"));
+
+ return res;
+}
+
+std::unique_ptr<filesystem_t> hp9825_image::mount(fsblk_t &blockdev) const
+{
+ return std::make_unique<hp98x5_impl>(blockdev, hp98x5_impl::FS_TYPE_9825);
+}
+
+// +--------------+
+// | hp9831_image |
+// +--------------+
+const char *hp9831_image::name() const
+{
+ return "hp9831";
+}
+
+const char *hp9831_image::description() const
+{
+ return "HP9831 FS";
+}
+
+void hp9831_image::enumerate_f(floppy_enumerator &fe) const
+{
+ fe.add(FLOPPY_HPI_FORMAT, floppy_image::FF_8, floppy_image::SSDD, SS_IMAGE_SIZE, "hp9831_ss", "HP9831 SS");
+}
+
+bool hp9831_image::can_format() const
+{
+ return true;
+}
+
+bool hp9831_image::can_read() const
+{
+ return true;
+}
+
+bool hp9831_image::can_write() const
+{
+ return true;
+}
+
+bool hp9831_image::has_rsrc() const
+{
+ return false;
+}
+
+std::vector<meta_description> hp9831_image::volume_meta_description() const
+{
+ std::vector<meta_description> res;
+
+ return res;
+}
+
+std::vector<meta_description> hp9831_image::file_meta_description() const
+{
+ std::vector<meta_description> res;
+
+ res.emplace_back(meta_description(meta_name::name, "Empty", false, [](const meta_value &m) { return m.as_string().size() <= 6; }, "File name, up to 6 characters"));
+ res.emplace_back(meta_description(meta_name::length, 0, true, nullptr, "File size"));
+ res.emplace_back(meta_description(meta_name::file_type, "", true, nullptr, "File type"));
+
+ return res;
+}
+
+std::unique_ptr<filesystem_t> hp9831_image::mount(fsblk_t &blockdev) const
+{
+ return std::make_unique<hp98x5_impl>(blockdev, hp98x5_impl::FS_TYPE_9831);
+}
+
+// +--------------+
+// | hp9845_image |
+// +--------------+
+const char *hp9845_image::name() const
+{
+ return "hp9845";
+}
+
+const char *hp9845_image::description() const
+{
+ return "HP9845 FS";
+}
+
+void hp9845_image::enumerate_f(floppy_enumerator &fe) const
+{
+ fe.add(FLOPPY_HPI_FORMAT, floppy_image::FF_8, floppy_image::SSDD, SS_IMAGE_SIZE, "hp9845_ss", "HP9845 SS");
+ fe.add(FLOPPY_HPI_FORMAT, floppy_image::FF_8, floppy_image::DSDD, DS_IMAGE_SIZE, "hp9845_ds", "HP9845 DS");
+}
+
+bool hp9845_image::can_format() const
+{
+ return true;
+}
+
+bool hp9845_image::can_read() const
+{
+ return true;
+}
+
+bool hp9845_image::can_write() const
+{
+ return true;
+}
+
+bool hp9845_image::has_rsrc() const
+{
+ return false;
+}
+
+std::vector<meta_description> hp9845_image::volume_meta_description() const
+{
+ std::vector<meta_description> res;
+
+ return res;
+}
+
+std::vector<meta_description> hp9845_image::file_meta_description() const
+{
+ std::vector<meta_description> res;
+
+ res.emplace_back(meta_description(meta_name::name, "Empty", false, [](const meta_value &m) { return m.as_string().size() <= 6; }, "File name, up to 6 characters"));
+ res.emplace_back(meta_description(meta_name::length, 0, true, nullptr, "File size"));
+ res.emplace_back(meta_description(meta_name::file_type, "", true, nullptr, "File type"));
+ res.emplace_back(meta_description(meta_name::attributes, "", true, nullptr, "File attributes"));
+
+ return res;
+}
+
+std::unique_ptr<filesystem_t> hp9845_image::mount(fsblk_t &blockdev) const
+{
+ return std::make_unique<hp98x5_impl>(blockdev, hp98x5_impl::FS_TYPE_9845);
+}
diff --git a/src/lib/formats/fs_hp98x5.h b/src/lib/formats/fs_hp98x5.h
new file mode 100644
index 00000000000..e011a5cee48
--- /dev/null
+++ b/src/lib/formats/fs_hp98x5.h
@@ -0,0 +1,88 @@
+// license:BSD-3-Clause
+// copyright-holders: F. Ulivi
+/*********************************************************************
+
+ fs_hp98x5.h
+
+ HP 9825, 9831 & 9845 filesystem handling
+
+*********************************************************************/
+
+#ifndef MAME_FORMATS_FS_HP98X5_H
+#define MAME_FORMATS_FS_HP98X5_H
+
+#pragma once
+
+#include "fsmgr.h"
+
+namespace fs {
+
+ class hp9825_image : public manager_t {
+ public:
+ hp9825_image() : manager_t() {}
+
+ virtual const char *name() const override;
+ virtual const char *description() const override;
+
+ virtual void enumerate_f(floppy_enumerator &fe) const override;
+
+ virtual bool can_format() const override;
+ virtual bool can_read() const override;
+ virtual bool can_write() const override;
+ virtual bool has_rsrc() const override;
+
+ virtual std::vector<meta_description> volume_meta_description() const override;
+ virtual std::vector<meta_description> file_meta_description() const override;
+
+ virtual std::unique_ptr<filesystem_t> mount(fsblk_t &blockdev) const override;
+
+ };
+
+ class hp9831_image : public manager_t {
+ public:
+ hp9831_image() : manager_t() {}
+
+ virtual const char *name() const override;
+ virtual const char *description() const override;
+
+ virtual void enumerate_f(floppy_enumerator &fe) const override;
+
+ virtual bool can_format() const override;
+ virtual bool can_read() const override;
+ virtual bool can_write() const override;
+ virtual bool has_rsrc() const override;
+
+ virtual std::vector<meta_description> volume_meta_description() const override;
+ virtual std::vector<meta_description> file_meta_description() const override;
+
+ virtual std::unique_ptr<filesystem_t> mount(fsblk_t &blockdev) const override;
+
+ };
+
+ class hp9845_image : public manager_t {
+ public:
+ hp9845_image() : manager_t() {}
+
+ virtual const char *name() const override;
+ virtual const char *description() const override;
+
+ virtual void enumerate_f(floppy_enumerator &fe) const override;
+
+ virtual bool can_format() const override;
+ virtual bool can_read() const override;
+ virtual bool can_write() const override;
+ virtual bool has_rsrc() const override;
+
+ virtual std::vector<meta_description> volume_meta_description() const override;
+ virtual std::vector<meta_description> file_meta_description() const override;
+
+ virtual std::unique_ptr<filesystem_t> mount(fsblk_t &blockdev) const override;
+
+ };
+
+ extern const hp9825_image HP9825;
+ extern const hp9831_image HP9831;
+ extern const hp9845_image HP9845;
+} // namespace fs
+
+#endif /* MAME_FORMATS_FS_HP98X5_H */
diff --git a/src/lib/formats/hpi_dsk.cpp b/src/lib/formats/hpi_dsk.cpp
index bb881ab6f38..5678bd82d56 100644
--- a/src/lib/formats/hpi_dsk.cpp
+++ b/src/lib/formats/hpi_dsk.cpp
@@ -68,17 +68,10 @@ constexpr uint32_t DATA_CD_PATTERN = 0x55552a44; // C/D pattern of 0xff + DAT
constexpr unsigned GAP1_SIZE = 17; // Size of gap 1 (+1)
constexpr unsigned GAP2_SIZE = 35; // Size of gap 2 (+1)
constexpr int ID_DATA_OFFSET = 30 * 16; // Nominal distance (in cells) between ID & DATA AM
-// Size of image file (holding 77 cylinders)
-constexpr unsigned HPI_IMAGE_SIZE = HPI_TRACKS * HPI_HEADS * HPI_SECTORS * HPI_SECTOR_SIZE;
constexpr unsigned HPI_RED_TRACKS = 75; // Reduced number of tracks
-constexpr unsigned HPI_9885_TRACKS = 67; // Tracks visible to HP9885 drives
-// Size of reduced image file (holding 75 cylinders)
-constexpr unsigned HPI_RED_IMAGE_SIZE = HPI_RED_TRACKS * HPI_HEADS * HPI_SECTORS * HPI_SECTOR_SIZE;
hpi_format::hpi_format()
{
- (void)HPI_IMAGE_SIZE;
- (void)HPI_RED_IMAGE_SIZE;
}
int hpi_format::identify(util::random_read &io, uint32_t form_factor, const std::vector<uint32_t> &variants) const
diff --git a/src/lib/formats/hpi_dsk.h b/src/lib/formats/hpi_dsk.h
index bea2e24c99a..8db40c61dc7 100644
--- a/src/lib/formats/hpi_dsk.h
+++ b/src/lib/formats/hpi_dsk.h
@@ -17,15 +17,16 @@
#include <array>
#include <vector>
-// Geometry constants
-constexpr unsigned HPI_TRACKS = 77;
-constexpr unsigned HPI_HEADS = 2;
-constexpr unsigned HPI_SECTORS = 30;
-constexpr unsigned HPI_SECTOR_SIZE = 256;
-
class hpi_format : public floppy_image_format_t
{
public:
+ // Geometry constants
+ static constexpr unsigned HPI_TRACKS = 77;
+ static constexpr unsigned HPI_9885_TRACKS = 67; // Tracks visible to HP9885 drives
+ static constexpr unsigned HPI_HEADS = 2;
+ static constexpr unsigned HPI_SECTORS = 30;
+ static constexpr unsigned HPI_SECTOR_SIZE = 256;
+
hpi_format();
virtual int identify(util::random_read &io, uint32_t form_factor, const std::vector<uint32_t> &variants) const override;