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.cpp661
1 files changed, 661 insertions, 0 deletions
diff --git a/src/lib/formats/fs_oric_jasmin.cpp b/src/lib/formats/fs_oric_jasmin.cpp
new file mode 100644
index 00000000000..db24a4a93f8
--- /dev/null
+++ b/src/lib/formats/fs_oric_jasmin.cpp
@@ -0,0 +1,661 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert
+
+// Management of Oric Jasmin floppy images
+
+#include "fs_oric_jasmin.h"
+#include "fsblk.h"
+#include "oric_dsk.h"
+
+#include "multibyte.h"
+
+#include <stdexcept>
+
+using namespace fs;
+
+namespace fs { const oric_jasmin_image ORIC_JASMIN; }
+
+
+// Floppy only, format is 41 tracks, 1/2 heads, 17 sectors.
+// Filesystem has no subdirectories.
+//
+// References are pair of bytes (track + head*41, sector)
+//
+// Track 20 sector 1 has the free bitmap and the volume name
+//
+// offset 3*(track + head*41): bitmap from track, 24 bits LSB first,
+// sector 1 in bit 16, sector 17 in bit 0.
+// 0x01ffff is empty track. Special value
+// 0x800000 marks a full track.
+// offset f6-f7: 0x8080
+// offset f8-ff: volume name, padded with 0x20
+//
+//
+// Track 20 sector 2 has the first directory sector.
+// Directory sector format:
+// offset 00-01: reference to the sector, (0, 0) for the first one
+// offset 02-03: reference to the next sector, (0, 0) on the first sector if it's the end, (ff, 00) for the end of another sector
+// 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 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
+//
+// Inodes:
+// offset 00-01: reference to the next inode sector, (ff, 00) on the last one
+// offset 02-03: loading address for the file on the first sector, ffff otherwise
+// 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
+
+namespace {
+
+class oric_jasmin_impl : public filesystem_t {
+public:
+ oric_jasmin_impl(fsblk_t &blockdev);
+ virtual ~oric_jasmin_impl() = default;
+
+ virtual meta_data volume_metadata() override;
+ virtual std::error_condition volume_metadata_change(const meta_data &info) override;
+ virtual std::pair<std::error_condition, meta_data> metadata(const std::vector<std::string> &path) override;
+ virtual std::error_condition metadata_change(const std::vector<std::string> &path, const meta_data &meta) override;
+
+ virtual std::pair<std::error_condition, std::vector<dir_entry>> directory_contents(const std::vector<std::string> &path) override;
+ virtual std::error_condition rename(const std::vector<std::string> &opath, const std::vector<std::string> &npath) override;
+ virtual std::error_condition remove(const std::vector<std::string> &path) override;
+
+ virtual std::error_condition file_create(const std::vector<std::string> &path, const meta_data &meta) override;
+
+ virtual std::pair<std::error_condition, std::vector<u8>> file_read(const std::vector<std::string> &path) override;
+ virtual std::error_condition file_write(const std::vector<std::string> &path, const std::vector<u8> &data) override;
+
+ virtual std::error_condition format(const meta_data &meta) override;
+
+ static bool validate_filename(std::string name);
+
+private:
+ static u32 cs_to_block(u16 ref);
+ [[maybe_unused]] static u16 block_to_cs(u32 block);
+
+ bool ref_valid(u16 ref);
+ static std::string read_file_name(const u8 *p);
+
+ std::vector<u16> allocate_blocks(u32 count);
+ void free_blocks(const std::vector<u16> &blocks);
+ u32 free_block_count();
+
+ static std::string file_name_read(const u8 *p);
+ static std::string file_name_prepare(std::string name);
+ static bool file_is_system(const u8 *entry);
+ meta_data file_metadata(const u8 *entry);
+ std::tuple<fsblk_t::block_t::ptr, u32, bool> file_find(std::string name);
+};
+}
+
+
+const char *oric_jasmin_image::name() const
+{
+ return "oric_jasmin";
+}
+
+const char *oric_jasmin_image::description() const
+{
+ return "Oric Jasmin";
+}
+
+void oric_jasmin_image::enumerate_f(floppy_enumerator &fe) const
+{
+ fe.add(FLOPPY_ORIC_JASMIN_FORMAT, floppy_image::FF_3, floppy_image::DSDD, 356864, "oric_jasmin_ds", "Oric Jasmin dual-sided");
+ fe.add(FLOPPY_ORIC_JASMIN_FORMAT, floppy_image::FF_3, floppy_image::SSDD, 178432, "oric_jasmin_ss", "Oric Jasmin single-sided");
+}
+
+std::unique_ptr<filesystem_t> oric_jasmin_image::mount(fsblk_t &blockdev) const
+{
+ return std::make_unique<oric_jasmin_impl>(blockdev);
+}
+
+bool oric_jasmin_image::can_format() const
+{
+ return true;
+}
+
+bool oric_jasmin_image::can_read() const
+{
+ return true;
+}
+
+bool oric_jasmin_image::can_write() const
+{
+ return true;
+}
+
+bool oric_jasmin_image::has_rsrc() const
+{
+ return false;
+}
+
+std::vector<meta_description> oric_jasmin_image::volume_meta_description() const
+{
+ std::vector<meta_description> res;
+ res.emplace_back(meta_description(meta_name::name, "UNTITLED", false, [](const meta_value &m) { return m.as_string().size() <= 8; }, "Volume name, up to 8 characters"));
+
+ return res;
+}
+
+std::vector<meta_description> oric_jasmin_image::file_meta_description() const
+{
+ std::vector<meta_description> res;
+ res.emplace_back(meta_description(meta_name::name, "", false, [](const meta_value &m) { return oric_jasmin_impl::validate_filename(m.as_string()); }, "File name, 8.3"));
+ res.emplace_back(meta_description(meta_name::loading_address, 0x501, false, [](const meta_value &m) { return m.as_number() < 0x10000; }, "Loading address of the file"));
+ res.emplace_back(meta_description(meta_name::length, 0, true, nullptr, "Size of the file in bytes"));
+ res.emplace_back(meta_description(meta_name::size_in_blocks, 0, true, nullptr, "Number of blocks used by the file"));
+ res.emplace_back(meta_description(meta_name::locked, false, false, nullptr, "File locked"));
+ res.emplace_back(meta_description(meta_name::sequential, true, false, nullptr, "File sequential"));
+ return res;
+}
+
+
+
+oric_jasmin_impl::oric_jasmin_impl(fsblk_t &blockdev) : filesystem_t(blockdev, 256)
+{
+}
+
+bool 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 oric_jasmin_impl::cs_to_block(u16 ref)
+{
+ u8 track = ref >> 8;
+ if(track == 0xff)
+ abort();
+ u8 sector = ref & 0xff;
+ return track * 17 + sector - 1;
+}
+
+u16 oric_jasmin_impl::block_to_cs(u32 block)
+{
+ u8 track = block / 17;
+ u8 sector = (block % 17) + 1;
+ return (track << 8) | sector;
+}
+
+bool oric_jasmin_impl::validate_filename(std::string name)
+{
+ auto pos = name.find('.');
+ if(pos != std::string::npos)
+ return pos <= 8 && pos > 0 && name.size()-pos-1 <= 3;
+ else
+ return name.size() > 0 && name.size() <= 8;
+}
+
+std::error_condition oric_jasmin_impl::format(const meta_data &meta)
+{
+ std::string volume_name = meta.get_string(meta_name::name, "UNTITLED");
+ u32 blocks = m_blockdev.block_count();
+
+ m_blockdev.fill_all(0x6c);
+
+ u32 bblk = 20*17;
+ auto fmap = m_blockdev.get(bblk);
+ u32 off = 0;
+ for(u32 blk = 0; blk != blocks; blk += 17) {
+ 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->w24l(off, 0x800000);
+ off += 3;
+ }
+
+ fmap->w8(0xf6, 0x80);
+ fmap->w8(0xf7, 0x80);
+ volume_name.resize(8, ' ');
+ fmap->wstr(0xf8, volume_name);
+
+ auto bdir = m_blockdev.get(20*17+1);
+ bdir->fill(0xff);
+ bdir->w16l(0, 0x0000);
+ bdir->w16l(2, 0x0000);
+
+ return std::error_condition();
+}
+
+meta_data oric_jasmin_impl::volume_metadata()
+{
+ meta_data res;
+ auto bdir = m_blockdev.get(20*17);
+ int len = 8;
+ while(len > 0 && bdir->rodata()[0xf8 + len - 1] == ' ')
+ len--;
+
+ res.set(meta_name::name, bdir->rstr(0xf8, len));
+ return res;
+}
+
+std::error_condition oric_jasmin_impl::volume_metadata_change(const meta_data &meta)
+{
+ if(meta.has(meta_name::name)) {
+ std::string volume_name = meta.get_string(meta_name::name);
+ volume_name.resize(8, ' ');
+ m_blockdev.get(20*17)->wstr(0xf8, volume_name);
+ }
+ return std::error_condition();
+}
+
+std::string 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;
+}
+
+std::string oric_jasmin_impl::file_name_read(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;
+}
+
+bool oric_jasmin_impl::file_is_system(const u8 *entry)
+{
+ u16 ref = get_u16be(entry);
+ return ref == 0 && get_u32be(entry+0xb) == 0x2e535953;
+}
+
+
+meta_data oric_jasmin_impl::file_metadata(const u8 *entry)
+{
+ meta_data res;
+
+ res.set(meta_name::name, file_name_read(entry + 3));
+ res.set(meta_name::locked, entry[2] == 'L');
+ res.set(meta_name::sequential, entry[0xf] == 'S');
+ res.set(meta_name::size_in_blocks, get_u16le(entry + 0x10));
+
+ bool sys = file_is_system(entry);
+ if(sys)
+ res.set(meta_name::length, 0x3e00);
+
+ else {
+ u16 ref = get_u16be(entry);
+ auto dblk = m_blockdev.get(cs_to_block(ref));
+ res.set(meta_name::loading_address, dblk->r16l(2));
+ res.set(meta_name::length, dblk->r16l(4));
+ }
+ return res;
+}
+
+std::tuple<fsblk_t::block_t::ptr, u32, bool> oric_jasmin_impl::file_find(std::string name)
+{
+ name = file_name_prepare(name);
+ auto bdir = m_blockdev.get(20*17+1);
+ for(;;) {
+ for(u32 i = 0; i != 14; i ++) {
+ u32 off = 4 + i*18;
+ u16 fref = bdir->r16b(off);
+ if(ref_valid(fref) || file_is_system(bdir->rodata()+off)) {
+ if(memcmp(bdir->rodata() + off + 3, name.data(), 12)) {
+ bool sys = file_is_system(bdir->rodata() + off);
+ return std::make_tuple(bdir, off, sys);
+ }
+ }
+ }
+ u16 ref = bdir->r16b(2);
+ if(!ref || !ref_valid(ref))
+ return std::make_tuple(bdir, 0U, false);
+
+ bdir = m_blockdev.get(cs_to_block(ref));
+ }
+}
+
+std::pair<std::error_condition, meta_data> oric_jasmin_impl::metadata(const std::vector<std::string> &path)
+{
+ if(path.size() != 1)
+ return std::make_pair(error::not_found, meta_data());
+
+ auto [bdir, off, sys] = file_find(path[0]);
+ std::ignore = sys;
+ if(!off)
+ return std::make_pair(error::not_found, meta_data());
+
+ return std::make_pair(std::error_condition(), file_metadata(bdir->rodata() + off));
+}
+
+std::error_condition oric_jasmin_impl::metadata_change(const std::vector<std::string> &path, const meta_data &meta)
+{
+ if(path.size() != 1)
+ return error::not_found;
+
+ auto [bdir, off, sys] = file_find(path[0]);
+ if(!off)
+ return error::not_found;
+
+ u8 *entry = bdir->data() + off;
+ if(meta.has(meta_name::locked))
+ entry[0x02] = meta.get_flag(meta_name::locked) ? 'L' : 'U';
+ if(meta.has(meta_name::name))
+ wstr(entry+0x03, file_name_prepare(meta.get_string(meta_name::name)));
+ if(meta.has(meta_name::sequential))
+ entry[0x0f] = meta.get_flag(meta_name::sequential) ? 'D' : 'S';
+ if(!sys && meta.has(meta_name::loading_address))
+ m_blockdev.get(cs_to_block(get_u16be(entry)))->w16l(2, meta.get_number(meta_name::loading_address));
+
+ return std::error_condition();
+}
+
+std::pair<std::error_condition, std::vector<dir_entry>> oric_jasmin_impl::directory_contents(const std::vector<std::string> &path)
+{
+ std::pair<std::error_condition, std::vector<dir_entry>> res;
+
+ if(path.size() != 0) {
+ res.first = error::not_found;
+ return res;
+ }
+
+ res.first = std::error_condition();
+
+ auto bdir = m_blockdev.get(20*17+1);
+ for(;;) {
+ for(u32 i = 0; i != 14; i ++) {
+ u32 off = 4 + i*18;
+ u16 fref = bdir->r16b(off);
+ if(ref_valid(fref) || file_is_system(bdir->rodata()+off)) {
+ meta_data meta = file_metadata(bdir->rodata()+off);
+ res.second.emplace_back(dir_entry(dir_entry_type::file, meta));
+ }
+ }
+ u16 ref = bdir->r16b(2);
+ if(!ref || !ref_valid(ref))
+ break;
+ bdir = m_blockdev.get(cs_to_block(ref));
+ }
+ return res;
+}
+
+std::error_condition oric_jasmin_impl::rename(const std::vector<std::string> &opath, const std::vector<std::string> &npath)
+{
+ if(opath.size() != 1 || npath.size() != 1)
+ return error::not_found;
+
+ auto [bdir, off, sys] = file_find(opath[0]);
+ std::ignore = sys;
+ if(!off)
+ return error::not_found;
+
+ wstr(bdir->data() + off + 0x03, file_name_prepare(npath[0]));
+
+ return std::error_condition();
+}
+
+std::error_condition oric_jasmin_impl::remove(const std::vector<std::string> &path)
+{
+ return error::unsupported;
+}
+
+std::error_condition oric_jasmin_impl::file_create(const std::vector<std::string> &path, const meta_data &meta)
+{
+ if(path.size() != 0)
+ return error::not_found;
+
+ // 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_blockdev.get(20*17+1);
+ u64 id = 0;
+ for(;;) {
+ for(u32 i = 0; i != 14; i ++) {
+ u32 off = 4 + i*18;
+ u16 ref = bdir->r16b(off);
+ if(!ref_valid(ref))
+ goto found;
+ id++;
+ }
+ u16 ref = bdir->r16b(2);
+ if(!ref || !ref_valid(ref)) {
+ nb ++;
+ break;
+ }
+ bdir = m_blockdev.get(cs_to_block(ref));
+ }
+ found:
+ auto block = allocate_blocks(nb);
+ if(block.empty())
+ return error::no_space;
+
+ auto sblk = m_blockdev.get(cs_to_block(block[0]));
+ sblk->w16b(0, 0xff00); // Next sector
+ sblk->w16l(2, meta.get_number(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_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, meta.get_flag(meta_name::locked, false) ? 'L' : 'U');
+ bdir->wstr(off+0x03, file_name_prepare(meta.get_string(meta_name::name, "")));
+ bdir->w8 (off+0x0f, meta.get_flag(meta_name::sequential, true) ? 'S' : 'D');
+ bdir->w16l(off+0x10, 2); // 2 sectors for an empty file
+
+ return std::error_condition();
+}
+
+std::pair<std::error_condition, std::vector<u8>> oric_jasmin_impl::file_read(const std::vector<std::string> &path)
+{
+ std::vector<u8> data;
+
+ if(path.size() != 1)
+ return std::make_pair(error::not_found, data);
+
+ auto [bdir, off, sys] = file_find(path[0]);
+ if(!off)
+ return std::make_pair(error::not_found, data);
+
+ if(sys) {
+ data.resize(0x3e00);
+ for(u32 i = 0; i != 62; i++) {
+ auto dblk = m_blockdev.get(i);
+ dblk->read(0, data.data() + 256 * i, 256);
+ }
+
+ } else {
+ const u8 *entry = bdir->rodata() + off;
+ u16 ref = get_u16be(entry);
+ auto iblk = m_blockdev.get(cs_to_block(ref));
+ u32 length = iblk->r16l(4);
+ while(ref_valid(ref)) {
+ for(u32 pos = 6; pos != 256 && data.size() < length; pos += 2) {
+ u16 dref = iblk->r16b(pos);
+ if(!ref_valid(dref))
+ goto done;
+ auto dblk = m_blockdev.get(cs_to_block(dref));
+ u32 dpos = data.size();
+ data.resize(dpos + 256);
+ dblk->read(0, data.data() + dpos, 256);
+ if(data.size() >= length)
+ goto done;
+ }
+ ref = iblk->r16b(2);
+ if(!ref_valid(ref))
+ break;
+ iblk = m_blockdev.get(cs_to_block(ref));
+ }
+ done:
+ data.resize(length);
+ }
+
+ return std::make_pair(std::error_condition(), data);
+}
+
+std::error_condition oric_jasmin_impl::file_write(const std::vector<std::string> &path, const std::vector<u8> &data)
+{
+ if(path.size() != 1)
+ return error::not_found;
+
+ auto [bdir, off, sys] = file_find(path[0]);
+ if(!off)
+ return error::not_found;
+
+ if(sys) {
+ if(data.size() != 0x3e00)
+ return error::incorrect_size;
+
+ for(u32 i=0; i != 0x3e; i++)
+ m_blockdev.get(i)->write(0, data.data() + i * 256, 256);
+
+ } else {
+ u8 *entry = bdir->data() + off;
+ u32 cur_ns = get_u16le(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 && free_block_count() < need_ns - cur_ns)
+ return error::no_space;
+
+ u16 load_address = 0;
+ std::vector<u16> tofree;
+ u16 iref = get_u16be(entry);
+ for(u32 i=0; i < cur_ns; i += 125+1) {
+ auto iblk = 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);
+ }
+ free_blocks(tofree);
+
+ std::vector<u16> blocks = allocate_blocks(need_ns);
+ for(u32 i=0; i < need_ns; i += 125+1) {
+ auto iblk = 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_blockdev.get(cs_to_block(blocks[i+j+1]));
+ if(size >= 256)
+ dblk->write(0, data.data() + dpos, 256);
+ else {
+ dblk->write(0, data.data() + dpos, size);
+ dblk->fill(size, 0x55, 256-size);
+ }
+ }
+ }
+ put_u16le(entry + 0x10, need_ns);
+ put_u16be(entry + 0x00, blocks[0]);
+ }
+ return std::error_condition();
+}
+
+std::vector<u16> 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 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 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;
+}