diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index de6b2ed0d..a5d8e6ec1 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -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 = ""; }; 4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Disk.hpp; sourceTree = ""; }; 4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Storage.hpp; sourceTree = ""; }; + 4BAF2B4C2004580C00480230 /* DMK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DMK.cpp; sourceTree = ""; }; + 4BAF2B4D2004580C00480230 /* DMK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DMK.hpp; sourceTree = ""; }; 4BB06B211F316A3F00600C7A /* ForceInline.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ForceInline.hpp; sourceTree = ""; }; 4BB146C61F49D7D700253439 /* Sleeper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sleeper.hpp; sourceTree = ""; }; 4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = ""; }; @@ -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 */, diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index de015839a..a5f0c8a78 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -265,11 +265,6 @@ $(PRODUCT_MODULE_NAME).MachineDocument - LSItemContentTypes - - org.akop.cocoamsx.filetype.cassette - com.clocksignal.cas - CFBundleTypeExtensions cas @@ -281,11 +276,30 @@ MSX Tape Image CFBundleTypeRole Viewer + LSItemContentTypes + + org.akop.cocoamsx.filetype.cassette + com.clocksignal.cas + LSTypeIsPackage 0 NSDocumentClass $(PRODUCT_MODULE_NAME).MachineDocument + + CFBundleTypeExtensions + + dmk + + CFBundleTypeIconFile + floppy35 + CFBundleTypeName + Disk Image + CFBundleTypeRole + Viewer + LSTypeIsPackage + 0 + CFBundleExecutable $(EXECUTABLE_NAME) diff --git a/StaticAnalyser/StaticAnalyser.cpp b/StaticAnalyser/StaticAnalyser.cpp index 2d71973a6..ce9214b9d 100644 --- a/StaticAnalyser/StaticAnalyser.cpp +++ b/StaticAnalyser/StaticAnalyser.cpp @@ -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, TargetPlatform::Commodore) // D64 + Format("dmk", result.disks, Disk::DiskImageHolder, TargetPlatform::MSX) // DMK Format("dsd", result.disks, Disk::DiskImageHolder, TargetPlatform::Acorn) // DSD Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC) Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::MSX) // DSK (MSX) diff --git a/Storage/Disk/DiskImage/Formats/AcornADF.hpp b/Storage/Disk/DiskImage/Formats/AcornADF.hpp index 186cf7be4..4cd4466fa 100644 --- a/Storage/Disk/DiskImage/Formats/AcornADF.hpp +++ b/Storage/Disk/DiskImage/Formats/AcornADF.hpp @@ -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: diff --git a/Storage/Disk/DiskImage/Formats/CPCDSK.hpp b/Storage/Disk/DiskImage/Formats/CPCDSK.hpp index ff3c210ce..e689d765c 100644 --- a/Storage/Disk/DiskImage/Formats/CPCDSK.hpp +++ b/Storage/Disk/DiskImage/Formats/CPCDSK.hpp @@ -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: diff --git a/Storage/Disk/DiskImage/Formats/D64.hpp b/Storage/Disk/DiskImage/Formats/D64.hpp index 2a0a8ef1b..ea835d3dd 100644 --- a/Storage/Disk/DiskImage/Formats/D64.hpp +++ b/Storage/Disk/DiskImage/Formats/D64.hpp @@ -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: diff --git a/Storage/Disk/DiskImage/Formats/DMK.cpp b/Storage/Disk/DiskImage/Formats/DMK.cpp new file mode 100644 index 000000000..39eac8df1 --- /dev/null +++ b/Storage/Disk/DiskImage/Formats/DMK.cpp @@ -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 new_encoder(Storage::Disk::PCMSegment &segment, bool is_double_density) { + std::unique_ptr 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(file_.get8()); + track_length_ = static_cast(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 track = file_.read(static_cast(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 segments; + std::unique_ptr 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(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(segment.data.size() * 8); + } + + return std::make_shared(segments); +} diff --git a/Storage/Disk/DiskImage/Formats/DMK.hpp b/Storage/Disk/DiskImage/Formats/DMK.hpp new file mode 100644 index 000000000..ce16ae7ee --- /dev/null +++ b/Storage/Disk/DiskImage/Formats/DMK.hpp @@ -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 */ diff --git a/Storage/Disk/DiskImage/Formats/G64.hpp b/Storage/Disk/DiskImage/Formats/G64.hpp index 65fe6287c..a48ea2511 100644 --- a/Storage/Disk/DiskImage/Formats/G64.hpp +++ b/Storage/Disk/DiskImage/Formats/G64.hpp @@ -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: diff --git a/Storage/Disk/DiskImage/Formats/HFE.hpp b/Storage/Disk/DiskImage/Formats/HFE.hpp index f2a27399e..0442ea39a 100644 --- a/Storage/Disk/DiskImage/Formats/HFE.hpp +++ b/Storage/Disk/DiskImage/Formats/HFE.hpp @@ -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: diff --git a/Storage/Disk/DiskImage/Formats/MFMSectorDump.hpp b/Storage/Disk/DiskImage/Formats/MFMSectorDump.hpp index 428241f39..ddb43cfa1 100644 --- a/Storage/Disk/DiskImage/Formats/MFMSectorDump.hpp +++ b/Storage/Disk/DiskImage/Formats/MFMSectorDump.hpp @@ -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: diff --git a/Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp b/Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp index da7fd0600..e638caf10 100644 --- a/Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp +++ b/Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp @@ -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 OricMFMDSK::get_track_at_position(Track::Address address) } segment.number_of_bits = static_cast(segment.data.size() * 8); - - std::shared_ptr track(new PCMTrack(segment)); - return track; + return std::make_shared(segment); } void OricMFMDSK::set_tracks(const std::map> &tracks) { diff --git a/Storage/Disk/DiskImage/Formats/SSD.hpp b/Storage/Disk/DiskImage/Formats/SSD.hpp index fa11d8c47..901595ca5 100644 --- a/Storage/Disk/DiskImage/Formats/SSD.hpp +++ b/Storage/Disk/DiskImage/Formats/SSD.hpp @@ -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: diff --git a/Storage/Disk/Track/PCMSegment.hpp b/Storage/Disk/Track/PCMSegment.hpp index d7289b66c..96a2364de 100644 --- a/Storage/Disk/Track/PCMSegment.hpp +++ b/Storage/Disk/Track/PCMSegment.hpp @@ -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 data; PCMSegment(Time length_of_a_bit, unsigned int number_of_bits, std::vector 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(); + } }; /*!