mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Merge branch 'master' into psos
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
#include "lib/vfs/vfs.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "lib/utils.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
class AcornDfsFilesystem;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
return _changedSectors.put(track, side, sectorId);
|
||||
}
|
||||
|
||||
virtual bool isReadOnly()
|
||||
virtual bool isReadOnly() override
|
||||
{
|
||||
return (_fluxSink == nullptr);
|
||||
}
|
||||
|
||||
@@ -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
294
lib/vfs/lif.cc
Normal 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);
|
||||
}
|
||||
@@ -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
223
lib/vfs/microdos.cc
Normal 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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
427
lib/vfs/roland.cc
Normal 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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
325
lib/vfs/zdos.cc
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user