diff options
Diffstat (limited to 'src/lib/formats/fs_coco_rsdos.cpp')
-rw-r--r-- | src/lib/formats/fs_coco_rsdos.cpp | 329 |
1 files changed, 328 insertions, 1 deletions
diff --git a/src/lib/formats/fs_coco_rsdos.cpp b/src/lib/formats/fs_coco_rsdos.cpp index 6028ded3467..8c23b09362e 100644 --- a/src/lib/formats/fs_coco_rsdos.cpp +++ b/src/lib/formats/fs_coco_rsdos.cpp @@ -28,7 +28,52 @@ public: coco_rsdos_impl(fsblk_t &blockdev); virtual ~coco_rsdos_impl() = default; + 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(coco_rsdos_impl &fs, const rsdos_dirent &dirent); + bool next(u8 &granule, u16 &byte_count); + + private: + fsblk_t::block_t m_granule_map; + std::optional<u8> m_current_granule; + u8 m_maximum_granules; + u16 m_last_sector_bytes; + std::bitset<256> m_visited_granules; + }; + + 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 std::pair<err_t, std::vector<u8>> file_read(const std::vector<std::string> &path) override; + virtual err_t format(const meta_data &meta) override; + static bool validate_filename(std::string_view name); + +private: + fsblk_t::block_t read_sector(int track, int sector) const; + u8 maximum_granules() const; + std::optional<rsdos_dirent> dirent_from_path(const std::vector<std::string> &path); + void iterate_directory_entries(const std::function<bool(const rsdos_dirent &dirent)> &callback); + meta_data get_metadata_from_dirent(const rsdos_dirent &dirent); + static std::string get_filename_from_dirent(const rsdos_dirent &dirent); }; } @@ -147,7 +192,7 @@ bool coco_rsdos_impl::validate_filename(std::string_view name) //------------------------------------------------- -// impl ctor +// coco_rsdos_impl ctor //------------------------------------------------- coco_rsdos_impl::coco_rsdos_impl(fsblk_t &blockdev) @@ -156,3 +201,285 @@ coco_rsdos_impl::coco_rsdos_impl(fsblk_t &blockdev) } +//------------------------------------------------- +// coco_rsdos_impl::metadata +//------------------------------------------------- + +std::pair<err_t, meta_data> coco_rsdos_impl::metadata(const std::vector<std::string> &path) +{ + // attempt to find the file + const std::optional<rsdos_dirent> dirent = dirent_from_path(path); + if (!dirent) + return std::make_pair(ERR_NOT_FOUND, meta_data()); + + return std::make_pair(ERR_OK, get_metadata_from_dirent(*dirent)); +} + + +//------------------------------------------------- +// coco_rsdos_impl::directory_contents +//------------------------------------------------- + +std::pair<err_t, std::vector<dir_entry>> coco_rsdos_impl::directory_contents(const std::vector<std::string> &path) +{ + std::vector<dir_entry> results; + auto callback = [this, &results](const rsdos_dirent &dirent) + { + results.emplace_back(dir_entry_type::file, get_metadata_from_dirent(dirent)); + return false; + }; + iterate_directory_entries(callback); + return std::make_pair(ERR_OK, std::move(results)); +} + + +//------------------------------------------------- +// coco_rsdos_impl::file_read +//------------------------------------------------- + +std::pair<err_t, std::vector<u8>> coco_rsdos_impl::file_read(const std::vector<std::string> &path) +{ + // attempt to find the file + const std::optional<rsdos_dirent> dirent = dirent_from_path(path); + if (!dirent) + return std::make_pair(ERR_NOT_FOUND, std::vector<u8>()); + + std::vector<u8> result; + u8 granule; + u16 byte_count; + granule_iterator iter(*this, *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 = 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 std::make_pair(ERR_OK, std::move(result)); +} + + +//------------------------------------------------- +// coco_rsdos_impl::format +//------------------------------------------------- + +err_t coco_rsdos_impl::format(const meta_data &meta) +{ + // formatting RS-DOS is easy - just fill everything with 0xFF + m_blockdev.fill(0xFF); + return ERR_OK; +} + + +//------------------------------------------------- +// fsblk_t::block_t coco_rsdos_impl::read_sector +//------------------------------------------------- + +fsblk_t::block_t coco_rsdos_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); +} + + +//------------------------------------------------- +// coco_rsdos_impl::maximum_granules +//------------------------------------------------- + +u8 coco_rsdos_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; +} + + +//------------------------------------------------- +// coco_rsdos_impl::dirent_from_path +//------------------------------------------------- + +std::optional<coco_rsdos_impl::rsdos_dirent> coco_rsdos_impl::dirent_from_path(const std::vector<std::string> &path) +{ + if (path.size() != 1) + return { }; + const std::string &target = path[0]; + + std::optional<rsdos_dirent> result; + auto callback = [&result, &target](const rsdos_dirent &dirent) + { + bool found = get_filename_from_dirent(dirent) == target; + if (found) + result = dirent; + return found; + }; + iterate_directory_entries(callback); + return result; +} + + +//------------------------------------------------- +// coco_rsdos_impl::iterate_directory_entries +//------------------------------------------------- + +void coco_rsdos_impl::iterate_directory_entries(const std::function<bool(const rsdos_dirent &dirent)> &callback) +{ + bool done = false; + for (int dir_sector = 3; !done && dir_sector <= 18; dir_sector++) + { + // read this directory sector + auto dir_block = read_sector(17, dir_sector); + const rsdos_dirent_sector §or = *reinterpret_cast<const rsdos_dirent_sector *>(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') + { + done = true; + } + else + { + // 0x00 marks a deleted file + if (ent.m_dirent.m_filename[0] != '\0') + done = callback(ent.m_dirent); + } + + if (done) + break; + } + } +} + + +//------------------------------------------------- +// coco_rsdos_impl::get_metadata_from_dirent +//------------------------------------------------- + +meta_data coco_rsdos_impl::get_metadata_from_dirent(const rsdos_dirent &dirent) +{ + 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(*this, dirent); + while (iter.next(granule, byte_count)) + { + granule_count++; + file_size += byte_count; + } + + // determine the file name + std::string name = get_filename_from_dirent(dirent); + + // 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' + dirent.m_asciiflag; + + // build the metadata and return it + meta_data result; + result.set(meta_name::name, std::move(name)); + result.set(meta_name::file_type, 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; +} + + +//------------------------------------------------- +// coco_rsdos_impl::get_filename_from_dirent +//------------------------------------------------- + +std::string coco_rsdos_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); +} + + +//------------------------------------------------- +// coco_rsdos_impl::granule_iterator ctor +//------------------------------------------------- + +coco_rsdos_impl::granule_iterator::granule_iterator(coco_rsdos_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) +{ + // visit the first granule + m_visited_granules.set(dirent.m_first_granule); +} + + +//------------------------------------------------- +// coco_rsdos_impl::granule_iterator::next +//------------------------------------------------- + +bool coco_rsdos_impl::granule_iterator::next(u8 &granule, u16 &byte_count) +{ + bool success = false; + granule = ~0; + byte_count = 0; + + if (m_current_granule) + { + std::optional<u8> next_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; + next_granule = granule_map_data[*m_current_granule]; + + // check for cycles, which should only happen if the disk is corrupt (or not in RS-DOS format) + if (m_visited_granules[*next_granule]) + next_granule = std::nullopt; // this is corrupt! + else + m_visited_granules.set(*next_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; + next_granule = std::nullopt; + } + else + { + // should not happen; but we'll treat this as an EOF + next_granule = std::nullopt; + } + m_current_granule = next_granule; + } + return success; +} + |