// license:BSD-3-Clause // copyright-holders:Olivier Galibert // Management of VTech images #include "emu.h" #include "fs_vtech.h" #include "vt_dsk.h" const fs_vtech FS_VTECH; // Floppy only, format is 40 tracks, 1 head, 16 sectors numbered 0-15, 256 bytes/sector. // Filesystem has no subdirectories. // // Track 0 sectors 0-14 have the file names. 16 bytes/entry // offset 0 : File type 'T' (basic) or 'B' (binary) // offset 1 : 0x3a // offset 2-9: File name // offset a : Track number of first file sector // offset b : Sector number of first file sector // offset c-d: Start address (little-endian) // offset e-f: End address (little-endian) // // Files are stored with 126 bytes/sector, and bytes 126 and 127 are // track/sector of the next file data sector. const char *fs_vtech::name() const { return "vtech"; } const char *fs_vtech::description() const { return "VTech (Laser 200/300)"; } void fs_vtech::enumerate_f(floppy_enumerator &fe, uint32_t form_factor, const std::vector &variants) const { if(has(form_factor, variants, floppy_image::FF_525, floppy_image::SSSD)) fe.add(FLOPPY_VTECH_BIN_FORMAT, 163840, "vtech", "VTech"); } std::unique_ptr fs_vtech::mount(fsblk_t &blockdev) const { return std::make_unique(blockdev); } bool fs_vtech::can_format() const { return true; } bool fs_vtech::can_read() const { return true; } bool fs_vtech::can_write() const { return true; } bool fs_vtech::has_rsrc() const { return false; } std::vector fs_vtech::volume_meta_description() const { std::vector res; return res; } fs_meta_data fs_vtech::impl::metadata() { fs_meta_data res; return res; } std::vector fs_vtech::file_meta_description() const { std::vector res; res.emplace_back(fs_meta_description(fs_meta_name::name, fs_meta_type::string, "", false, [](const fs_meta &m) { return m.as_string().size() <= 8; }, "File name, 8 chars")); res.emplace_back(fs_meta_description(fs_meta_name::loading_address, fs_meta_type::number, 0x7ae9, 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::basic, fs_meta_type::flag, true, true, nullptr, "Basic file")); return res; } void fs_vtech::impl::format(const fs_meta_data &meta) { m_blockdev.fill(0); } fs_vtech::impl::impl(fsblk_t &blockdev) : filesystem_t(blockdev, 128), m_root(true) { } filesystem_t::dir_t fs_vtech::impl::root() { if(!m_root) m_root = new root_dir(*this); return m_root.strong(); } void fs_vtech::impl::drop_root_ref() { m_root = nullptr; } void fs_vtech::impl::root_dir::drop_weak_references() { m_fs.drop_root_ref(); } fs_meta_data fs_vtech::impl::root_dir::metadata() { return fs_meta_data(); } std::vector fs_vtech::impl::root_dir::contents() { std::vector res; uint64_t id = 0; for(int sect = 0; sect != 14; sect++) { auto bdir = m_fs.m_blockdev.get(sect); for(u32 i = 0; i != 8; i ++) { u32 off = i*16; u8 type = bdir.r8(off); if(type != 'T' && type != 'B') continue; if(bdir.r8(off+1) != ':') continue; std::string fname = trim_end_spaces(bdir.rstr(off+2, 8)); res.emplace_back(fs_dir_entry(fname, fs_dir_entry_type::file, id)); id++; } } return res; } filesystem_t::file_t fs_vtech::impl::root_dir::file_get(uint64_t key) { if(key >= 15*8) fatalerror("Key out of range\n"); auto bdir = m_fs.m_blockdev.get(key >> 3); int off = (key & 7) << 4; return file_t(new file(m_fs, this, bdir.rodata() + off, key)); } void fs_vtech::impl::root_dir::update_file(u16 key, const u8 *entry) { auto bdir = m_fs.m_blockdev.get(key >> 3); int off = (key & 7) << 4; bdir.copy(off, entry, 16); } filesystem_t::dir_t fs_vtech::impl::root_dir::dir_get(uint64_t key) { fatalerror("Directories not supported\n"); } fs_vtech::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, 16); } void fs_vtech::impl::file::drop_weak_references() { } fs_meta_data fs_vtech::impl::file::metadata() { fs_meta_data res; res.set(fs_meta_name::name, trim_end_spaces(rstr(m_entry+2, 8))); res.set(fs_meta_name::basic, m_entry[0] == 'T'); res.set(fs_meta_name::loading_address, r16l(m_entry + 0xc)); res.set(fs_meta_name::length, ((r16l(m_entry + 0xe) - r16l(m_entry + 0xc) + 1) & 0xffff)); return res; } std::vector fs_vtech::impl::file::read_all() { u8 track = m_entry[0xa]; u8 sector = m_entry[0xb]; int len = ((r16l(m_entry + 0xe) - r16l(m_entry + 0xc)) & 0xffff) + 1; std::vector data(len, 0); int pos = 0; while(pos < len) { if(track >= 40 || sector >= 16) break; auto dblk = m_fs.m_blockdev.get(track*16 + sector); int size = len - pos; if(size > 126) size = 126; memcpy(data.data() + pos, dblk.data(), size); pos += size; track = dblk.r8(126); sector = dblk.r8(127); } return data; } fs_vtech::impl::file_t fs_vtech::impl::root_dir::file_create(const fs_meta_data &info) { // Find the key for the next unused entry for(int sect = 0; sect != 14; sect++) { auto bdir = m_fs.m_blockdev.get(sect); uint64_t id = 0; for(u32 i = 0; i != 16; i ++) { u32 off = i*16; u8 type = bdir.r8(off); if(type != 'T' && type != 'B') { std::string fname = info.get_string(fs_meta_name::name, ""); fname.resize(8, ' '); bdir.w8 (off+0x0, info.get_flag(fs_meta_name::basic, true) ? 'T' : 'B'); bdir.w8 (off+0x1, ':'); bdir.wstr(off+0x2, fname); bdir.w8 (off+0xa, 0x00); bdir.w8 (off+0xb, 0x00); bdir.w16l(off+0xc, info.get_number(fs_meta_name::loading_address, 0x7ae9)); bdir.w16l(off+0xe, bdir.r16l(off+0xc) - 1); // Size 0 initially return file_t(new file(m_fs, this, bdir.rodata() + off, id)); } id++; } } return nullptr; } void fs_vtech::impl::root_dir::file_delete(uint64_t key) { } void fs_vtech::impl::file::replace(const std::vector &data) { u32 cur_len = ((r16l(m_entry + 0xe) - r16l(m_entry + 0xc) + 1) & 0xffff); u32 new_len = data.size(); if(new_len > 65535) new_len = 65535; u32 cur_ns = (cur_len + 125)/126; u32 need_ns = (new_len + 125) / 126; // Enough space? if(cur_ns < need_ns && m_fs.free_block_count() < need_ns - cur_ns) return; u8 track = m_entry[0xa]; u8 sector = m_entry[0xb]; std::vector> tofree; for(u32 i = 0; i != cur_ns; i++) { tofree.emplace_back(std::make_pair(track, sector)); auto dblk = m_fs.m_blockdev.get(track*16 + sector); track = dblk.r8(126); sector = dblk.r8(127); } m_fs.free_blocks(tofree); std::vector> blocks = m_fs.allocate_blocks(need_ns); for(u32 i=0; i != need_ns; i ++) { auto dblk = m_fs.m_blockdev.get(blocks[i].first * 16 + blocks[i].second); u32 len = new_len - i*126; if(len > 126) len = 126; else if(len < 126) dblk.fill(0x00); memcpy(dblk.data(), data.data() + 126*i, len); if(i < need_ns) { dblk.w8(126, blocks[i+1].first); dblk.w8(127, blocks[i+1].second); } else dblk.w16l(126, 0); } u16 end_address = (r16l(m_entry + 0xc) + data.size() - 1) & 0xffff; w16l(m_entry + 0xe, end_address); if(need_ns) { w8(m_entry + 0xa, blocks[0].first); w8(m_entry + 0xb, blocks[0].second); } else w16l(m_entry + 0xa, 0); m_dir->update_file(m_key, m_entry); } void fs_vtech::impl::root_dir::metadata_change(const fs_meta_data &info) { } void fs_vtech::impl::metadata_change(const fs_meta_data &info) { } void fs_vtech::impl::file::metadata_change(const fs_meta_data &info) { if(info.has(fs_meta_name::basic)) w8 (m_entry+0x0, info.get_flag(fs_meta_name::basic) ? 'T' : 'B'); if(info.has(fs_meta_name::name)) { std::string name = info.get_string(fs_meta_name::name); name.resize(8, ' '); wstr(m_entry+0x2, name); } if(info.has(fs_meta_name::loading_address)) { u16 new_loading = info.get_number(fs_meta_name::loading_address); u16 new_end = r16l(m_entry + 0xe) - r16l(m_entry + 0xc) + new_loading; w16l(m_entry + 0xc, new_loading); w16l(m_entry + 0xe, new_end); } m_dir->update_file(m_key, m_entry); } std::vector> fs_vtech::impl::allocate_blocks(u32 count) { std::vector> blocks; if(free_block_count() < count) return blocks; auto fmap = m_blockdev.get(15); for(u8 track = 1; track != 40; track++) for(u8 sector = 0; sector != 16; sector++) { u32 off = (track-1)*2 + (sector / 8); u32 bit = 1 << (sector & 7); if(!(fmap.r8(off) & bit)) { fmap.w8(off, fmap.r8(off) | bit); blocks.emplace_back(std::make_pair(track, sector)); if(blocks.size() == count) return blocks; } } abort(); } void fs_vtech::impl::free_blocks(const std::vector> &blocks) { auto fmap = m_blockdev.get(15); for(auto ref : blocks) { u8 track = ref.first; u8 sector = ref.second; u32 off = (track-1)*2 + (sector / 8); u32 bit = 1 << (sector & 7); fmap.w8(off, fmap.r8(off) & ~bit); } } u32 fs_vtech::impl::free_block_count() { auto fmap = m_blockdev.get(15); u32 nf = 0; for(u32 off = 0; off != (40-1)*2; off++) { u8 m = fmap.r8(off); // Count 1 bits; m = ((m & 0xaa) >> 1) | (m & 0x55); m = ((m & 0xcc) >> 2) | (m & 0x33); m = ((m & 0xf0) >> 4) | (m & 0x0f); nf += 7-m; } return nf; }