Merge branch 'master' into psos

This commit is contained in:
David Given
2023-08-20 21:42:13 +02:00
committed by GitHub
455 changed files with 18318 additions and 12111 deletions

View File

@@ -2,7 +2,6 @@
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include "lib/utils.h"
#include <fmt/format.h>
class AcornDfsFilesystem;

View File

@@ -4,7 +4,6 @@
#include "lib/proto.h"
#include "lib/layout.h"
#include "lib/logger.h"
#include <fmt/format.h>
#include "adflib.h"
#include "adf_blk.h"
@@ -57,7 +56,7 @@ public:
{
}
uint32_t capabilities() const
uint32_t capabilities() const override
{
return OP_GETFSDATA | OP_CREATE | OP_LIST | OP_GETFILE | OP_PUTFILE |
OP_GETDIRENT | OP_DELETE | OP_MOVE | OP_CREATEDIR;
@@ -89,8 +88,8 @@ public:
dev.readOnly = false;
dev.isNativeDev = true;
dev.devType = DEVTYPE_FLOPDD;
dev.cylinders = config.layout().tracks();
dev.heads = config.layout().sides();
dev.cylinders = globalConfig()->layout().tracks();
dev.heads = globalConfig()->layout().sides();
dev.sectors = Layout::getLayoutOfTrack(0, 0)->numSectors;
adfInitDevice(&dev, nullptr, false);
int res = adfCreateFlop(&dev, (char*)volumeName.c_str(), 0);
@@ -230,7 +229,7 @@ public:
throw CannotWriteException();
}
void createDirectory(const Path& path)
void createDirectory(const Path& path) override
{
AdfMount m(this);
if (path.empty())
@@ -420,7 +419,7 @@ private:
static void onAdfWarning(char* message)
{
Logger() << message;
log(message);
}
static void onAdfError(char* message)

View File

@@ -2,7 +2,6 @@
#include "lib/vfs/vfs.h"
#include "lib/utils.h"
#include "lib/config.pb.h"
#include <fmt/format.h>
/* This is described here:
* http://fileformats.archiveteam.org/wiki/Apple_DOS_file_system
@@ -51,7 +50,7 @@ public:
{
}
uint32_t capabilities() const
uint32_t capabilities() const override
{
return OP_LIST | OP_GETDIRENT | OP_GETFSDATA | OP_GETFILE;
}

View File

@@ -1,7 +1,6 @@
#include "lib/globals.h"
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include <fmt/format.h>
/* Number of sectors on a 120kB disk. */
static constexpr int SECTOR_COUNT = 468;
@@ -233,7 +232,7 @@ public:
{
}
uint32_t capabilities() const
uint32_t capabilities() const override
{
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_PUTFILE | OP_GETDIRENT |
OP_DELETE;

View File

@@ -3,7 +3,6 @@
#include "lib/config.pb.h"
#include "lib/proto.h"
#include "lib/utils.h"
#include <fmt/format.h>
enum
{
@@ -127,7 +126,8 @@ class CbmfsFilesystem : public Filesystem
bam.resize(fs->getLogicalSectorCount());
usedBlocks = 0;
unsigned block = 0;
for (int track = 0; track < config.layout().tracks(); track++)
for (int track = 0; track < globalConfig()->layout().tracks();
track++)
{
uint8_t blocks = br.read_8();
uint32_t bitmap = br.read_le24();
@@ -195,7 +195,7 @@ public:
{
}
uint32_t capabilities() const
uint32_t capabilities() const override
{
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
}

View File

@@ -1,7 +1,6 @@
#include "lib/globals.h"
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include <fmt/format.h>
class CpmFsFilesystem : public Filesystem
{
@@ -82,7 +81,7 @@ public:
{
}
uint32_t capabilities() const
uint32_t capabilities() const override
{
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
}

View File

@@ -2,7 +2,6 @@
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include "lib/utils.h"
#include <fmt/format.h>
extern "C"
{
@@ -37,7 +36,7 @@ public:
{
}
uint32_t capabilities() const
uint32_t capabilities() const override
{
return OP_GETFSDATA | OP_CREATE | OP_LIST | OP_GETFILE | OP_PUTFILE |
OP_GETDIRENT | OP_MOVE | OP_CREATEDIR | OP_DELETE;
@@ -80,6 +79,8 @@ public:
currentFatFs = this;
MKFS_PARM parm = {
.fmt = FM_SFD | FM_ANY,
.n_root = _config.root_directory_entries(),
.au_size = _config.cluster_size(),
};
FRESULT res = f_mkfs("", &parm, buffer, sizeof(buffer));
throwError(res);
@@ -198,7 +199,7 @@ public:
throwError(res);
}
void createDirectory(const Path& path)
void createDirectory(const Path& path) override
{
mount();
auto pathStr = path.to_str();
@@ -295,7 +296,7 @@ private:
default:
throw FilesystemException(
fmt::format("unknown fatfs error {}", res));
fmt::format("unknown fatfs error {}", (int)res));
}
}

View File

@@ -44,7 +44,7 @@ public:
return _changedSectors.put(track, side, sectorId);
}
virtual bool isReadOnly()
virtual bool isReadOnly() override
{
return (_fluxSink == nullptr);
}

View File

@@ -20,19 +20,19 @@ public:
public:
std::shared_ptr<const Sector> get(
unsigned track, unsigned side, unsigned sectorId)
unsigned track, unsigned side, unsigned sectorId) override
{
return _image->get(track, side, sectorId);
}
std::shared_ptr<Sector> put(
unsigned track, unsigned side, unsigned sectorId)
unsigned track, unsigned side, unsigned sectorId) override
{
_changed = true;
return _image->put(track, side, sectorId);
}
virtual bool isReadOnly()
virtual bool isReadOnly() override
{
return (_writer == nullptr);
}

294
lib/vfs/lif.cc Normal file
View File

@@ -0,0 +1,294 @@
#include "lib/globals.h"
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include "lib/utils.h"
#include <iomanip>
/* See https://www.hp9845.net/9845/projects/hpdir/#lif_filesystem for
* a description. */
static std::map<uint16_t, std::string> numberToFileType = {
{0x0001, "TEXT" },
{0x00ff, "D-LEX" },
{0xe008, "BIN8x" },
{0xe010, "DTA8x" },
{0xe020, "BAS8x" },
{0xe030, "XM41" },
{0xe040, "ALL41" },
{0xe050, "KEY41" },
{0xe052, "TXT75" },
{0xe053, "APP75" },
{0xe058, "DAT75" },
{0xe060, "STA41" },
{0xe070, "X-M41" },
{0xe080, "PGM41" },
{0xe088, "BAS75" },
{0xe089, "LEX75" },
{0xe08a, "WKS75" },
{0xe08b, "ROM75" },
{0xe0d0, "SDATA" },
{0xe0d1, "TEXT_S" },
{0xe0f0, "DAT71" },
{0xe0f1, "DAT71_S" },
{0xe204, "BIN71" },
{0xe205, "BIN71_S" },
{0xe206, "BIN71_P" },
{0xe207, "BIN71_SP"},
{0xe208, "LEX71" },
{0xe209, "LEX71_S" },
{0xe20a, "LEX71_P" },
{0xe20b, "LEX71_SP"},
{0xe20c, "KEY71" },
{0xe20d, "KEY71_S" },
{0xe214, "BAS71" },
{0xe215, "BAS71_S" },
{0xe216, "BAS71_P" },
{0xe217, "BAS71_SP"},
{0xe218, "FTH71" },
{0xe219, "FTH71_S" },
{0xe21a, "FTH71_P" },
{0xe21b, "FTH71_SP"},
{0xe21c, "ROM71" },
{0xe222, "GRA71" },
{0xe224, "ADR71" },
{0xe22e, "SYM71" },
{0xe942, "SYS9k" },
{0xe946, "HP-UX" },
{0xe950, "BAS9k" },
{0xe961, "BDA9k" },
{0xe971, "BIN9k" },
{0xea0a, "DTA9k" },
{0xea32, "COD9k" },
{0xea3e, "TXT9k" },
};
static std::map<std::string, uint16_t> fileTypeToNumber =
reverseMap(numberToFileType);
static void trimZeros(std::string s)
{
s.erase(std::remove(s.begin(), s.end(), 0), s.end());
}
class LifFilesystem : public Filesystem
{
class LifDirent : public Dirent
{
public:
LifDirent(const LifProto& config, Bytes& bytes)
{
file_type = TYPE_FILE;
ByteReader br(bytes);
filename = trimWhitespace(br.read(10));
uint16_t type = br.read_be16();
location = br.read_be32();
length = br.read_be32() * config.block_size();
int year = unbcd(br.read_8());
int month = unbcd(br.read_8()) + 1;
int day = unbcd(br.read_8());
int hour = unbcd(br.read_8());
int minute = unbcd(br.read_8());
int second = unbcd(br.read_8());
uint16_t volume = br.read_be16();
uint16_t protection = br.read_be16();
uint16_t recordSize = br.read_be16();
if (year >= 70)
year += 1900;
else
year += 2000;
std::tm tm = {.tm_sec = second,
.tm_min = minute,
.tm_hour = hour,
.tm_mday = day,
.tm_mon = month,
.tm_year = year - 1900,
.tm_isdst = -1};
std::stringstream ss;
ss << std::put_time(&tm, "%FT%T%z");
ctime = ss.str();
auto it = numberToFileType.find(type);
if (it != numberToFileType.end())
mode = it->second;
else
mode = fmt::format("0x{:04x}", type);
path = {filename};
attributes[Filesystem::FILENAME] = filename;
attributes[Filesystem::LENGTH] = std::to_string(length);
attributes[Filesystem::FILE_TYPE] = "file";
attributes[Filesystem::MODE] = mode;
attributes["lif.ctime"] = ctime;
attributes["lif.volume"] = std::to_string(volume & 0x7fff);
attributes["lif.protection"] = fmt::format("0x{:x}", protection);
attributes["lif.record_size"] = std::to_string(recordSize);
}
public:
uint32_t location;
std::string ctime;
};
public:
LifFilesystem(
const LifProto& config, std::shared_ptr<SectorInterface> sectors):
Filesystem(sectors),
_config(config)
{
}
uint32_t capabilities() const
{
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
}
FilesystemStatus check() override
{
return FS_OK;
}
std::map<std::string, std::string> getMetadata() override
{
mount();
std::map<std::string, std::string> attributes;
attributes[VOLUME_NAME] = _volumeLabel;
attributes[TOTAL_BLOCKS] = std::to_string(_totalBlocks);
attributes[USED_BLOCKS] = std::to_string(_usedBlocks);
attributes[BLOCK_SIZE] = std::to_string(_config.block_size());
attributes["lif.directory_block"] = std::to_string(_directoryBlock);
attributes["lif.directory_size"] = std::to_string(_directorySize);
return attributes;
}
std::shared_ptr<Dirent> getDirent(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
return findFile(path.front());
}
std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
{
mount();
if (!path.empty())
throw FileNotFoundException();
std::vector<std::shared_ptr<Dirent>> result;
for (auto& de : _dirents)
result.push_back(de);
return result;
}
Bytes getFile(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
auto dirent = findFile(path.front());
return getLifBlock(
dirent->location, dirent->length / _config.block_size());
}
private:
void mount()
{
_sectorSize = getLogicalSectorSize();
_sectorsPerBlock = _sectorSize / _config.block_size();
_rootBlock = getLifBlock(0);
ByteReader rbr(_rootBlock);
if (rbr.read_be16() != 0x8000)
throw BadFilesystemException();
_volumeLabel = trimWhitespace(rbr.read(6));
_directoryBlock = rbr.read_be32();
rbr.skip(4);
_directorySize = rbr.read_be32();
rbr.skip(4);
unsigned tracks = rbr.read_be32();
unsigned heads = rbr.read_be32();
unsigned sectors = rbr.read_be32();
_usedBlocks = 1 + _directorySize;
Bytes directory = getLifBlock(_directoryBlock, _directorySize);
_dirents.clear();
ByteReader br(directory);
while (!br.eof())
{
Bytes direntBytes = br.read(32);
if (direntBytes[0] != 0xff)
{
auto dirent = std::make_unique<LifDirent>(_config, direntBytes);
_usedBlocks += dirent->length / _config.block_size();
_dirents.push_back(std::move(dirent));
}
}
_totalBlocks = std::max(tracks * heads * sectors, _usedBlocks);
}
std::shared_ptr<LifDirent> findFile(const std::string filename)
{
for (const auto& dirent : _dirents)
{
if (dirent->filename == filename)
return dirent;
}
throw FileNotFoundException();
}
Bytes getLifBlock(uint32_t number, uint32_t count)
{
Bytes b;
ByteWriter bw(b);
while (count)
{
bw += getLifBlock(number);
number++;
count--;
}
return b;
}
Bytes getLifBlock(uint32_t number)
{
/* LIF uses 256-byte blocks, but the underlying format can have much
* bigger sectors. */
Bytes sector = getLogicalSector(number / _sectorsPerBlock);
unsigned offset = number % _sectorsPerBlock;
return sector.slice(
offset * _config.block_size(), _config.block_size());
}
private:
const LifProto& _config;
int _sectorSize;
int _sectorsPerBlock;
Bytes _rootBlock;
std::string _volumeLabel;
unsigned _directoryBlock;
unsigned _directorySize;
unsigned _totalBlocks;
unsigned _usedBlocks;
std::vector<std::shared_ptr<LifDirent>> _dirents;
};
std::unique_ptr<Filesystem> Filesystem::createLifFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
{
return std::make_unique<LifFilesystem>(config.lif(), sectors);
}

View File

@@ -3,7 +3,6 @@
#include "lib/config.pb.h"
#include "lib/vfs/applesingle.h"
#include "lib/utils.h"
#include <fmt/format.h>
extern "C"
{
@@ -24,7 +23,7 @@ public:
{
}
uint32_t capabilities() const
uint32_t capabilities() const override
{
return OP_GETFSDATA | OP_CREATE | OP_LIST | OP_GETFILE | OP_PUTFILE |
OP_GETDIRENT | OP_MOVE | OP_CREATEDIR | OP_DELETE;

223
lib/vfs/microdos.cc Normal file
View File

@@ -0,0 +1,223 @@
#include "lib/globals.h"
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include "lib/utils.h"
#include <fmt/format.h>
/* See https://www.hp9845.net/9845/projects/hpdir/#lif_filesystem for
* a description. */
static void trimZeros(std::string s)
{
s.erase(std::remove(s.begin(), s.end(), 0), s.end());
}
class MicrodosFilesystem : public Filesystem
{
struct SDW
{
unsigned start;
unsigned length;
};
class MicrodosDirent : public Dirent
{
public:
MicrodosDirent(MicrodosFilesystem& fs, Bytes& bytes)
{
file_type = TYPE_FILE;
ByteReader br(bytes);
auto stem = trimWhitespace(br.read(6));
auto ext = trimWhitespace(br.read(3));
filename = fmt::format("{}.{}", stem, ext);
br.skip(1);
ssn = br.read_be16();
attr = br.read_8();
Bytes rib = fs.getLogicalSector(ssn);
ByteReader rbr(rib);
for (int i = 0; i < 57; i++)
{
unsigned w = rbr.read_be16();
if (w & 0x8000)
{
/* Last. */
sectors = w & 0x7fff;
break;
}
else
{
/* Each record except the last is 24 bits long. */
w = (w << 8) | rbr.read_8();
sdws.emplace_back(SDW{w & 0xffff, (w >> 16) + 1});
}
}
rbr.seek(500);
lastSectorBytes = rbr.read_be16();
loadSectors = rbr.read_be16();
loadAddress = rbr.read_be16();
startAddress = rbr.read_be16();
length = sectors * 512;
mode = "";
path = {filename};
attributes[Filesystem::FILENAME] = filename;
attributes[Filesystem::LENGTH] = std::to_string(length);
attributes[Filesystem::FILE_TYPE] = "file";
attributes[Filesystem::MODE] = mode;
attributes["microdos.ssn"] = std::to_string(ssn);
attributes["microdos.attr"] = fmt::format("0x{:x}", attr);
attributes["microdos.sdw_count"] = std::to_string(sdws.size());
attributes["microdos.total_sectors"] = std::to_string(sectors);
attributes["microdos.lastSectorBytes"] =
std::to_string(lastSectorBytes);
attributes["microdos.loadSectors"] = std::to_string(loadSectors);
attributes["microdos.loadAddress"] =
fmt::format("0x{:x}", loadAddress);
attributes["microdos.startAddress"] =
fmt::format("0x{:x}", startAddress);
}
public:
unsigned ssn;
unsigned attr;
std::vector<SDW> sdws;
unsigned sectors;
unsigned lastSectorBytes;
unsigned loadSectors;
unsigned loadAddress;
unsigned startAddress;
};
public:
MicrodosFilesystem(
const MicrodosProto& config, std::shared_ptr<SectorInterface> sectors):
Filesystem(sectors),
_config(config)
{
}
uint32_t capabilities() const
{
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
}
FilesystemStatus check() override
{
return FS_OK;
}
std::map<std::string, std::string> getMetadata() override
{
mount();
std::map<std::string, std::string> attributes;
attributes[VOLUME_NAME] = _volumeLabel;
attributes[TOTAL_BLOCKS] = std::to_string(_totalBlocks);
attributes[USED_BLOCKS] = std::to_string(_usedBlocks);
attributes[BLOCK_SIZE] = "512";
return attributes;
}
std::shared_ptr<Dirent> getDirent(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
return findFile(path.front());
}
std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
{
mount();
if (!path.empty())
throw FileNotFoundException();
std::vector<std::shared_ptr<Dirent>> result;
for (auto& de : _dirents)
result.push_back(de);
return result;
}
Bytes getFile(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
auto dirent = findFile(path.front());
Bytes data;
ByteWriter bw(data);
for (const auto& sdw : dirent->sdws)
bw += getLogicalSector(sdw.start, sdw.length);
return data.slice(512);
}
private:
void mount()
{
_rootBlock = getLogicalSector(0);
_catBlock = getLogicalSector(9);
Bytes directory = getLogicalSector(1, 8);
ByteReader rbr(_rootBlock);
rbr.seek(20);
_volumeLabel = trimWhitespace(rbr.read(44));
_dirents.clear();
ByteReader dbr(directory);
while (!dbr.eof())
{
Bytes direntBytes = dbr.read(16);
if ((direntBytes[0] != 0) && (direntBytes[0] != 0xff))
{
auto dirent =
std::make_unique<MicrodosDirent>(*this, direntBytes);
_dirents.push_back(std::move(dirent));
}
}
ByteReader cbr(_catBlock);
_totalBlocks = 630;
_usedBlocks = 0;
for (int i = 0; i < _totalBlocks / 8; i++)
{
uint8_t b = cbr.read_8();
_usedBlocks += countSetBits(b);
}
}
std::shared_ptr<MicrodosDirent> findFile(const std::string filename)
{
for (const auto& dirent : _dirents)
{
if (dirent->filename == filename)
return dirent;
}
throw FileNotFoundException();
}
private:
const MicrodosProto& _config;
Bytes _rootBlock;
Bytes _catBlock;
std::string _volumeLabel;
unsigned _totalBlocks;
unsigned _usedBlocks;
std::vector<std::shared_ptr<MicrodosDirent>> _dirents;
};
std::unique_ptr<Filesystem> Filesystem::createMicrodosFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
{
return std::make_unique<MicrodosFilesystem>(config.microdos(), sectors);
}

View File

@@ -2,7 +2,6 @@
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include "lib/utils.h"
#include <fmt/format.h>
/* Root block:
*
@@ -165,7 +164,7 @@ public:
{
}
uint32_t capabilities() const
uint32_t capabilities() const override
{
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
}

View File

@@ -1,7 +1,6 @@
#include "lib/globals.h"
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include <fmt/format.h>
/* This is described here:
* http://fileformats.archiveteam.org/wiki/ProDOS_file_system
@@ -141,7 +140,7 @@ public:
{
}
uint32_t capabilities() const
uint32_t capabilities() const override
{
return OP_LIST | OP_GETDIRENT | OP_GETFILE | OP_GETFSDATA;
}

427
lib/vfs/roland.cc Normal file
View File

@@ -0,0 +1,427 @@
#include "lib/globals.h"
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include "lib/utils.h"
#include <regex>
static std::string unmangleFilename(const std::string& mangled)
{
std::string extension = mangled.substr(10);
extension.erase(extension.find_last_not_of("_") + 1);
std::string root = mangled.substr(0, 10);
root.erase(root.find_last_not_of("_") + 1);
if (!extension.empty())
return root + "." + extension;
return root;
}
static std::string mangleFilename(const std::string& human)
{
int dot = human.rfind('.');
std::string extension =
(dot == std::string::npos) ? "" : human.substr(dot + 1);
std::string root =
(dot == std::string::npos) ? human : human.substr(0, dot);
if (extension.empty())
extension = "___";
if (extension.size() > 3)
throw BadPathException("Invalid filename: extension too long");
if (root.size() > 10)
throw BadPathException("Invalid filename: root too long");
root = (root + std::string(10, '_')).substr(0, 10);
std::string mangled = root + extension;
static const std::regex checker("[A-Z0-9_$.]*");
if (!std::regex_match(mangled, checker))
throw BadPathException(
"Invalid filename: unsupported characters (remember to use "
"uppercase)");
return mangled;
}
class RolandFsFilesystem : public Filesystem
{
private:
class RolandDirent : public Dirent
{
public:
RolandDirent(const std::string& filename)
{
file_type = TYPE_FILE;
rename(filename);
length = 0;
attributes[Filesystem::FILENAME] = filename;
attributes[Filesystem::LENGTH] = std::to_string(length);
attributes[Filesystem::FILE_TYPE] = "file";
attributes[Filesystem::MODE] = "";
}
void rename(const std::string& name)
{
filename = name;
path = {filename};
}
void putBlock(RolandFsFilesystem* fs, uint8_t offset, uint8_t block)
{
if (blocks.size() <= offset)
blocks.resize(offset+1);
blocks[offset] = block;
length = (offset+1) * fs->_blockSectors * fs->_sectorSize;
attributes[Filesystem::LENGTH] = std::to_string(length);
}
void putBlocks(RolandFsFilesystem* fs, uint8_t offset, Bytes& dirent)
{
for (int i = 0; i < 16; i++)
{
uint8_t blocknumber = dirent[16 + i];
if (!blocknumber)
break;
putBlock(fs, offset+i, blocknumber);
}
}
public:
std::vector<int> blocks;
};
public:
RolandFsFilesystem(
const RolandFsProto& config, std::shared_ptr<SectorInterface> sectors):
Filesystem(sectors),
_config(config)
{
}
uint32_t capabilities() const override
{
return OP_CREATE | OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_PUTFILE |
OP_GETDIRENT | OP_DELETE | OP_MOVE;
}
std::map<std::string, std::string> getMetadata() override
{
mount();
int usedBlocks = 0;
for (bool b : _allocationBitmap)
usedBlocks += b;
std::map<std::string, std::string> attributes;
attributes[VOLUME_NAME] = "";
attributes[TOTAL_BLOCKS] = std::to_string(_filesystemBlocks);
attributes[USED_BLOCKS] = std::to_string(usedBlocks);
attributes[BLOCK_SIZE] = std::to_string(_config.block_size());
return attributes;
}
FilesystemStatus check() override
{
return FS_OK;
}
void create(bool quick, const std::string& volumeName) override
{
if (!quick)
eraseEverythingOnDisk();
init();
_allocationBitmap[0] = true;
rewriteDirectory();
}
std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
{
mount();
if (!path.empty())
throw FileNotFoundException();
std::vector<std::shared_ptr<Dirent>> result;
for (auto& de : _dirents)
result.push_back(de);
return result;
}
std::shared_ptr<Dirent> getDirent(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
return findFile(path.front());
}
Bytes getFile(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
Bytes data;
ByteWriter bw(data);
auto f = findFile(path.front());
for (uint8_t b : f->blocks)
bw += getRolandBlock(b);
return data;
}
void putFile(const Path& path, const Bytes& bytes) override
{
mount();
if (path.size() != 1)
throw BadPathException();
if (findFileOrReturnNull(path.front()))
throw BadPathException("File exists");
int blocks = bytes.size() / _config.block_size();
auto de = std::make_shared<RolandDirent>(
RolandDirent(mangleFilename(path.front())));
ByteReader br(bytes);
int offset = 0;
while (!br.eof())
{
Bytes data =
br.read(_config.block_size()).slice(0, _config.block_size());
int block = allocateBlock();
de->putBlock(this, offset, block);
putRolandBlock(block, data);
offset++;
}
_dirents.push_back(de);
rewriteDirectory();
}
void deleteFile(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
auto de = findFile(path.front());
for (uint8_t b : de->blocks)
freeBlock(b);
for (auto it = _dirents.begin(); it != _dirents.end(); it++)
{
if (*it == de)
{
_dirents.erase(it);
break;
}
}
rewriteDirectory();
}
void moveFile(const Path& oldName, const Path& newName) override
{
mount();
if ((oldName.size() != 1) || (newName.size() != 1))
throw BadPathException();
if (findFileOrReturnNull(newName.front()))
throw BadPathException("File exists");
auto de = findFile(oldName.front());
de->rename(mangleFilename(newName.front()));
rewriteDirectory();
}
private:
void init()
{
_directoryLba = getOffsetOfSector(_config.directory_track(), 0, 0);
_sectorSize = getLogicalSectorSize(0, 0);
_blockSectors = _config.block_size() / _sectorSize;
_filesystemBlocks = getLogicalSectorCount() / _blockSectors;
_midBlock = (getLogicalSectorCount() - _directoryLba) / _blockSectors;
_dirents.clear();
_allocationBitmap.clear();
_allocationBitmap.resize(_filesystemBlocks);
}
void rewriteDirectory()
{
Bytes directory;
ByteWriter bw(directory);
bw.write_8(0);
bw.append("ROLAND-GCRDOS");
bw.write_le16(0x4e);
bw.pad(16);
for (auto& de : _dirents)
{
int blockIndex = 0;
for (;;)
{
if (bw.pos == 0xa00)
throw DiskFullException();
if ((blockIndex % 16) == 0)
{
bw.write_8(0);
int len = de->filename.size();
bw.append(de->filename);
bw.pad(13 - len, '_');
bw.write_8(0);
bw.write_8(blockIndex / 16);
}
if (blockIndex == de->blocks.size())
break;
bw.write_8(de->blocks[blockIndex]);
blockIndex++;
}
bw.pad(16 - (blockIndex % 16), 0);
}
while (bw.pos != 0xa00)
{
bw.write_8(0xe5);
bw.pad(13, ' ');
bw.write_le16(0);
bw.pad(16);
}
for (bool b : _allocationBitmap)
bw.write_8(b ? 0xff : 0x00);
putRolandBlock(0, directory.slice(0, _config.block_size()));
}
void mount()
{
init();
Bytes directory = getRolandBlock(0);
ByteReader br(directory);
br.seek(1);
if (br.read(13) != "ROLAND-GCRDOS")
throw BadFilesystemException();
br.seek(32);
std::map<std::string, std::shared_ptr<RolandDirent>> files;
for (int i = 0; i < _config.directory_entries(); i++)
{
Bytes direntBytes = br.read(32);
if (direntBytes[0] == 0)
{
int extent = direntBytes[15];
std::string filename =
unmangleFilename(direntBytes.slice(1, 13));
std::shared_ptr<RolandDirent> de;
auto it = files.find(filename);
if (it == files.end())
{
files[filename] = de =
std::make_shared<RolandDirent>(filename);
_dirents.push_back(de);
}
else
de = it->second;
de->putBlocks(this, extent*16, direntBytes);
}
}
br.seek(0xa00);
for (int i = 0; i < _filesystemBlocks; i++)
_allocationBitmap[i] = br.read_8();
}
std::shared_ptr<RolandDirent> findFileOrReturnNull(
const std::string filename)
{
for (const auto& dirent : _dirents)
{
if (dirent->filename == filename)
return dirent;
}
return nullptr;
}
std::shared_ptr<RolandDirent> findFile(const std::string filename)
{
std::shared_ptr<RolandDirent> de = findFileOrReturnNull(filename);
if (!de)
throw FileNotFoundException();
return de;
}
int allocateBlock()
{
for (int i = 0; i < _filesystemBlocks; i++)
if (!_allocationBitmap[i])
{
_allocationBitmap[i] = true;
return i;
}
throw DiskFullException();
}
void freeBlock(int block)
{
if (block >= _filesystemBlocks)
throw BadFilesystemException();
if (!_allocationBitmap[block])
throw BadFilesystemException();
_allocationBitmap[block] = false;
}
unsigned blockToLogicalSectorNumber(int block)
{
int track;
if (block < _midBlock)
track = _config.directory_track() + block;
else
track = _config.directory_track() - (1 + block - _midBlock);
return track * _blockSectors;
}
Bytes getRolandBlock(int number)
{
return getLogicalSector(
blockToLogicalSectorNumber(number), _blockSectors);
}
void putRolandBlock(int number, const Bytes& bytes)
{
assert(bytes.size() == _config.block_size());
putLogicalSector(blockToLogicalSectorNumber(number), bytes);
}
private:
const RolandFsProto& _config;
unsigned _sectorSize;
unsigned _blockSectors;
unsigned _midBlock;
unsigned _directoryLba;
unsigned _filesystemBlocks;
std::vector<std::shared_ptr<RolandDirent>> _dirents;
std::vector<bool> _allocationBitmap;
};
std::unique_ptr<Filesystem> Filesystem::createRolandFsFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
{
return std::make_unique<RolandFsFilesystem>(config.roland(), sectors);
}

View File

@@ -11,6 +11,9 @@ class Encoder;
class SectorInterface
{
public:
virtual ~SectorInterface() {}
public:
virtual std::shared_ptr<const Sector> get(
unsigned track, unsigned side, unsigned sectorId) = 0;

View File

@@ -2,7 +2,6 @@
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include "lib/utils.h"
#include <fmt/format.h>
/* A directory entry looks like:
*
@@ -140,7 +139,7 @@ public:
{
}
uint32_t capabilities() const
uint32_t capabilities() const override
{
return OP_LIST | OP_GETFILE | OP_GETFSDATA | OP_GETDIRENT;
}

View File

@@ -151,11 +151,11 @@ void Filesystem::discardChanges()
Filesystem::Filesystem(std::shared_ptr<SectorInterface> sectors):
_sectors(sectors)
{
auto& layout = config.layout();
auto& layout = globalConfig()->layout();
if (!layout.has_tracks() || !layout.has_sides())
Error()
<< "FS: filesystem support cannot be used without concrete layout "
"information";
error(
"FS: filesystem support cannot be used without concrete layout "
"information");
unsigned block = 0;
for (const auto& p :
@@ -166,8 +166,9 @@ Filesystem::Filesystem(std::shared_ptr<SectorInterface> sectors):
auto trackLayout = Layout::getLayoutOfTrack(track, side);
if (trackLayout->numSectors == 0)
Error() << "FS: filesystem support cannot be used without concrete "
"layout information";
error(
"FS: filesystem support cannot be used without concrete "
"layout information");
for (int sectorId : trackLayout->filesystemSectorOrder)
_locations.push_back(std::make_tuple(track, side, sectorId));
@@ -212,8 +213,20 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystem(
case FilesystemProto::PHILE:
return Filesystem::createPhileFilesystem(config, image);
case FilesystemProto::LIF:
return Filesystem::createLifFilesystem(config, image);
case FilesystemProto::MICRODOS:
return Filesystem::createMicrodosFilesystem(config, image);
case FilesystemProto::ZDOS:
return Filesystem::createZDosFilesystem(config, image);
case FilesystemProto::ROLAND:
return Filesystem::createRolandFsFilesystem(config, image);
default:
Error() << "no filesystem configured";
error("no filesystem configured");
return std::unique_ptr<Filesystem>();
}
}
@@ -221,21 +234,21 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystem(
std::unique_ptr<Filesystem> Filesystem::createFilesystemFromConfig()
{
std::shared_ptr<SectorInterface> sectorInterface;
if (config.has_flux_source() || config.has_flux_sink())
if (globalConfig().hasFluxSource() || globalConfig().hasFluxSink())
{
std::shared_ptr<FluxSource> fluxSource;
std::shared_ptr<Decoder> decoder;
std::shared_ptr<FluxSink> fluxSink;
std::shared_ptr<Encoder> encoder;
if (config.flux_source().type() != FluxSourceProto::NOT_SET)
if (globalConfig().hasFluxSource())
{
fluxSource = FluxSource::create(config.flux_source());
decoder = Decoder::create(config.decoder());
fluxSource = globalConfig().getFluxSource();
decoder = globalConfig().getDecoder();
}
if (config.flux_sink().type() == FluxSinkProto::DRIVE)
if (globalConfig()->flux_sink().type() == FLUXTYPE_DRIVE)
{
fluxSink = FluxSink::create(config.flux_sink());
encoder = Encoder::create(config.encoder());
fluxSink = globalConfig().getFluxSink();
encoder = globalConfig().getEncoder();
}
sectorInterface = SectorInterface::createFluxSectorInterface(
fluxSource, fluxSink, encoder, decoder);
@@ -244,17 +257,17 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystemFromConfig()
{
std::shared_ptr<ImageReader> reader;
std::shared_ptr<ImageWriter> writer;
if ((config.image_reader().type() != ImageReaderProto::NOT_SET) &&
doesFileExist(config.image_reader().filename()))
reader = ImageReader::create(config.image_reader());
if (config.image_writer().type() != ImageWriterProto::NOT_SET)
writer = ImageWriter::create(config.image_writer());
if (globalConfig().hasImageReader() &&
doesFileExist(globalConfig()->image_reader().filename()))
reader = globalConfig().getImageReader();
if (globalConfig().hasImageWriter())
writer = globalConfig().getImageWriter();
sectorInterface =
SectorInterface::createImageSectorInterface(reader, writer);
}
return createFilesystem(config.filesystem(), sectorInterface);
return createFilesystem(globalConfig()->filesystem(), sectorInterface);
}
Bytes Filesystem::getSector(unsigned track, unsigned side, unsigned sector)
@@ -269,8 +282,8 @@ Bytes Filesystem::getLogicalSector(uint32_t number, uint32_t count)
{
if ((number + count) > _locations.size())
throw BadFilesystemException(
fmt::format("invalid filesystem: sector {} is out of bounds",
number + count - 1));
fmt::format("invalid filesystem: sector {} is out of bounds ({} maximum)",
number + count - 1, _locations.size()));
Bytes data;
ByteWriter bw(data);

View File

@@ -101,6 +101,14 @@ public:
DiskFullException(const std::string& msg): CannotWriteException(msg) {}
};
class ReadErrorException : public FilesystemException
{
public:
ReadErrorException(): FilesystemException("Fatal read error") {}
ReadErrorException(const std::string& msg): FilesystemException(msg) {}
};
class ReadOnlyFilesystemException : public FilesystemException
{
public:
@@ -255,6 +263,14 @@ public:
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createPhileFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createLifFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createMicrodosFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createZDosFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createRolandFsFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);

View File

@@ -17,7 +17,14 @@ message AcornDfsProto
message Brother120FsProto {}
message FatFsProto {}
message FatFsProto {
optional uint32 cluster_size = 1
[ (help) = "cluster size (for new filesystems); 0 to select automatically",
default = 0 ];
optional uint32 root_directory_entries = 2
[ (help) = "number of entries in the root directory (for new filesystems); 0 to select automatically",
default = 0 ];
}
message CpmFsProto
{
@@ -59,26 +66,58 @@ message CbmfsProto
message ProdosProto {}
message AppledosProto {
optional uint32 filesystem_offset_sectors = 1 [
default = 0,
(help) = "offset the entire offset up the disk this many sectors"
];
message AppledosProto
{
optional uint32 filesystem_offset_sectors = 1 [
default = 0,
(help) = "offset the entire offset up the disk this many sectors"
];
}
message Smaky6FsProto {}
message PhileProto {
optional uint32 block_size = 1 [
default = 1024,
(help) = "Phile filesystem block size"
];
message PhileProto
{
optional uint32 block_size = 1
[ default = 1024, (help) = "Phile filesystem block size" ];
}
// NEXT_TAG: 14
message LifProto
{
optional uint32 block_size = 1
[ default = 256, (help) = "LIF filesystem block size" ];
}
message MicrodosProto {}
// NEXT_TAG: 16
message ZDosProto
{
message Location
{
optional uint32 track = 1 [ (help) = "track number" ];
optional uint32 sector = 3 [ (help) = "sector ID" ];
}
optional Location filesystem_start = 1
[ (help) = "position of the filesystem superblock" ];
}
message RolandFsProto
{
optional uint32 directory_track = 1
[ (help) = "position of the directory", default = 39 ];
optional uint32 block_size = 2
[ (help) = "filesystem block size", default = 3072 ];
optional uint32 directory_entries = 3
[ (help) = "number of directory entries", default = 79 ];
}
// NEXT_TAG: 18
message FilesystemProto
{
enum FilesystemType {
enum FilesystemType
{
NOT_SET = 0;
ACORNDFS = 1;
BROTHER120 = 2;
@@ -88,12 +127,17 @@ message FilesystemProto
MACHFS = 6;
CBMFS = 7;
PRODOS = 8;
SMAKY6 = 9;
APPLEDOS = 10;
PHILE = 11;
SMAKY6 = 9;
APPLEDOS = 10;
PHILE = 11;
LIF = 12;
MICRODOS = 13;
ZDOS = 14;
ROLAND = 15;
}
optional FilesystemType type = 10 [default = NOT_SET, (help) = "filesystem type"];
optional FilesystemType type = 10
[ default = NOT_SET, (help) = "filesystem type" ];
optional AcornDfsProto acorndfs = 1;
optional Brother120FsProto brother120 = 2;
@@ -103,9 +147,14 @@ message FilesystemProto
optional MacHfsProto machfs = 6;
optional CbmfsProto cbmfs = 7;
optional ProdosProto prodos = 8;
optional AppledosProto appledos = 12;
optional Smaky6FsProto smaky6 = 11;
optional PhileProto phile = 13;
optional SectorListProto sector_order = 9 [(help) = "specify the filesystem order of sectors"];
optional AppledosProto appledos = 12;
optional Smaky6FsProto smaky6 = 11;
optional PhileProto phile = 13;
optional LifProto lif = 14;
optional MicrodosProto microdos = 15;
optional ZDosProto zdos = 16;
optional RolandFsProto roland = 17;
optional SectorListProto sector_order = 9
[ (help) = "specify the filesystem order of sectors" ];
}

325
lib/vfs/zdos.cc Normal file
View File

@@ -0,0 +1,325 @@
#include "lib/globals.h"
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include "lib/layout.h"
#include <iomanip>
/* See
* https://oldcomputers.dyndns.org/public/pub/rechner/zilog/zds/manuals/z80-rio_os_userman.pdf,
* page 116. */
enum
{
ZDOS_TYPE_DATA = 0x10,
ZDOS_TYPE_ASCII = 0x20,
ZDOS_TYPE_DIRECTORY = 0x40,
ZDOS_TYPE_PROCEDURE = 0x80
};
enum
{
ZDOS_MODE_FORCE = 1 << 2,
ZDOS_MODE_RANDOM = 1 << 3,
ZDOS_MODE_SECRET = 1 << 4,
ZDOS_MODE_LOCKED = 1 << 5,
ZDOS_MODE_ERASEPROTECT = 1 << 6,
ZDOS_MODE_WRITEPROTECT = 1 << 7
};
static const std::map<uint8_t, std::string> fileTypeMap = {
{0, "INVALID" },
{ZDOS_TYPE_DATA, "DATA" },
{ZDOS_TYPE_ASCII, "ASCII" },
{ZDOS_TYPE_DIRECTORY, "DIRECTORY"},
{ZDOS_TYPE_PROCEDURE, "PROCEDURE"}
};
static std::string convertTime(std::string zdosTime)
{
/* Due to a bug in std::get_time, we can't parse the string directly --- a
* pattern of %y%m%d causes the first four digits of the string to become
* the year. So we need to reform the string. */
zdosTime = fmt::format("{}-{}-{}",
zdosTime.substr(0, 2),
zdosTime.substr(2, 2),
zdosTime.substr(4, 2));
std::tm tm = {};
std::stringstream(zdosTime) >> std::get_time(&tm, "%y-%m-%d");
std::stringstream ss;
ss << std::put_time(&tm, "%FT%T%z");
return ss.str();
}
class ZDosFilesystem : public Filesystem
{
class ZDosDescriptor
{
public:
ZDosDescriptor(ZDosFilesystem* zfs, int block): zfs(zfs)
{
Bytes bytes = zfs->getLogicalSector(block);
ByteReader br(bytes);
br.seek(8);
firstRecord = zfs->readBlockNumber(br);
br.seek(12);
type = br.read_8();
recordCount = br.read_le16();
recordSize = br.read_le16();
br.seek(19);
properties = br.read_8();
startAddress = br.read_le16();
lastRecordSize = br.read_le16();
br.seek(24);
ctime = br.read(8);
mtime = br.read(8);
rewind();
}
void rewind()
{
currentRecord = firstRecord;
eof = false;
}
Bytes readRecord()
{
assert(!eof);
int count = recordSize / 0x80;
Bytes result;
ByteWriter bw(result);
while (count--)
{
Bytes sector = zfs->getLogicalSector(currentRecord);
ByteReader br(sector);
bw += br.read(0x80);
br.skip(2);
int sectorId = br.read_8();
int track = br.read_8();
currentRecord = zfs->toBlockNumber(sectorId, track);
if (sectorId == 0xff)
eof = true;
}
return result;
}
public:
ZDosFilesystem* zfs;
uint16_t firstRecord;
uint8_t type;
uint16_t recordCount;
uint16_t recordSize;
uint8_t properties;
uint16_t startAddress;
uint16_t lastRecordSize;
std::string ctime;
std::string mtime;
uint16_t currentRecord;
bool eof;
};
class ZDosDirent : public Dirent
{
public:
ZDosDirent(ZDosFilesystem* zfs,
const std::string& filename,
int descriptorBlock):
zfs(zfs),
descriptorBlock(descriptorBlock),
zd(zfs, descriptorBlock)
{
file_type = TYPE_FILE;
this->filename = filename;
length = (zd.recordCount - 1) * zd.recordSize + zd.lastRecordSize;
mode = "";
if (zd.properties & ZDOS_MODE_FORCE)
mode += 'F';
if (zd.properties & ZDOS_MODE_RANDOM)
mode += 'R';
if (zd.properties & ZDOS_MODE_SECRET)
mode += 'S';
if (zd.properties & ZDOS_MODE_LOCKED)
mode += 'L';
if (zd.properties & ZDOS_MODE_ERASEPROTECT)
mode += 'E';
if (zd.properties & ZDOS_MODE_WRITEPROTECT)
mode += 'W';
path = {filename};
attributes[Filesystem::FILENAME] = filename;
attributes[Filesystem::LENGTH] = std::to_string(length);
attributes[Filesystem::FILE_TYPE] = "file";
attributes[Filesystem::MODE] = mode;
attributes["zdos.descriptor_record"] =
std::to_string(descriptorBlock);
attributes["zdos.first_record"] = std::to_string(zd.firstRecord);
attributes["zdos.record_size"] = std::to_string(zd.recordSize);
attributes["zdos.record_count"] = std::to_string(zd.recordCount);
attributes["zdos.last_record_size"] =
std::to_string(zd.lastRecordSize);
attributes["zdos.start_address"] =
fmt::format("0x{:04x}", zd.startAddress);
attributes["zdos.type"] = fileTypeMap.at(zd.type & 0xf0);
attributes["zdos.ctime"] = convertTime(zd.ctime);
attributes["zdos.mtime"] = convertTime(zd.mtime);
}
public:
ZDosFilesystem* zfs;
int descriptorBlock;
ZDosDescriptor zd;
};
public:
ZDosFilesystem(
const ZDosProto& config, std::shared_ptr<SectorInterface> sectors):
Filesystem(sectors),
_config(config)
{
}
uint32_t capabilities() const
{
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
}
FilesystemStatus check() override
{
return FS_OK;
}
std::map<std::string, std::string> getMetadata() override
{
mount();
std::map<std::string, std::string> attributes;
attributes[VOLUME_NAME] = "";
attributes[TOTAL_BLOCKS] = std::to_string(_totalBlocks);
attributes[USED_BLOCKS] = std::to_string(_usedBlocks);
attributes[BLOCK_SIZE] = "128";
return attributes;
}
std::shared_ptr<Dirent> getDirent(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
return findFile(path.front());
}
std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
{
mount();
if (!path.empty())
throw FileNotFoundException();
std::vector<std::shared_ptr<Dirent>> result;
for (auto& de : _dirents)
result.push_back(de);
return result;
}
Bytes getFile(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
auto dirent = findFile(path.front());
dirent->zd.rewind();
Bytes bytes;
ByteWriter bw(bytes);
while (!dirent->zd.eof)
{
bw += dirent->zd.readRecord();
}
return bytes.slice(0, dirent->length);
}
private:
void mount()
{
_sectorsPerTrack = Layout::getLayoutOfTrack(0, 0)->numSectors;
int rootBlock = toBlockNumber(_config.filesystem_start().sector(),
_config.filesystem_start().track());
ZDosDescriptor zd(this, rootBlock);
if (zd.type != ZDOS_TYPE_DIRECTORY)
throw BadFilesystemException();
_totalBlocks = getLogicalSectorCount();
_usedBlocks = (zd.recordCount * zd.recordSize) / 0x80 + 1;
while (!zd.eof)
{
Bytes bytes = zd.readRecord();
ByteReader br(bytes);
for (;;)
{
int len = br.read_8();
if (len == 0xff)
break;
std::string filename = br.read(len & 0x7f);
int descriptorBlock = readBlockNumber(br);
auto dirent = std::make_unique<ZDosDirent>(
this, filename, descriptorBlock);
_usedBlocks +=
(dirent->zd.recordCount * dirent->zd.recordSize) / 0x80 + 1;
_dirents.push_back(std::move(dirent));
}
}
}
std::shared_ptr<ZDosDirent> findFile(const std::string filename)
{
for (const auto& dirent : _dirents)
{
if (dirent->filename == filename)
return dirent;
}
throw FileNotFoundException();
}
int toBlockNumber(int sectorId, int track)
{
return track * _sectorsPerTrack + sectorId;
}
int readBlockNumber(ByteReader& br)
{
int sectorId = br.read_8();
int track = br.read_8();
return toBlockNumber(sectorId, track);
}
private:
const ZDosProto& _config;
unsigned _sectorsPerTrack;
unsigned _totalBlocks;
unsigned _usedBlocks;
std::vector<std::shared_ptr<ZDosDirent>> _dirents;
};
std::unique_ptr<Filesystem> Filesystem::createZDosFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
{
return std::make_unique<ZDosFilesystem>(config.zdos(), sectors);
}