summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author Olivier Galibert <galibert@pobox.com>2021-05-02 21:10:57 +0200
committer Olivier Galibert <galibert@pobox.com>2021-05-02 21:24:45 +0200
commitba6e0b72a7908309ce8c428ddbe59bf1f5f1ac52 (patch)
tree29b40fe78b76ff5e1dd85bdfcfb1a23602931171
parent0a8757673ff571dd6e47d9ee5b95c3745078b2db (diff)
prodos: Start of read support
-rw-r--r--src/lib/formats/fs_prodos.cpp111
-rw-r--r--src/lib/formats/fs_prodos.h27
-rw-r--r--src/lib/formats/fsmgr.cpp9
-rw-r--r--src/lib/formats/fsmgr.h11
-rw-r--r--src/lib/util/timeconv.cpp17
-rw-r--r--src/lib/util/timeconv.h2
-rw-r--r--src/tools/floptool.cpp199
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_meta_description> fs_prodos::volume_meta_description() const
{
std::vector<fs_meta_description> 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<std::string>(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<uint64_t>(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<uint64_t>(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<std::string>(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_dir_entry> fs_prodos::impl::root_dir::contents()
+{
+ std::vector<fs_dir_entry> 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<fs_prodos>;;
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<fs_dir_entry> 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<std::string>(m);
case fs_meta_type::number: return util::string_format("0x%x", std::get<uint64_t>(m));
case fs_meta_type::flag: return std::get<bool>(m) ? "t" : "f";
- case fs_meta_type::date: abort();
+ case fs_meta_type::date: {
+ auto dt = std::get<util::arbitrary_datetime>(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 <variant>
#include <unordered_map>
@@ -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<std::string, uint64_t, bool>;
+using fs_meta = std::variant<std::string, uint64_t, bool, util::arbitrary_datetime>;
using fs_meta_data = std::unordered_map<fs_meta_name, fs_meta>;
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 <inputfile> [<inputfile> ...]\n");
- fprintf(stderr, " floptool.exe convert [input_format|auto] output_format <inputfile> <outputfile>\n");
- fprintf(stderr, " floptool.exe create output_format filesystem <outputfile>\n");
- fprintf(stderr, " floptool.exe dir input_format filesystem <image>\n");
- fprintf(stderr, " floptool.exe read input_format filesystem <image> <path> <outputfile>\n");
+ fprintf(stderr, " floptool.exe identify <inputfile> [<inputfile> ...] -- Identify a floppy image format\n");
+ fprintf(stderr, " floptool.exe convert [input_format|auto] output_format <inputfile> <outputfile> -- Convert a floppy image\n");
+ fprintf(stderr, " floptool.exe flopcreate output_format filesystem <outputfile> -- Create a preformatted floppy image\n");
+ fprintf(stderr, " floptool.exe flopdir input_format filesystem <image> -- List the contents of a floppy image\n");
+ fprintf(stderr, " floptool.exe flopread input_format filesystem <image> <path> <outputfile> -- 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::vector<std::vector
}
}
-static int dir(int argc, char *argv[])
+static int generic_dir(const filesystem_manager_t *fm, fsblk_t &blockdev)
+{
+ auto load_fs = fm->mount(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<fs_meta_name> 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<fs_meta_name, size_t> nmap;
+ for(size_t i = 0; i != names.size(); i++)
+ nmap[names[i]] = i;
+
+ auto root = load_fs->root();
+ std::vector<std::vector<std::string>> 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<u32> sizes(names.size());
+
+ for(const auto &e : entries)
+ for(unsigned int i=0; i != names.size(); i++)
+ sizes[i] = std::max<u32>(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<fs_meta_name> 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<fs_meta_name, size_t> 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<std::vector<std::string>> 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<u32> 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<u32>(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<u8> 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;
}
}