-- license:BSD-3-Clause
-- copyright-holders:MAMEdev Team
---------------------------------------------------------------------------
--
-- tests.lua
--
-- Rules for building tests
--
---------------------------------------------------------------------------
project("mametests")
uuid ("66d4c639-196b-4065-a411-7ee9266564f5")
kind "ConsoleApp"
flags {
"Symbols", -- always include minimum symbols for executables
}
if _OPTIONS["SEPARATE_BIN"]~="1" then
targetdir(MAME_DIR)
end
configuration { "x64", "Release" }
targetsuffix "64"
if _OPTIONS["PROFILE"] then
targetsuffix "64p"
end
configuration { "x64", "Debug" }
targetsuffix "64d"
if _OPTIONS["PROFILE"] then
targetsuffix "64dp"
end
configuration { "x32", "Release" }
targetsuffix ""
if _OPTIONS["PROFILE"] then
targetsuffix "p"
end
configuration { "x32", "Debug" }
targetsuffix "d"
if _OPTIONS["PROFILE"] then
targetsuffix "dp"
end
configuration { "Native", "Release" }
targetsuffix ""
if _OPTIONS["PROFILE"] then
targetsuffix "p"
end
configuration { "Native", "Debug" }
targetsuffix "d"
if _OPTIONS["PROFILE"] then
targetsuffix "dp"
end
configuration { "mingw*" or "vs*" }
targetextension ".exe"
configuration { "rpi" }
targetextension ""
configuration { }
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888 } /* Comment */
.highlight .err { color: #A61717; background-color: #E3D2D2 } /* Error */
.highlight .k { color: #080; font-weight: bold } /* Keyword */
.highlight .ch { color: #888 } /* Comment.Hashbang */
.highlight .cm { color: #888 } /* Comment.Multiline */
.highlight .cp { color: #C00; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888 } /* Comment.Single */
.highlight .cs { color: #C00; font-weight: bold; background-color: #FFF0F0 } /* Comment.Special */
.highlight .gd { color: #000; background-color: #FDD } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #A00 } /* Generic.Error */
.highlight .gh { color: #333 } /* Generic.Heading */
.highlight .gi { color: #000; background-color: #DFD } /* Generic.Inserted */
.highlight .go { color: #888 } /* Generic.Output */
.highlight .gp { color: #555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666 } /* Generic.Subheading */
.highlight .gt { color: #A00 } /* Generic.Traceback */
.highlight .kc { color: #080; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #080; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #080; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #080 } /* Keyword.Pseudo */
.highlight .kr { color: #080; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #00D; font-weight: bold } /* Literal.Number */
.highlight .s { color: #D20; background-color: #FFF0F0 } /* Literal.String */
.highlight .na { color: #369 } /* Name.Attribute */
.highlight .nb { color: #038 } /* Name.Builtin */
.highlight .nc { color: #B06; font-weight: bold } /* Name.Class */
.highlight .no { color: #036; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555 } /* Name.Decorator */
.highlight .ne { color: #B06; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #06B; font-weight: bold } /* Name.Function */
.highlight .nl { color: #369; font-style: italic } /* Name.Label */
.highlight .nn { color: #B06; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #369; font-weight: bold } /* Name.Property */
.highlight .nt { color: #B06; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #369 } /* Name.Variable */
.highlight .ow { color: #080 } /* Operator.Word */
.highlight .w { color: #BBB } /* Text.Whitespace */
.highlight .mb { color: #00D; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #00D; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #00D; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #00D; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #00D; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #D20; background-color: #FFF0F0 } /* Literal.String.Affix */
.highlight .sb { color: #D20; background-color: #FFF0F0 } /* Literal.String.Backtick */
.highlight .sc { color: #D20; background-color: #FFF0F0 } /* Literal.String.Char */
.highlight .dl { color: #D20; background-color: #FFF0F0 } /* Literal.String.Delimiter */
.highlight .sd { color: #D20; background-color: #FFF0F0 } /* Literal.String.Doc */
.highlight .s2 { color: #D20; background-color: #FFF0F0 } /* Literal.String.Double */
.highlight .se { color: #04D; background-color: #FFF0F0 } /* Literal.String.Escape */
.highlight .sh { color: #D20; background-color: #FFF0F0 } /* Literal.String.Heredoc */
.highlight .si { color: #33B; background-color: #FFF0F0 } /* Literal.String.Interpol */
.highlight .sx { color: #2B2; background-color: #F0FFF0 } /* Literal.String.Other */
.highlight .sr { color: #080; background-color: #FFF0FF } /* Literal.String.Regex */
.highlight .s1 { color: #D20; background-color: #FFF0F0 } /* Literal.String.Single */
.highlight .ss { color: #A60; background-color: #FFF0F0 } /* Literal.String.Symbol */
.highlight .bp { color: #038 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #06B; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #369 } /* Name.Variable.Class */
.highlight .vg { color: #D70 } /* Name.Variable.Global */
.highlight .vi { color: #33B } /* Name.Variable.Instance */
.highlight .vm { color: #369 } /* Name.Variable.Magic */
.highlight .il { color: #00D; font-weight: bold } /* Literal.Number.Integer.Long */// license:BSD-3-Clause
// copyright-holders:Olivier Galibert
// Management of VTech images
#include "fs_vtech.h"
#include "vt_dsk.h"
#include <stdexcept>
using namespace fs;
namespace fs { const vtech_image 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.
namespace {
class vtech_impl : public filesystem_t {
public:
vtech_impl(fsblk_t &blockdev);
virtual ~vtech_impl() = default;
virtual meta_data volume_metadata() override;
virtual err_t volume_metadata_change(const meta_data &info) override;
virtual std::pair<err_t, meta_data> metadata(const std::vector<std::string> &path) override;
virtual err_t metadata_change(const std::vector<std::string> &path, const meta_data &meta) override;
virtual std::pair<err_t, std::vector<dir_entry>> directory_contents(const std::vector<std::string> &path) override;
virtual err_t rename(const std::vector<std::string> &opath, const std::vector<std::string> &npath) override;
virtual err_t remove(const std::vector<std::string> &path) override;
virtual err_t file_create(const std::vector<std::string> &path, const meta_data &meta) override;
virtual std::pair<err_t, std::vector<u8>> file_read(const std::vector<std::string> &path) override;
virtual err_t file_write(const std::vector<std::string> &path, const std::vector<u8> &data) override;
virtual err_t format(const meta_data &meta) override;
private:
meta_data file_metadata(const u8 *entry);
std::tuple<fsblk_t::block_t, u32> file_find(std::string name);
std::vector<std::pair<u8, u8>> allocate_blocks(u32 count);
void free_blocks(const std::vector<std::pair<u8, u8>> &blocks);
u32 free_block_count();
};
}
const char *vtech_image::name() const
{
return "vtech";
}
const char *vtech_image::description() const
{
return "VTech (Laser 200/300)";
}
void vtech_image::enumerate_f(floppy_enumerator &fe, u32 form_factor, const std::vector<u32> &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<filesystem_t> vtech_image::mount(fsblk_t &blockdev) const
{
return std::make_unique<vtech_impl>(blockdev);
}
bool vtech_image::can_format() const
{
return true;
}
bool vtech_image::can_read() const
{
return true;
}
bool vtech_image::can_write() const
{
return true;
}
bool vtech_image::has_rsrc() const
{
return false;
}
std::vector<meta_description> vtech_image::volume_meta_description() const
{
std::vector<meta_description> res;
return res;
}
std::vector<meta_description> vtech_image::file_meta_description() const
{
std::vector<meta_description> res;
res.emplace_back(meta_description(meta_name::name, "", false, [](const meta_value &m) { return m.as_string().size() <= 8; }, "File name, 8 chars"));
res.emplace_back(meta_description(meta_name::loading_address, 0x7ae9, 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::basic, true, true, nullptr, "Basic file"));
return res;
}
vtech_impl::vtech_impl(fsblk_t &blockdev) : filesystem_t(blockdev, 128)
{
}
err_t vtech_impl::format(const meta_data &meta)
{
m_blockdev.fill(0);
return ERR_OK;
}
meta_data vtech_impl::volume_metadata()
{
return meta_data();
}
err_t vtech_impl::volume_metadata_change(const meta_data &meta)
{
return ERR_OK;
}
meta_data vtech_impl::file_metadata(const u8 *entry)
{
meta_data res;
res.set(meta_name::name, trim_end_spaces(rstr(entry+2, 8)));
res.set(meta_name::basic, entry[0] == 'T');
res.set(meta_name::loading_address, r16l(entry + 0xc));
res.set(meta_name::length, ((r16l(entry + 0xe) - r16l(entry + 0xc) + 1) & 0xffff));
return res;
}
std::tuple<fsblk_t::block_t, u32> vtech_impl::file_find(std::string name)
{
for(int sect = 0; sect != 14; sect++) {
auto bdir = 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;
if(trim_end_spaces(bdir.rstr(off+2, 8)) == name) {
return std::make_tuple(bdir, i);
}
}
}
return std::make_tuple(fsblk_t::block_t(), 0xffffffff);
}
std::pair<err_t, meta_data> vtech_impl::metadata(const std::vector<std::string> &path)
{
if(path.size() != 1)
return std::make_pair(ERR_NOT_FOUND, meta_data());
auto [bdir, off] = file_find(path[0]);
if(off == 0xffffffff)
return std::make_pair(ERR_NOT_FOUND, meta_data());
return std::make_pair(ERR_OK, file_metadata(bdir.rodata() + off));
}
err_t vtech_impl::metadata_change(const std::vector<std::string> &path, const meta_data &meta)
{
if(path.size() != 1)
return ERR_NOT_FOUND;
auto [bdir, off] = file_find(path[0]);
if(!off)
return ERR_NOT_FOUND;
u8 *entry = bdir.data() + off;
if(meta.has(meta_name::basic))
w8 (entry+0x0, meta.get_flag(meta_name::basic) ? 'T' : 'B');
if(meta.has(meta_name::name)) {
std::string name = meta.get_string(meta_name::name);
name.resize(8, ' ');
wstr(entry+0x2, name);
}
if(meta.has(meta_name::loading_address)) {
u16 new_loading = meta.get_number(meta_name::loading_address);
u16 new_end = r16l(entry + 0xe) - r16l(entry + 0xc) + new_loading;
w16l(entry + 0xc, new_loading);
w16l(entry + 0xe, new_end);
}
return ERR_OK;
}
std::pair<err_t, std::vector<dir_entry>> vtech_impl::directory_contents(const std::vector<std::string> &path)
{
std::pair<err_t, std::vector<dir_entry>> res;
if(path.size() != 0) {
res.first = ERR_NOT_FOUND;
return res;
}
res.first = ERR_OK;
for(int sect = 0; sect != 14; sect++) {
auto bdir = 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;
meta_data meta = file_metadata(bdir.rodata()+off);
res.second.emplace_back(dir_entry(dir_entry_type::file, meta));
}
}
return res;
}
err_t vtech_impl::rename(const std::vector<std::string> &opath, const std::vector<std::string> &npath)
{
if(opath.size() != 1 || npath.size() != 1)
return ERR_NOT_FOUND;
auto [bdir, off] = file_find(opath[0]);
if(!off)
return ERR_NOT_FOUND;
std::string name = npath[0];
name.resize(8, ' ');
wstr(bdir.data() + off + 2, name);
return ERR_OK;
}
err_t vtech_impl::remove(const std::vector<std::string> &path)
{
return ERR_NOT_FOUND;
}
err_t vtech_impl::file_create(const std::vector<std::string> &path, const meta_data &meta)
{
if(path.size() != 0)
return ERR_NOT_FOUND;
// Find the key for the next unused entry
for(int sect = 0; sect != 14; sect++) {
auto bdir = m_blockdev.get(sect);
for(u32 i = 0; i != 16; i ++) {
u32 off = i*16;
u8 type = bdir.r8(off);
if(type != 'T' && type != 'B') {
std::string fname = meta.get_string(meta_name::name, "");
fname.resize(8, ' ');
bdir.w8 (off+0x0, meta.get_flag(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, meta.get_number(meta_name::loading_address, 0x7ae9));
bdir.w16l(off+0xe, bdir.r16l(off+0xc) - 1); // Size 0 initially
return ERR_OK;
}
}
}
return ERR_NO_SPACE;
}
std::pair<err_t, std::vector<u8>> vtech_impl::file_read(const std::vector<std::string> &path)
{
std::vector<u8> data;
if(path.size() != 1)
return std::make_pair(ERR_NOT_FOUND, data);
auto [bdir, off] = file_find(path[0]);
if(off == 0xffffffff)
return std::make_pair(ERR_NOT_FOUND, data);
const u8 *entry = bdir.rodata() + off;
u8 track = entry[0xa];
u8 sector = entry[0xb];
int len = ((r16l(entry + 0xe) - r16l(entry + 0xc)) & 0xffff) + 1;
data.resize(len, 0);
int pos = 0;
while(pos < len) {
if(track >= 40 || sector >= 16)
break;
auto dblk = 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 std::make_pair(ERR_OK, data);
}
err_t vtech_impl::file_write(const std::vector<std::string> &path, const std::vector<u8> &data)
{
if(path.size() != 1)
return ERR_NOT_FOUND;
auto [bdir, off] = file_find(path[0]);
if(off == 0xffffffff)
return ERR_NOT_FOUND;
u8 *entry = bdir.data() + off;
u32 cur_len = ((r16l(entry + 0xe) - r16l(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 && free_block_count() < need_ns - cur_ns)
return ERR_NO_SPACE;
u8 track = entry[0xa];
u8 sector = entry[0xb];
std::vector<std::pair<u8, u8>> tofree;
for(u32 i = 0; i != cur_ns; i++) {
tofree.emplace_back(std::make_pair(track, sector));
auto dblk = m_blockdev.get(track*16 + sector);
track = dblk.r8(126);
sector = dblk.r8(127);
}
free_blocks(tofree);
std::vector<std::pair<u8, u8>> blocks = allocate_blocks(need_ns);
for(u32 i=0; i != need_ns; i ++) {
auto dblk = 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(entry + 0xc) + data.size() - 1) & 0xffff;
w16l(entry + 0xe, end_address);
if(need_ns) {
w8(entry + 0xa, blocks[0].first);
w8(entry + 0xb, blocks[0].second);
} else
w16l(entry + 0xa, 0);
return ERR_OK;
}
std::vector<std::pair<u8, u8>> vtech_impl::allocate_blocks(u32 count)
{
std::vector<std::pair<u8, u8>> 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 vtech_impl::free_blocks(const std::vector<std::pair<u8, u8>> &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 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;
}