From ba6e0b72a7908309ce8c428ddbe59bf1f5f1ac52 Mon Sep 17 00:00:00 2001 From: Olivier Galibert Date: Sun, 2 May 2021 21:10:57 +0200 Subject: prodos: Start of read support --- src/lib/formats/fs_prodos.cpp | 111 +++++++++++++++++++++-- src/lib/formats/fs_prodos.h | 27 +++++- src/lib/formats/fsmgr.cpp | 9 +- src/lib/formats/fsmgr.h | 11 ++- src/lib/util/timeconv.cpp | 17 ++++ src/lib/util/timeconv.h | 2 + src/tools/floptool.cpp | 199 ++++++++++++++++++++++++++---------------- 7 files changed, 289 insertions(+), 87 deletions(-) diff --git a/src/lib/formats/fs_prodos.cpp b/src/lib/formats/fs_prodos.cpp index acf47607085..7089a1a6d3c 100644 --- a/src/lib/formats/fs_prodos.cpp +++ b/src/lib/formats/fs_prodos.cpp @@ -62,7 +62,7 @@ bool fs_prodos::can_format() const bool fs_prodos::can_read() const { - return false; + return true; } bool fs_prodos::can_write() const @@ -79,7 +79,12 @@ std::vector fs_prodos::volume_meta_description() const { std::vector res; res.emplace_back(fs_meta_description(fs_meta_name::name, fs_meta_type::string, "UNTITLED", false, [](const fs_meta &m) { std::string n = std::get(m); return n.size() <= 15; }, "Volume name, up to 15 characters")); + res.emplace_back(fs_meta_description(fs_meta_name::os_version, fs_meta_type::number, 5, false, [](const fs_meta &m) { return std::get(m) <= 255; }, "Creator OS version")); + res.emplace_back(fs_meta_description(fs_meta_name::os_minimum_version, fs_meta_type::number, 5, false, [](const fs_meta &m) { return std::get(m) <= 255; }, "Minimum OS version")); + auto now = util::arbitrary_datetime::now(); + res.emplace_back(fs_meta_description(fs_meta_name::creation_date, fs_meta_type::date, now, false, nullptr, "Creation time")); + res.emplace_back(fs_meta_description(fs_meta_name::modification_date, fs_meta_type::date, now, false, nullptr, "Modification time")); return res; } @@ -100,6 +105,10 @@ void fs_prodos::impl::format(const fs_meta_data &meta) std::string volume_name = std::get(meta.find(fs_meta_name::name)->second); u32 blocks = m_blockdev.block_count(); + // Maximum usable partition size = 32M - 512 bytes (65535 blocks) + if(blocks >= 0x10000) + blocks = 0xffff; + m_blockdev.get(0).copy(0x000, boot, 0x200); // Standard ProDOS boot sector as written by a 2gs m_blockdev.get(1).fill(0x00); // No SOS boot sector @@ -131,11 +140,101 @@ void fs_prodos::impl::format(const fs_meta_data &meta) kblk4.w16l(0x00, 0x0004); // Backwards block pointer of the fourth volume block kblk4.w16l(0x02, 0x0000); // Forwards block pointer of the fourth volume block (null) - auto fmap = m_blockdev.get(6); - u8 *fdata = fmap.data(); - // Mark blocks 7 to max as free - for(u32 i = 7; i != blocks; i++) - fdata[i >> 3] |= 0x80 >> (i & 7); + u32 fmap_block_count = (blocks + 4095) / 4096; + u32 first_free_block = 6 + fmap_block_count; + + // Mark blocks from first_free_block to blocks-1 (the last one) as free + for(u32 i = 0; i != fmap_block_count; i++) { + auto fmap = m_blockdev.get(6 + i); + u8 *fdata = fmap.data(); + u32 start = i ? 0 : first_free_block; + u32 end = i != fmap_block_count - 1 ? 4095 : (blocks - 1) & 4095; + end += 1; + u32 sb = start >> 3; + u32 si = start & 7; + u32 eb = end >> 3; + u32 ei = end & 7; + if(sb == eb) + fdata[sb] = (0xff >> si) & ~(0xff >> ei); + else { + fdata[sb] = 0xff >> si; + if(eb != 512) + fdata[eb] = ~(0xff >> ei); + if(eb - sb > 1) + memset(fdata+sb, 0xff, eb-sb-1); + } + } +} + +fs_prodos::impl::impl(fsblk_t &blockdev) : filesystem_t(blockdev, 512), m_root(true) +{ +} + +util::arbitrary_datetime fs_prodos::impl::prodos_to_dt(u32 date) +{ + util::arbitrary_datetime dt; + dt.second = 0; + dt.minute = ((date >> 16) & 0x3f); + dt.hour = ((date >> 24) & 0x1f); + dt.day_of_month = ((date >> 0) & 0x1f); + dt.month = ((date >> 5) & 0x0f) + 1; + dt.year = ((date >> 9) & 0x7f) + 1900; + if (dt.year <= 1949) + dt.year += 100; + + return dt; +} + +fs_meta_data fs_prodos::impl::metadata() +{ + fs_meta_data res; + auto bdir = m_blockdev.get(2); + int len = bdir.r8(0x04) & 0xf; + res[fs_meta_name::name] = bdir.rstr(0x05, len); + res[fs_meta_name::os_version] = uint64_t(bdir.r8(0x20)); + res[fs_meta_name::os_minimum_version] = uint64_t(bdir.r8(0x21)); + res[fs_meta_name::creation_date] = prodos_to_dt(bdir.r32l(0x1c)); + res[fs_meta_name::modification_date] = prodos_to_dt(bdir.r32l(0x16)); + return res; +} + +filesystem_t::dir_t fs_prodos::impl::root() +{ + if(!m_root) + m_root = new root_dir(*this); + return m_root.strong(); +} + +void fs_prodos::impl::drop_root_ref() +{ + m_root = nullptr; +} + + +void fs_prodos::impl::root_dir::drop_weak_references() +{ + m_fs.drop_root_ref(); +} + +fs_meta_data fs_prodos::impl::root_dir::metadata() +{ + return fs_meta_data(); +} + +std::vector fs_prodos::impl::root_dir::contents() +{ + std::vector res; + return res; +} + +filesystem_t::file_t fs_prodos::impl::root_dir::file_get(uint64_t key) +{ + return nullptr; +} + +filesystem_t::dir_t fs_prodos::impl::root_dir::dir_get(uint64_t key) +{ + return nullptr; } const filesystem_manager_type FS_PRODOS = &filesystem_manager_creator;; diff --git a/src/lib/formats/fs_prodos.h b/src/lib/formats/fs_prodos.h index 0d39251e2f2..d4b61f7d27e 100644 --- a/src/lib/formats/fs_prodos.h +++ b/src/lib/formats/fs_prodos.h @@ -14,13 +14,38 @@ class fs_prodos : public filesystem_manager_t { public: class impl : public filesystem_t { public: - impl(fsblk_t &blockdev) : filesystem_t(blockdev, 512) {} + 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 fs_meta_data metadata() override; + virtual std::vector contents() override; + virtual file_t file_get(uint64_t key) override; + virtual dir_t dir_get(uint64_t key) override; + + private: + impl &m_fs; + }; + + impl(fsblk_t &blockdev); virtual ~impl() = default; virtual void format(const fs_meta_data &meta) override; + virtual fs_meta_data metadata() override; + virtual dir_t root() override; + + void drop_root_ref(); + + static util::arbitrary_datetime prodos_to_dt(u32 date); + private: static const u8 boot[512]; + + dir_t m_root; }; fs_prodos() : filesystem_manager_t() {} diff --git a/src/lib/formats/fsmgr.cpp b/src/lib/formats/fsmgr.cpp index a16eb57b288..2bad6c00636 100644 --- a/src/lib/formats/fsmgr.cpp +++ b/src/lib/formats/fsmgr.cpp @@ -270,6 +270,8 @@ const char *fs_meta_get_name(fs_meta_name name) case fs_meta_name::modification_date: return "modification_date"; case fs_meta_name::name: return "name"; case fs_meta_name::size_in_blocks: return "size_in_blocks"; + case fs_meta_name::os_version: return "os_version"; + case fs_meta_name::os_minimum_version: return "os_minimum_version"; } return ""; } @@ -280,7 +282,12 @@ std::string fs_meta_to_string(fs_meta_type type, const fs_meta &m) case fs_meta_type::string: return std::get(m); case fs_meta_type::number: return util::string_format("0x%x", std::get(m)); case fs_meta_type::flag: return std::get(m) ? "t" : "f"; - case fs_meta_type::date: abort(); + case fs_meta_type::date: { + auto dt = std::get(m); + return util::string_format("%04d-%02d-%02d %02d:%02d:%02d", + dt.year, dt.month, dt.day_of_month, + dt.hour, dt.minute, dt.second); + } } return std::string(""); } diff --git a/src/lib/formats/fsmgr.h b/src/lib/formats/fsmgr.h index 305dd0fe92e..5dc328eb817 100644 --- a/src/lib/formats/fsmgr.h +++ b/src/lib/formats/fsmgr.h @@ -9,6 +9,7 @@ #pragma once #include "flopimg.h" +#include "timeconv.h" #include #include @@ -18,17 +19,19 @@ enum class fs_meta_name { length, loading_address, locked, - sequential, modification_date, name, + os_minimum_version, + os_version, + sequential, size_in_blocks, }; enum class fs_meta_type { - string, - number, date, flag, + number, + string, }; enum class fs_dir_entry_type { @@ -37,7 +40,7 @@ enum class fs_dir_entry_type { system_file, }; -using fs_meta = std::variant; +using fs_meta = std::variant; using fs_meta_data = std::unordered_map; const char *fs_meta_get_name(fs_meta_name name); diff --git a/src/lib/util/timeconv.cpp b/src/lib/util/timeconv.cpp index 20f4e29a03e..084e989b513 100644 --- a/src/lib/util/timeconv.cpp +++ b/src/lib/util/timeconv.cpp @@ -32,6 +32,23 @@ std::chrono::system_clock::duration system_clock_adjustment(calculate_system_clo IMPLEMENTATION ***************************************************************************/ +arbitrary_datetime arbitrary_datetime::now() +{ + time_t sec; + time(&sec); + auto t = *localtime(&sec); + + arbitrary_datetime dt; + dt.year = t.tm_year + 1900; + dt.month = t.tm_mon + 1; + dt.day_of_month = t.tm_mday; + dt.hour = t.tm_hour; + dt.minute = t.tm_min; + dt.second = t.tm_sec; + + return dt; +} + static std::chrono::system_clock::duration calculate_system_clock_adjustment() { constexpr auto days_in_year(365); diff --git a/src/lib/util/timeconv.h b/src/lib/util/timeconv.h index f1ed7d383e4..29ec6c23515 100644 --- a/src/lib/util/timeconv.h +++ b/src/lib/util/timeconv.h @@ -48,6 +48,8 @@ struct arbitrary_datetime int hour; // hour (0-23) int minute; // minute (0-59) int second; // second (0-59) + + static struct arbitrary_datetime now(); }; diff --git a/src/tools/floptool.cpp b/src/tools/floptool.cpp index bc86393dc5e..0204feb0607 100644 --- a/src/tools/floptool.cpp +++ b/src/tools/floptool.cpp @@ -1,12 +1,8 @@ // license:BSD-3-Clause -// copyright-holders:Miodrag Milanovic +// copyright-holders:Olivier Galibert /*************************************************************************** - main.c - - Floptool command line front end - - 20/07/2011 Initial version by Miodrag Milanovic + (Floppy) image command-line manager ***************************************************************************/ @@ -274,11 +270,11 @@ static const fs_info *find_fs_by_name(const char *name) static void display_usage() { fprintf(stderr, "Usage: \n"); - fprintf(stderr, " floptool.exe identify [ ...]\n"); - fprintf(stderr, " floptool.exe convert [input_format|auto] output_format \n"); - fprintf(stderr, " floptool.exe create output_format filesystem \n"); - fprintf(stderr, " floptool.exe dir input_format filesystem \n"); - fprintf(stderr, " floptool.exe read input_format filesystem \n"); + fprintf(stderr, " floptool.exe identify [ ...] -- Identify a floppy image format\n"); + fprintf(stderr, " floptool.exe convert [input_format|auto] output_format -- Convert a floppy image\n"); + fprintf(stderr, " floptool.exe flopcreate output_format filesystem -- Create a preformatted floppy image\n"); + fprintf(stderr, " floptool.exe flopdir input_format filesystem -- List the contents of a floppy image\n"); + fprintf(stderr, " floptool.exe flopread input_format filesystem -- Extract a file from a floppy image\n"); } static void display_formats() @@ -295,7 +291,7 @@ static void display_formats() sk = sz; } - fprintf(stderr, "Supported formats:\n\n"); + fprintf(stderr, "Supported floppy formats:\n\n"); for(const auto &e : formats_by_category) if(!e.second.empty()) { fprintf(stderr, "%s:\n", e.first.c_str()); @@ -304,7 +300,7 @@ static void display_formats() } fprintf(stderr, "\n\n"); - fprintf(stderr, "Supported filesystems:\n\n"); + fprintf(stderr, "Supported floppy filesystems:\n\n"); for(const auto &e : fs_by_category) if(!e.second.empty()) { fprintf(stderr, "%s:\n", e.first.c_str()); @@ -548,7 +544,63 @@ static void dir_scan(u32 depth, filesystem_t::dir_t dir, std::vectormount(blockdev); + auto vmetad = fm->volume_meta_description(); + auto fmetad = fm->file_meta_description(); + auto dmetad = fm->directory_meta_description(); + + auto vmeta = load_fs->metadata(); + if(!vmeta.empty()) { + std::string vinf = "Volume:"; + for(const auto &e : vmetad) + vinf += util::string_format(" %s=%s", fs_meta_get_name(e.m_name), fs_meta_to_string(e.m_type, vmeta[e.m_name])); + printf("%s\n\n", vinf.c_str()); + } + + std::vector names; + names.push_back(fs_meta_name::name); + for(const auto &e : fmetad) + if(e.m_name != fs_meta_name::name) + names.push_back(e.m_name); + for(const auto &e : dmetad) + if(std::find(names.begin(), names.end(), e.m_name) == names.end()) + names.push_back(e.m_name); + + std::unordered_map nmap; + for(size_t i = 0; i != names.size(); i++) + nmap[names[i]] = i; + + auto root = load_fs->root(); + std::vector> entries; + + entries.resize(1); + for(fs_meta_name n : names) + entries[0].push_back(fs_meta_get_name(n)); + + dir_scan(0, root, entries, nmap, names.size(), dmetad, fmetad); + + std::vector sizes(names.size()); + + for(const auto &e : entries) + for(unsigned int i=0; i != names.size(); i++) + sizes[i] = std::max(sizes[i], e[i].size()); + + for(const auto &e : entries) { + std::string l; + for(unsigned int i=0; i != names.size(); i++) { + if(i) + l += ' '; + l += util::string_format("%-*s", sizes[i], e[i]); + } + printf("%s\n", l.c_str()); + } + + return 0; +} + +static int flopdir(int argc, char *argv[]) { if (argc!=5) { fprintf(stderr, "Incorrect number of arguments.\n\n"); @@ -599,62 +651,52 @@ static int dir(int argc, char *argv[]) delete iog; fsblk_vec_t blockdev(img); - auto load_fs = fs->m_manager->mount(blockdev); - auto vmetad = fs->m_manager->volume_meta_description(); - auto fmetad = fs->m_manager->file_meta_description(); - auto dmetad = fs->m_manager->directory_meta_description(); - - auto vmeta = load_fs->metadata(); - if(!vmeta.empty()) { - std::string vinf = "Volume:"; - for(const auto &e : vmetad) - vinf += util::string_format(" %s=%s", fs_meta_get_name(e.m_name), fs_meta_to_string(e.m_type, vmeta[e.m_name])); - printf("%s\n\n", vinf.c_str()); - } - - std::vector names; - names.push_back(fs_meta_name::name); - for(const auto &e : fmetad) - if(e.m_name != fs_meta_name::name) - names.push_back(e.m_name); - for(const auto &e : dmetad) - if(std::find(names.begin(), names.end(), e.m_name) == names.end()) - names.push_back(e.m_name); - - std::unordered_map nmap; - for(size_t i = 0; i != names.size(); i++) - nmap[names[i]] = i; + return generic_dir(fs->m_manager, blockdev); - auto root = load_fs->root(); - std::vector> entries; + return 0; +} - entries.resize(1); - for(fs_meta_name n : names) - entries[0].push_back(fs_meta_get_name(n)); +// Should use chd&friends instead, but one thing at a time - dir_scan(0, root, entries, nmap, names.size(), dmetad, fmetad); +static int hddir(int argc, char *argv[]) +{ + if (argc!=4) { + fprintf(stderr, "Incorrect number of arguments.\n\n"); + display_usage(); + return 1; + } - std::vector sizes(names.size()); + auto fs = find_fs_by_name(argv[2]); + if(!fs) { + fprintf(stderr, "Error: Filesystem '%s' unknown\n", argv[2]); + return 1; + } - for(const auto &e : entries) - for(unsigned int i=0; i != names.size(); i++) - sizes[i] = std::max(sizes[i], e[i].size()); + if(!fs->m_manager || !fs->m_manager->can_read()) { + fprintf(stderr, "Error: Filesystem '%s' does not implement reading\n", argv[2]); + return 1; + } - for(const auto &e : entries) { - std::string l; - for(unsigned int i=0; i != names.size(); i++) { - if(i) - l += ' '; - l += util::string_format("%-*s", sizes[i], e[i]); - } - printf("%s\n", l.c_str()); + char msg[4096]; + sprintf(msg, "Error opening %s for reading", argv[3]); + FILE *f = fopen(argv[3], "rb"); + if (!f) { + perror(msg); + return 1; } - - return 0; + fseek(f, 0, SEEK_END); + size_t size = ftell(f); + rewind(f); + std::vector img(size); + fread(img.data(), size, 1, f); + fclose(f); + + fsblk_vec_t blockdev(img); + return generic_dir(fs->m_manager, blockdev); } -static int doread(int argc, char *argv[]) +static int flopread(int argc, char *argv[]) { if (argc!=7) { fprintf(stderr, "Incorrect number of arguments.\n\n"); @@ -664,13 +706,13 @@ static int doread(int argc, char *argv[]) auto format = find_format_by_name(argv[2]); if(!format) { - fprintf(stderr, "Error: Format '%s' unknown\n", argv[3]); + fprintf(stderr, "Error: Format '%s' unknown\n", argv[2]); return 1; } auto fs = find_fs_by_name(argv[3]); if(!fs) { - fprintf(stderr, "Error: Filesystem '%s' unknown\n", argv[2]); + fprintf(stderr, "Error: Filesystem '%s' unknown\n", argv[3]); return 1; } @@ -777,19 +819,26 @@ int CLIB_DECL main(int argc, char *argv[]) return 0; } - if (!core_stricmp("identify", argv[1])) - return identify(argc, argv); - else if (!core_stricmp("convert", argv[1])) - return convert(argc, argv); - else if (!core_stricmp("create", argv[1])) - return create(argc, argv); - else if (!core_stricmp("dir", argv[1])) - return dir(argc, argv); - else if (!core_stricmp("read", argv[1])) - return doread(argc, argv); - else { - fprintf(stderr, "Unknown command '%s'\n\n", argv[1]); - display_usage(); + try { + if (!core_stricmp("identify", argv[1])) + return identify(argc, argv); + else if (!core_stricmp("convert", argv[1])) + return convert(argc, argv); + else if (!core_stricmp("create", argv[1])) + return create(argc, argv); + else if (!core_stricmp("flopdir", argv[1])) + return flopdir(argc, argv); + else if (!core_stricmp("flopread", argv[1])) + return flopread(argc, argv); + else if (!core_stricmp("hddir", argv[1])) + return hddir(argc, argv); + else { + fprintf(stderr, "Unknown command '%s'\n\n", argv[1]); + display_usage(); + return 1; + } + } catch(const emu_fatalerror &err) { + fprintf(stderr, "Error: %s", err.what()); return 1; } } -- cgit v1.2.3