From 3afebb520a0217285bfc7ffc341cd5acbf7bd988 Mon Sep 17 00:00:00 2001 From: Olivier Galibert Date: Sun, 16 May 2021 19:43:32 +0200 Subject: jasmin: Support write --- src/frontend/mame/ui/floppycntrl.cpp | 6 - src/lib/formats/fs_oric_jasmin.cpp | 361 ++++++++++++++++++++++++++--------- src/lib/formats/fs_oric_jasmin.h | 27 ++- src/lib/formats/fs_prodos.cpp | 88 ++------- src/lib/formats/fs_prodos.h | 2 - src/lib/formats/fsmgr.cpp | 43 ++++- src/lib/formats/fsmgr.h | 53 +++-- src/tools/floptool.cpp | 329 ++++++++++++++++++++++++++----- 8 files changed, 662 insertions(+), 247 deletions(-) diff --git a/src/frontend/mame/ui/floppycntrl.cpp b/src/frontend/mame/ui/floppycntrl.cpp index f06c122280f..d933fafac74 100644 --- a/src/frontend/mame/ui/floppycntrl.cpp +++ b/src/frontend/mame/ui/floppycntrl.cpp @@ -54,12 +54,6 @@ void menu_control_floppy_image::do_load_create() err = fd.finish_load(); if (err == image_init_result::PASS) { fs_meta_data meta; - if(create_fs->m_manager) { - auto metav = create_fs->m_manager->volume_meta_description(); - for(const auto &e : metav) - if(!e.m_ro) - meta[e.m_name] = e.m_default; - } fd.init_fs(create_fs, meta); } } diff --git a/src/lib/formats/fs_oric_jasmin.cpp b/src/lib/formats/fs_oric_jasmin.cpp index fec9261364e..686eb2bb71a 100644 --- a/src/lib/formats/fs_oric_jasmin.cpp +++ b/src/lib/formats/fs_oric_jasmin.cpp @@ -64,7 +64,7 @@ bool fs_oric_jasmin::can_read() const bool fs_oric_jasmin::can_write() const { - return false; + return true; } bool fs_oric_jasmin::has_rsrc() const @@ -88,7 +88,7 @@ fs_meta_data fs_oric_jasmin::impl::metadata() while(len > 0 && bdir.rodata()[0xf8 + len - 1] == ' ') len--; - res[fs_meta_name::name] = bdir.rstr(0xf8, len); + res.set(fs_meta_name::name, bdir.rstr(0xf8, len)); return res; } @@ -116,7 +116,7 @@ std::vector fs_oric_jasmin::file_meta_description() const void fs_oric_jasmin::impl::format(const fs_meta_data &meta) { - std::string volume_name = meta.find(fs_meta_name::name)->second.as_string(); + std::string volume_name = meta.get_string(fs_meta_name::name, "UNTITLED"); u32 blocks = m_blockdev.block_count(); m_blockdev.fill(0x6c); @@ -139,7 +139,7 @@ void fs_oric_jasmin::impl::format(const fs_meta_data &meta) fmap.w8(0xf6, 0x80); fmap.w8(0xf7, 0x80); - fmap.fill(0xf8, 0x20, 8); + volume_name.resize(8, ' '); fmap.wstr(0xf8, volume_name); auto bdir = m_blockdev.get(20*17+1); @@ -266,11 +266,18 @@ filesystem_t::file_t fs_oric_jasmin::impl::root_dir::file_get(uint64_t key) u16 ref = bdir.r16b(off); bool system = ref == 0 && key == 0 && bdir.r32b(off+0xb) == 0x2e535953; if(system) - return file_t(new system_file(m_fs, bdir.rodata() + off)); + return file_t(new system_file(m_fs, this, bdir.rodata() + off, key)); if(!m_fs.ref_valid(ref)) fatalerror("Key to deleted/non-existent file\n"); - return file_t(new file(m_fs, bdir.rodata() + off, key)); + return file_t(new file(m_fs, this, bdir.rodata() + off, key)); +} + +void fs_oric_jasmin::impl::root_dir::update_file(u16 key, const u8 *entry) +{ + uint64_t rkey = key; + auto [bdir, off] = get_dir_block(rkey); + bdir.copy(off, entry, 18); } filesystem_t::dir_t fs_oric_jasmin::impl::root_dir::dir_get(uint64_t key) @@ -278,10 +285,29 @@ filesystem_t::dir_t fs_oric_jasmin::impl::root_dir::dir_get(uint64_t key) fatalerror("Directories not supported\n"); } -fs_oric_jasmin::impl::file::file(impl &fs, const u8 *entry, u16 key) : m_fs(fs), m_key(key) +std::string fs_oric_jasmin::impl::file_name_prepare(std::string fname) +{ + std::string nname; + size_t i; + for(i = 0; i != 8 && i != fname.size() && fname[i] != '.'; i++) + nname += fname[i]; + if(nname.size() != 8) + nname.insert(nname.end(), 8 - nname.size(), ' '); + nname += '.'; + while(i != fname.size() && fname[i] != '.') + i++; + if(i != fname.size()) + i++; + while(i != fname.size() && nname.size() != 12) + nname += fname[i++]; + if(nname.size() != 12) + nname.insert(nname.end(), 12 - nname.size(), ' '); + return nname; +} + +fs_oric_jasmin::impl::file::file(impl &fs, root_dir *dir, const u8 *entry, u16 key) : m_fs(fs), m_dir(dir), m_key(key) { memcpy(m_entry, entry, 18); - (void)m_key; } void fs_oric_jasmin::impl::file::drop_weak_references() @@ -292,79 +318,36 @@ fs_meta_data fs_oric_jasmin::impl::file::metadata() { fs_meta_data res; - res[fs_meta_name::name] = read_file_name(m_entry + 3); - res[fs_meta_name::locked] = m_entry[2] == 'L'; - res[fs_meta_name::sequential] = m_entry[0xf] == 'S'; - res[fs_meta_name::size_in_blocks] = r16l(m_entry + 0x10); + res.set(fs_meta_name::name, read_file_name(m_entry + 3)); + res.set(fs_meta_name::locked, m_entry[2] == 'L'); + res.set(fs_meta_name::sequential, m_entry[0xf] == 'S'); + res.set(fs_meta_name::size_in_blocks, r16l(m_entry + 0x10)); u16 ref = r16b(m_entry); auto dblk = m_fs.m_blockdev.get(cs_to_block(ref)); - res[fs_meta_name::loading_address] = uint64_t(dblk.r16l(2)); - res[fs_meta_name::length] = uint64_t(dblk.r16l(4)); + res.set(fs_meta_name::loading_address, dblk.r16l(2)); + res.set(fs_meta_name::length, dblk.r16l(4)); return res; } std::vector fs_oric_jasmin::impl::file::read_all() { - auto [sect, length] = build_data_sector_table(); - std::vector data(length); - u32 pos = 0; - for(u16 ref : sect) { - u32 npos = pos + 256; - if(npos > length) - npos = length; - if(npos > pos) { - auto dblk = m_fs.m_blockdev.get(cs_to_block(ref)); - memcpy(data.data() + pos, dblk.rodata(), npos - pos); - } else - break; - pos = npos; - } - return data; -} - -std::vector fs_oric_jasmin::impl::file::read(u64 start, u64 length) -{ - auto [sect, rlength] = build_data_sector_table(); - length += start; - if(length > rlength) - length = rlength; - - if(rlength < start) - return std::vector(); - - std::vector data(length - start); - u32 pos = 0; - for(u16 ref : sect) { - u32 npos = pos + 256; - if(npos > length) - npos = length; - if(npos > pos) { - if(npos > start) { - u32 off = pos < start ? start & 0xff : 0; - auto dblk = m_fs.m_blockdev.get(cs_to_block(ref)); - memcpy(data.data() + pos + off - start, dblk.rodata() + off, npos - pos - off); - } - } else - break; - pos = npos; - } - return data; -} - -std::pair, u32> fs_oric_jasmin::impl::file::build_data_sector_table() -{ - std::pair, u32> res; + std::vector data; u16 ref = r16b(m_entry); auto iblk = m_fs.m_blockdev.get(cs_to_block(ref)); u32 length = iblk.r16l(4); while(m_fs.ref_valid(ref)) { - for(u32 pos = 6; pos != 256; pos += 2) { + for(u32 pos = 6; pos != 256 && data.size() < length; pos += 2) { u16 dref = iblk.r16b(pos); if(!m_fs.ref_valid(dref)) goto done; - res.first.push_back(dref); + auto dblk = m_fs.m_blockdev.get(cs_to_block(dref)); + u32 dpos = data.size(); + data.resize(dpos + 256); + memcpy(data.data() + dpos, dblk.rodata(), 256); + if(data.size() >= length) + goto done; } ref = iblk.r16b(2); if(!m_fs.ref_valid(ref)) @@ -372,13 +355,11 @@ std::pair, u32> fs_oric_jasmin::impl::file::build_data_sector_t iblk = m_fs.m_blockdev.get(cs_to_block(ref)); } done: - if(length > 256 * res.first.size()) - length = 256 * res.first.size(); - res.second = length; - return res; + data.resize(length); + return data; } -fs_oric_jasmin::impl::system_file::system_file(impl &fs, const u8 *entry) : m_fs(fs) +fs_oric_jasmin::impl::system_file::system_file(impl &fs, root_dir *dir, const u8 *entry, u16 key) : m_fs(fs), m_dir(dir), m_key(key) { memcpy(m_entry, entry, 18); } @@ -391,11 +372,11 @@ fs_meta_data fs_oric_jasmin::impl::system_file::metadata() { fs_meta_data res; - res[fs_meta_name::name] = read_file_name(m_entry + 3); - res[fs_meta_name::locked] = m_entry[2] == 'L'; - res[fs_meta_name::sequential] = m_entry[0xf] == 'S'; - res[fs_meta_name::size_in_blocks] = r16l(m_entry + 0x10); - res[fs_meta_name::length] = 0x3e00; + res.set(fs_meta_name::name, read_file_name(m_entry + 3)); + res.set(fs_meta_name::locked, m_entry[2] == 'L'); + res.set(fs_meta_name::sequential, m_entry[0xf] == 'S'); + res.set(fs_meta_name::size_in_blocks, r16l(m_entry + 0x10)); + res.set(fs_meta_name::length, 0x3e00); return res; } @@ -410,25 +391,225 @@ std::vector fs_oric_jasmin::impl::system_file::read_all() return data; } -std::vector fs_oric_jasmin::impl::system_file::read(u64 start, u64 length) +fs_oric_jasmin::impl::file_t fs_oric_jasmin::impl::root_dir::file_create(const fs_meta_data &info) { - if(start >= 0x3e00) - return std::vector(); - length += start; - if(length > 0x3e00) - length = 0x3e00; - std::vector data(length - start); - - for(u32 i = start / 256; i <= (length - 1) / 256; i++) { - u32 pos = i * 256; - u32 off = i * 256 < start ? start & 0xff : 0; - u32 len = i * 256 > length ? length & 0xff : 0x100; - auto dblk = m_fs.m_blockdev.get(i); - memcpy(data.data() + pos + off - start, dblk.rodata() + off, len); + // One block of sector list, one block of data + u32 nb = 2; + + // Find the key for the next entry, increase nb if needed + auto bdir = m_fs.m_blockdev.get(20*17+1); + uint64_t id = 0; + for(;;) { + for(u32 i = 0; i != 14; i ++) { + u32 off = 4 + i*18; + u16 ref = bdir.r16b(off); + if(!m_fs.ref_valid(ref)) + goto found; + id++; + } + u16 ref = bdir.r16b(2); + if(!ref || !m_fs.ref_valid(ref)) { + nb ++; + break; + } + bdir = m_fs.m_blockdev.get(cs_to_block(ref)); + } + found: + auto block = m_fs.allocate_blocks(nb); + if(block.empty()) + return nullptr; + + auto sblk = m_fs.m_blockdev.get(cs_to_block(block[0])); + sblk.w16b(0, 0xff00); // Next sector + sblk.w16l(2, info.get_number(fs_meta_name::loading_address, 0x500)); + sblk.w16l(4, 0); // Length + sblk.w16b(6, block[1]); // Data block + + if(nb == 3) { + bdir.w16l(0, block[2]); // Link to the next directory sector + bdir = m_fs.m_blockdev.get(cs_to_block(block[2])); + bdir.fill(0xff); + bdir.w16l(0, block[2]); // Reference to itself + bdir.w16l(2, 0xff00); // No next directory sector } - return data; + u32 off = 4 + (id % 14) * 18; + bdir.w16b(off+0x00, block[0]); // First (and only) sector in the sector list + bdir.w8 (off+0x02, info.get_flag(fs_meta_name::locked, false) ? 'L' : 'U'); + bdir.wstr(off+0x03, file_name_prepare(info.get_string(fs_meta_name::name, ""))); + bdir.w8 (off+0x0f, info.get_flag(fs_meta_name::sequential, true) ? 'S' : 'D'); + bdir.w16l(off+0x10, 2); // 2 sectors for an empty file + + return file_t(new file(m_fs, this, bdir.rodata() + off, id)); } + +void fs_oric_jasmin::impl::root_dir::file_delete(uint64_t key) +{ +} + +void fs_oric_jasmin::impl::file::replace(const std::vector &data) +{ + u32 cur_ns = r16l(m_entry + 0x10); + // Data sectors first + u32 need_ns = (data.size() + 255) / 256; + if(need_ns == 0) + need_ns = 1; + // Add the sector list sectors, 125 entries/sector + need_ns += (need_ns + 124)/125; + + // Enough space? + if(cur_ns < need_ns && m_fs.free_block_count() < need_ns - cur_ns) + return; + + u16 load_address = 0; + std::vector tofree; + u16 iref = r16b(m_entry); + for(u32 i=0; i < cur_ns; i += 125+1) { + auto iblk = m_fs.m_blockdev.get(cs_to_block(iref)); + if(!i) + load_address = iblk.r16l(2); + tofree.push_back(iref); + for(u32 j=0; j != 125 && i+j+1 != cur_ns; j++) + tofree.push_back(iblk.r16b(6+2*j)); + iref = iblk.r16b(2); + } + m_fs.free_blocks(tofree); + + std::vector blocks = m_fs.allocate_blocks(need_ns); + for(u32 i=0; i < need_ns; i += 125+1) { + auto iblk = m_fs.m_blockdev.get(cs_to_block(blocks[i])); + iblk.fill(0xff); + if(!i) { + iblk.w16l(2, load_address); + iblk.w16l(4, data.size()); + } + if(i + 126 < need_ns) + iblk.w16b(0, blocks[i+126]); + else + iblk.w16b(0, 0xff00); + + for(u32 j=0; j != 125 && i+j+1 != need_ns; j++) { + u32 dpos = 256 * (j + i/126*125); + u32 size = data.size() - dpos; + iblk.w16b(6+j*2, blocks[i+j+1]); + auto dblk = m_fs.m_blockdev.get(cs_to_block(blocks[i+j+1])); + if(size >= 256) + dblk.copy(0, data.data() + dpos, 256); + else { + dblk.copy(0, data.data() + dpos, size); + dblk.fill(size, 0x55, 256-size); + } + } + } + w16l(m_entry + 0x10, need_ns); + w16b(m_entry + 0x00, blocks[0]); + m_dir->update_file(m_key, m_entry); +} + +void fs_oric_jasmin::impl::root_dir::metadata_change(const fs_meta_data &info) +{ +} + +void fs_oric_jasmin::impl::metadata_change(const fs_meta_data &info) +{ + if(info.has(fs_meta_name::name)) { + std::string volume_name = info.get_string(fs_meta_name::name); + volume_name.resize(8, ' '); + m_blockdev.get(20*17).wstr(0xf8, volume_name); + } +} + +void fs_oric_jasmin::impl::file::metadata_change(const fs_meta_data &info) +{ + if(info.has(fs_meta_name::locked)) + w8 (m_entry+0x02, info.get_flag(fs_meta_name::locked) ? 'L' : 'U'); + if(info.has(fs_meta_name::name)) + wstr(m_entry+0x03, file_name_prepare(info.get_string(fs_meta_name::name))); + if(info.has(fs_meta_name::sequential)) + w8 (m_entry+0x0f, info.get_flag(fs_meta_name::sequential) ? 'D' : 'S'); + if(info.has(fs_meta_name::loading_address)) + m_fs.m_blockdev.get(cs_to_block(r16b(m_entry))).w16l(2, info.get_number(fs_meta_name::loading_address)); + m_dir->update_file(m_key, m_entry); +} + +void fs_oric_jasmin::impl::system_file::replace(const std::vector &data) +{ + if(data.size() != 0x3e00) + return; + + for(u32 i=0; i != 0x3e; i++) + m_fs.m_blockdev.get(i).copy(0, data.data() + i * 256, 256); +} + +void fs_oric_jasmin::impl::system_file::metadata_change(const fs_meta_data &info) +{ + if(info.has(fs_meta_name::locked)) + w8 (m_entry+0x02, info.get_flag(fs_meta_name::locked) ? 'L' : 'U'); + if(info.has(fs_meta_name::name)) + wstr(m_entry+0x03, file_name_prepare(info.get_string(fs_meta_name::name))); + if(info.has(fs_meta_name::sequential)) + w8 (m_entry+0x0f, info.get_flag(fs_meta_name::sequential) ? 'S' : 'D'); + if(info.has(fs_meta_name::loading_address)) + m_fs.m_blockdev.get(cs_to_block(r16b(m_entry))).w16l(2, info.get_number(fs_meta_name::loading_address)); + m_dir->update_file(m_key, m_entry); +} + +std::vector fs_oric_jasmin::impl::allocate_blocks(u32 count) +{ + std::vector blocks; + if(free_block_count() < count) + return blocks; + + auto fmap = m_blockdev.get(20*17); + u32 nf = 0; + for(u32 track = 0; track != 2*41 && nf != count; track++) { + u32 map = fmap.r24l(track*3); + if(map != 0x800000) { + for(u32 sect = 1; sect <= 17 && nf != count; sect++) + if(map & (0x20000 >> sect)) { + blocks.push_back((track << 8) | sect); + map &= ~(0x20000 >> sect); + nf++; + } + if(!map) + map = 0x800000; + fmap.w24l(track*3, map); + } + } + return blocks; +} + +void fs_oric_jasmin::impl::free_blocks(const std::vector &blocks) +{ + auto fmap = m_blockdev.get(20*17); + for(u16 ref : blocks) { + u32 track = ref >> 8; + u32 sect = ref & 0xff; + u32 map = fmap.r24l(track*3); + if(map == 0x800000) + map = 0; + map |= 0x20000 >> sect; + fmap.w24l(track*3, map); + } +} + +u32 fs_oric_jasmin::impl::free_block_count() +{ + auto fmap = m_blockdev.get(20*17); + u32 nf = 0; + for(u32 track = 0; track != 2*41; track++) { + u32 map = fmap.r24l(track*3); + if(map != 0x800000) { + for(u32 sect = 1; sect <= 17; sect++) + if(map & (0x20000 >> sect)) + nf++; + } + } + return nf; +} + + + const filesystem_manager_type FS_ORIC_JASMIN = &filesystem_manager_creator;; diff --git a/src/lib/formats/fs_oric_jasmin.h b/src/lib/formats/fs_oric_jasmin.h index a38fe2b6952..c71cbf9097e 100644 --- a/src/lib/formats/fs_oric_jasmin.h +++ b/src/lib/formats/fs_oric_jasmin.h @@ -22,9 +22,14 @@ public: virtual void drop_weak_references() override; virtual fs_meta_data metadata() override; + virtual void metadata_change(const fs_meta_data &info) override; virtual std::vector contents() override; virtual file_t file_get(uint64_t key) override; virtual dir_t dir_get(uint64_t key) override; + virtual file_t file_create(const fs_meta_data &info) override; + virtual void file_delete(uint64_t key) override; + + void update_file(u16 key, const u8 *entry); private: impl &m_fs; @@ -34,36 +39,39 @@ public: class file : public ifile_t { public: - file(impl &fs, const u8 *entry, u16 key); + file(impl &fs, root_dir *dir, const u8 *entry, u16 key); virtual ~file() = default; virtual void drop_weak_references() override; virtual fs_meta_data metadata() override; + virtual void metadata_change(const fs_meta_data &info) override; virtual std::vector read_all() override; - virtual std::vector read(u64 start, u64 length) override; + virtual void replace(const std::vector &data) override; private: impl &m_fs; + root_dir *m_dir; u16 m_key; u8 m_entry[18]; - - std::pair, u32> build_data_sector_table(); }; class system_file : public ifile_t { public: - system_file(impl &fs, const u8 *entry); + system_file(impl &fs, root_dir *dir, const u8 *entry, u16 key); virtual ~system_file() = default; virtual void drop_weak_references() override; virtual fs_meta_data metadata() override; + virtual void metadata_change(const fs_meta_data &info) override; virtual std::vector read_all() override; - virtual std::vector read(u64 start, u64 length) override; + virtual void replace(const std::vector &data) override; private: impl &m_fs; + root_dir *m_dir; + u16 m_key; u8 m_entry[18]; }; @@ -72,6 +80,7 @@ public: virtual void format(const fs_meta_data &meta) override; virtual fs_meta_data metadata() override; + virtual void metadata_change(const fs_meta_data &info) override; virtual dir_t root() override; static u32 cs_to_block(u16 ref); @@ -81,6 +90,12 @@ public: static std::string read_file_name(const u8 *p); void drop_root_ref(); + std::vector allocate_blocks(u32 count); + void free_blocks(const std::vector &blocks); + u32 free_block_count(); + + static std::string file_name_prepare(std::string name); + private: dir_t m_root; }; diff --git a/src/lib/formats/fs_prodos.cpp b/src/lib/formats/fs_prodos.cpp index 1845f4eb853..844e1d9c81d 100644 --- a/src/lib/formats/fs_prodos.cpp +++ b/src/lib/formats/fs_prodos.cpp @@ -123,7 +123,7 @@ std::vector fs_prodos::directory_meta_description() const void fs_prodos::impl::format(const fs_meta_data &meta) { - std::string volume_name = meta.find(fs_meta_name::name)->second.as_string(); + std::string volume_name = meta.get_string(fs_meta_name::name, "UNTITLED"); u32 blocks = m_blockdev.block_count(); // Maximum usable partition size = 32M - 512 bytes (65535 blocks) @@ -211,11 +211,11 @@ 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)); + res.set(fs_meta_name::name, bdir.rstr(0x05, len)); + res.set(fs_meta_name::os_version, bdir.r8(0x20)); + res.set(fs_meta_name::os_minimum_version, bdir.r8(0x21)); + res.set(fs_meta_name::creation_date, prodos_to_dt(bdir.r32l(0x1c))); + res.set(fs_meta_name::modification_date, prodos_to_dt(bdir.r32l(0x16))); return res; } @@ -246,11 +246,11 @@ fs_meta_data fs_prodos::impl::dir::metadata() auto bdir = m_fs.m_blockdev.get(m_base_block); 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)); + res.set(fs_meta_name::name, bdir.rstr(0x05, len)); + res.set(fs_meta_name::os_version, bdir.r8(0x20)); + res.set(fs_meta_name::os_minimum_version, bdir.r8(0x21)); + res.set(fs_meta_name::creation_date, prodos_to_dt(bdir.r32l(0x1c))); + res.set(fs_meta_name::modification_date, prodos_to_dt(bdir.r32l(0x16))); return res; } @@ -357,14 +357,14 @@ fs_meta_data fs_prodos::impl::file::metadata() u8 type = r8(m_entry); std::string name = rstr(m_entry+1, type & 0xf); type >>= 4; - res[fs_meta_name::name] = name; + res.set(fs_meta_name::name, name); if(type == 5) { auto rootblk = m_fs.m_blockdev.get(r16l(m_entry+0x11)); - res[fs_meta_name::length] = rootblk.r24l(0x005); - res[fs_meta_name::rsrc_length] = rootblk.r24l(0x105); + res.set(fs_meta_name::length, rootblk.r24l(0x005)); + res.set(fs_meta_name::rsrc_length, rootblk.r24l(0x105)); } else if(type >= 1 && type <= 3) - res[fs_meta_name::length] = r24l(m_entry + 0x15); + res.set(fs_meta_name::length, r24l(m_entry + 0x15)); else fatalerror("fs_prodos::impl::file::metadata: Unhandled file type %d\n", type); @@ -467,35 +467,6 @@ std::vector fs_prodos::impl::file::read_all() return data; } -std::vector fs_prodos::impl::file::read(u64 start, u64 length) -{ - auto [blocks, rlength] = data_blocks(); - length += start; - if(length > rlength) - length = rlength; - - if(rlength < start) - return std::vector(); - - std::vector data(length - start); - u32 pos = 0; - for(u16 block : blocks) { - u32 npos = pos + 512; - if(npos > length) - npos = length; - if(npos > pos) { - if(npos > start) { - u32 off = pos < start ? start & 0xff : 0; - auto dblk = m_fs.m_blockdev.get(block); - memcpy(data.data() + pos + off - start, dblk.rodata() + off, npos - pos - off); - } - } else - break; - pos = npos; - } - return data; -} - std::vector fs_prodos::impl::file::rsrc_read_all() { auto [blocks, length] = rsrc_blocks(); @@ -516,33 +487,4 @@ std::vector fs_prodos::impl::file::rsrc_read_all() return data; } -std::vector fs_prodos::impl::file::rsrc_read(u64 start, u64 length) -{ - auto [blocks, rlength] = rsrc_blocks(); - length += start; - if(length > rlength) - length = rlength; - - if(rlength < start) - return std::vector(); - - std::vector data(length - start); - u32 pos = 0; - for(u16 block : blocks) { - u32 npos = pos + 512; - if(npos > length) - npos = length; - if(npos > pos) { - if(npos > start) { - u32 off = pos < start ? start & 0xff : 0; - auto dblk = m_fs.m_blockdev.get(block); - memcpy(data.data() + pos + off - start, dblk.rodata() + off, npos - pos - off); - } - } else - break; - pos = npos; - } - return data; -} - 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 be83ab5cd03..49ac796783b 100644 --- a/src/lib/formats/fs_prodos.h +++ b/src/lib/formats/fs_prodos.h @@ -44,9 +44,7 @@ public: virtual fs_meta_data metadata() override; virtual std::vector read_all() override; - virtual std::vector read(u64 start, u64 length) override; virtual std::vector rsrc_read_all() override; - virtual std::vector rsrc_read(u64 start, u64 length) override; private: impl &m_fs; diff --git a/src/lib/formats/fsmgr.cpp b/src/lib/formats/fsmgr.cpp index ddf9e04869e..2f762796e4c 100644 --- a/src/lib/formats/fsmgr.cpp +++ b/src/lib/formats/fsmgr.cpp @@ -369,17 +369,48 @@ uint32_t filesystem_t::r32l(const uint8_t *p) return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); } -std::vector filesystem_t::ifile_t::rsrc_read_all() +filesystem_t::file_t filesystem_t::idir_t::file_create(const fs_meta_data &info) +{ + fatalerror("file_create called on a filesystem not supporting write\n"); +} + +void filesystem_t::idir_t::file_delete(uint64_t key) +{ + fatalerror("file_delete called on a filesystem not supporting write\n"); +} + + +void filesystem_t::ifile_t::replace(const std::vector &data) +{ + fatalerror("replace called on a filesystem not supporting write \n"); +} + +void filesystem_t::ifile_t::rsrc_replace(const std::vector &data) { - fatalerror("rsrc_read_all called on filesystem without resource forks\n"); + fatalerror("rsrc_replace called on a filesystem not supporting write or resource forks \n"); } -std::vector filesystem_t::ifile_t::rsrc_read(u64 start, u64 length) +void filesystem_t::ifile_t::metadata_change(const fs_meta_data &info) +{ + fatalerror("metadata_change called on a filesystem not supporting write \n"); +} + +void filesystem_t::idir_t::metadata_change(const fs_meta_data &info) +{ + fatalerror("metadata_change called on a filesystem not supporting write \n"); +} + +void filesystem_t::metadata_change(const fs_meta_data &info) +{ + fatalerror("metadata_change called on a filesystem not supporting write \n"); +} + +std::vector filesystem_t::ifile_t::rsrc_read_all() { - fatalerror("rsrc_read called on filesystem without resource forks\n"); + fatalerror("rsrc_read_all called on a filesystem without resource forks\n"); } -const char *fs_meta_get_name(fs_meta_name name) +const char *fs_meta_data::entry_name(fs_meta_name name) { switch(name) { case fs_meta_name::creation_date: return "creation_date"; @@ -397,7 +428,7 @@ const char *fs_meta_get_name(fs_meta_name name) return ""; } -std::string fs_meta_to_string(fs_meta_type type, const fs_meta &m) +std::string fs_meta::to_string(fs_meta_type type, const fs_meta &m) { switch(type) { case fs_meta_type::string: return m.as_string(); diff --git a/src/lib/formats/fsmgr.h b/src/lib/formats/fsmgr.h index ed3a553843e..3404f422265 100644 --- a/src/lib/formats/fsmgr.h +++ b/src/lib/formats/fsmgr.h @@ -43,6 +43,9 @@ enum class fs_dir_entry_type { class fs_meta { public: + static std::string to_string(fs_meta_type type, const fs_meta &m); + static fs_meta from_string(fs_meta_type type, std::string value); + fs_meta() { value = false; } fs_meta(std::string str) { value = str; } fs_meta(bool b) { value = b; } @@ -61,11 +64,31 @@ private: std::variant value; }; -using fs_meta_data = std::unordered_map; - -const char *fs_meta_get_name(fs_meta_name name); -std::string fs_meta_to_string(fs_meta_type type, const fs_meta &m); -fs_meta fs_meta_from_string(fs_meta_type type, std::string value); +class fs_meta_data { +public: + std::unordered_map meta; + + static const char *entry_name(fs_meta_name name); + + bool has(fs_meta_name name) const { return meta.find(name) != meta.end(); } + bool empty() const { return meta.empty(); } + + void set(fs_meta_name name, const fs_meta &val) { meta[name] = val; } + void set(fs_meta_name name, std::string str) { set(name, fs_meta(str)); } + void set(fs_meta_name name, bool b) { set(name, fs_meta(b)); } + void set(fs_meta_name name, int32_t num) { set(name, fs_meta(num)); } + void set(fs_meta_name name, uint32_t num) { set(name, fs_meta(num)); } + void set(fs_meta_name name, int64_t num) { set(name, fs_meta(num)); } + void set(fs_meta_name name, uint64_t num) { set(name, fs_meta(num)); } + void set(fs_meta_name name, util::arbitrary_datetime dt) { set(name, fs_meta(dt)); } + void set_now(fs_meta_name name) { set(name, fs_meta(util::arbitrary_datetime::now())); } + + fs_meta get(fs_meta_name name) const { auto i = meta.find(name); if(i == meta.end()) fatalerror("Entry %s not found\n", entry_name(name)); else return i->second; } + util::arbitrary_datetime get_date(fs_meta_name name, util::arbitrary_datetime def = util::arbitrary_datetime::now()) const { auto i = meta.find(name); if(i == meta.end()) return def; else return i->second.as_date(); } + bool get_flag(fs_meta_name name, bool def = false) const { auto i = meta.find(name); if(i == meta.end()) return def; else return i->second.as_flag(); } + uint64_t get_number(fs_meta_name name, uint64_t def = 0) const { auto i = meta.find(name); if(i == meta.end()) return def; else return i->second.as_number(); } + std::string get_string(fs_meta_name name, std::string def = "") const { auto i = meta.find(name); if(i == meta.end()) return def; else return i->second.as_string(); } +}; template class fs_refcounted_outer { public: @@ -268,9 +291,12 @@ protected: virtual ~idir_t() = default; virtual fs_meta_data metadata() = 0; + virtual void metadata_change(const fs_meta_data &info); virtual std::vector contents() = 0; virtual file_t file_get(uint64_t key) = 0; virtual dir_t dir_get(uint64_t key) = 0; + virtual file_t file_create(const fs_meta_data &info); + virtual void file_delete(uint64_t key); }; class ifile_t : public fs_refcounted_inner { @@ -279,10 +305,11 @@ protected: virtual ~ifile_t() = default; virtual fs_meta_data metadata() = 0; + virtual void metadata_change(const fs_meta_data &info); virtual std::vector read_all() = 0; - virtual std::vector read(u64 start, u64 length) = 0; + virtual void replace(const std::vector &data); virtual std::vector rsrc_read_all(); - virtual std::vector rsrc_read(u64 start, u64 length); + virtual void rsrc_replace(const std::vector &data); }; public: @@ -296,9 +323,12 @@ public: dir_t weak() { return dir_t(m_object, true); } fs_meta_data metadata() { return m_object->metadata(); } + void metadata_change(const fs_meta_data &info) { m_object->metadata_change(info); } std::vector contents() { return m_object->contents(); } file_t file_get(uint64_t key) { return m_object->file_get(key); } dir_t dir_get(uint64_t key) { return m_object->dir_get(key); } + file_t file_create(const fs_meta_data &info) { return m_object->file_create(info); } + void file_delete(uint64_t key) { m_object->file_delete(key); } }; class file_t : public fs_refcounted_outer { @@ -311,11 +341,11 @@ public: file_t weak() { return file_t(m_object, true); } fs_meta_data metadata() { return m_object->metadata(); } - + void metadata_change(const fs_meta_data &info) { m_object->metadata_change(info); } std::vector read_all() { return m_object->read_all(); } - std::vector read(u32 start, u32 length) { return m_object->read(start, length); } + void replace(const std::vector &data) { m_object->replace(data); } std::vector rsrc_read_all() { return m_object->rsrc_read_all(); } - std::vector rsrc_read(u32 start, u32 length) { return m_object->rsrc_read(start, length); } + void rsrc_replace(const std::vector &data) { m_object->rsrc_replace(data); } }; filesystem_t(fsblk_t &blockdev, u32 size) : m_blockdev(blockdev) { @@ -324,9 +354,10 @@ public: virtual ~filesystem_t() = default; + virtual dir_t root(); virtual void format(const fs_meta_data &meta); virtual fs_meta_data metadata(); - virtual dir_t root(); + virtual void metadata_change(const fs_meta_data &info); static void copy(uint8_t *p, const uint8_t *src, uint32_t size); static void fill(uint8_t *p, uint8_t data, uint32_t size); diff --git a/src/tools/floptool.cpp b/src/tools/floptool.cpp index a3186b2374b..515ee3a87dd 100644 --- a/src/tools/floptool.cpp +++ b/src/tools/floptool.cpp @@ -275,6 +275,7 @@ static void display_usage() 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"); + fprintf(stderr, " floptool.exe flopwrite input_format filesystem -- Write a file into a floppy image\n"); } static void display_formats() @@ -456,11 +457,7 @@ static int create(int argc, char *argv[]) floppy_image image(84, 2, floppy_image::FF_UNKNOWN); if(source_fs->m_type) { - auto metav = source_fs->m_manager->volume_meta_description(); fs_meta_data meta; - for(const auto &e : metav) - if(!e.m_ro) - meta[e.m_name] = e.m_default; std::vector img(source_fs->m_image_size); fsblk_vec_t blockdev(img); @@ -514,10 +511,10 @@ static void dir_scan(u32 depth, filesystem_t::dir_t dir, std::vectorsecond; - std::string val = fs_meta_to_string(m.m_type, meta.find(m.m_name)->second); + std::string val = fs_meta::to_string(m.m_type, meta.get(m.m_name)); if(slot == 0) val = head + "dir " + val; entries[id][slot] = val; @@ -530,10 +527,10 @@ static void dir_scan(u32 depth, filesystem_t::dir_t dir, std::vectorsecond; - std::string val = fs_meta_to_string(m.m_type, meta.find(m.m_name)->second); + std::string val = fs_meta::to_string(m.m_type, meta.get(m.m_name)); if(slot == 0) val = head + (c.m_type == fs_dir_entry_type::system_file ? "sys " : "file ") + val; entries[id][slot] = val; @@ -555,7 +552,7 @@ static int generic_dir(const filesystem_manager_t *fm, fsblk_t &blockdev) 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])); + vinf += util::string_format(" %s=%s", fs_meta_data::entry_name(e.m_name), fs_meta::to_string(e.m_type, vmeta.get(e.m_name))); printf("%s\n\n", vinf.c_str()); } @@ -577,7 +574,7 @@ static int generic_dir(const filesystem_manager_t *fm, fsblk_t &blockdev) entries.resize(1); for(fs_meta_name n : names) - entries[0].push_back(fs_meta_get_name(n)); + entries[0].push_back(fs_meta_data::entry_name(n)); dir_scan(0, root, entries, nmap, names.size(), dmetad, fmetad); @@ -694,31 +691,93 @@ static int hddir(int argc, char *argv[]) } - - -static int generic_read(const filesystem_manager_t *fm, fsblk_t &blockdev, const char *srcpath, const char *dstpath) +static std::vector path_split(const filesystem_manager_t *fm, std::string path) { - auto load_fs = fm->mount(blockdev); - - std::string opath = srcpath; - std::vector path; + std::string opath = path; + std::vector rpath; if(fm->has_subdirectories()) { std::string element; char sep = fm->directory_separator(); for(char c : opath) { if(c == sep) { if(!element.empty()) { - path.push_back(element); + rpath.push_back(element); element.clear(); } } else element += c; } if(!element.empty()) - path.push_back(element); + rpath.push_back(element); } else - path.push_back(opath); + rpath.push_back(opath); + + return rpath; +} + +static std::vector fload(std::string path) +{ + char msg[4096]; + sprintf(msg, "Error opening %s for reading", path.c_str()); + auto fi = fopen(path.c_str(), "rb"); + if (!fi) { + perror(msg); + exit(1); + } + fseek(fi, 0, SEEK_END); + long size = ftell(fi); + std::vector filedata(size); + fseek(fi, 0, SEEK_SET); + fread(filedata.data(), filedata.size(), 1, fi); + fclose(fi); + + return filedata; +} + +static void fsave(std::string path, const std::vector &data) +{ + char msg[4096]; + sprintf(msg, "Error opening %s for writing", path.c_str()); + auto fo = fopen(path.c_str(), "wb"); + if (!fo) { + perror(msg); + exit(1); + } + + fwrite(data.data(), data.size(), 1, fo); + fclose(fo); +} + +static bool fexists(std::string path) +{ + auto f = fopen(path.c_str(), "rb"); + if(f != nullptr) { + fclose(f); + return true; + } + return false; +} + + +static std::string path_make_rsrc(std::string path) +{ + auto p = path.end(); + while(p != path.begin() && p[-1] != '/') + p--; + std::string rpath(path.begin(), p); + rpath += "._"; + rpath += std::string(p, path.end()); + return rpath; +} + + + +static int generic_read(const filesystem_manager_t *fm, fsblk_t &blockdev, const char *srcpath, const char *dstpath) +{ + auto load_fs = fm->mount(blockdev); + + std::vector path = path_split(fm, srcpath); auto dir = load_fs->root(); std::string apath; @@ -752,43 +811,22 @@ static int generic_read(const filesystem_manager_t *fm, fsblk_t &blockdev, const auto file = dir.file_get(c[j].m_key); auto meta = file.metadata(); - if(meta.find(fs_meta_name::length) == meta.end()) { + if(!meta.has(fs_meta_name::length)) { fprintf(stderr, "Error: %s%c%s is not a readable file\n", apath.c_str(), fm->directory_separator(), path.back().c_str()); return 1; } - auto filedata = file.read_all(); - - char msg[4096]; - sprintf(msg, "Error opening %s for writing", dstpath); - auto fo = fopen(dstpath, "wb"); - if (!fo) { - perror(msg); - return 1; - } - - fwrite(filedata.data(), filedata.size(), 1, fo); - fclose(fo); + fsave(dstpath, file.read_all()); - bool has_rsrc = fm->has_rsrc() && meta.find(fs_meta_name::rsrc_length) != meta.end(); + bool has_rsrc = fm->has_rsrc() && meta.has(fs_meta_name::rsrc_length); if(has_rsrc) { - const char *d = dstpath + strlen(dstpath); - while(d != dstpath && d[-1] != '/') - d--; - std::string dpath(dstpath, d); - dpath += "._"; - dpath += d; - - sprintf(msg, "Error opening %s for writing", dstpath); - auto fo = fopen(dpath.c_str(), "wb"); - if (!fo) { - perror(msg); - return 1; - } + std::string rpath = path_make_rsrc(dstpath); + + auto filedata = file.rsrc_read_all(); + filedata.insert(filedata.begin(), 0x2a, 0); - filedata = file.rsrc_read_all(); - u8 head[0x2a]; + u8 *head = filedata.data(); filesystem_t::w32b(head+0x00, 0x00051607); // Magic filesystem_t::w32b(head+0x04, 0x00020000); // Version filesystem_t::fill(head+0x08, 0, 16); // Filler @@ -797,9 +835,7 @@ static int generic_read(const filesystem_manager_t *fm, fsblk_t &blockdev, const filesystem_t::w32b(head+0x22, 0x2a); // Offset in the file filesystem_t::w32b(head+0x26, filedata.size()); // Length - fwrite(head, 0x2a, 1, fo); - fwrite(filedata.data(), filedata.size(), 1, fo); - fclose(fo); + fsave(rpath, filedata); } return 0; @@ -901,6 +937,189 @@ static int hdread(int argc, char *argv[]) } + +static int generic_write(const filesystem_manager_t *fm, fsblk_t &blockdev, const char *srcpath, const char *dstpath) +{ + auto load_fs = fm->mount(blockdev); + + std::vector path = path_split(fm, dstpath); + + auto dir = load_fs->root(); + std::string apath; + for(unsigned int i = 0; i < path.size() - 1; i++) { + auto c = dir.contents(); + unsigned int j; + for(j = 0; j != c.size(); j++) + if(c[j].m_name == path[i]) + break; + if(j == c.size()) { + fprintf(stderr, "Error: directory %s%c%s not found\n", apath.c_str(), fm->directory_separator(), path[i].c_str()); + return 1; + } + if(c[j].m_type != fs_dir_entry_type::dir) { + fprintf(stderr, "Error: %s%c%s is not a directory\n", apath.c_str(), fm->directory_separator(), path[i].c_str()); + return 1; + } + dir = dir.dir_get(c[j].m_key); + apath += fm->directory_separator() + path[i]; + } + + + fs_meta_data meta; + meta.set(fs_meta_name::name, path.back()); + + auto file = dir.file_create(meta); + auto filedata = fload(srcpath); + file.replace(filedata); + + bool has_rsrc = fm->has_rsrc(); + + if(has_rsrc) { + std::string rpath = path_make_rsrc(dstpath); + + if(fexists(rpath)) { + filedata = fload(rpath); + const u8 *head = filedata.data(); + + if(filesystem_t::r32b(head+0x00) == 0x00051607 && + filesystem_t::r32b(head+0x04) == 0x00020000) { + u16 nent = filesystem_t::r16b(head+0x18); + for(u16 i=0; i != nent; i++) { + const u8 *e = head + 12*i; + if(filesystem_t::r32b(e+0) == 2) { + u32 start = filesystem_t::r32b(e+4); + u32 len = filesystem_t::r32b(e+8); + filedata.erase(filedata.begin(), filedata.begin() + start); + filedata.erase(filedata.begin() + len, filedata.end()); + file.rsrc_replace(filedata); + break; + } + } + } + } + } + + return 0; +} + + +static int flopwrite(int argc, char *argv[]) +{ + if (argc!=7) { + fprintf(stderr, "Incorrect number of arguments.\n\n"); + display_usage(); + return 1; + } + + auto format = find_format_by_name(argv[2]); + if(!format) { + 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[3]); + return 1; + } + + if(!fs->m_manager || !fs->m_manager->can_read()) { + fprintf(stderr, "Error: Filesystem '%s' does not implement reading\n", argv[2]); + return 1; + } + + char msg[4096]; + sprintf(msg, "Error opening %s for reading", argv[4]); + FILE *f = fopen(argv[4], "rb"); + if (!f) { + perror(msg); + return 1; + } + io_generic io; + io.file = f; + io.procs = &stdio_ioprocs_noclose; + io.filler = 0xff; + + floppy_image image(84, 2, floppy_image::FF_UNKNOWN); + if(!format->load(&io, floppy_image::FF_UNKNOWN, variants, &image)) { + fprintf(stderr, "Error: parsing input file as '%s' failed\n", format->name()); + return 1; + } + + std::vector img; + auto iog = ram_open(img); + auto load_format = fs->m_type(); + load_format->save(iog, variants, &image); + + fsblk_vec_t blockdev(img); + generic_write(fs->m_manager, blockdev, argv[5], argv[6]); + + load_format->load(iog, image.get_form_factor(), variants, &image); + delete load_format; + delete iog; + + sprintf(msg, "Error oapening %s for writing", argv[4]); + f = fopen(argv[4], "wb"); + if (!f) { + perror(msg); + return 1; + } + + io_generic dest_io; + dest_io.file = f; + dest_io.procs = &stdio_ioprocs_noclose; + dest_io.filler = 0xff; + + if(!format->save(&dest_io, variants, &image)) { + fprintf(stderr, "Error: writing output file as '%s' failed\n", format->name()); + return 1; + } + + fclose((FILE *)dest_io.file); + return 0; +} + + +// Should use chd&friends instead, but one thing at a time + +static int hdwrite(int argc, char *argv[]) +{ + if (argc!=6) { + fprintf(stderr, "Incorrect number of arguments.\n\n"); + display_usage(); + return 1; + } + + auto fs = find_fs_by_name(argv[2]); + if(!fs) { + fprintf(stderr, "Error: Filesystem '%s' unknown\n", argv[2]); + return 1; + } + + if(!fs->m_manager || !fs->m_manager->can_read()) { + fprintf(stderr, "Error: Filesystem '%s' does not implement reading\n", argv[2]); + return 1; + } + + char msg[4096]; + sprintf(msg, "Error opening %s for reading", argv[3]); + FILE *f = fopen(argv[3], "rb"); + if (!f) { + perror(msg); + return 1; + } + 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_write(fs->m_manager, blockdev, argv[4], argv[5]); +} + + int CLIB_DECL main(int argc, char *argv[]) { init_formats(); @@ -915,16 +1134,20 @@ int CLIB_DECL main(int argc, char *argv[]) return identify(argc, argv); else if (!core_stricmp("convert", argv[1])) return convert(argc, argv); - else if (!core_stricmp("create", argv[1])) + else if (!core_stricmp("flopcreate", 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("flopwrite", argv[1])) + return flopwrite(argc, argv); else if (!core_stricmp("hddir", argv[1])) return hddir(argc, argv); else if (!core_stricmp("hdread", argv[1])) return hdread(argc, argv); + else if (!core_stricmp("hdwrite", argv[1])) + return hdwrite(argc, argv); else { fprintf(stderr, "Unknown command '%s'\n\n", argv[1]); display_usage(); -- cgit v1.2.3