mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-24 11:11:02 -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 |
|
||||
| [`mac`](doc/disk-mac.md) | Macintosh: 400kB/800kB 3.5" GCR | 🦄 | 🦄 | MACHFS |
|
||||
| [`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 | 🦖 | | |
|
||||
| [`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 | 🦄 | 🦄 | |
|
||||
|
||||
@@ -81,6 +81,7 @@ LIBFLUXENGINE_SRCS = \
|
||||
lib/vfs/imagesectorinterface.cc \
|
||||
lib/vfs/lif.cc \
|
||||
lib/vfs/machfs.cc \
|
||||
lib/vfs/microdos.cc \
|
||||
lib/vfs/philefs.cc \
|
||||
lib/vfs/prodos.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();
|
||||
}
|
||||
|
||||
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 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 tohex(const std::string& s);
|
||||
extern bool doesFileExist(const std::string& filename);
|
||||
extern int countSetBits(uint32_t word);
|
||||
extern uint32_t unbcd(uint32_t bcd);
|
||||
|
||||
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:
|
||||
return Filesystem::createLifFilesystem(config, image);
|
||||
|
||||
case FilesystemProto::MICRODOS:
|
||||
return Filesystem::createMicrodosFilesystem(config, image);
|
||||
|
||||
case FilesystemProto::ZDOS:
|
||||
return Filesystem::createZDosFilesystem(config, image);
|
||||
|
||||
|
||||
@@ -264,6 +264,8 @@ public:
|
||||
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(
|
||||
|
||||
@@ -88,6 +88,9 @@ message LifProto
|
||||
[ default = 256, (help) = "LIF filesystem block size" ];
|
||||
}
|
||||
|
||||
message MicrodosProto {}
|
||||
|
||||
// NEXT_TAG: 16
|
||||
message ZDosProto
|
||||
{
|
||||
message Location
|
||||
@@ -110,7 +113,7 @@ message RolandFsProto
|
||||
[ (help) = "number of directory entries", default = 79 ];
|
||||
}
|
||||
|
||||
// NEXT_TAG: 17
|
||||
// NEXT_TAG: 18
|
||||
message FilesystemProto
|
||||
{
|
||||
enum FilesystemType
|
||||
@@ -128,8 +131,9 @@ message FilesystemProto
|
||||
APPLEDOS = 10;
|
||||
PHILE = 11;
|
||||
LIF = 12;
|
||||
ZDOS = 13;
|
||||
ROLAND = 14;
|
||||
MICRODOS = 13;
|
||||
ZDOS = 14;
|
||||
ROLAND = 15;
|
||||
}
|
||||
|
||||
optional FilesystemType type = 10
|
||||
@@ -147,8 +151,9 @@ message FilesystemProto
|
||||
optional Smaky6FsProto smaky6 = 11;
|
||||
optional PhileProto phile = 13;
|
||||
optional LifProto lif = 14;
|
||||
optional ZDosProto zdos = 15;
|
||||
optional RolandFsProto roland = 16;
|
||||
optional MicrodosProto microdos = 15;
|
||||
optional ZDosProto zdos = 16;
|
||||
optional RolandFsProto roland = 17;
|
||||
|
||||
optional SectorListProto sector_order = 9
|
||||
[ (help) = "specify the filesystem order of sectors" ];
|
||||
|
||||
@@ -21,6 +21,7 @@ FORMATS = \
|
||||
icl30 \
|
||||
mac \
|
||||
micropolis \
|
||||
ms2000 \
|
||||
mx \
|
||||
n88basic \
|
||||
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