diff --git a/src/lib/formats/fs_oric_jasmin.cpp b/src/lib/formats/fs_oric_jasmin.cpp index 3f4e0b04e14..fec9261364e 100644 --- a/src/lib/formats/fs_oric_jasmin.cpp +++ b/src/lib/formats/fs_oric_jasmin.cpp @@ -67,6 +67,11 @@ bool fs_oric_jasmin::can_write() const return false; } +bool fs_oric_jasmin::has_rsrc() const +{ + return false; +} + std::vector fs_oric_jasmin::volume_meta_description() const { std::vector res; @@ -290,9 +295,9 @@ fs_meta_data fs_oric_jasmin::impl::file::metadata() 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] = uint64_t(m_entry[0x10] | (m_entry[0x11] << 8)); + res[fs_meta_name::size_in_blocks] = r16l(m_entry + 0x10); - u16 ref = (m_entry[0] << 8) | m_entry[1]; + 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)); @@ -351,7 +356,7 @@ std::vector fs_oric_jasmin::impl::file::read(u64 start, u64 length) std::pair, u32> fs_oric_jasmin::impl::file::build_data_sector_table() { std::pair, u32> res; - u16 ref = (m_entry[0] << 8) | m_entry[1]; + 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)) { @@ -389,8 +394,8 @@ fs_meta_data fs_oric_jasmin::impl::system_file::metadata() 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] = uint64_t(m_entry[0x10] | (m_entry[0x11] << 8)); - res[fs_meta_name::length] = uint64_t(0x3e00); + res[fs_meta_name::size_in_blocks] = r16l(m_entry + 0x10); + res[fs_meta_name::length] = 0x3e00; return res; } diff --git a/src/lib/formats/fs_oric_jasmin.h b/src/lib/formats/fs_oric_jasmin.h index 823dcc90538..a38fe2b6952 100644 --- a/src/lib/formats/fs_oric_jasmin.h +++ b/src/lib/formats/fs_oric_jasmin.h @@ -93,6 +93,7 @@ public: 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 volume_meta_description() const override; virtual std::vector file_meta_description() const override; diff --git a/src/lib/formats/fs_prodos.cpp b/src/lib/formats/fs_prodos.cpp index 5a4d3e6edf5..1845f4eb853 100644 --- a/src/lib/formats/fs_prodos.cpp +++ b/src/lib/formats/fs_prodos.cpp @@ -70,6 +70,11 @@ bool fs_prodos::can_write() const return false; } +bool fs_prodos::has_rsrc() const +{ + return true; +} + char fs_prodos::directory_separator() const { return '/'; @@ -333,12 +338,13 @@ filesystem_t::dir_t fs_prodos::impl::dir::dir_get(uint64_t key) if(type != 0xd) fatalerror("Unhandled directory type %x\n", type); - return new dir(m_fs, entry[17] | (entry[18] << 8), key); + return new dir(m_fs, r16l(entry+0x11), key); } fs_prodos::impl::file::file(impl &fs, const u8 *entry, u16 key) : m_fs(fs), m_key(key) { memcpy(m_entry, entry, 39); + (void)m_key; } void fs_prodos::impl::file::drop_weak_references() @@ -348,31 +354,195 @@ void fs_prodos::impl::file::drop_weak_references() fs_meta_data fs_prodos::impl::file::metadata() { fs_meta_data res; - std::string name; - u8 type = m_entry[0]; - for(u8 i = 0; i != (type & 0xf); i++) - name += char(m_entry[i+1]); + u8 type = r8(m_entry); + std::string name = rstr(m_entry+1, type & 0xf); type >>= 4; res[fs_meta_name::name] = name; if(type == 5) { - auto rootblk = m_fs.m_blockdev.get(m_entry[0x11] | (m_entry[0x12] << 8)); + 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); - } else if((type >= 1 && type <= 3) || 1) - res[fs_meta_name::length] = m_entry[0x15] | (m_entry[0x16] << 8) | (m_entry[0x17] << 16); + } else if(type >= 1 && type <= 3) + res[fs_meta_name::length] = r24l(m_entry + 0x15); + + else + fatalerror("fs_prodos::impl::file::metadata: Unhandled file type %d\n", type); return res; } +std::vector fs_prodos::impl::file::get_file_blocks(uint8_t type, u16 block, u32 length) +{ + u32 nb = (length+1)/512; + std::vector res; + switch(type) { + case 1: + if(nb) + res.push_back(block); + break; + + case 2: { + auto iblk = m_fs.m_blockdev.get(block); + if(nb > 255) + nb = 255; + for(u32 i=0; i != nb; i++) + res.push_back(iblk.r8(i) | (iblk.r8(i | 0x100) << 8)); + break; + } + + case 3: { + auto mblk = m_fs.m_blockdev.get(block); + for(u32 j=0; j < nb; j += 256) { + u32 idx = j/256; + auto iblk = m_fs.m_blockdev.get(mblk.r8(idx) | (mblk.r8(idx | 0x100) << 8)); + for(u32 i=0; i != 256 && res.size() != nb; i++) + res.push_back(iblk.r8(i) | (iblk.r8(i | 0x100) << 8)); + } + break; + } + + default: + fatalerror("fs_prodos::impl::file::get_file_blocks: unknown file type %d\n", type); + } + return res; +} + +std::pair, u32> fs_prodos::impl::file::data_blocks() +{ + std::vector blocks; + + u8 type = r8(m_entry) >> 4; + u32 length = 0; + if(type >= 1 && type <= 3) { + length = r24l(m_entry + 0x15); + blocks = get_file_blocks(type, r16l(m_entry+0x11), length); + + } else if(type == 5) { + auto kblk = m_fs.m_blockdev.get(r16l(m_entry+0x11)); + length = kblk.r24l(0x005); + blocks = get_file_blocks(kblk.r8(0x000), kblk.r16l(0x001), length); + + } else + fatalerror("fs_prodos::impl::file::data_blocks: Unhandled file type %d\n", type); + + return std::make_pair(blocks, length); +} + +std::pair, u32> fs_prodos::impl::file::rsrc_blocks() +{ + std::vector blocks; + + u8 type = r8(m_entry) >> 4; + u32 length = 0; + + if(type == 5) { + auto kblk = m_fs.m_blockdev.get(r16l(m_entry+0x11)); + length = kblk.r24l(0x105); + blocks = get_file_blocks(kblk.r8(0x100), kblk.r16l(0x101), length); + + } else + fatalerror("fs_prodos::impl::file::rsrc_blocks: Unhandled file type %d\n", type); + + return std::make_pair(blocks, length); +} + std::vector fs_prodos::impl::file::read_all() { - abort(); + auto [blocks, length] = data_blocks(); + + std::vector data(length); + u32 pos = 0; + for(u16 block : blocks) { + u32 npos = pos + 512; + if(npos > length) + npos = length; + if(npos > pos) { + auto dblk = m_fs.m_blockdev.get(block); + memcpy(data.data() + pos, dblk.rodata(), npos - pos); + } else + break; + pos = npos; + } + return data; } std::vector fs_prodos::impl::file::read(u64 start, u64 length) { - abort(); + 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(); + + std::vector data(length); + u32 pos = 0; + for(u16 block : blocks) { + u32 npos = pos + 512; + if(npos > length) + npos = length; + if(npos > pos) { + auto dblk = m_fs.m_blockdev.get(block); + memcpy(data.data() + pos, dblk.rodata(), npos - pos); + } else + break; + pos = npos; + } + 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 2f33254842b..be83ab5cd03 100644 --- a/src/lib/formats/fs_prodos.h +++ b/src/lib/formats/fs_prodos.h @@ -45,13 +45,17 @@ 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; u16 m_key; u8 m_entry[39]; - std::vector build_block_table(); + std::vector get_file_blocks(uint8_t type, u16 block, u32 length); + std::pair, uint32_t> data_blocks(); + std::pair, uint32_t> rsrc_blocks(); }; impl(fsblk_t &blockdev); @@ -80,6 +84,7 @@ public: 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 char directory_separator() const override; virtual std::vector volume_meta_description() const override; diff --git a/src/lib/formats/fs_unformatted.cpp b/src/lib/formats/fs_unformatted.cpp index 573be8ac2b9..2dc927b70fd 100644 --- a/src/lib/formats/fs_unformatted.cpp +++ b/src/lib/formats/fs_unformatted.cpp @@ -116,4 +116,9 @@ bool fs_unformatted::can_write() const return false; } +bool fs_unformatted::has_rsrc() const +{ + return false; +} + const filesystem_manager_type FS_UNFORMATTED = &filesystem_manager_creator; diff --git a/src/lib/formats/fs_unformatted.h b/src/lib/formats/fs_unformatted.h index ce472cafdb3..fe5ac68973d 100644 --- a/src/lib/formats/fs_unformatted.h +++ b/src/lib/formats/fs_unformatted.h @@ -46,6 +46,7 @@ public: virtual bool can_format() const override; virtual bool can_read() const override; virtual bool can_write() const override; + virtual bool has_rsrc() const override; }; extern const filesystem_manager_type FS_UNFORMATTED; diff --git a/src/lib/formats/fsmgr.cpp b/src/lib/formats/fsmgr.cpp index 2b15b1aeb93..ddf9e04869e 100644 --- a/src/lib/formats/fsmgr.cpp +++ b/src/lib/formats/fsmgr.cpp @@ -262,6 +262,123 @@ uint32_t fsblk_t::block_t::r32l(u32 offset) return blk[0] | (blk[1] << 8) | (blk[2] << 16) | (blk[3] << 24); } + + +void filesystem_t::copy(uint8_t *p, const uint8_t *src, u32 size) +{ + memcpy(p, src, size); +} + +void filesystem_t::fill(uint8_t *p, uint8_t data, u32 size) +{ + memset(p, data, size); +} + +void filesystem_t::wstr(uint8_t *p, const std::string &str) +{ + memcpy(p, str.data(), str.size()); +} + +void filesystem_t::w8(uint8_t *p, uint8_t data) +{ + p[0] = data; +} + +void filesystem_t::w16b(uint8_t *p, u16 data) +{ + p[0] = data >> 8; + p[1] = data; +} + +void filesystem_t::w24b(uint8_t *p, u32 data) +{ + p[0] = data >> 16; + p[1] = data >> 8; + p[2] = data; +} + +void filesystem_t::w32b(uint8_t *p, u32 data) +{ + p[0] = data >> 24; + p[1] = data >> 16; + p[2] = data >> 8; + p[3] = data; +} + +void filesystem_t::w16l(uint8_t *p, u16 data) +{ + p[0] = data; + p[1] = data >> 8; +} + +void filesystem_t::w24l(uint8_t *p, u32 data) +{ + p[0] = data; + p[1] = data >> 8; + p[2] = data >> 16; +} + +void filesystem_t::w32l(uint8_t *p, u32 data) +{ + p[0] = data; + p[1] = data >> 8; + p[2] = data >> 16; + p[3] = data >> 24; +} + +std::string filesystem_t::rstr(const uint8_t *p, u32 size) +{ + std::string res; + for(u32 i=0; i != size; i++) + res += char(*p++); + return res; +} + +uint8_t filesystem_t::r8(const uint8_t *p) +{ + return p[0]; +} + +uint16_t filesystem_t::r16b(const uint8_t *p) +{ + return (p[0] << 8) | p[1]; +} + +uint32_t filesystem_t::r24b(const uint8_t *p) +{ + return (p[0] << 16) | (p[1] << 8) | p[2]; +} + +uint32_t filesystem_t::r32b(const uint8_t *p) +{ + return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; +} + +uint16_t filesystem_t::r16l(const uint8_t *p) +{ + return p[0] | (p[1] << 8); +} + +uint32_t filesystem_t::r24l(const uint8_t *p) +{ + return p[0] | (p[1] << 8) | (p[2] << 16); +} + +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() +{ + fatalerror("rsrc_read_all called on filesystem without resource forks\n"); +} + +std::vector filesystem_t::ifile_t::rsrc_read(u64 start, u64 length) +{ + fatalerror("rsrc_read called on filesystem without resource forks\n"); +} + const char *fs_meta_get_name(fs_meta_name name) { switch(name) { diff --git a/src/lib/formats/fsmgr.h b/src/lib/formats/fsmgr.h index 6ca909e3bcd..ed3a553843e 100644 --- a/src/lib/formats/fsmgr.h +++ b/src/lib/formats/fsmgr.h @@ -281,6 +281,8 @@ protected: virtual fs_meta_data metadata() = 0; virtual std::vector read_all() = 0; virtual std::vector read(u64 start, u64 length) = 0; + virtual std::vector rsrc_read_all(); + virtual std::vector rsrc_read(u64 start, u64 length); }; public: @@ -312,6 +314,8 @@ public: std::vector read_all() { return m_object->read_all(); } std::vector read(u32 start, u32 length) { return m_object->read(start, length); } + 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); } }; filesystem_t(fsblk_t &blockdev, u32 size) : m_blockdev(blockdev) { @@ -324,6 +328,26 @@ public: virtual fs_meta_data metadata(); virtual dir_t root(); + 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); + static void wstr(uint8_t *p, const std::string &str); + static void w8( uint8_t *p, uint8_t data); + static void w16b(uint8_t *p, uint16_t data); + static void w24b(uint8_t *p, uint32_t data); + static void w32b(uint8_t *p, uint32_t data); + static void w16l(uint8_t *p, uint16_t data); + static void w24l(uint8_t *p, uint32_t data); + static void w32l(uint8_t *p, uint32_t data); + + static std::string rstr(const uint8_t *p, uint32_t size); + static uint8_t r8( const uint8_t *p); + static uint16_t r16b(const uint8_t *p); + static uint32_t r24b(const uint8_t *p); + static uint32_t r32b(const uint8_t *p); + static uint16_t r16l(const uint8_t *p); + static uint32_t r24l(const uint8_t *p); + static uint32_t r32l(const uint8_t *p); + protected: fsblk_t &m_blockdev; }; @@ -361,6 +385,7 @@ public: virtual bool can_format() const = 0; virtual bool can_read() const = 0; virtual bool can_write() const = 0; + virtual bool has_rsrc() const = 0; virtual char directory_separator() const; bool has_subdirectories() const { return directory_separator() != 0; } diff --git a/src/tools/floptool.cpp b/src/tools/floptool.cpp index 0204feb0607..a3186b2374b 100644 --- a/src/tools/floptool.cpp +++ b/src/tools/floptool.cpp @@ -652,8 +652,6 @@ static int flopdir(int argc, char *argv[]) fsblk_vec_t blockdev(img); return generic_dir(fs->m_manager, blockdev); - - return 0; } // Should use chd&friends instead, but one thing at a time @@ -696,6 +694,118 @@ 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) +{ + auto load_fs = fm->mount(blockdev); + + std::string opath = srcpath; + std::vector path; + 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); + element.clear(); + } + } else + element += c; + } + if(!element.empty()) + path.push_back(element); + + } else + path.push_back(opath); + + 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]; + } + + auto c = dir.contents(); + unsigned int j; + for(j = 0; j != c.size(); j++) + if(c[j].m_name == path.back()) + break; + if(j == c.size()) { + fprintf(stderr, "Error: file %s%c%s not found\n", apath.c_str(), fm->directory_separator(), path.back().c_str()); + return 1; + } + auto file = dir.file_get(c[j].m_key); + auto meta = file.metadata(); + + if(meta.find(fs_meta_name::length) == meta.end()) { + 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); + + bool has_rsrc = fm->has_rsrc() && meta.find(fs_meta_name::rsrc_length) != meta.end(); + + 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; + } + + filedata = file.rsrc_read_all(); + u8 head[0x2a]; + filesystem_t::w32b(head+0x00, 0x00051607); // Magic + filesystem_t::w32b(head+0x04, 0x00020000); // Version + filesystem_t::fill(head+0x08, 0, 16); // Filler + filesystem_t::w16b(head+0x18, 1); // Number of entries + filesystem_t::w32b(head+0x1a, 2); // Resource fork + 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); + } + + return 0; +} + + static int flopread(int argc, char *argv[]) { if (argc!=7) { @@ -747,66 +857,47 @@ static int flopread(int argc, char *argv[]) delete iog; fsblk_vec_t blockdev(img); - auto load_fs = fs->m_manager->mount(blockdev); + return generic_read(fs->m_manager, blockdev, argv[5], argv[6]); +} - std::string opath = argv[5]; - std::vector path; - if(fs->m_manager->has_subdirectories()) { - abort(); - } else - path.push_back(opath); +// Should use chd&friends instead, but one thing at a time - 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(), fs->m_manager->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(), fs->m_manager->directory_separator(), path[i].c_str()); - return 1; - } - dir = dir.dir_get(c[j].m_key); - apath += fs->m_manager->directory_separator() + path[i]; - } - - auto c = dir.contents(); - unsigned int j; - for(j = 0; j != c.size(); j++) - if(c[j].m_name == path.back()) - break; - if(j == c.size()) { - fprintf(stderr, "Error: file %s%c%s not found\n", apath.c_str(), fs->m_manager->directory_separator(), path.back().c_str()); - return 1; - } - auto file = dir.file_get(c[j].m_key); - auto meta = file.metadata(); - - if(meta.find(fs_meta_name::length) == meta.end()) { - fprintf(stderr, "Error: %s%c%s is not a readable file\n", apath.c_str(), fs->m_manager->directory_separator(), path.back().c_str()); +static int hdread(int argc, char *argv[]) +{ + if (argc!=6) { + fprintf(stderr, "Incorrect number of arguments.\n\n"); + display_usage(); return 1; } - auto filedata = file.read_all(); + auto fs = find_fs_by_name(argv[2]); + if(!fs) { + fprintf(stderr, "Error: Filesystem '%s' unknown\n", argv[2]); + return 1; + } - sprintf(msg, "Error opening %s for writing", argv[6]); - auto fo = fopen(argv[6], "wb"); - if (!fo) { + 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); - fwrite(filedata.data(), filedata.size(), 1, fo); - fclose(fo); - - return 0; + fsblk_vec_t blockdev(img); + return generic_read(fs->m_manager, blockdev, argv[4], argv[5]); } @@ -832,6 +923,8 @@ int CLIB_DECL main(int argc, char *argv[]) return flopread(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 { fprintf(stderr, "Unknown command '%s'\n\n", argv[1]); display_usage();