From 6eb56a15645c541ac88b840cef041956065c9dbf Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Mon, 8 Jan 2018 20:55:40 -0500
Subject: [PATCH 1/4] Corrects various comment typos.

---
 Storage/Disk/DiskImage/Formats/AcornADF.hpp      | 2 +-
 Storage/Disk/DiskImage/Formats/CPCDSK.hpp        | 2 +-
 Storage/Disk/DiskImage/Formats/D64.hpp           | 2 +-
 Storage/Disk/DiskImage/Formats/G64.hpp           | 2 +-
 Storage/Disk/DiskImage/Formats/HFE.hpp           | 2 +-
 Storage/Disk/DiskImage/Formats/MFMSectorDump.hpp | 2 +-
 Storage/Disk/DiskImage/Formats/SSD.hpp           | 2 +-
 7 files changed, 7 insertions(+), 7 deletions(-)

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/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/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:

From 4e124047c6d22e44c8b4629c0e456e76601e7543 Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Mon, 8 Jan 2018 21:57:11 -0500
Subject: [PATCH 2/4] Introduces enough DMK support to progress to failure to
 parse a track.

---
 .../Clock Signal.xcodeproj/project.pbxproj    |  8 ++
 OSBindings/Mac/Clock Signal/Info.plist        | 24 ++++--
 StaticAnalyser/StaticAnalyser.cpp             |  2 +
 Storage/Disk/DiskImage/Formats/DMK.cpp        | 75 +++++++++++++++++++
 Storage/Disk/DiskImage/Formats/DMK.hpp        | 57 ++++++++++++++
 5 files changed, 161 insertions(+), 5 deletions(-)
 create mode 100644 Storage/Disk/DiskImage/Formats/DMK.cpp
 create mode 100644 Storage/Disk/DiskImage/Formats/DMK.hpp

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 = "<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 */,
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 @@
 			<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>
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<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)
diff --git a/Storage/Disk/DiskImage/Formats/DMK.cpp b/Storage/Disk/DiskImage/Formats/DMK.cpp
new file mode 100644
index 000000000..8ea7db34e
--- /dev/null
+++ b/Storage/Disk/DiskImage/Formats/DMK.cpp
@@ -0,0 +1,75 @@
+//
+//  DMK.cpp
+//  Clock Signal
+//
+//  Created by Thomas Harte on 08/01/2018.
+//  Copyright © 2018 Thomas Harte. All rights reserved.
+//
+
+#include "DMK.hpp"
+
+using namespace Storage::Disk;
+
+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 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);
+
+	uint16_t idam_locations[64];
+	std::size_t index = 0;
+	for(std::size_t c = 0; c < sizeof(idam_locations); ++c) {
+		idam_locations[index] = file_.get16le();
+		if((idam_locations[index] & 0x7fff) >= 128) {
+			index++;
+		}
+	}
+
+	// `index` is now the final (sensical) entry in the IDAM location table.
+	// TODO: parse track contents.
+	printf("number of IDAMs: %d", index);
+
+	return nullptr;
+}
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 */

From 4ef583813a985ef01136f2bc94036a1c60a01e55 Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Tue, 9 Jan 2018 22:12:34 -0500
Subject: [PATCH 3/4] Minor tidying of PCMSegment and Oric MFM DSK.

---
 Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp | 6 ++----
 Storage/Disk/Track/PCMSegment.hpp             | 7 ++++++-
 2 files changed, 8 insertions(+), 5 deletions(-)

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<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) {
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<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();
+	}
 };
 
 /*!

From aafdff49bec6dcfed00e0df5b3d007ccf106682c Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Tue, 9 Jan 2018 22:13:04 -0500
Subject: [PATCH 4/4] Implements the ugly stuff of converting a DMK back to
 flux.

---
 Storage/Disk/DiskImage/Formats/DMK.cpp | 133 +++++++++++++++++++++++--
 1 file changed, 123 insertions(+), 10 deletions(-)

diff --git a/Storage/Disk/DiskImage/Formats/DMK.cpp b/Storage/Disk/DiskImage/Formats/DMK.cpp
index 8ea7db34e..39eac8df1 100644
--- a/Storage/Disk/DiskImage/Formats/DMK.cpp
+++ b/Storage/Disk/DiskImage/Formats/DMK.cpp
@@ -8,8 +8,30 @@
 
 #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,
@@ -48,7 +70,9 @@ int DMK::get_head_count() {
 }
 
 bool DMK::get_is_read_only() {
-	return 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) {
@@ -58,18 +82,107 @@ long DMK::get_file_offset_for_position(Track::Address address) {
 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 index = 0;
-	for(std::size_t c = 0; c < sizeof(idam_locations); ++c) {
-		idam_locations[index] = file_.get16le();
-		if((idam_locations[index] & 0x7fff) >= 128) {
-			index++;
+	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++;
 		}
 	}
 
-	// `index` is now the final (sensical) entry in the IDAM location table.
-	// TODO: parse track contents.
-	printf("number of IDAMs: %d", index);
+	// Grab the rest of the track.
+	std::vector<uint8_t> track = file_.read(static_cast<std::size_t>(track_length_ - 0x80));
 
-	return nullptr;
+	// 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);
 }