diff --git a/scripts/src/formats.lua b/scripts/src/formats.lua index 0c017edf693..686c5c32e9f 100644 --- a/scripts/src/formats.lua +++ b/scripts/src/formats.lua @@ -563,6 +563,18 @@ if opt_tool(FORMATS, "COCO_CAS") then } end +-------------------------------------------------- +-- +--@src/lib/formats/coco_rawdsk.h,FORMATS["COCO_RAWDSK"] = true +-------------------------------------------------- + +if opt_tool(FORMATS, "COCO_RAWDSK") then + files { + MAME_DIR.. "src/lib/formats/coco_rawdsk.cpp", + MAME_DIR.. "src/lib/formats/coco_rawdsk.h", + } +end + -------------------------------------------------- -- --@src/lib/formats/comx35_dsk.h,FORMATS["COMX35_DSK"] = true @@ -2100,4 +2112,16 @@ if opt_tool(FORMATS, "FS_ORIC_JASMIN") then } end +-------------------------------------------------- +-- +--@src/lib/formats/fs_coco_rsdos.h,FORMATS["FS_COCO_RSDOS"] = true +-------------------------------------------------- + +if opt_tool(FORMATS, "FS_COCO_RSDOS") then + files { + MAME_DIR.. "src/lib/formats/fs_coco_rsdos.cpp", + MAME_DIR.. "src/lib/formats/fs_coco_rsdos.h", + } +end + end diff --git a/scripts/target/mame/mess.lua b/scripts/target/mame/mess.lua index ed67c12d482..db3b3f69b86 100644 --- a/scripts/target/mame/mess.lua +++ b/scripts/target/mame/mess.lua @@ -1184,6 +1184,7 @@ FORMATS["XDF_DSK"] = true FORMATS["ZX81_P"] = true FORMATS["FS_PRODOS"] = true FORMATS["FS_ORIC_JASMIN"] = true +FORMATS["FS_COCO_RSDOS"] = true -------------------------------------------------- -- this is the list of driver libraries that diff --git a/src/lib/formats/all.cpp b/src/lib/formats/all.cpp index 7be97cec4f7..692977d4d03 100644 --- a/src/lib/formats/all.cpp +++ b/src/lib/formats/all.cpp @@ -356,6 +356,14 @@ #include "os9_dsk.h" #endif +#ifdef HAS_FORMATS_COCO_RAWDSK +#include "coco_rawdsk.h" +#endif + +#ifdef HAS_FORMATS_FS_COCO_RSDOS +#include "fs_coco_rsdos.h" +#endif + #ifdef HAS_FORMATS_JFD_DSK #include "jfd_dsk.h" #endif @@ -1071,6 +1079,12 @@ void mame_formats_full_list(mame_formats_enumerator &en) en.add(FLOPPY_JV1_FORMAT); // trs80_dsk.h en.add(FLOPPY_JV3_FORMAT); // trs80_dsk.h #endif +#ifdef HAS_FORMATS_COCO_RAWDSK + en.add(FLOPPY_COCO_RAWDSK_FORMAT); // coco_rawdsk.h +#endif +#ifdef HAS_FORMATS_FS_COCO_RSDOS + en.add(fs::COCO_RSDOS); // fs_coco_rsdos.h +#endif en.category("Kaypro"); #ifdef HAS_FORMATS_KAYPRO_DSK diff --git a/src/lib/formats/coco_rawdsk.cpp b/src/lib/formats/coco_rawdsk.cpp new file mode 100644 index 00000000000..6100483dda3 --- /dev/null +++ b/src/lib/formats/coco_rawdsk.cpp @@ -0,0 +1,45 @@ +// license:BSD-3-Clause +// copyright-holders:Nathan Woods +/*************************************************************************** + + CoCo Raw Disk + +***************************************************************************/ + +#include "coco_rawdsk.h" + + +coco_rawdsk_format::coco_rawdsk_format() : wd177x_format(formats) +{ +} + +const char *coco_rawdsk_format::name() const +{ + return "coco_rawdsk"; +} + +const char *coco_rawdsk_format::description() const +{ + return "CoCo Raw Disk"; +} + +const char *coco_rawdsk_format::extensions() const +{ + return "raw"; +} + +const coco_rawdsk_format::format coco_rawdsk_format::formats[] = +{ + { + floppy_image::FF_525, floppy_image::SSDD, floppy_image::MFM, + 2000, 18, 35, 1, 256, {}, -1, { 1,12,5,16,9,2,13,6,17,10,3,14,7,18,11,4,15,8 }, 32, 22, 24 + }, + { + floppy_image::FF_525, floppy_image::SSDD, floppy_image::MFM, + 2000, 18, 40, 1, 256, {}, -1, { 1,12,5,16,9,2,13,6,17,10,3,14,7,18,11,4,15,8 }, 32, 22, 24 + }, + {} +}; + + +const floppy_format_type FLOPPY_COCO_RAWDSK_FORMAT = &floppy_image_format_creator; diff --git a/src/lib/formats/coco_rawdsk.h b/src/lib/formats/coco_rawdsk.h new file mode 100644 index 00000000000..035d941a7b2 --- /dev/null +++ b/src/lib/formats/coco_rawdsk.h @@ -0,0 +1,33 @@ +// license:BSD-3-Clause +// copyright-holders:Nathan Woods +/*************************************************************************** + + CoCo Raw Disk + +***************************************************************************/ + +#ifndef MAME_FORMATS_COCO_RAWDSK_H +#define MAME_FORMATS_COCO_RAWDSK_H + +#pragma once + +#include "wd177x_dsk.h" + + +class coco_rawdsk_format : public wd177x_format +{ +public: + coco_rawdsk_format(); + + virtual const char *name() const override; + virtual const char *description() const override; + virtual const char *extensions() const override; + +private: + static const format formats[]; +}; + + +extern const floppy_format_type FLOPPY_COCO_RAWDSK_FORMAT; + +#endif // MAME_FORMATS_COCO_RAWDSK_H diff --git a/src/lib/formats/fs_coco_rsdos.cpp b/src/lib/formats/fs_coco_rsdos.cpp new file mode 100644 index 00000000000..99c36bb97e6 --- /dev/null +++ b/src/lib/formats/fs_coco_rsdos.cpp @@ -0,0 +1,297 @@ +// license:BSD-3-Clause +// copyright-holders:Nathan Woods +/*************************************************************************** + + fs_coco_rsdos.cpp + + Management of CoCo "RS-DOS" floppy images + +***************************************************************************/ + +#include "fs_coco_rsdos.h" +#include "coco_rawdsk.h" +#include "util/corestr.h" +#include "util/strformat.h" + +#include +#include + +namespace fs { + +const coco_rsdos_image COCO_RSDOS; + +const char *coco_rsdos_image::name() const +{ + return "coco_rsdos"; +} + +const char *coco_rsdos_image::description() const +{ + return "CoCo RS-DOS"; +} + +void coco_rsdos_image::enumerate_f(floppy_enumerator &fe, u32 form_factor, const std::vector &variants) const +{ + if (has(form_factor, variants, floppy_image::FF_525, floppy_image::SSDD)) + { + fe.add(FLOPPY_COCO_RAWDSK_FORMAT, 161280, "coco_rawdsk_rsdos_35", "CoCo Raw Disk RS-DOS single-sided 35 tracks"); + fe.add(FLOPPY_COCO_RAWDSK_FORMAT, 184320, "coco_rawdsk_rsdos_40", "CoCo Raw Disk RS-DOS single-sided 40 tracks"); + } +} + +bool coco_rsdos_image::can_format() const +{ + return false; +} + +bool coco_rsdos_image::can_read() const +{ + return true; +} + +bool coco_rsdos_image::can_write() const +{ + return false; +} + +bool coco_rsdos_image::has_rsrc() const +{ + return false; +} + +std::vector coco_rsdos_image::file_meta_description() const +{ + std::vector results; + results.emplace_back(meta_description(meta_name::name, meta_type::string, "", false, [](const meta_value &m) { return validate_filename(m.as_string()); }, "File name, 8.3")); + results.emplace_back(meta_description(meta_name::file_type, meta_type::number, 0, true, nullptr, "Type of the file")); + results.emplace_back(meta_description(meta_name::ascii_flag, meta_type::string, 0, true, nullptr, "Ascii or binary flag")); + results.emplace_back(meta_description(meta_name::size_in_blocks, meta_type::number, 0, true, nullptr, "Number of granules used by the file")); + results.emplace_back(meta_description(meta_name::length, meta_type::number, 0, true, nullptr, "Size of the file in bytes")); + return results; +} + +std::unique_ptr coco_rsdos_image::mount(fsblk_t &blockdev) const +{ + return std::make_unique(blockdev); +} + +bool coco_rsdos_image::validate_filename(std::string_view name) +{ + auto pos = name.find('.'); + auto stem_length = pos != std::string::npos ? pos : name.size(); + auto ext_length = pos != std::string::npos ? name.size() - pos - 1 : 0; + return stem_length > 0 && stem_length <= 8 && ext_length <= 3; +} + +coco_rsdos_image::impl::impl(fsblk_t &blockdev) + : filesystem_t(blockdev, 256) +{ +} + +meta_data coco_rsdos_image::impl::metadata() +{ + return meta_data(); +} + +filesystem_t::dir_t coco_rsdos_image::impl::root() +{ + if (!m_root) + m_root = new root_dir(*this); + return m_root.strong(); +} + +void coco_rsdos_image::impl::drop_root_ref() +{ + m_root = nullptr; +} + +fsblk_t::block_t coco_rsdos_image::impl::read_sector(int track, int sector) const +{ + // the CoCo RS-DOS world thinks in terms of tracks/sectors, but we have a block device + // abstraction + return m_blockdev.get(track * 18 + sector - 1); +} + +u8 coco_rsdos_image::impl::maximum_granules() const +{ + u32 sector_count = m_blockdev.block_count(); + u32 granule_count = (sector_count / 9) - 2; + return granule_count <= 0xFF ? (u8)granule_count : 0xFF; +} + +std::string coco_rsdos_image::impl::get_filename_from_dirent(const rsdos_dirent &dirent) +{ + std::string_view stem = strtrimrightspace(std::string_view(&dirent.m_filename[0], 8)); + std::string_view ext = strtrimrightspace(std::string_view(&dirent.m_filename[8], 3)); + return util::string_format("%s.%s", stem, ext); +} + +void coco_rsdos_image::impl::root_dir::drop_weak_references() +{ + m_fs.drop_root_ref(); +} + +meta_data coco_rsdos_image::impl::root_dir::metadata() +{ + return meta_data(); +} + +std::vector coco_rsdos_image::impl::root_dir::contents() +{ + u64 key = 0; + std::vector results; + for (int dir_sector = 3; dir_sector <= 18; dir_sector++) + { + // read this directory sector + auto dir_block = m_fs.read_sector(17, dir_sector); + const rsdos_dirent_sector §or = *reinterpret_cast(dir_block.rodata()); + + // and loop through all entries + for (const auto &ent : sector.m_entries) + { + // 0xFF marks the end of the directory + if (ent.m_dirent.m_filename[0] == '\xFF') + return results; + + // 0x00 marks a deleted file + if (ent.m_dirent.m_filename[0] != '\0') + results.emplace_back(get_filename_from_dirent(ent.m_dirent), dir_entry_type::file, key); + + key++; + } + } + return results; +} + +filesystem_t::file_t coco_rsdos_image::impl::root_dir::file_get(u64 key) +{ + auto dir_block = m_fs.read_sector(17, 3 + key / 4); + const rsdos_dirent_sector §or = *reinterpret_cast(dir_block.rodata()); + const rsdos_dirent &ent = sector.m_entries[key % 4].m_dirent; + return file_t(new file(m_fs, rsdos_dirent(ent))); +} + +filesystem_t::dir_t coco_rsdos_image::impl::root_dir::dir_get(u64 key) +{ + throw std::logic_error("Directories not supported"); +} + +coco_rsdos_image::impl::granule_iterator::granule_iterator(impl &fs, const rsdos_dirent &dirent) + : m_granule_map(fs.read_sector(17, 2)) + , m_current_granule(dirent.m_first_granule) + , m_maximum_granules(fs.maximum_granules()) + , m_last_sector_bytes(((u16) dirent.m_last_sector_bytes_msb << 8) | dirent.m_last_sector_bytes_lsb) +{ +} + +bool coco_rsdos_image::impl::granule_iterator::next(u8 &granule, u16 &byte_count) +{ + bool success = false; + granule = ~0; + byte_count = 0; + + if (m_current_granule) + { + const u8 *granule_map_data = m_granule_map.rodata(); + if (granule_map_data[*m_current_granule] < m_maximum_granules) + { + // this entry points to the next granule + success = true; + granule = *m_current_granule; + byte_count = 9 * 256; + m_current_granule = granule_map_data[*m_current_granule]; + } + else if (granule_map_data[*m_current_granule] >= 0xC0 && granule_map_data[*m_current_granule] <= 0xC9) + { + // this is the last granule in the file + success = true; + granule = *m_current_granule; + u16 sector_count = std::max(granule_map_data[*m_current_granule], (u8)0xC1) - 0xC1; + byte_count = sector_count * 256 + m_last_sector_bytes; + m_current_granule = std::nullopt; + } + else + { + // should not happen; but we'll treat this as an EOF + m_current_granule = std::nullopt; + } + } + return success; +} + +coco_rsdos_image::impl::file::file(impl &fs, rsdos_dirent &&dirent) + : m_fs(fs) + , m_dirent(std::move(dirent)) +{ +} + +void coco_rsdos_image::impl::file::drop_weak_references() +{ +} + +meta_data coco_rsdos_image::impl::file::metadata() +{ + u32 file_size = 0; + int granule_count = 0; + + // we need to iterate on the file to determine the size and granule/block count + u8 granule; + u16 byte_count; + granule_iterator iter(m_fs, m_dirent); + while (iter.next(granule, byte_count)) + { + granule_count++; + file_size += byte_count; + } + + // turn the ASCII flag to a single character (this reflects what the user sees when doing a directory listing on a real CoCo) + char file_type_char = 'B' + m_dirent.m_asciiflag; + + // build the metadata and return it + meta_data result; + result.set(meta_name::name, get_filename_from_dirent(m_dirent)); + result.set(meta_name::file_type, m_dirent.m_filetype); + result.set(meta_name::ascii_flag, std::string(1, file_type_char)); + result.set(meta_name::size_in_blocks, granule_count); + result.set(meta_name::length, file_size); + return result; +} + +std::vector coco_rsdos_image::impl::file::read_all() +{ + std::vector result; + + u8 granule; + u16 byte_count; + granule_iterator iter(m_fs, m_dirent); + while (iter.next(granule, byte_count)) + { + // resize the results + size_t current_size = result.size(); + result.resize(current_size + byte_count); + + // determine which track and sector this granule starts at + int track = granule / 2 + (granule >= 34 ? 1 : 0); + int sector = granule % 2 * 9 + 1; + + // and read all the sectors + while (byte_count > 0) + { + // read this sector + auto block = m_fs.read_sector(track, sector); + const u8 *data = block.rodata(); + u16 data_length = std::min(byte_count, (u16)256); + + // and append it to the results + memcpy(result.data() + current_size, data, data_length); + + // and advance + current_size += data_length; + byte_count -= data_length; + sector++; + } + } + return result; +} + +} // namespace fs diff --git a/src/lib/formats/fs_coco_rsdos.h b/src/lib/formats/fs_coco_rsdos.h new file mode 100644 index 00000000000..e02a5a831f4 --- /dev/null +++ b/src/lib/formats/fs_coco_rsdos.h @@ -0,0 +1,125 @@ +// license:BSD-3-Clause +// copyright-holders:Nathan Woods +/*************************************************************************** + + fs_coco_rsdos.h + + Management of CoCo "RS-DOS" floppy images + +***************************************************************************/ + +#ifndef MAME_FORMATS_FS_COCO_RSDOS_H +#define MAME_FORMATS_FS_COCO_RSDOS_H + +#pragma once + +#include "fsmgr.h" +#include +#include + +namespace fs { + +class coco_rsdos_image : public manager_t { +public: + coco_rsdos_image() : manager_t() {} + + virtual const char *name() const override; + virtual const char *description() const override; + + virtual void enumerate_f(floppy_enumerator &fe, u32 form_factor, const std::vector &variants) const override; + virtual std::unique_ptr mount(fsblk_t &blockdev) 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 file_meta_description() const override; + +private: + class impl : public filesystem_t { + public: + class root_dir : public idir_t { + public: + root_dir(impl &i) : m_fs(i) {} + virtual ~root_dir() = default; + + virtual void drop_weak_references() override; + virtual meta_data metadata() override; + virtual std::vector contents() override; + virtual file_t file_get(u64 key) override; + virtual dir_t dir_get(u64 key) override; + + private: + impl &m_fs; + }; + + struct rsdos_dirent + { + char m_filename[11]; + u8 m_filetype; + u8 m_asciiflag; + u8 m_first_granule; + u8 m_last_sector_bytes_msb; + u8 m_last_sector_bytes_lsb; + }; + + struct rsdos_dirent_sector + { + struct + { + rsdos_dirent m_dirent; + u8 m_unused[16]; + } m_entries[4]; + }; + + class granule_iterator { + public: + granule_iterator(impl &fs, const rsdos_dirent &dirent); + bool next(u8 &granule, u16 &byte_count); + + private: + fsblk_t::block_t m_granule_map; + std::optional m_current_granule; + u8 m_maximum_granules; + u16 m_last_sector_bytes; + }; + + class file : public ifile_t { + public: + file(impl &fs, rsdos_dirent &&dirent); + virtual ~file() = default; + + virtual void drop_weak_references() override; + + virtual meta_data metadata() override; + virtual std::vector read_all() override; + + private: + impl & m_fs; + rsdos_dirent m_dirent; + }; + + impl(fsblk_t &blockdev); + virtual ~impl() = default; + + virtual meta_data metadata() override; + virtual dir_t root() override; + + private: + dir_t m_root; + + void drop_root_ref(); + fsblk_t::block_t read_sector(int track, int sector) const; + u8 maximum_granules() const; + static std::string get_filename_from_dirent(const rsdos_dirent &dirent); + }; + + static bool validate_filename(std::string_view name); +}; + +extern const coco_rsdos_image COCO_RSDOS; + +} // namespace fs + +#endif // MAME_FORMATS_FS_COCO_RSDOS_H diff --git a/src/lib/formats/fsmeta.cpp b/src/lib/formats/fsmeta.cpp index c5675d67a04..0159269a0d6 100644 --- a/src/lib/formats/fsmeta.cpp +++ b/src/lib/formats/fsmeta.cpp @@ -24,6 +24,8 @@ const char *meta_data::entry_name(meta_name name) case meta_name::rsrc_length: return "rsrc_length"; case meta_name::sequential: return "sequential"; case meta_name::size_in_blocks: return "size_in_blocks"; + case meta_name::file_type: return "file_type"; + case meta_name::ascii_flag: return "ascii_flag"; } return ""; } diff --git a/src/lib/formats/fsmeta.h b/src/lib/formats/fsmeta.h index dad70d86b6d..948df0c1f06 100644 --- a/src/lib/formats/fsmeta.h +++ b/src/lib/formats/fsmeta.h @@ -31,6 +31,8 @@ enum class meta_name { rsrc_length, sequential, size_in_blocks, + file_type, + ascii_flag }; enum class meta_type {