summaryrefslogtreecommitdiffstatshomepage
path: root/src/lib/formats/fs_oric_jasmin.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/formats/fs_oric_jasmin.cpp')
-rw-r--r--src/lib/formats/fs_oric_jasmin.cpp517
1 files changed, 491 insertions, 26 deletions
diff --git a/src/lib/formats/fs_oric_jasmin.cpp b/src/lib/formats/fs_oric_jasmin.cpp
index 59ff60fd4b7..dadf76026a7 100644
--- a/src/lib/formats/fs_oric_jasmin.cpp
+++ b/src/lib/formats/fs_oric_jasmin.cpp
@@ -1,12 +1,14 @@
// license:BSD-3-Clause
// copyright-holders:Olivier Galibert
-// Creation of Apple Oric_Jasmin floppy images
+// Management of Oric Jasmin floppy images
#include "emu.h"
#include "fs_oric_jasmin.h"
#include "oric_dsk.h"
+const fs_oric_jasmin FS_ORIC_JASMIN;
+
// Floppy only, format is 41 tracks, 1/2 heads, 17 sectors.
// Filesystem has no subdirectories.
//
@@ -29,7 +31,7 @@
// offset 04+ : 14 file entries, 18 bytes each
// offset 00-01: reference to the first sector of the inode, (ff, xx) when no entry
// offset 02 : U/L for (U)nlocked or (L)ocked file
-// offset 03-0e: filename.ext, space padiing between filename and '.'
+// offset 03-0e: filename.ext, space padding between filename and '.'
// offset 0f : S/D for (S)equential (normal) or (D)irect-access files
// offset 10-11: number of sectors used by the file, including inode, little-endian
//
@@ -39,12 +41,22 @@
// offset 04-05: length of the file in bytes on the first sector, ffff otherwise
// offset 06+ : reference to data sectors, (ff, ff) when done
+const char *fs_oric_jasmin::name() const
+{
+ return "oric_jasmin";
+}
+
+const char *fs_oric_jasmin::description() const
+{
+ return "Oric Jasmin";
+}
+
void fs_oric_jasmin::enumerate_f(floppy_enumerator &fe, uint32_t form_factor, const std::vector<uint32_t> &variants) const
{
if(has(form_factor, variants, floppy_image::FF_3, floppy_image::DSDD))
- fe.add(this, FLOPPY_ORIC_JASMIN_FORMAT, 356864, "oric_jasmin_ds", "Oric Jasmin dual-sided");
+ fe.add(FLOPPY_ORIC_JASMIN_FORMAT, 356864, "oric_jasmin_ds", "Oric Jasmin dual-sided");
if(has(form_factor, variants, floppy_image::FF_3, floppy_image::SSDD))
- fe.add(this, FLOPPY_ORIC_JASMIN_FORMAT, 178432, "oric_jasmin_ss", "Oric Jasmin single-sided");
+ fe.add(FLOPPY_ORIC_JASMIN_FORMAT, 178432, "oric_jasmin_ss", "Oric Jasmin single-sided");
}
std::unique_ptr<filesystem_t> fs_oric_jasmin::mount(fsblk_t &blockdev) const
@@ -64,10 +76,10 @@ bool fs_oric_jasmin::can_read() const
bool fs_oric_jasmin::can_write() const
{
- return false;
+ return true;
}
-bool fs_oric_jasmin::has_subdirectories() const
+bool fs_oric_jasmin::has_rsrc() const
{
return false;
}
@@ -75,7 +87,7 @@ bool fs_oric_jasmin::has_subdirectories() const
std::vector<fs_meta_description> fs_oric_jasmin::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() <= 8; }, "Volume name, up to 8 characters"));
+ res.emplace_back(fs_meta_description(fs_meta_name::name, fs_meta_type::string, "UNTITLED", false, [](const fs_meta &m) { return m.as_string().size() <= 8; }, "Volume name, up to 8 characters"));
return res;
}
@@ -88,8 +100,8 @@ 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);
- return res;
+ res.set(fs_meta_name::name, bdir.rstr(0xf8, len));
+ return res;
}
bool fs_oric_jasmin::validate_filename(std::string name)
@@ -104,8 +116,8 @@ bool fs_oric_jasmin::validate_filename(std::string name)
std::vector<fs_meta_description> fs_oric_jasmin::file_meta_description() const
{
std::vector<fs_meta_description> res;
- res.emplace_back(fs_meta_description(fs_meta_name::name, fs_meta_type::string, "", false, [](const fs_meta &m) { std::string n = std::get<std::string>(m); return validate_filename(n); }, "File name, 8.3"));
- res.emplace_back(fs_meta_description(fs_meta_name::loading_address, fs_meta_type::number, 0x501, false, [](const fs_meta &m) { uint64_t n = std::get<uint64_t>(m); return n < 0x10000; }, "Loading address of the file"));
+ res.emplace_back(fs_meta_description(fs_meta_name::name, fs_meta_type::string, "", false, [](const fs_meta &m) { return validate_filename(m.as_string()); }, "File name, 8.3"));
+ res.emplace_back(fs_meta_description(fs_meta_name::loading_address, fs_meta_type::number, 0x501, false, [](const fs_meta &m) { return m.as_number() < 0x10000; }, "Loading address of the file"));
res.emplace_back(fs_meta_description(fs_meta_name::length, fs_meta_type::number, 0, true, nullptr, "Size of the file in bytes"));
res.emplace_back(fs_meta_description(fs_meta_name::size_in_blocks, fs_meta_type::number, 0, true, nullptr, "Number of blocks used by the file"));
res.emplace_back(fs_meta_description(fs_meta_name::locked, fs_meta_type::flag, false, false, nullptr, "File locked"));
@@ -116,7 +128,7 @@ std::vector<fs_meta_description> fs_oric_jasmin::file_meta_description() const
void fs_oric_jasmin::impl::format(const fs_meta_data &meta)
{
- std::string volume_name = std::get<std::string>(meta.find(fs_meta_name::name)->second);
+ std::string volume_name = meta.get_string(fs_meta_name::name, "UNTITLED");
u32 blocks = m_blockdev.block_count();
m_blockdev.fill(0x6c);
@@ -125,28 +137,21 @@ void fs_oric_jasmin::impl::format(const fs_meta_data &meta)
auto fmap = m_blockdev.get(bblk);
u32 off = 0;
for(u32 blk = 0; blk != blocks; blk += 17) {
- if(blk == bblk) {
- fmap.w8(off , 0xff);
- fmap.w8(off + 1, 0x7f);
- fmap.w8(off + 2, 0x00);
- } else {
- fmap.w8(off , 0xff);
- fmap.w8(off + 1, 0xff);
- fmap.w8(off + 2, 0x01);
- }
+ if(blk == bblk)
+ fmap.w24l(off, 0x07fff);
+ else
+ fmap.w24l(off, 0x1ffff);
off += 3;
}
for(u32 blk = blocks; blk != 17*42*2; blk += 17) {
- fmap.w8(off , 0x00);
- fmap.w8(off + 1, 0x00);
- fmap.w8(off + 2, 0x80);
+ fmap.w24l(off, 0x800000);
off += 3;
}
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);
@@ -155,4 +160,464 @@ void fs_oric_jasmin::impl::format(const fs_meta_data &meta)
bdir.w16l(2, 0x0000);
}
-const filesystem_manager_type FS_ORIC_JASMIN = &filesystem_manager_creator<fs_oric_jasmin>;;
+fs_oric_jasmin::impl::impl(fsblk_t &blockdev) : filesystem_t(blockdev, 256), m_root(true)
+{
+}
+
+bool fs_oric_jasmin::impl::ref_valid(u16 ref)
+{
+ u8 track = ref >> 8;
+ u8 sector = ref & 0xff;
+ if(sector < 1 || sector > 17)
+ return false;
+ if(track >= m_blockdev.block_count()/17)
+ return false;
+ return true;
+}
+
+u32 fs_oric_jasmin::impl::cs_to_block(u16 ref)
+{
+ u8 track = ref >> 8;
+ u8 sector = ref & 0xff;
+ return track * 17 + sector - 1;
+}
+
+u16 fs_oric_jasmin::impl::block_to_cs(u32 block)
+{
+ u8 track = block / 17;
+ u8 sector = (block % 17) + 1;
+ return (track << 8) | sector;
+}
+
+std::string fs_oric_jasmin::impl::read_file_name(const u8 *p)
+{
+ int main_len;
+ for(main_len = 8; main_len > 0; main_len--)
+ if(p[main_len - 1] != ' ')
+ break;
+ int ext_len;
+ for(ext_len = 4; ext_len > 0; ext_len--)
+ if(p[ext_len + 8 - 1] != ' ')
+ break;
+ std::string name;
+ for(int i=0; i != main_len; i++)
+ name += char(p[i]);
+ for(int i=0; i != ext_len; i++)
+ name += char(p[8 + i]);
+ return name;
+}
+
+
+filesystem_t::dir_t fs_oric_jasmin::impl::root()
+{
+ if(!m_root)
+ m_root = new root_dir(*this);
+ return m_root.strong();
+}
+
+void fs_oric_jasmin::impl::drop_root_ref()
+{
+ m_root = nullptr;
+}
+
+void fs_oric_jasmin::impl::root_dir::drop_weak_references()
+{
+ m_fs.drop_root_ref();
+}
+
+fs_meta_data fs_oric_jasmin::impl::root_dir::metadata()
+{
+ return fs_meta_data();
+}
+
+std::vector<fs_dir_entry> fs_oric_jasmin::impl::root_dir::contents()
+{
+ std::vector<fs_dir_entry> res;
+
+ 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);
+ std::string fname = read_file_name(bdir.rodata()+off+3);
+ bool system = ref == 0 && id == 0 && bdir.r32b(off+0xb) == 0x2e535953;
+ if(system)
+ res.emplace_back(fs_dir_entry(fname, fs_dir_entry_type::system_file, 0));
+
+ else if(m_fs.ref_valid(ref))
+ res.emplace_back(fs_dir_entry(fname, fs_dir_entry_type::file, id));
+
+ id++;
+ }
+ u16 ref = bdir.r16b(2);
+ if(!ref || !m_fs.ref_valid(ref))
+ break;
+ bdir = m_fs.m_blockdev.get(cs_to_block(ref));
+ }
+ return res;
+}
+
+std::pair<fsblk_t::block_t, u32> fs_oric_jasmin::impl::root_dir::get_dir_block(uint64_t key)
+{
+ auto bdir = m_fs.m_blockdev.get(20*17+1);
+ while(key >= 14) {
+ u16 ref = bdir.r16b(2);
+ if(!ref || !m_fs.ref_valid(ref))
+ fatalerror("Incorrect file key\n");
+ bdir = m_fs.m_blockdev.get(cs_to_block(ref));
+ key -= 14;
+ }
+ return std::pair<fsblk_t::block_t, u32>(bdir, 4 + key * 18);
+}
+
+filesystem_t::file_t fs_oric_jasmin::impl::root_dir::file_get(uint64_t key)
+{
+ uint64_t rkey = key;
+ auto [bdir, off] = get_dir_block(rkey);
+ 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, 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, 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)
+{
+ fatalerror("Directories not supported\n");
+}
+
+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 fs_oric_jasmin::impl::file::drop_weak_references()
+{
+}
+
+fs_meta_data fs_oric_jasmin::impl::file::metadata()
+{
+ fs_meta_data res;
+
+ 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.set(fs_meta_name::loading_address, dblk.r16l(2));
+ res.set(fs_meta_name::length, dblk.r16l(4));
+
+ return res;
+}
+
+std::vector<u8> fs_oric_jasmin::impl::file::read_all()
+{
+ std::vector<u8> 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 && data.size() < length; pos += 2) {
+ u16 dref = iblk.r16b(pos);
+ if(!m_fs.ref_valid(dref))
+ goto done;
+ 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))
+ break;
+ iblk = m_fs.m_blockdev.get(cs_to_block(ref));
+ }
+ done:
+ data.resize(length);
+ return data;
+}
+
+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);
+}
+
+void fs_oric_jasmin::impl::system_file::drop_weak_references()
+{
+}
+
+fs_meta_data fs_oric_jasmin::impl::system_file::metadata()
+{
+ fs_meta_data res;
+
+ 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;
+}
+
+std::vector<u8> fs_oric_jasmin::impl::system_file::read_all()
+{
+ std::vector<u8> data(0x3e00);
+ for(u32 i = 0; i != 62; i++) {
+ auto dblk = m_fs.m_blockdev.get(i);
+ memcpy(data.data() + 256 * i, dblk.rodata(), 256);
+ }
+ return data;
+}
+
+fs_oric_jasmin::impl::file_t fs_oric_jasmin::impl::root_dir::file_create(const fs_meta_data &info)
+{
+ // 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
+ }
+
+ 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<u8> &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<u16> 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<u16> 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<u8> &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<u16> fs_oric_jasmin::impl::allocate_blocks(u32 count)
+{
+ std::vector<u16> 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<u16> &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;
+}