1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 08:49:37 +00:00

Merge pull request #334 from TomHarte/DMK

Adds support for the DMK file format
This commit is contained in:
Thomas Harte 2018-01-09 19:22:00 -08:00 committed by GitHub
commit e7bc7b94c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 289 additions and 17 deletions

View File

@ -281,6 +281,8 @@
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */; };
4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA799931D8B656E0045123D /* StaticAnalyser.cpp */; };
4BAD13441FF709C700FD114A /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; };
4BAF2B4E2004580C00480230 /* DMK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAF2B4C2004580C00480230 /* DMK.cpp */; };
4BAF2B4F2004580C00480230 /* DMK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAF2B4C2004580C00480230 /* DMK.cpp */; };
4BB17D4E1ED7909F00ABD1E1 /* tests.expected.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */; };
4BB17D4F1ED7909F00ABD1E1 /* tests.in.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */; };
4BB298F11B587D8400A49093 /* start in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E51B587D8300A49093 /* start */; };
@ -938,6 +940,8 @@
4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ConfigurationTarget.hpp; sourceTree = "<group>"; };
4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Disk.hpp; sourceTree = "<group>"; };
4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Storage.hpp; sourceTree = "<group>"; };
4BAF2B4C2004580C00480230 /* DMK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DMK.cpp; sourceTree = "<group>"; };
4BAF2B4D2004580C00480230 /* DMK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DMK.hpp; sourceTree = "<group>"; };
4BB06B211F316A3F00600C7A /* ForceInline.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ForceInline.hpp; sourceTree = "<group>"; };
4BB146C61F49D7D700253439 /* Sleeper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sleeper.hpp; sourceTree = "<group>"; };
4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = "<group>"; };
@ -1786,6 +1790,7 @@
4B45188D1F75FD1B00926311 /* AcornADF.cpp */,
4B45188F1F75FD1B00926311 /* CPCDSK.cpp */,
4B4518911F75FD1B00926311 /* D64.cpp */,
4BAF2B4C2004580C00480230 /* DMK.cpp */,
4B4518931F75FD1B00926311 /* G64.cpp */,
4B4518951F75FD1B00926311 /* HFE.cpp */,
4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */,
@ -1795,6 +1800,7 @@
4B45188E1F75FD1B00926311 /* AcornADF.hpp */,
4B4518901F75FD1B00926311 /* CPCDSK.hpp */,
4B4518921F75FD1B00926311 /* D64.hpp */,
4BAF2B4D2004580C00480230 /* DMK.hpp */,
4B4518941F75FD1B00926311 /* G64.hpp */,
4B4518961F75FD1B00926311 /* HFE.hpp */,
4B58601D1F806AB200AEE2E3 /* MFMSectorDump.hpp */,
@ -3406,6 +3412,7 @@
4B055ADF1FAE9B4C0060FFFF /* IRQDelegatePortHandler.cpp in Sources */,
4B055AB51FAE860F0060FFFF /* TapePRG.cpp in Sources */,
4B055AE01FAE9B660060FFFF /* CRT.cpp in Sources */,
4BAF2B4F2004580C00480230 /* DMK.cpp in Sources */,
4B0E04F21FC9EAA800F43484 /* StaticAnalyser.cpp in Sources */,
4B055AD01FAE9B030060FFFF /* Tape.cpp in Sources */,
4B055A961FAE85BB0060FFFF /* Commodore.cpp in Sources */,
@ -3439,6 +3446,7 @@
4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */,
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */,
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */,
4BAF2B4E2004580C00480230 /* DMK.cpp in Sources */,
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */,
4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */,
4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */,

View File

@ -265,11 +265,6 @@
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>org.akop.cocoamsx.filetype.cassette</string>
<string>com.clocksignal.cas</string>
</array>
<key>CFBundleTypeExtensions</key>
<array>
<string>cas</string>
@ -281,11 +276,30 @@
<string>MSX Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>org.akop.cocoamsx.filetype.cassette</string>
<string>com.clocksignal.cas</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>dmk</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy35</string>
<key>CFBundleTypeName</key>
<string>Disk Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>

