mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Merge pull request #668 from davidgiven/ms2000
Add basic support for the MS2000 Microdos file system.
This commit is contained in:
@@ -128,6 +128,7 @@ choices because they can store multiple types of file system.
|
|||||||
| [`icl30`](doc/disk-icl30.md) | ICL Model 30: CP/M; 263kB 35-track DSSD | 🦖 | | CPMFS |
|
| [`icl30`](doc/disk-icl30.md) | ICL Model 30: CP/M; 263kB 35-track DSSD | 🦖 | | CPMFS |
|
||||||
| [`mac`](doc/disk-mac.md) | Macintosh: 400kB/800kB 3.5" GCR | 🦄 | 🦄 | MACHFS |
|
| [`mac`](doc/disk-mac.md) | Macintosh: 400kB/800kB 3.5" GCR | 🦄 | 🦄 | MACHFS |
|
||||||
| [`micropolis`](doc/disk-micropolis.md) | Micropolis: 100tpi MetaFloppy disks | 🦄 | 🦄 | |
|
| [`micropolis`](doc/disk-micropolis.md) | Micropolis: 100tpi MetaFloppy disks | 🦄 | 🦄 | |
|
||||||
|
| [`ms2000`](doc/disk-ms2000.md) | : MS2000 Microdisk Development System | | | MICRODOS |
|
||||||
| [`mx`](doc/disk-mx.md) | DVK MX: Soviet-era PDP-11 clone | 🦖 | | |
|
| [`mx`](doc/disk-mx.md) | DVK MX: Soviet-era PDP-11 clone | 🦖 | | |
|
||||||
| [`n88basic`](doc/disk-n88basic.md) | N88-BASIC: PC8800/PC98 5.25" 77-track 26-sector DSHD | 🦄 | 🦄 | |
|
| [`n88basic`](doc/disk-n88basic.md) | N88-BASIC: PC8800/PC98 5.25" 77-track 26-sector DSHD | 🦄 | 🦄 | |
|
||||||
| [`northstar`](doc/disk-northstar.md) | Northstar: 5.25" hard sectored | 🦄 | 🦄 | |
|
| [`northstar`](doc/disk-northstar.md) | Northstar: 5.25" hard sectored | 🦄 | 🦄 | |
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ LIBFLUXENGINE_SRCS = \
|
|||||||
lib/vfs/imagesectorinterface.cc \
|
lib/vfs/imagesectorinterface.cc \
|
||||||
lib/vfs/lif.cc \
|
lib/vfs/lif.cc \
|
||||||
lib/vfs/machfs.cc \
|
lib/vfs/machfs.cc \
|
||||||
|
lib/vfs/microdos.cc \
|
||||||
lib/vfs/philefs.cc \
|
lib/vfs/philefs.cc \
|
||||||
lib/vfs/prodos.cc \
|
lib/vfs/prodos.cc \
|
||||||
lib/vfs/roland.cc \
|
lib/vfs/roland.cc \
|
||||||
|
|||||||
11
lib/utils.cc
11
lib/utils.cc
@@ -197,6 +197,17 @@ bool doesFileExist(const std::string& filename)
|
|||||||
return f.good();
|
return f.good();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int countSetBits(uint32_t word)
|
||||||
|
{
|
||||||
|
int b = 0;
|
||||||
|
while (word)
|
||||||
|
{
|
||||||
|
b += word & 1;
|
||||||
|
word >>= 1;
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t unbcd(uint32_t bcd)
|
uint32_t unbcd(uint32_t bcd)
|
||||||
{
|
{
|
||||||
uint32_t dec = 0;
|
uint32_t dec = 0;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ extern std::string quote(const std::string& s);
|
|||||||
extern std::string unhex(const std::string& s);
|
extern std::string unhex(const std::string& s);
|
||||||
extern std::string tohex(const std::string& s);
|
extern std::string tohex(const std::string& s);
|
||||||
extern bool doesFileExist(const std::string& filename);
|
extern bool doesFileExist(const std::string& filename);
|
||||||
|
extern int countSetBits(uint32_t word);
|
||||||
extern uint32_t unbcd(uint32_t bcd);
|
extern uint32_t unbcd(uint32_t bcd);
|
||||||
|
|
||||||
template <class K, class V>
|
template <class K, class V>
|
||||||
|
|||||||
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);
|
||||||
|
}
|
||||||
@@ -216,6 +216,9 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystem(
|
|||||||
case FilesystemProto::LIF:
|
case FilesystemProto::LIF:
|
||||||
return Filesystem::createLifFilesystem(config, image);
|
return Filesystem::createLifFilesystem(config, image);
|
||||||
|
|
||||||
|
case FilesystemProto::MICRODOS:
|
||||||
|
return Filesystem::createMicrodosFilesystem(config, image);
|
||||||
|
|
||||||
case FilesystemProto::ZDOS:
|
case FilesystemProto::ZDOS:
|
||||||
return Filesystem::createZDosFilesystem(config, image);
|
return Filesystem::createZDosFilesystem(config, image);
|
||||||
|
|
||||||
|
|||||||
@@ -264,6 +264,8 @@ public:
|
|||||||
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
|
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
|
||||||
static std::unique_ptr<Filesystem> createLifFilesystem(
|
static std::unique_ptr<Filesystem> createLifFilesystem(
|
||||||
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
|
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(
|
static std::unique_ptr<Filesystem> createZDosFilesystem(
|
||||||
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
|
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
|
||||||
static std::unique_ptr<Filesystem> createRolandFsFilesystem(
|
static std::unique_ptr<Filesystem> createRolandFsFilesystem(
|
||||||
|
|||||||
@@ -88,6 +88,9 @@ message LifProto
|
|||||||
[ default = 256, (help) = "LIF filesystem block size" ];
|
[ default = 256, (help) = "LIF filesystem block size" ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message MicrodosProto {}
|
||||||
|
|
||||||
|
// NEXT_TAG: 16
|
||||||
message ZDosProto
|
message ZDosProto
|
||||||
{
|
{
|
||||||
message Location
|
message Location
|
||||||
@@ -110,7 +113,7 @@ message RolandFsProto
|
|||||||
[ (help) = "number of directory entries", default = 79 ];
|
[ (help) = "number of directory entries", default = 79 ];
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEXT_TAG: 17
|
// NEXT_TAG: 18
|
||||||
message FilesystemProto
|
message FilesystemProto
|
||||||
{
|
{
|
||||||
enum FilesystemType
|
enum FilesystemType
|
||||||
@@ -128,8 +131,9 @@ message FilesystemProto
|
|||||||
APPLEDOS = 10;
|
APPLEDOS = 10;
|
||||||
PHILE = 11;
|
PHILE = 11;
|
||||||
LIF = 12;
|
LIF = 12;
|
||||||
ZDOS = 13;
|
MICRODOS = 13;
|
||||||
ROLAND = 14;
|
ZDOS = 14;
|
||||||
|
ROLAND = 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional FilesystemType type = 10
|
optional FilesystemType type = 10
|
||||||
@@ -147,8 +151,9 @@ message FilesystemProto
|
|||||||
optional Smaky6FsProto smaky6 = 11;
|
optional Smaky6FsProto smaky6 = 11;
|
||||||
optional PhileProto phile = 13;
|
optional PhileProto phile = 13;
|
||||||
optional LifProto lif = 14;
|
optional LifProto lif = 14;
|
||||||
optional ZDosProto zdos = 15;
|
optional MicrodosProto microdos = 15;
|
||||||
optional RolandFsProto roland = 16;
|
optional ZDosProto zdos = 16;
|
||||||
|
optional RolandFsProto roland = 17;
|
||||||
|
|
||||||
optional SectorListProto sector_order = 9
|
optional SectorListProto sector_order = 9
|
||||||
[ (help) = "specify the filesystem order of sectors" ];
|
[ (help) = "specify the filesystem order of sectors" ];
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ FORMATS = \
|
|||||||
icl30 \
|
icl30 \
|
||||||
mac \
|
mac \
|
||||||
micropolis \
|
micropolis \
|
||||||
|
ms2000 \
|
||||||
mx \
|
mx \
|
||||||
n88basic \
|
n88basic \
|
||||||
northstar \
|
northstar \
|
||||||
|
|||||||
58
src/formats/ms2000.textpb
Normal file
58
src/formats/ms2000.textpb
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
comment: 'MS2000 Microdisk Development System'
|
||||||
|
|
||||||
|
documentation:
|
||||||
|
<<<
|
||||||
|
The RCA MicroDisk Development System MS2000 is a highly obscure (i.e. I gather
|
||||||
|
that single digit numbers of original machines exist) development system for the
|
||||||
|
RCA1802 series of CPUs, as made famous by the Cosmac ELF. It was a fairly
|
||||||
|
straightforward big bag o'RAM system with a 2kB boot ROM, 62kB of RAM, twin
|
||||||
|
floppy drives and a serial terminal --- CP/M users will find it very familiar.
|
||||||
|
|
||||||
|
Read and writing disks is currently not supported by FluxEngine, but there is
|
||||||
|
basic support for the MicroDisk operating system's file system. This should
|
||||||
|
allow files to be read from MS2000 disk images.
|
||||||
|
|
||||||
|
The disks are normal DD 3.5" disks, using a 70-track, single sided variation of
|
||||||
|
the venerable IBM floppy disk scheme, so allowing 315kB of storage per disk.
|
||||||
|
|
||||||
|
If you have access to flux files for MS2000 disks, please [get in
|
||||||
|
touch](https://github.com/davidgiven/cpm65/issues/new) --- I would like to add
|
||||||
|
better support for these.
|
||||||
|
>>>
|
||||||
|
|
||||||
|
documentation:
|
||||||
|
<<<
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [The EMMA-02 emulator](https://www.emma02.hobby-site.com/ms2000.html), which
|
||||||
|
supports the MS2000 and provides information on it.
|
||||||
|
>>>
|
||||||
|
|
||||||
|
image_reader {
|
||||||
|
filename: "ms2000.img"
|
||||||
|
type: IMAGETYPE_IMG
|
||||||
|
}
|
||||||
|
|
||||||
|
image_writer {
|
||||||
|
filename: "ms2000.img"
|
||||||
|
type: IMAGETYPE_IMG
|
||||||
|
}
|
||||||
|
|
||||||
|
layout {
|
||||||
|
tracks: 70
|
||||||
|
sides: 1
|
||||||
|
tpi: 135
|
||||||
|
layoutdata {
|
||||||
|
sector_size: 512
|
||||||
|
physical {
|
||||||
|
start_sector: 1
|
||||||
|
count: 9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filesystem {
|
||||||
|
type: MICRODOS
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user