View File

@ -29,6 +29,7 @@
#include "../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
#include "../Storage/Disk/DiskImage/Formats/D64.hpp"
#include "../Storage/Disk/DiskImage/Formats/G64.hpp"
#include "../Storage/Disk/DiskImage/Formats/DMK.hpp"
#include "../Storage/Disk/DiskImage/Formats/HFE.hpp"
#include "../Storage/Disk/DiskImage/Formats/MSXDSK.hpp"
#include "../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
@ -90,6 +91,7 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)

View File

@ -15,7 +15,7 @@ namespace Storage {
namespace Disk {
/*!
Provies a @c Disk containing an ADF disk image a decoded sector dump of an Acorn ADFS disk.
Provides a @c Disk containing an ADF disk image a decoded sector dump of an Acorn ADFS disk.
*/
class AcornADF: public MFMSectorDump {
public:

View File

@ -19,7 +19,7 @@ namespace Storage {
namespace Disk {
/*!
Provies a @c Disk containing an Amstrad CPC-stype disk image some arrangement of sectors with status bits.
Provides a @c Disk containing an Amstrad CPC-type disk image some arrangement of sectors with status bits.
*/
class CPCDSK: public DiskImage {
public:

View File

@ -16,7 +16,7 @@ namespace Storage {
namespace Disk {
/*!
Provies a @c Disk containing a D64 disk image a decoded sector dump of a C1540-format disk.
Provides a @c Disk containing a D64 disk image a decoded sector dump of a C1540-format disk.
*/
class D64: public DiskImage {
public:

View File

@ -0,0 +1,188 @@
//
// DMK.cpp
// Clock Signal
//
// Created by Thomas Harte on 08/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "DMK.hpp"
#include "../../Encodings/MFM/Constants.hpp"
#include "../../Encodings/MFM/Encoder.hpp"
#include "../../Track/PCMTrack.hpp"
using namespace Storage::Disk;
namespace {
std::unique_ptr<Storage::Encodings::MFM::Encoder> new_encoder(Storage::Disk::PCMSegment &segment, bool is_double_density) {
std::unique_ptr<Storage::Encodings::MFM::Encoder> encoder;
if(is_double_density) {
encoder = Storage::Encodings::MFM::GetMFMEncoder(segment.data);
segment.length_of_a_bit = Storage::Encodings::MFM::MFMBitLength;
} else {
encoder = Storage::Encodings::MFM::GetFMEncoder(segment.data);
segment.length_of_a_bit = Storage::Encodings::MFM::FMBitLength;
}
return encoder;
}
}
DMK::DMK(const char *file_name) :
file_(file_name) {
// Determine whether this DMK represents a read-only disk (whether intentionally,
// or by virtue of placement).
uint8_t read_only_byte = file_.get8();
if(read_only_byte != 0x00 && read_only_byte != 0xff) throw ErrorNotDMK;
is_read_only_ = (read_only_byte == 0xff) || file_.get_is_known_read_only();
// Read track count and size.
head_position_count_ = static_cast<int>(file_.get8());
track_length_ = static_cast<long>(file_.get16le());
// Track length must be at least 0x80, as that's the size of the IDAM
// table before track contents.
if(track_length_ < 0x80) throw ErrorNotDMK;
// Read the file flags and apply them.
uint8_t flags = file_.get8();
head_count_ = 2 - ((flags & 0x10) >> 4);
head_position_count_ /= head_count_;
is_purely_single_density_ = !!(flags & 0x40);
// Skip to the end of the header and check that this is
// "in the emulator's native format".
file_.seek(0xc, SEEK_SET);
uint32_t format = file_.get32le();
if(format) throw ErrorNotDMK;
}
int DMK::get_head_position_count() {
return head_position_count_;
}
int DMK::get_head_count() {
return head_count_;
}
bool DMK::get_is_read_only() {
return true;
// Given that track serialisation is not yet implemented, treat all DMKs as read-only.
// return is_read_only_;
}
long DMK::get_file_offset_for_position(Track::Address address) {
return (address.head*head_count_ + address.position) * track_length_ + 16;
}
std::shared_ptr<::Storage::Disk::Track> DMK::get_track_at_position(::Storage::Disk::Track::Address address) {
file_.seek(get_file_offset_for_position(address), SEEK_SET);
// Read the IDAM table.
uint16_t idam_locations[64];
std::size_t idam_count = 0;
for(std::size_t c = 0; c < sizeof(idam_locations) / sizeof(*idam_locations); ++c) {
idam_locations[idam_count] = file_.get16le();
if((idam_locations[idam_count] & 0x7fff) >= 128) {
idam_count++;
}
}
// Grab the rest of the track.
std::vector<uint8_t> track = file_.read(static_cast<std::size_t>(track_length_ - 0x80));
// Default to outputting double density unless the disk doesn't support it.
bool is_double_density = !is_purely_single_density_;
std::vector<PCMSegment> segments;
std::unique_ptr<Encodings::MFM::Encoder> encoder;
segments.emplace_back();
encoder = new_encoder(segments.back(), is_double_density);
std::size_t idam_pointer = 0;
const std::size_t track_length = static_cast<std::size_t>(track_length_) - 0x80;
std::size_t track_pointer = 0;
while(track_pointer < track_length) {
// Determine bytes left until next IDAM.
std::size_t destination;
if(idam_pointer != idam_count) {
destination = (idam_locations[idam_pointer] & 0x7fff) - 0x80;
} else {
destination = track_length;
}
// Output every intermediate byte.
if(!is_double_density && !is_purely_single_density_) {
is_double_density = true;
segments.emplace_back();
encoder = new_encoder(segments.back(), is_double_density);
}
while(track_pointer < destination) {
encoder->add_byte(track[track_pointer]);
track_pointer++;
}
// Exit now if that's it.
if(destination == track_length) break;
// Being now located at the IDAM, check for a change of encoding.
bool next_is_double_density = !!(idam_locations[idam_pointer] & 0x8000);
if(next_is_double_density != is_double_density) {
is_double_density = next_is_double_density;
segments.emplace_back();
encoder = new_encoder(segments.back(), is_double_density);
}
// Now at the IDAM, which will always be an FE regardless of FM/MFM encoding,
// presumably through misunderstanding of the designer? Write out a real IDAM
// for the current density, then the rest of the ID — four bytes for the address
// plus two for the CRC. Keep a copy of the header while we're here, so that the
// size of the sector is known momentarily.
std::size_t step_rate = (!is_double_density && !is_purely_single_density_) ? 2 : 1;
encoder->add_ID_address_mark();
uint8_t header[6];
for(int c = 0; c < 6; ++c) {
track_pointer += step_rate;
encoder->add_byte(track[track_pointer]);
header[c] = track[track_pointer];
}
track_pointer += step_rate;
// Now write out as many bytes as are found prior to an FB or F8 (same comment as
// above: those are the FM-esque marks, but it seems as though transcription to MFM
// is implicit).
while(true) {
uint8_t next_byte = track[track_pointer];
track_pointer += step_rate;
if(next_byte == 0xfb || next_byte == 0xf8) {
// Write a data or deleted data address mark.
if(next_byte == 0xfb) encoder->add_data_address_mark();
else encoder->add_deleted_data_address_mark();
break;
}
encoder->add_byte(next_byte);
}
// Now write out a data mark (the file format appears to leave these implicit?),
// then the sector contents plus the CRC.
encoder->add_data_address_mark();
int sector_size = 2 + (128 << header[3]);
while(sector_size--) {
encoder->add_byte(track[track_pointer]);
track_pointer += step_rate;
}
idam_pointer++;
}
// All segments should be exactly their number of bits in length.
for(auto &segment : segments) {
segment.number_of_bits = static_cast<unsigned int>(segment.data.size() * 8);
}
return std::make_shared<PCMTrack>(segments);
}

View File

@ -0,0 +1,57 @@
//
// DMK.hpp
// Clock Signal
//
// Created by Thomas Harte on 08/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef DMK_hpp
#define DMK_hpp
#include "../DiskImage.hpp"
#include "../../../FileHolder.hpp"
namespace Storage {
namespace Disk {
/*!
Provides a @c Disk containing a DMK disk image mostly a decoded byte stream, but with
a record of IDAM locations.
*/
class DMK: public DiskImage {
public:
/*!
Construct a @c DMK containing content from the file with name @c file_name.
@throws ErrorNotDMK if this file doesn't appear to be a DMK.
*/
DMK(const char *file_name);
enum {
ErrorNotDMK
};
// implemented to satisfy @c Disk
int get_head_position_count() override;
int get_head_count() override;
bool get_is_read_only() override;
std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) override;
private:
FileHolder file_;
long get_file_offset_for_position(Track::Address address);
bool is_read_only_;
int head_position_count_;
int head_count_;
long track_length_;
bool is_purely_single_density_;
};
}
}
#endif /* DMK_hpp */

View File

@ -16,7 +16,7 @@ namespace Storage {
namespace Disk {
/*!
Provies a @c Disk containing a G64 disk image a raw but perfectly-clocked GCR stream.
Provides a @c Disk containing a G64 disk image a raw but perfectly-clocked GCR stream.
*/
class G64: public DiskImage {
public:

View File

@ -16,7 +16,7 @@ namespace Storage {
namespace Disk {
/*!
Provies a @c Disk containing an HFE disk image a bit stream representation of a floppy.
Provides a @c Disk containing an HFE disk image a bit stream representation of a floppy.
*/
class HFE: public DiskImage {
public:

View File

@ -16,7 +16,7 @@ namespace Storage {
namespace Disk {
/*!
Provies the base for writeable [M]FM disk images that just contain contiguous sector content dumps.
Provides the base for writeable [M]FM disk images that just contain contiguous sector content dumps.
*/
class MFMSectorDump: public DiskImage {
public:

View File

@ -8,10 +8,10 @@
#include "OricMFMDSK.hpp"
#include "../../Track/PCMTrack.hpp"
#include "../../Encodings/MFM/Constants.hpp"
#include "../../Encodings/MFM/Shifter.hpp"
#include "../../Encodings/MFM/Encoder.hpp"
#include "../../Track/PCMTrack.hpp"
#include "../../Track/TrackSerialiser.hpp"
using namespace Storage::Disk;
@ -109,9 +109,7 @@ std::shared_ptr<Track> OricMFMDSK::get_track_at_position(Track::Address address)
}
segment.number_of_bits = static_cast<unsigned int>(segment.data.size() * 8);
std::shared_ptr<PCMTrack> track(new PCMTrack(segment));
return track;
return std::make_shared<PCMTrack>(segment);
}
void OricMFMDSK::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) {

View File

@ -15,7 +15,7 @@ namespace Storage {
namespace Disk {
/*!
Provies a @c Disk containing a DSD or SSD disk image a decoded sector dump of an Acorn DFS disk.
Provides a @c Disk containing a DSD or SSD disk image a decoded sector dump of an Acorn DFS disk.
*/
class SSD: public MFMSectorDump {
public:

View File

@ -26,7 +26,7 @@ namespace Disk {
*/
struct PCMSegment {
Time length_of_a_bit;
unsigned int number_of_bits;
unsigned int number_of_bits = 0;
std::vector<uint8_t> data;
PCMSegment(Time length_of_a_bit, unsigned int number_of_bits, std::vector<uint8_t> data)
@ -36,6 +36,11 @@ struct PCMSegment {
int bit(std::size_t index) const {
return (data[index >> 3] >> (7 ^ (index & 7)))&1;
}
void clear() {
number_of_bits = 0;
data.clear();
}
};
/*!