mirror of
https://github.com/TomHarte/CLK.git
synced 2025-04-03 06:29:47 +00:00
Merge pull request #250 from TomHarte/TrackToBits
Refactors MFM support, breaking it into components
This commit is contained in:
commit
cd1e5dea4d
@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
#include "1770.hpp"
|
||||
#include "../../Storage/Disk/Encodings/MFM.hpp"
|
||||
#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
|
||||
|
||||
using namespace WD;
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
#include "i8272.hpp"
|
||||
#include "../../Storage/Disk/Encodings/MFM.hpp"
|
||||
//#include "../../Storage/Disk/Encodings/MFM/Encoder.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
|
@ -84,6 +84,7 @@
|
||||
4B50730A1DDFCFDF00C48FBD /* ArrayBuilderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */; };
|
||||
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */; };
|
||||
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; };
|
||||
4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */; };
|
||||
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */; };
|
||||
4B5A12571DD55862007A2231 /* Disassembler6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5A12551DD55862007A2231 /* Disassembler6502.cpp */; };
|
||||
4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FADB81DE3151600AEC565 /* FileHolder.cpp */; };
|
||||
@ -94,6 +95,10 @@
|
||||
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; };
|
||||
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
|
||||
4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */; };
|
||||
4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136841F78724F008B8ED9 /* Encoder.cpp */; };
|
||||
4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136871F78725F008B8ED9 /* Shifter.cpp */; };
|
||||
4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368C1F788112008B8ED9 /* Parser.cpp */; };
|
||||
4B7136911F789C93008B8ED9 /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */; };
|
||||
4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.cpp */; };
|
||||
4B79E4441E3AF38600141F11 /* cassette.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4411E3AF38600141F11 /* cassette.png */; };
|
||||
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4421E3AF38600141F11 /* floppy35.png */; };
|
||||
@ -416,6 +421,7 @@
|
||||
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */; };
|
||||
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; };
|
||||
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */; };
|
||||
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; };
|
||||
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */; };
|
||||
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */; };
|
||||
4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5E4901D7ED365008CF980 /* StaticAnalyser.cpp */; };
|
||||
@ -448,12 +454,12 @@
|
||||
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; };
|
||||
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; };
|
||||
4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */; };
|
||||
4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8295B1D8F048B001BAE39 /* MFM.cpp */; };
|
||||
4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF829641D8F732B001BAE39 /* Disk.cpp */; };
|
||||
4BFCA1241ECBDCB400AC40C1 /* AllRAMProcessor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1211ECBDCAF00AC40C1 /* AllRAMProcessor.cpp */; };
|
||||
4BFCA1271ECBE33200AC40C1 /* TestMachineZ80.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1261ECBE33200AC40C1 /* TestMachineZ80.mm */; };
|
||||
4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BFCA1281ECBE7A700AC40C1 /* zexall.com */; };
|
||||
4BFCA12B1ECBE7C400AC40C1 /* ZexallTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA12A1ECBE7C400AC40C1 /* ZexallTests.swift */; };
|
||||
4BFDD78C1F7F2DB4008579B9 /* ImplicitSectors.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFDD78B1F7F2DB4008579B9 /* ImplicitSectors.cpp */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -626,6 +632,8 @@
|
||||
4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOpenGLView.h; sourceTree = "<group>"; };
|
||||
4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSOpenGLView.m; sourceTree = "<group>"; };
|
||||
4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = "<group>"; };
|
||||
4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MFMSectorDump.cpp; sourceTree = "<group>"; };
|
||||
4B58601D1F806AB200AEE2E3 /* MFMSectorDump.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MFMSectorDump.hpp; sourceTree = "<group>"; };
|
||||
4B59199A1DAC6C46005BB85C /* OricTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OricTAP.cpp; sourceTree = "<group>"; };
|
||||
4B59199B1DAC6C46005BB85C /* OricTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OricTAP.hpp; sourceTree = "<group>"; };
|
||||
4B5A12551DD55862007A2231 /* Disassembler6502.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Disassembler6502.cpp; path = ../../StaticAnalyser/Disassembler/Disassembler6502.cpp; sourceTree = "<group>"; };
|
||||
@ -647,6 +655,16 @@
|
||||
4B6A4C911F58F09E00E3F787 /* 6502AllRAM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502AllRAM.cpp; sourceTree = "<group>"; };
|
||||
4B6A4C921F58F09E00E3F787 /* 6502AllRAM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6502AllRAM.hpp; sourceTree = "<group>"; };
|
||||
4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502Base.cpp; sourceTree = "<group>"; };
|
||||
4B7136841F78724F008B8ED9 /* Encoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Encoder.cpp; sourceTree = "<group>"; };
|
||||
4B7136851F78724F008B8ED9 /* Encoder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Encoder.hpp; sourceTree = "<group>"; };
|
||||
4B7136871F78725F008B8ED9 /* Shifter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Shifter.cpp; sourceTree = "<group>"; };
|
||||
4B7136881F78725F008B8ED9 /* Shifter.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Shifter.hpp; sourceTree = "<group>"; };
|
||||
4B71368A1F787349008B8ED9 /* Constants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Constants.hpp; sourceTree = "<group>"; };
|
||||
4B71368B1F7880D1008B8ED9 /* Sector.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sector.hpp; sourceTree = "<group>"; };
|
||||
4B71368C1F788112008B8ED9 /* Parser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Parser.cpp; sourceTree = "<group>"; };
|
||||
4B71368D1F788112008B8ED9 /* Parser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Parser.hpp; sourceTree = "<group>"; };
|
||||
4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SegmentParser.cpp; sourceTree = "<group>"; };
|
||||
4B7136901F789C93008B8ED9 /* SegmentParser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SegmentParser.hpp; sourceTree = "<group>"; };
|
||||
4B77069C1EC904570053B588 /* Z80.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Z80.hpp; path = Z80/Z80.hpp; sourceTree = "<group>"; };
|
||||
4B7913CA1DFCD80E00175A82 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Electron/Video.cpp; sourceTree = "<group>"; };
|
||||
4B7913CB1DFCD80E00175A82 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Electron/Video.hpp; sourceTree = "<group>"; };
|
||||
@ -680,6 +698,7 @@
|
||||
4B8805FA1DCFF807003085B1 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Parsers/Oric.hpp; sourceTree = "<group>"; };
|
||||
4B8805FC1DD02552003085B1 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = ../../StaticAnalyser/Oric/Tape.cpp; sourceTree = "<group>"; };
|
||||
4B8805FD1DD02552003085B1 /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/Oric/Tape.hpp; sourceTree = "<group>"; };
|
||||
4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = "<group>"; };
|
||||
4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = "<group>"; };
|
||||
4B8FE2141DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Atari2600Options.xib"; sourceTree = SOURCE_ROOT; };
|
||||
4B8FE2161DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MachineDocument.xib"; sourceTree = SOURCE_ROOT; };
|
||||
@ -1015,6 +1034,7 @@
|
||||
4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTTypes.hpp; sourceTree = "<group>"; };
|
||||
4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Parsers/ZX8081.cpp; sourceTree = "<group>"; };
|
||||
4BBFBB6B1EE8401E00C01E7A /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Parsers/ZX8081.hpp; sourceTree = "<group>"; };
|
||||
4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TrackSerialiser.cpp; sourceTree = "<group>"; };
|
||||
4BC3B74D1CD194CC00F86E85 /* Shader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Shader.cpp; sourceTree = "<group>"; };
|
||||
4BC3B74E1CD194CC00F86E85 /* Shader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Shader.hpp; sourceTree = "<group>"; };
|
||||
4BC3B7501CD1956900F86E85 /* OutputShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OutputShader.cpp; sourceTree = "<group>"; };
|
||||
@ -1093,8 +1113,6 @@
|
||||
4BF4A2D91F534DB300B171F4 /* TargetPlatforms.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TargetPlatforms.hpp; sourceTree = "<group>"; };
|
||||
4BF4A2DA1F5365C600B171F4 /* CSZX8081+Instantiation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CSZX8081+Instantiation.h"; sourceTree = "<group>"; };
|
||||
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ClockReceiver.hpp; sourceTree = "<group>"; };
|
||||
4BF8295B1D8F048B001BAE39 /* MFM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MFM.cpp; path = Encodings/MFM.cpp; sourceTree = "<group>"; };
|
||||
4BF8295C1D8F048B001BAE39 /* MFM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = MFM.hpp; path = Encodings/MFM.hpp; sourceTree = "<group>"; };
|
||||
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = "<group>"; };
|
||||
4BF829641D8F732B001BAE39 /* Disk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Disk.cpp; path = ../../StaticAnalyser/Acorn/Disk.cpp; sourceTree = "<group>"; };
|
||||
4BF829651D8F732B001BAE39 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Disk.hpp; path = ../../StaticAnalyser/Acorn/Disk.hpp; sourceTree = "<group>"; };
|
||||
@ -1105,6 +1123,8 @@
|
||||
4BFCA1261ECBE33200AC40C1 /* TestMachineZ80.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestMachineZ80.mm; sourceTree = "<group>"; };
|
||||
4BFCA1281ECBE7A700AC40C1 /* zexall.com */ = {isa = PBXFileReference; lastKnownFileType = file; name = zexall.com; path = Zexall/zexall.com; sourceTree = "<group>"; };
|
||||
4BFCA12A1ECBE7C400AC40C1 /* ZexallTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZexallTests.swift; sourceTree = "<group>"; };
|
||||
4BFDD78A1F7F2DB4008579B9 /* ImplicitSectors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ImplicitSectors.hpp; sourceTree = "<group>"; };
|
||||
4BFDD78B1F7F2DB4008579B9 /* ImplicitSectors.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ImplicitSectors.cpp; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -1407,14 +1427,16 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B4518711F75E91800926311 /* PCMPatchedTrack.cpp */,
|
||||
4B4518721F75E91800926311 /* PCMPatchedTrack.hpp */,
|
||||
4B4518731F75E91800926311 /* PCMSegment.cpp */,
|
||||
4B4518741F75E91800926311 /* PCMSegment.hpp */,
|
||||
4B4518751F75E91800926311 /* PCMTrack.cpp */,
|
||||
4B4518761F75E91800926311 /* PCMTrack.hpp */,
|
||||
4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */,
|
||||
4B4518771F75E91800926311 /* UnformattedTrack.cpp */,
|
||||
4B4518781F75E91800926311 /* UnformattedTrack.hpp */,
|
||||
4B4518721F75E91800926311 /* PCMPatchedTrack.hpp */,
|
||||
4B4518741F75E91800926311 /* PCMSegment.hpp */,
|
||||
4B4518761F75E91800926311 /* PCMTrack.hpp */,
|
||||
4B4518881F75ECB100926311 /* Track.hpp */,
|
||||
4B8D287E1F77207100645199 /* TrackSerialiser.hpp */,
|
||||
4B4518781F75E91800926311 /* UnformattedTrack.hpp */,
|
||||
);
|
||||
path = Track;
|
||||
sourceTree = "<group>";
|
||||
@ -1463,10 +1485,13 @@
|
||||
4B4518941F75FD1B00926311 /* G64.hpp */,
|
||||
4B4518951F75FD1B00926311 /* HFE.cpp */,
|
||||
4B4518961F75FD1B00926311 /* HFE.hpp */,
|
||||
4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */,
|
||||
4B58601D1F806AB200AEE2E3 /* MFMSectorDump.hpp */,
|
||||
4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */,
|
||||
4B4518981F75FD1B00926311 /* OricMFMDSK.hpp */,
|
||||
4B4518991F75FD1B00926311 /* SSD.cpp */,
|
||||
4B45189A1F75FD1B00926311 /* SSD.hpp */,
|
||||
4BFDD7891F7F2DB4008579B9 /* Utility */,
|
||||
);
|
||||
path = Formats;
|
||||
sourceTree = "<group>";
|
||||
@ -1649,6 +1674,24 @@
|
||||
path = Implementation;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B7136831F78724F008B8ED9 /* MFM */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B7136841F78724F008B8ED9 /* Encoder.cpp */,
|
||||
4B71368C1F788112008B8ED9 /* Parser.cpp */,
|
||||
4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */,
|
||||
4B7136871F78725F008B8ED9 /* Shifter.cpp */,
|
||||
4B71368A1F787349008B8ED9 /* Constants.hpp */,
|
||||
4B7136851F78724F008B8ED9 /* Encoder.hpp */,
|
||||
4B71368D1F788112008B8ED9 /* Parser.hpp */,
|
||||
4B71368B1F7880D1008B8ED9 /* Sector.hpp */,
|
||||
4B7136901F789C93008B8ED9 /* SegmentParser.hpp */,
|
||||
4B7136881F78725F008B8ED9 /* Shifter.hpp */,
|
||||
);
|
||||
name = MFM;
|
||||
path = Encodings/MFM;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B77069E1EC9045B0053B588 /* Z80 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -2016,10 +2059,9 @@
|
||||
4BB697CF1D4BA44900248BDF /* Encodings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B7136831F78724F008B8ED9 /* MFM */,
|
||||
4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */,
|
||||
4BB697CD1D4BA44400248BDF /* CommodoreGCR.hpp */,
|
||||
4BF8295B1D8F048B001BAE39 /* MFM.cpp */,
|
||||
4BF8295C1D8F048B001BAE39 /* MFM.hpp */,
|
||||
);
|
||||
name = Encodings;
|
||||
sourceTree = "<group>";
|
||||
@ -2423,6 +2465,15 @@
|
||||
path = ../../ClockReceiver;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BFDD7891F7F2DB4008579B9 /* Utility */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BFDD78B1F7F2DB4008579B9 /* ImplicitSectors.cpp */,
|
||||
4BFDD78A1F7F2DB4008579B9 /* ImplicitSectors.hpp */,
|
||||
);
|
||||
path = Utility;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@ -2851,6 +2902,8 @@
|
||||
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */,
|
||||
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */,
|
||||
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */,
|
||||
4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */,
|
||||
4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */,
|
||||
4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */,
|
||||
4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */,
|
||||
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */,
|
||||
@ -2874,6 +2927,7 @@
|
||||
4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */,
|
||||
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */,
|
||||
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
|
||||
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */,
|
||||
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */,
|
||||
4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */,
|
||||
4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */,
|
||||
@ -2895,6 +2949,7 @@
|
||||
4B4518831F75E91A00926311 /* PCMTrack.cpp in Sources */,
|
||||
4B45189F1F75FD1C00926311 /* AcornADF.cpp in Sources */,
|
||||
4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */,
|
||||
4B7136911F789C93008B8ED9 /* SegmentParser.cpp in Sources */,
|
||||
4B4518A21F75FD1C00926311 /* G64.cpp in Sources */,
|
||||
4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */,
|
||||
4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */,
|
||||
@ -2906,7 +2961,7 @@
|
||||
4B8334841F5DA0360097E338 /* Z80Storage.cpp in Sources */,
|
||||
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */,
|
||||
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
|
||||
4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */,
|
||||
4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */,
|
||||
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */,
|
||||
4B14978B1EE4AC5E00CE2596 /* StaticAnalyser.cpp in Sources */,
|
||||
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */,
|
||||
@ -2953,6 +3008,8 @@
|
||||
4BEA52661DF3472B007E74F2 /* Speaker.cpp in Sources */,
|
||||
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */,
|
||||
4B83348A1F5DB94B0097E338 /* IRQDelegatePortHandler.cpp in Sources */,
|
||||
4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */,
|
||||
4BFDD78C1F7F2DB4008579B9 /* ImplicitSectors.cpp in Sources */,
|
||||
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */,
|
||||
4B5073071DDD3B9400C48FBD /* ArrayBuilder.cpp in Sources */,
|
||||
4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */,
|
||||
|
@ -95,9 +95,16 @@ class MachineDocument:
|
||||
}
|
||||
|
||||
override func close() {
|
||||
bestEffortUpdater.flush()
|
||||
optionsPanel?.setIsVisible(false)
|
||||
optionsPanel = nil
|
||||
|
||||
openGLView.delegate = nil
|
||||
bestEffortUpdater.delegate = nil
|
||||
bestEffortUpdater = nil
|
||||
|
||||
actionLock.lock()
|
||||
drawLock.lock()
|
||||
machine = nil
|
||||
openGLView.invalidate()
|
||||
openGLView.openGLContext!.makeCurrentContext()
|
||||
actionLock.unlock()
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
@property (nonatomic, assign) double clockRate;
|
||||
@property (nonatomic, assign) BOOL runAsUnlimited;
|
||||
@property (nonatomic, weak) id<CSBestEffortUpdaterDelegate> delegate;
|
||||
@property (atomic, weak) id<CSBestEffortUpdaterDelegate> delegate;
|
||||
|
||||
- (void)update;
|
||||
- (void)flush;
|
||||
|
@ -20,6 +20,7 @@
|
||||
NSTimeInterval _previousTimeInterval;
|
||||
NSTimeInterval _cyclesError;
|
||||
BOOL _hasSkipped;
|
||||
id<CSBestEffortUpdaterDelegate> _delegate;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
@ -35,15 +36,12 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)update
|
||||
{
|
||||
- (void)update {
|
||||
// Always post an -openGLView:didUpdateToTime: if a previous one isn't still ongoing. This is the hook upon which the substantial processing occurs.
|
||||
if(!atomic_flag_test_and_set(&_updateIsOngoing))
|
||||
{
|
||||
if(!atomic_flag_test_and_set(&_updateIsOngoing)) {
|
||||
dispatch_async(_serialDispatchQueue, ^{
|
||||
NSTimeInterval timeInterval = [NSDate timeIntervalSinceReferenceDate];
|
||||
if(_previousTimeInterval > DBL_EPSILON && timeInterval > _previousTimeInterval)
|
||||
{
|
||||
if(_previousTimeInterval > DBL_EPSILON && timeInterval > _previousTimeInterval) {
|
||||
NSTimeInterval timeToRunFor = timeInterval - _previousTimeInterval;
|
||||
double cyclesToRunFor = timeToRunFor * self.clockRate + _cyclesError;
|
||||
|
||||
@ -52,24 +50,35 @@
|
||||
|
||||
// treat 'unlimited' as running at a factor of 10
|
||||
if(self.runAsUnlimited) integerCyclesToRunFor *= 10;
|
||||
[self.delegate bestEffortUpdater:self runForCycles:integerCyclesToRunFor didSkipPreviousUpdate:_hasSkipped];
|
||||
[_delegate bestEffortUpdater:self runForCycles:integerCyclesToRunFor didSkipPreviousUpdate:_hasSkipped];
|
||||
}
|
||||
_previousTimeInterval = timeInterval;
|
||||
_hasSkipped = NO;
|
||||
atomic_flag_clear(&_updateIsOngoing);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
dispatch_async(_serialDispatchQueue, ^{
|
||||
_hasSkipped = YES;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)flush
|
||||
{
|
||||
- (void)flush {
|
||||
dispatch_sync(_serialDispatchQueue, ^{});
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id<CSBestEffortUpdaterDelegate>)delegate {
|
||||
dispatch_sync(_serialDispatchQueue, ^{
|
||||
_delegate = delegate;
|
||||
});
|
||||
}
|
||||
|
||||
- (id<CSBestEffortUpdaterDelegate>)delegate {
|
||||
__block id<CSBestEffortUpdaterDelegate> delegate;
|
||||
dispatch_sync(_serialDispatchQueue, ^{
|
||||
delegate = _delegate;
|
||||
});
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include "Disk.hpp"
|
||||
#include "../../Storage/Disk/Controller/DiskController.hpp"
|
||||
#include "../../Storage/Disk/Encodings/MFM.hpp"
|
||||
#include "../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||
#include "../../NumberTheory/CRC.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
@ -19,8 +19,8 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
|
||||
std::unique_ptr<Catalogue> catalogue(new Catalogue);
|
||||
Storage::Encodings::MFM::Parser parser(false, disk);
|
||||
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> names = parser.get_sector(0, 0, 0);
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> details = parser.get_sector(0, 0, 1);
|
||||
Storage::Encodings::MFM::Sector *names = parser.get_sector(0, 0, 0);
|
||||
Storage::Encodings::MFM::Sector *details = parser.get_sector(0, 0, 1);
|
||||
|
||||
if(!names || !details) return nullptr;
|
||||
if(names->data.size() != 256 || details->data.size() != 256) return nullptr;
|
||||
@ -61,7 +61,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
|
||||
uint8_t track = (uint8_t)(start_sector / 10);
|
||||
start_sector++;
|
||||
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> next_sector = parser.get_sector(0, track, sector);
|
||||
Storage::Encodings::MFM::Sector *next_sector = parser.get_sector(0, track, sector);
|
||||
if(!next_sector) break;
|
||||
|
||||
long length_from_sector = std::min(data_length, 256l);
|
||||
@ -77,13 +77,13 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh
|
||||
std::unique_ptr<Catalogue> catalogue(new Catalogue);
|
||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> free_space_map_second_half = parser.get_sector(0, 0, 1);
|
||||
Storage::Encodings::MFM::Sector *free_space_map_second_half = parser.get_sector(0, 0, 1);
|
||||
if(!free_space_map_second_half) return nullptr;
|
||||
|
||||
std::vector<uint8_t> root_directory;
|
||||
root_directory.reserve(5 * 256);
|
||||
for(uint8_t c = 2; c < 7; c++) {
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, 0, c);
|
||||
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, c);
|
||||
if(!sector) return nullptr;
|
||||
root_directory.insert(root_directory.end(), sector->data.begin(), sector->data.end());
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include "../../Storage/Disk/Parsers/CPM.hpp"
|
||||
#include "../../Storage/Disk/Encodings/MFM.hpp"
|
||||
#include "../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||
|
||||
static bool strcmp_insensitive(const char *a, const char *b) {
|
||||
if(strlen(a) != strlen(b)) return false;
|
||||
@ -152,7 +152,7 @@ static void InspectCatalogue(
|
||||
|
||||
static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, StaticAnalyser::Target &target) {
|
||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> boot_sector = parser.get_sector(0, 0, 0x41);
|
||||
Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
|
||||
if(boot_sector != nullptr) {
|
||||
// Check that the first 64 bytes of the sector aren't identical; if they are then probably
|
||||
// this disk was formatted and the filler byte never replaced.
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include "MFMDiskController.hpp"
|
||||
|
||||
#include "../Encodings/MFM.hpp"
|
||||
#include "../Encodings/MFM/Constants.hpp"
|
||||
|
||||
using namespace Storage::Disk;
|
||||
|
||||
@ -16,7 +16,7 @@ MFMController::MFMController(Cycles clock_rate) :
|
||||
Storage::Disk::Controller(clock_rate),
|
||||
crc_generator_(0x1021, 0xffff),
|
||||
data_mode_(DataMode::Scanning),
|
||||
is_awaiting_marker_value_(false) {
|
||||
shifter_(&crc_generator_) {
|
||||
}
|
||||
|
||||
void MFMController::process_index_hole() {
|
||||
@ -34,7 +34,7 @@ void MFMController::set_is_double_density(bool is_double_density) {
|
||||
bit_length.clock_rate = is_double_density ? 500000 : 250000;
|
||||
set_expected_bit_length(bit_length);
|
||||
|
||||
if(!is_double_density) is_awaiting_marker_value_ = false;
|
||||
shifter_.set_is_double_density(is_double_density);
|
||||
}
|
||||
|
||||
bool MFMController::get_is_double_density() {
|
||||
@ -43,6 +43,7 @@ bool MFMController::get_is_double_density() {
|
||||
|
||||
void MFMController::set_data_mode(DataMode mode) {
|
||||
data_mode_ = mode;
|
||||
shifter_.set_should_obey_syncs(mode == DataMode::Scanning);
|
||||
}
|
||||
|
||||
MFMController::Token MFMController::get_latest_token() {
|
||||
@ -56,102 +57,32 @@ NumberTheory::CRC16 &MFMController::get_crc_generator() {
|
||||
void MFMController::process_input_bit(int value) {
|
||||
if(data_mode_ == DataMode::Writing) return;
|
||||
|
||||
shift_register_ = (shift_register_ << 1) | value;
|
||||
bits_since_token_++;
|
||||
|
||||
if(data_mode_ == DataMode::Scanning) {
|
||||
Token::Type token_type = Token::Byte;
|
||||
if(!is_double_density_) {
|
||||
switch(shift_register_ & 0xffff) {
|
||||
case Storage::Encodings::MFM::FMIndexAddressMark:
|
||||
token_type = Token::Index;
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::IndexAddressByte);
|
||||
break;
|
||||
case Storage::Encodings::MFM::FMIDAddressMark:
|
||||
token_type = Token::ID;
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::IDAddressByte);
|
||||
break;
|
||||
case Storage::Encodings::MFM::FMDataAddressMark:
|
||||
token_type = Token::Data;
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::DataAddressByte);
|
||||
break;
|
||||
case Storage::Encodings::MFM::FMDeletedDataAddressMark:
|
||||
token_type = Token::DeletedData;
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::DeletedDataAddressByte);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch(shift_register_ & 0xffff) {
|
||||
case Storage::Encodings::MFM::MFMIndexSync:
|
||||
bits_since_token_ = 0;
|
||||
is_awaiting_marker_value_ = true;
|
||||
|
||||
token_type = Token::Sync;
|
||||
latest_token_.byte_value = Storage::Encodings::MFM::MFMIndexSyncByteValue;
|
||||
break;
|
||||
case Storage::Encodings::MFM::MFMSync:
|
||||
bits_since_token_ = 0;
|
||||
is_awaiting_marker_value_ = true;
|
||||
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
|
||||
|
||||
token_type = Token::Sync;
|
||||
latest_token_.byte_value = Storage::Encodings::MFM::MFMSyncByteValue;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(token_type != Token::Byte) {
|
||||
latest_token_.type = token_type;
|
||||
bits_since_token_ = 0;
|
||||
posit_event((int)Event::Token);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(bits_since_token_ == 16) {
|
||||
latest_token_.type = Token::Byte;
|
||||
latest_token_.byte_value = (uint8_t)(
|
||||
((shift_register_ & 0x0001) >> 0) |
|
||||
((shift_register_ & 0x0004) >> 1) |
|
||||
((shift_register_ & 0x0010) >> 2) |
|
||||
((shift_register_ & 0x0040) >> 3) |
|
||||
((shift_register_ & 0x0100) >> 4) |
|
||||
((shift_register_ & 0x0400) >> 5) |
|
||||
((shift_register_ & 0x1000) >> 6) |
|
||||
((shift_register_ & 0x4000) >> 7));
|
||||
bits_since_token_ = 0;
|
||||
|
||||
if(is_awaiting_marker_value_ && is_double_density_) {
|
||||
is_awaiting_marker_value_ = false;
|
||||
switch(latest_token_.byte_value) {
|
||||
case Storage::Encodings::MFM::IndexAddressByte:
|
||||
latest_token_.type = Token::Index;
|
||||
break;
|
||||
case Storage::Encodings::MFM::IDAddressByte:
|
||||
latest_token_.type = Token::ID;
|
||||
break;
|
||||
case Storage::Encodings::MFM::DataAddressByte:
|
||||
latest_token_.type = Token::Data;
|
||||
break;
|
||||
case Storage::Encodings::MFM::DeletedDataAddressByte:
|
||||
latest_token_.type = Token::DeletedData;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
crc_generator_.add(latest_token_.byte_value);
|
||||
posit_event((int)Event::Token);
|
||||
shifter_.add_input_bit(value);
|
||||
switch(shifter_.get_token()) {
|
||||
case Encodings::MFM::Shifter::Token::None:
|
||||
return;
|
||||
|
||||
case Encodings::MFM::Shifter::Token::Index:
|
||||
latest_token_.type = Token::Index;
|
||||
break;
|
||||
case Encodings::MFM::Shifter::Token::ID:
|
||||
latest_token_.type = Token::ID;
|
||||
break;
|
||||
case Encodings::MFM::Shifter::Token::Data:
|
||||
latest_token_.type = Token::Data;
|
||||
break;
|
||||
case Encodings::MFM::Shifter::Token::DeletedData:
|
||||
latest_token_.type = Token::DeletedData;
|
||||
break;
|
||||
case Encodings::MFM::Shifter::Token::Sync:
|
||||
latest_token_.type = Token::Sync;
|
||||
break;
|
||||
case Encodings::MFM::Shifter::Token::Byte:
|
||||
latest_token_.type = Token::Byte;
|
||||
break;
|
||||
}
|
||||
latest_token_.byte_value = shifter_.get_byte();
|
||||
posit_event((int)Event::Token);
|
||||
}
|
||||
|
||||
void MFMController::write_bit(int bit) {
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "DiskController.hpp"
|
||||
#include "../../../NumberTheory/CRC.hpp"
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../Encodings/MFM/Shifter.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
@ -150,18 +151,14 @@ class MFMController: public Controller {
|
||||
virtual void process_index_hole();
|
||||
virtual void process_write_completed();
|
||||
|
||||
// PLL input state
|
||||
int bits_since_token_;
|
||||
int shift_register_;
|
||||
bool is_awaiting_marker_value_;
|
||||
// Reading state.
|
||||
Token latest_token_;
|
||||
Encodings::MFM::Shifter shifter_;
|
||||
|
||||
// input configuration
|
||||
bool is_double_density_;
|
||||
DataMode data_mode_;
|
||||
|
||||
// output
|
||||
Token latest_token_;
|
||||
|
||||
// writing
|
||||
int last_bit_;
|
||||
|
||||
|
@ -8,23 +8,20 @@
|
||||
|
||||
#include "AcornADF.hpp"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include "../../Encodings/MFM.hpp"
|
||||
#include "Utility/ImplicitSectors.hpp"
|
||||
|
||||
namespace {
|
||||
static const unsigned int sectors_per_track = 16;
|
||||
static const unsigned int bytes_per_sector = 256;
|
||||
static const unsigned int sector_size = 1;
|
||||
}
|
||||
|
||||
using namespace Storage::Disk;
|
||||
|
||||
AcornADF::AcornADF(const char *file_name) :
|
||||
Storage::FileHolder(file_name) {
|
||||
AcornADF::AcornADF(const char *file_name) : MFMSectorDump(file_name) {
|
||||
// very loose validation: the file needs to be a multiple of 256 bytes
|
||||
// and not ungainly large
|
||||
if(file_stats_.st_size % bytes_per_sector) throw ErrorNotAcornADF;
|
||||
if(file_stats_.st_size < 7 * bytes_per_sector) throw ErrorNotAcornADF;
|
||||
if(file_stats_.st_size % (off_t)(128 << sector_size)) throw ErrorNotAcornADF;
|
||||
if(file_stats_.st_size < 7 * (off_t)(128 << sector_size)) throw ErrorNotAcornADF;
|
||||
|
||||
// check that the initial directory's 'Hugo's are present
|
||||
fseek(file_, 513, SEEK_SET);
|
||||
@ -35,6 +32,8 @@ AcornADF::AcornADF(const char *file_name) :
|
||||
fseek(file_, 0x6fb, SEEK_SET);
|
||||
fread(bytes, 1, 4, file_);
|
||||
if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF;
|
||||
|
||||
set_geometry(sectors_per_track, sector_size, true);
|
||||
}
|
||||
|
||||
unsigned int AcornADF::get_head_position_count() {
|
||||
@ -45,64 +44,6 @@ unsigned int AcornADF::get_head_count() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool AcornADF::get_is_read_only() {
|
||||
return is_read_only_;
|
||||
}
|
||||
|
||||
long AcornADF::get_file_offset_for_position(unsigned int head, unsigned int position) {
|
||||
return (position * 1 + head) * bytes_per_sector * sectors_per_track;
|
||||
}
|
||||
|
||||
std::shared_ptr<Track> AcornADF::get_track_at_position(unsigned int head, unsigned int position) {
|
||||
std::shared_ptr<Track> track;
|
||||
|
||||
if(head >= 2) return track;
|
||||
long file_offset = get_file_offset_for_position(head, position);
|
||||
|
||||
std::vector<Storage::Encodings::MFM::Sector> sectors;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock_guard(file_access_mutex_);
|
||||
fseek(file_, file_offset, SEEK_SET);
|
||||
|
||||
for(unsigned int sector = 0; sector < sectors_per_track; sector++) {
|
||||
Storage::Encodings::MFM::Sector new_sector;
|
||||
new_sector.track = (uint8_t)position;
|
||||
new_sector.side = (uint8_t)head;
|
||||
new_sector.sector = (uint8_t)sector;
|
||||
new_sector.size = sector_size;
|
||||
|
||||
new_sector.data.resize(bytes_per_sector);
|
||||
fread(&new_sector.data[0], 1, bytes_per_sector, file_);
|
||||
if(feof(file_))
|
||||
break;
|
||||
|
||||
sectors.push_back(std::move(new_sector));
|
||||
}
|
||||
}
|
||||
|
||||
if(sectors.size()) return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors);
|
||||
|
||||
return track;
|
||||
}
|
||||
|
||||
void AcornADF::set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track) {
|
||||
std::vector<uint8_t> parsed_track;
|
||||
Storage::Encodings::MFM::Parser parser(true, track);
|
||||
for(unsigned int c = 0; c < sectors_per_track; c++) {
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, (uint8_t)position, (uint8_t)c);
|
||||
if(sector) {
|
||||
parsed_track.insert(parsed_track.end(), sector->data.begin(), sector->data.end());
|
||||
} else {
|
||||
// TODO: what's correct here? Warn the user that whatever has been written to the disk,
|
||||
// it can no longer be stored as an SSD? If so, warn them by what route?
|
||||
parsed_track.resize(parsed_track.size() + bytes_per_sector);
|
||||
}
|
||||
}
|
||||
|
||||
long file_offset = get_file_offset_for_position(head, position);
|
||||
|
||||
std::lock_guard<std::mutex> lock_guard(file_access_mutex_);
|
||||
ensure_file_is_at_least_length(file_offset);
|
||||
fseek(file_, file_offset, SEEK_SET);
|
||||
fwrite(parsed_track.data(), 1, parsed_track.size(), file_);
|
||||
return (position * 1 + head) * (128 << sector_size) * sectors_per_track;
|
||||
}
|
||||
|
@ -9,8 +9,7 @@
|
||||
#ifndef AcornADF_hpp
|
||||
#define AcornADF_hpp
|
||||
|
||||
#include "../DiskImage.hpp"
|
||||
#include "../../../FileHolder.hpp"
|
||||
#include "MFMSectorDump.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
@ -18,7 +17,7 @@ namespace Disk {
|
||||
/*!
|
||||
Provies a @c Disk containing an ADF disk image — a decoded sector dump of an Acorn ADFS disk.
|
||||
*/
|
||||
class AcornADF: public DiskImage, public Storage::FileHolder {
|
||||
class AcornADF: public MFMSectorDump {
|
||||
public:
|
||||
/*!
|
||||
Construct an @c AcornADF containing content from the file with name @c file_name.
|
||||
@ -34,12 +33,8 @@ class AcornADF: public DiskImage, public Storage::FileHolder {
|
||||
|
||||
unsigned int get_head_position_count();
|
||||
unsigned int get_head_count();
|
||||
bool get_is_read_only();
|
||||
void set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track);
|
||||
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
|
||||
|
||||
private:
|
||||
std::mutex file_access_mutex_;
|
||||
long get_file_offset_for_position(unsigned int head, unsigned int position);
|
||||
};
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include "CPCDSK.hpp"
|
||||
|
||||
#include "../../Encodings/MFM.hpp"
|
||||
#include "../../Encodings/MFM/Encoder.hpp"
|
||||
|
||||
using namespace Storage::Disk;
|
||||
|
||||
@ -114,9 +114,9 @@ std::shared_ptr<Track> CPCDSK::get_track_at_position(unsigned int head, unsigned
|
||||
std::vector<Storage::Encodings::MFM::Sector> sectors;
|
||||
for(auto §or_info : sector_infos) {
|
||||
Storage::Encodings::MFM::Sector new_sector;
|
||||
new_sector.track = sector_info.track;
|
||||
new_sector.side = sector_info.side;
|
||||
new_sector.sector = sector_info.sector;
|
||||
new_sector.address.track = sector_info.track;
|
||||
new_sector.address.side = sector_info.side;
|
||||
new_sector.address.sector = sector_info.sector;
|
||||
new_sector.size = sector_info.length;
|
||||
|
||||
size_t data_size;
|
||||
|
54
Storage/Disk/DiskImage/Formats/MFMSectorDump.cpp
Normal file
54
Storage/Disk/DiskImage/Formats/MFMSectorDump.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
//
|
||||
// MFMSectorDump.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 30/09/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MFMSectorDump.hpp"
|
||||
|
||||
#include "Utility/ImplicitSectors.hpp"
|
||||
|
||||
using namespace Storage::Disk;
|
||||
|
||||
MFMSectorDump::MFMSectorDump(const char *file_name) : Storage::FileHolder(file_name) {}
|
||||
|
||||
void MFMSectorDump::set_geometry(int sectors_per_track, uint8_t sector_size, bool is_double_density) {
|
||||
sectors_per_track_ = sectors_per_track;
|
||||
sector_size_ = sector_size;
|
||||
is_double_density_ = is_double_density;
|
||||
}
|
||||
|
||||
bool MFMSectorDump::get_is_read_only() {
|
||||
return is_read_only_;
|
||||
}
|
||||
|
||||
std::shared_ptr<Track> MFMSectorDump::get_track_at_position(unsigned int head, unsigned int position) {
|
||||
uint8_t sectors[(128 << sector_size_)*sectors_per_track_];
|
||||
|
||||
if(head > 1) return nullptr;
|
||||
long file_offset = get_file_offset_for_position(head, position);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock_guard(file_access_mutex_);
|
||||
fseek(file_, file_offset, SEEK_SET);
|
||||
fread(sectors, 1, sizeof(sectors), file_);
|
||||
}
|
||||
|
||||
return track_for_sectors(sectors, (uint8_t)position, (uint8_t)head, 0, sector_size_, is_double_density_);
|
||||
}
|
||||
|
||||
void MFMSectorDump::set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track) {
|
||||
uint8_t parsed_track[(128 << sector_size_)*sectors_per_track_];
|
||||
// Assumption here: sector IDs will run from 0.
|
||||
decode_sectors(*track, parsed_track, 0, (uint8_t)(sectors_per_track_-1), sector_size_, is_double_density_);
|
||||
|
||||
long file_offset = get_file_offset_for_position(head, position);
|
||||
|
||||
std::lock_guard<std::mutex> lock_guard(file_access_mutex_);
|
||||
ensure_file_is_at_least_length(file_offset);
|
||||
fseek(file_, file_offset, SEEK_SET);
|
||||
fwrite(parsed_track, 1, sizeof(parsed_track), file_);
|
||||
fflush(file_);
|
||||
}
|
42
Storage/Disk/DiskImage/Formats/MFMSectorDump.hpp
Normal file
42
Storage/Disk/DiskImage/Formats/MFMSectorDump.hpp
Normal file
@ -0,0 +1,42 @@
|
||||
//
|
||||
// MFMSectorDump.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 30/09/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef SectorDump_hpp
|
||||
#define SectorDump_hpp
|
||||
|
||||
#include "../DiskImage.hpp"
|
||||
#include "../../../FileHolder.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
|
||||
/*!
|
||||
Provies the base for writeable [M]FM disk images that just contain contiguous sector content dumps.
|
||||
*/
|
||||
class MFMSectorDump: public DiskImage, public Storage::FileHolder {
|
||||
public:
|
||||
MFMSectorDump(const char *file_name);
|
||||
void set_geometry(int sectors_per_track, uint8_t sector_size, bool is_double_density);
|
||||
|
||||
bool get_is_read_only();
|
||||
void set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track);
|
||||
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
|
||||
|
||||
private:
|
||||
std::mutex file_access_mutex_;
|
||||
virtual long get_file_offset_for_position(unsigned int head, unsigned int position) = 0;
|
||||
|
||||
int sectors_per_track_ = 0;
|
||||
uint8_t sector_size_ = 0;
|
||||
bool is_double_density_ = true;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* SectorDump_hpp */
|
@ -9,7 +9,10 @@
|
||||
#include "OricMFMDSK.hpp"
|
||||
|
||||
#include "../../Track/PCMTrack.hpp"
|
||||
#include "../../Encodings/MFM.hpp"
|
||||
#include "../../Encodings/MFM/Constants.hpp"
|
||||
#include "../../Encodings/MFM/Shifter.hpp"
|
||||
#include "../../Encodings/MFM/Encoder.hpp"
|
||||
#include "../../Track/TrackSerialiser.hpp"
|
||||
|
||||
using namespace Storage::Disk;
|
||||
|
||||
@ -116,12 +119,48 @@ std::shared_ptr<Track> OricMFMDSK::get_track_at_position(unsigned int head, unsi
|
||||
}
|
||||
|
||||
void OricMFMDSK::set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track) {
|
||||
Storage::Encodings::MFM::Parser parser(true, track);
|
||||
std::vector<uint8_t> parsed_track = parser.get_track(0);
|
||||
PCMSegment segment = Storage::Disk::track_serialisation(*track, Storage::Encodings::MFM::MFMBitLength);
|
||||
Storage::Encodings::MFM::Shifter shifter;
|
||||
shifter.set_is_double_density(true);
|
||||
shifter.set_should_obey_syncs(true);
|
||||
std::vector<uint8_t> parsed_track;
|
||||
int size = 0;
|
||||
int offset = 0;
|
||||
bool capture_size = false;
|
||||
|
||||
for(unsigned int bit = 0; bit < segment.number_of_bits; ++bit) {
|
||||
shifter.add_input_bit(segment.bit(bit));
|
||||
if(shifter.get_token() == Storage::Encodings::MFM::Shifter::Token::None) continue;
|
||||
parsed_track.push_back(shifter.get_byte());
|
||||
|
||||
if(offset) {
|
||||
offset--;
|
||||
if(!offset) {
|
||||
shifter.set_should_obey_syncs(true);
|
||||
}
|
||||
if(capture_size && offset == 2) {
|
||||
size = parsed_track.back();
|
||||
capture_size = false;
|
||||
}
|
||||
}
|
||||
|
||||
if( shifter.get_token() == Storage::Encodings::MFM::Shifter::Token::Data ||
|
||||
shifter.get_token() == Storage::Encodings::MFM::Shifter::Token::DeletedData) {
|
||||
offset = 128 << size;
|
||||
shifter.set_should_obey_syncs(false);
|
||||
}
|
||||
|
||||
if(shifter.get_token() == Storage::Encodings::MFM::Shifter::Token::ID) {
|
||||
offset = 6;
|
||||
shifter.set_should_obey_syncs(false);
|
||||
capture_size = true;
|
||||
}
|
||||
}
|
||||
|
||||
long file_offset = get_file_offset_for_position(head, position);
|
||||
|
||||
std::lock_guard<std::mutex> lock_guard(file_access_mutex_);
|
||||
fseek(file_, file_offset, SEEK_SET);
|
||||
std::lock_guard<std::mutex> lock_guard(file_access_mutex_);
|
||||
fseek(file_, file_offset, SEEK_SET);
|
||||
size_t track_size = std::min((size_t)6400, parsed_track.size());
|
||||
fwrite(parsed_track.data(), 1, track_size, file_);
|
||||
}
|
||||
|
@ -8,12 +8,16 @@
|
||||
|
||||
#include "SSD.hpp"
|
||||
|
||||
#include "../../Encodings/MFM.hpp"
|
||||
#include "Utility/ImplicitSectors.hpp"
|
||||
|
||||
namespace {
|
||||
static const unsigned int sectors_per_track = 10;
|
||||
static const unsigned int sector_size = 1;
|
||||
}
|
||||
|
||||
using namespace Storage::Disk;
|
||||
|
||||
SSD::SSD(const char *file_name) :
|
||||
Storage::FileHolder(file_name) {
|
||||
SSD::SSD(const char *file_name) : MFMSectorDump(file_name) {
|
||||
// very loose validation: the file needs to be a multiple of 256 bytes
|
||||
// and not ungainly large
|
||||
|
||||
@ -26,6 +30,8 @@ SSD::SSD(const char *file_name) :
|
||||
track_count_ = (unsigned int)(file_stats_.st_size / (256 * 10));
|
||||
if(track_count_ < 40) track_count_ = 40;
|
||||
else if(track_count_ < 80) track_count_ = 80;
|
||||
|
||||
set_geometry(sectors_per_track, sector_size, false);
|
||||
}
|
||||
|
||||
unsigned int SSD::get_head_position_count() {
|
||||
@ -36,66 +42,6 @@ unsigned int SSD::get_head_count() {
|
||||
return head_count_;
|
||||
}
|
||||
|
||||
bool SSD::get_is_read_only() {
|
||||
return is_read_only_;
|
||||
}
|
||||
|
||||
long SSD::get_file_offset_for_position(unsigned int head, unsigned int position) {
|
||||
return (position * head_count_ + head) * 256 * 10;
|
||||
}
|
||||
|
||||
std::shared_ptr<Track> SSD::get_track_at_position(unsigned int head, unsigned int position) {
|
||||
std::shared_ptr<Track> track;
|
||||
|
||||
if(head >= head_count_) return track;
|
||||
|
||||
std::vector<Storage::Encodings::MFM::Sector> sectors;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock_guard(file_access_mutex_);
|
||||
fseek(file_, get_file_offset_for_position(head, position), SEEK_SET);
|
||||
|
||||
for(int sector = 0; sector < 10; sector++) {
|
||||
Storage::Encodings::MFM::Sector new_sector;
|
||||
new_sector.track = (uint8_t)position;
|
||||
new_sector.side = 0;
|
||||
new_sector.sector = (uint8_t)sector;
|
||||
new_sector.size = 1;
|
||||
|
||||
new_sector.data.resize(256);
|
||||
fread(new_sector.data.data(), 1, 256, file_);
|
||||
|
||||
// zero out if this wasn't present in the disk image; it's still appropriate to put a sector
|
||||
// on disk because one will have been placed during formatting, but there's no reason to leak
|
||||
// information from outside the emulated machine's world
|
||||
if(feof(file_)) memset(new_sector.data.data(), 0, 256);
|
||||
|
||||
sectors.push_back(std::move(new_sector));
|
||||
}
|
||||
}
|
||||
|
||||
if(sectors.size()) return Storage::Encodings::MFM::GetFMTrackWithSectors(sectors);
|
||||
|
||||
return track;
|
||||
}
|
||||
|
||||
void SSD::set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track) {
|
||||
std::vector<uint8_t> data;
|
||||
Storage::Encodings::MFM::Parser parser(false, track);
|
||||
for(unsigned int c = 0; c < 10; c++) {
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, (uint8_t)position, (uint8_t)c);
|
||||
if(sector) {
|
||||
data.insert(data.end(), sector->data.begin(), sector->data.end());
|
||||
} else {
|
||||
// TODO: what's correct here? Warn the user that whatever has been written to the disk,
|
||||
// it can no longer be stored as an SSD? If so, warn them by what route?
|
||||
data.resize(data.size() + 256);
|
||||
}
|
||||
}
|
||||
|
||||
long file_offset = get_file_offset_for_position(head, position);
|
||||
|
||||
std::lock_guard<std::mutex> lock_guard(file_access_mutex_);
|
||||
ensure_file_is_at_least_length(file_offset);
|
||||
fseek(file_, file_offset, SEEK_SET);
|
||||
fwrite(data.data(), 1, data.size(), file_);
|
||||
}
|
||||
|
@ -9,8 +9,7 @@
|
||||
#ifndef SSD_hpp
|
||||
#define SSD_hpp
|
||||
|
||||
#include "../DiskImage.hpp"
|
||||
#include "../../../FileHolder.hpp"
|
||||
#include "MFMSectorDump.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
@ -18,7 +17,7 @@ namespace Disk {
|
||||
/*!
|
||||
Provies a @c Disk containing a DSD or SSD disk image — a decoded sector dump of an Acorn DFS disk.
|
||||
*/
|
||||
class SSD: public DiskImage, public Storage::FileHolder {
|
||||
class SSD: public MFMSectorDump {
|
||||
public:
|
||||
/*!
|
||||
Construct an @c SSD containing content from the file with name @c file_name.
|
||||
@ -32,15 +31,10 @@ class SSD: public DiskImage, public Storage::FileHolder {
|
||||
ErrorNotSSD,
|
||||
};
|
||||
|
||||
// implemented to satisfy @c Disk
|
||||
unsigned int get_head_position_count();
|
||||
unsigned int get_head_count();
|
||||
bool get_is_read_only();
|
||||
void set_track_at_position(unsigned int head, unsigned int position, const std::shared_ptr<Track> &track);
|
||||
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
|
||||
|
||||
private:
|
||||
std::mutex file_access_mutex_;
|
||||
long get_file_offset_for_position(unsigned int head, unsigned int position);
|
||||
|
||||
unsigned int head_count_;
|
||||
|
58
Storage/Disk/DiskImage/Formats/Utility/ImplicitSectors.cpp
Normal file
58
Storage/Disk/DiskImage/Formats/Utility/ImplicitSectors.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
//
|
||||
// ImplicitSectors.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/09/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "ImplicitSectors.hpp"
|
||||
|
||||
#include "../../../Encodings/MFM/Sector.hpp"
|
||||
#include "../../../Encodings/MFM/Encoder.hpp"
|
||||
#include "../../../Encodings/MFM/Constants.hpp"
|
||||
#include "../../../Track/TrackSerialiser.hpp"
|
||||
#include "../../../Encodings/MFM/SegmentParser.hpp"
|
||||
|
||||
using namespace Storage::Disk;
|
||||
|
||||
std::shared_ptr<Track> Storage::Disk::track_for_sectors(uint8_t *const source, uint8_t track, uint8_t side, uint8_t first_sector, uint8_t size, bool is_double_density) {
|
||||
std::vector<Storage::Encodings::MFM::Sector> sectors;
|
||||
|
||||
off_t byte_size = (off_t)(128 << size);
|
||||
off_t source_pointer = 0;
|
||||
for(int sector = 0; sector < 10; sector++) {
|
||||
sectors.emplace_back();
|
||||
|
||||
Storage::Encodings::MFM::Sector &new_sector = sectors.back();
|
||||
new_sector.address.track = track;
|
||||
new_sector.address.side = size;
|
||||
new_sector.address.sector = first_sector;
|
||||
first_sector++;
|
||||
new_sector.size = size;
|
||||
|
||||
new_sector.data.insert(new_sector.data.begin(), source + source_pointer, source + source_pointer + byte_size);
|
||||
source_pointer += byte_size;
|
||||
}
|
||||
|
||||
if(sectors.size()) {
|
||||
return is_double_density ? Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors) : Storage::Encodings::MFM::GetFMTrackWithSectors(sectors);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Storage::Disk::decode_sectors(Track &track, uint8_t *const destination, uint8_t first_sector, uint8_t last_sector, uint8_t sector_size, bool is_double_density) {
|
||||
std::map<size_t, Storage::Encodings::MFM::Sector> sectors =
|
||||
Storage::Encodings::MFM::sectors_from_segment(
|
||||
Storage::Disk::track_serialisation(track, is_double_density ? Storage::Encodings::MFM::MFMBitLength : Storage::Encodings::MFM::FMBitLength),
|
||||
is_double_density);
|
||||
|
||||
size_t byte_size = (size_t)(128 << sector_size);
|
||||
for(auto &pair : sectors) {
|
||||
if(pair.second.address.sector > last_sector) continue;
|
||||
if(pair.second.address.sector < first_sector) continue;
|
||||
if(pair.second.size != sector_size) continue;
|
||||
memcpy(&destination[pair.second.address.sector * byte_size], pair.second.data.data(), std::min(pair.second.data.size(), byte_size));
|
||||
}
|
||||
}
|
25
Storage/Disk/DiskImage/Formats/Utility/ImplicitSectors.hpp
Normal file
25
Storage/Disk/DiskImage/Formats/Utility/ImplicitSectors.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
//
|
||||
// ImplicitSectors.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/09/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ImplicitSectors_hpp
|
||||
#define ImplicitSectors_hpp
|
||||
|
||||
#include "../../../Track/Track.hpp"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
|
||||
std::shared_ptr<Track> track_for_sectors(uint8_t *const source, uint8_t track, uint8_t side, uint8_t first_sector, uint8_t size, bool is_double_density);
|
||||
void decode_sectors(Track &track, uint8_t *const destination, uint8_t first_sector, uint8_t last_sector, uint8_t sector_size, bool is_double_density);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ImplicitSectors_hpp */
|
@ -1,534 +0,0 @@
|
||||
//
|
||||
// MFM.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/09/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MFM.hpp"
|
||||
|
||||
#include "../Track/PCMTrack.hpp"
|
||||
#include "../SingleTrackDisk/SingleTrackDisk.hpp"
|
||||
#include "../../../NumberTheory/CRC.hpp"
|
||||
|
||||
#include <set>
|
||||
|
||||
using namespace Storage::Encodings::MFM;
|
||||
|
||||
class MFMEncoder: public Encoder {
|
||||
public:
|
||||
MFMEncoder(std::vector<uint8_t> &target) : Encoder(target) {}
|
||||
|
||||
void add_byte(uint8_t input) {
|
||||
crc_generator_.add(input);
|
||||
uint16_t spread_value =
|
||||
(uint16_t)(
|
||||
((input & 0x01) << 0) |
|
||||
((input & 0x02) << 1) |
|
||||
((input & 0x04) << 2) |
|
||||
((input & 0x08) << 3) |
|
||||
((input & 0x10) << 4) |
|
||||
((input & 0x20) << 5) |
|
||||
((input & 0x40) << 6) |
|
||||
((input & 0x80) << 7)
|
||||
);
|
||||
uint16_t or_bits = (uint16_t)((spread_value << 1) | (spread_value >> 1) | (last_output_ << 15));
|
||||
uint16_t output = spread_value | ((~or_bits) & 0xaaaa);
|
||||
output_short(output);
|
||||
}
|
||||
|
||||
void add_index_address_mark() {
|
||||
for(int c = 0; c < 3; c++) output_short(MFMIndexSync);
|
||||
add_byte(IndexAddressByte);
|
||||
}
|
||||
|
||||
void add_ID_address_mark() {
|
||||
output_sync();
|
||||
add_byte(IDAddressByte);
|
||||
}
|
||||
|
||||
void add_data_address_mark() {
|
||||
output_sync();
|
||||
add_byte(DataAddressByte);
|
||||
}
|
||||
|
||||
void add_deleted_data_address_mark() {
|
||||
output_sync();
|
||||
add_byte(DeletedDataAddressByte);
|
||||
}
|
||||
|
||||
private:
|
||||
uint16_t last_output_;
|
||||
void output_short(uint16_t value) {
|
||||
last_output_ = value;
|
||||
Encoder::output_short(value);
|
||||
}
|
||||
|
||||
void output_sync() {
|
||||
for(int c = 0; c < 3; c++) output_short(MFMSync);
|
||||
crc_generator_.set_value(MFMPostSyncCRCValue);
|
||||
}
|
||||
};
|
||||
|
||||
class FMEncoder: public Encoder {
|
||||
// encodes each 16-bit part as clock, data, clock, data [...]
|
||||
public:
|
||||
FMEncoder(std::vector<uint8_t> &target) : Encoder(target) {}
|
||||
|
||||
void add_byte(uint8_t input) {
|
||||
crc_generator_.add(input);
|
||||
output_short(
|
||||
(uint16_t)(
|
||||
((input & 0x01) << 0) |
|
||||
((input & 0x02) << 1) |
|
||||
((input & 0x04) << 2) |
|
||||
((input & 0x08) << 3) |
|
||||
((input & 0x10) << 4) |
|
||||
((input & 0x20) << 5) |
|
||||
((input & 0x40) << 6) |
|
||||
((input & 0x80) << 7) |
|
||||
0xaaaa
|
||||
));
|
||||
}
|
||||
|
||||
void add_index_address_mark() {
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(IndexAddressByte);
|
||||
output_short(FMIndexAddressMark);
|
||||
}
|
||||
|
||||
void add_ID_address_mark() {
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(IDAddressByte);
|
||||
output_short(FMIDAddressMark);
|
||||
}
|
||||
|
||||
void add_data_address_mark() {
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(DataAddressByte);
|
||||
output_short(FMDataAddressMark);
|
||||
}
|
||||
|
||||
void add_deleted_data_address_mark() {
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(DeletedDataAddressByte);
|
||||
output_short(FMDeletedDataAddressMark);
|
||||
}
|
||||
};
|
||||
|
||||
template<class T> std::shared_ptr<Storage::Disk::Track>
|
||||
GetTrackWithSectors(
|
||||
const std::vector<Sector> §ors,
|
||||
size_t post_index_address_mark_bytes, uint8_t post_index_address_mark_value,
|
||||
size_t pre_address_mark_bytes,
|
||||
size_t post_address_mark_bytes, uint8_t post_address_mark_value,
|
||||
size_t pre_data_mark_bytes,
|
||||
size_t post_data_bytes, uint8_t post_data_value,
|
||||
size_t expected_track_bytes) {
|
||||
Storage::Disk::PCMSegment segment;
|
||||
segment.data.reserve(expected_track_bytes);
|
||||
T shifter(segment.data);
|
||||
|
||||
// output the index mark
|
||||
shifter.add_index_address_mark();
|
||||
|
||||
// add the post-index mark
|
||||
for(size_t c = 0; c < post_index_address_mark_bytes; c++) shifter.add_byte(post_index_address_mark_value);
|
||||
|
||||
// add sectors
|
||||
for(const Sector §or : sectors) {
|
||||
// gap
|
||||
for(size_t c = 0; c < pre_address_mark_bytes; c++) shifter.add_byte(0x00);
|
||||
|
||||
// sector header
|
||||
shifter.add_ID_address_mark();
|
||||
shifter.add_byte(sector.track);
|
||||
shifter.add_byte(sector.side);
|
||||
shifter.add_byte(sector.sector);
|
||||
shifter.add_byte(sector.size);
|
||||
shifter.add_crc(sector.has_header_crc_error);
|
||||
|
||||
// gap
|
||||
for(size_t c = 0; c < post_address_mark_bytes; c++) shifter.add_byte(post_address_mark_value);
|
||||
for(size_t c = 0; c < pre_data_mark_bytes; c++) shifter.add_byte(0x00);
|
||||
|
||||
// data, if attached
|
||||
if(!sector.data.empty()) {
|
||||
if(sector.is_deleted)
|
||||
shifter.add_deleted_data_address_mark();
|
||||
else
|
||||
shifter.add_data_address_mark();
|
||||
|
||||
size_t c = 0;
|
||||
size_t declared_length = (size_t)(128 << sector.size);
|
||||
for(c = 0; c < sector.data.size() && c < declared_length; c++) {
|
||||
shifter.add_byte(sector.data[c]);
|
||||
}
|
||||
for(; c < declared_length; c++) {
|
||||
shifter.add_byte(0x00);
|
||||
}
|
||||
shifter.add_crc(sector.has_data_crc_error);
|
||||
}
|
||||
|
||||
// gap
|
||||
for(size_t c = 0; c < post_data_bytes; c++) shifter.add_byte(post_data_value);
|
||||
}
|
||||
|
||||
while(segment.data.size() < expected_track_bytes) shifter.add_byte(0x00);
|
||||
|
||||
// Allow the amount of data written to be up to 10% more than the expected size. Which is generous.
|
||||
size_t max_size = expected_track_bytes + (expected_track_bytes / 10);
|
||||
if(segment.data.size() > max_size) segment.data.resize(max_size);
|
||||
|
||||
segment.number_of_bits = (unsigned int)(segment.data.size() * 8);
|
||||
return std::shared_ptr<Storage::Disk::Track>(new Storage::Disk::PCMTrack(std::move(segment)));
|
||||
}
|
||||
|
||||
Encoder::Encoder(std::vector<uint8_t> &target) :
|
||||
crc_generator_(0x1021, 0xffff),
|
||||
target_(target) {}
|
||||
|
||||
void Encoder::output_short(uint16_t value) {
|
||||
target_.push_back(value >> 8);
|
||||
target_.push_back(value & 0xff);
|
||||
}
|
||||
|
||||
void Encoder::add_crc(bool incorrectly) {
|
||||
uint16_t crc_value = crc_generator_.get_value();
|
||||
add_byte(crc_value >> 8);
|
||||
add_byte((crc_value & 0xff) ^ (incorrectly ? 1 : 0));
|
||||
}
|
||||
|
||||
const size_t Storage::Encodings::MFM::DefaultSectorGapLength = (size_t)~0;
|
||||
|
||||
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector<Sector> §ors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
|
||||
return GetTrackWithSectors<FMEncoder>(
|
||||
sectors,
|
||||
26, 0xff,
|
||||
6,
|
||||
11, 0xff,
|
||||
6,
|
||||
(sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 27, 0xff,
|
||||
6250); // i.e. 250kbps (including clocks) * 60 = 15000kpm, at 300 rpm => 50 kbits/rotation => 6250 bytes/rotation
|
||||
}
|
||||
|
||||
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector<Sector> §ors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
|
||||
return GetTrackWithSectors<MFMEncoder>(
|
||||
sectors,
|
||||
50, 0x4e,
|
||||
12,
|
||||
22, 0x4e,
|
||||
12,
|
||||
(sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 54, 0xff,
|
||||
12500); // unintelligently: double the single-density bytes/rotation (or: 500kbps @ 300 rpm)
|
||||
}
|
||||
|
||||
std::unique_ptr<Encoder> Storage::Encodings::MFM::GetMFMEncoder(std::vector<uint8_t> &target) {
|
||||
return std::unique_ptr<Encoder>(new MFMEncoder(target));
|
||||
}
|
||||
|
||||
std::unique_ptr<Encoder> Storage::Encodings::MFM::GetFMEncoder(std::vector<uint8_t> &target) {
|
||||
return std::unique_ptr<Encoder>(new FMEncoder(target));
|
||||
}
|
||||
|
||||
#pragma mark - Parser
|
||||
|
||||
Parser::Parser(bool is_mfm) :
|
||||
Storage::Disk::Controller(4000000),
|
||||
crc_generator_(0x1021, 0xffff),
|
||||
shift_register_(0), is_mfm_(is_mfm),
|
||||
track_(0), head_(0) {
|
||||
Storage::Time bit_length;
|
||||
bit_length.length = 1;
|
||||
bit_length.clock_rate = is_mfm ? 500000 : 250000; // i.e. 250 kbps (including clocks)
|
||||
set_expected_bit_length(bit_length);
|
||||
|
||||
drive_.reset(new Storage::Disk::Drive(4000000, 300, 2));
|
||||
set_drive(drive_);
|
||||
drive_->set_motor_on(true);
|
||||
}
|
||||
|
||||
Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Disk> &disk) :
|
||||
Parser(is_mfm) {
|
||||
drive_->set_disk(disk);
|
||||
}
|
||||
|
||||
Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Track> &track) :
|
||||
Parser(is_mfm) {
|
||||
drive_->set_disk(std::make_shared<Disk::DiskImageHolder<Disk::SingleTrackDiskImage>>(track));
|
||||
}
|
||||
|
||||
void Parser::seek_to_track(uint8_t track) {
|
||||
int difference = (int)track - (int)track_;
|
||||
track_ = track;
|
||||
|
||||
if(difference) {
|
||||
int direction = difference < 0 ? -1 : 1;
|
||||
difference *= direction;
|
||||
|
||||
for(int c = 0; c < difference; c++) drive_->step(direction);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Sector> Parser::get_sector(uint8_t head, uint8_t track, uint8_t sector) {
|
||||
// Switch head and track if necessary.
|
||||
if(head_ != head) {
|
||||
drive_->set_head(head);
|
||||
}
|
||||
seek_to_track(track);
|
||||
int track_index = get_index(head, track, 0);
|
||||
|
||||
// Populate the sector cache if it's not already populated by asking for sectors unless and until
|
||||
// one is returned that has already been seen.
|
||||
if(decoded_tracks_.find(track_index) == decoded_tracks_.end()) {
|
||||
std::shared_ptr<Sector> first_sector = get_next_sector();
|
||||
std::set<uint8_t> visited_sectors;
|
||||
if(first_sector) {
|
||||
while(1) {
|
||||
std::shared_ptr<Sector> next_sector = get_next_sector();
|
||||
if(next_sector) {
|
||||
if(visited_sectors.find(next_sector->sector) != visited_sectors.end()) {
|
||||
break;
|
||||
}
|
||||
visited_sectors.insert(next_sector->sector);
|
||||
}
|
||||
}
|
||||
}
|
||||
decoded_tracks_.insert(track_index);
|
||||
}
|
||||
|
||||
// Check cache for sector.
|
||||
int index = get_index(head, track, sector);
|
||||
auto cached_sector = sectors_by_index_.find(index);
|
||||
if(cached_sector != sectors_by_index_.end()) {
|
||||
return cached_sector->second;
|
||||
}
|
||||
|
||||
// If it wasn't found, it doesn't exist.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Parser::get_track(uint8_t track) {
|
||||
seek_to_track(track);
|
||||
return get_track();
|
||||
}
|
||||
|
||||
void Parser::process_input_bit(int value) {
|
||||
shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0xffff;
|
||||
bit_count_++;
|
||||
}
|
||||
|
||||
void Parser::process_index_hole() {
|
||||
index_count_++;
|
||||
}
|
||||
|
||||
uint8_t Parser::get_byte_for_shift_value(uint16_t value) {
|
||||
return (uint8_t)(
|
||||
((value&0x0001) >> 0) |
|
||||
((value&0x0004) >> 1) |
|
||||
((value&0x0010) >> 2) |
|
||||
((value&0x0040) >> 3) |
|
||||
((value&0x0100) >> 4) |
|
||||
((value&0x0400) >> 5) |
|
||||
((value&0x1000) >> 6) |
|
||||
((value&0x4000) >> 7));
|
||||
}
|
||||
|
||||
uint8_t Parser::get_next_byte() {
|
||||
bit_count_ = 0;
|
||||
// Archetypal MFM is 500,000 bps given that the drive has an RPM of 300. Clock rate was
|
||||
// specified at 4,000,000. So that's an idealised 8 cycles per bit, Jump ahead 14
|
||||
// times that...
|
||||
run_for(Cycles(14 * 8));
|
||||
|
||||
// ... and proceed at half-idealised-bit intervals to get the next bit. Then proceed very gingerly indeed.
|
||||
while(bit_count_ < 15) run_for(Cycles(4));
|
||||
while(bit_count_ < 16) run_for(Cycles(2));
|
||||
|
||||
uint8_t byte = get_byte_for_shift_value((uint16_t)shift_register_);
|
||||
crc_generator_.add(byte);
|
||||
return byte;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Parser::get_track() {
|
||||
std::vector<uint8_t> result;
|
||||
int distance_until_permissible_sync = 0;
|
||||
uint8_t last_id[6] = {0, 0, 0, 0, 0, 0};
|
||||
int last_id_pointer = 0;
|
||||
bool next_is_type = false;
|
||||
|
||||
// align to the next index hole
|
||||
index_count_ = 0;
|
||||
while(!index_count_) run_for(Cycles(1));
|
||||
|
||||
// capture every other bit until the next index hole
|
||||
index_count_ = 0;
|
||||
while(1) {
|
||||
// wait until either another bit or the index hole arrives
|
||||
bit_count_ = 0;
|
||||
bool found_sync = false;
|
||||
while(!index_count_ && !found_sync && bit_count_ < 16) {
|
||||
int previous_bit_count = bit_count_;
|
||||
run_for(Cycles(1));
|
||||
|
||||
if(!distance_until_permissible_sync && bit_count_ != previous_bit_count) {
|
||||
uint16_t low_shift_register = (shift_register_&0xffff);
|
||||
if(is_mfm_) {
|
||||
found_sync = (low_shift_register == MFMIndexSync) || (low_shift_register == MFMSync);
|
||||
} else {
|
||||
found_sync =
|
||||
(low_shift_register == FMIndexAddressMark) ||
|
||||
(low_shift_register == FMIDAddressMark) ||
|
||||
(low_shift_register == FMDataAddressMark) ||
|
||||
(low_shift_register == FMDeletedDataAddressMark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if that was the index hole then finish
|
||||
if(index_count_) {
|
||||
if(bit_count_) result.push_back(get_byte_for_shift_value((uint16_t)(shift_register_ << (16 - bit_count_))));
|
||||
break;
|
||||
}
|
||||
|
||||
// store whatever the current byte is
|
||||
uint8_t byte_value = get_byte_for_shift_value((uint16_t)shift_register_);
|
||||
result.push_back(byte_value);
|
||||
if(last_id_pointer < 6) last_id[last_id_pointer++] = byte_value;
|
||||
|
||||
// if no syncs are permissible here, decrement the waiting period and perform no further contemplation
|
||||
bool found_id = false, found_data = false;
|
||||
if(distance_until_permissible_sync) {
|
||||
distance_until_permissible_sync--;
|
||||
} else {
|
||||
if(found_sync) {
|
||||
if(is_mfm_) {
|
||||
next_is_type = true;
|
||||
} else {
|
||||
switch(shift_register_&0xffff) {
|
||||
case FMIDAddressMark: found_id = true; break;
|
||||
case FMDataAddressMark:
|
||||
case FMDeletedDataAddressMark: found_data = true; break;
|
||||
}
|
||||
}
|
||||
} else if(next_is_type) {
|
||||
switch(byte_value) {
|
||||
case IDAddressByte: found_id = true; break;
|
||||
case DataAddressByte:
|
||||
case DeletedDataAddressByte: found_data = true; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(found_id) {
|
||||
distance_until_permissible_sync = 6;
|
||||
last_id_pointer = 0;
|
||||
}
|
||||
|
||||
if(found_data) {
|
||||
distance_until_permissible_sync = 128 << last_id[3];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<Sector> Parser::get_next_sector() {
|
||||
std::shared_ptr<Sector> sector(new Sector);
|
||||
index_count_ = 0;
|
||||
|
||||
while(index_count_ < 2) {
|
||||
// look for an ID address mark
|
||||
bool id_found = false;
|
||||
while(!id_found) {
|
||||
run_for(Cycles(1));
|
||||
if(is_mfm_) {
|
||||
while(shift_register_ == MFMSync) {
|
||||
uint8_t mark = get_next_byte();
|
||||
if(mark == IDAddressByte) {
|
||||
crc_generator_.set_value(MFMPostSyncCRCValue);
|
||||
id_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(shift_register_ == FMIDAddressMark) {
|
||||
crc_generator_.reset();
|
||||
id_found = true;
|
||||
}
|
||||
}
|
||||
if(index_count_ >= 2) return nullptr;
|
||||
}
|
||||
|
||||
crc_generator_.add(IDAddressByte);
|
||||
sector->track = get_next_byte();
|
||||
sector->side = get_next_byte();
|
||||
sector->sector = get_next_byte();
|
||||
sector->size = get_next_byte();
|
||||
uint16_t header_crc = crc_generator_.get_value();
|
||||
if((header_crc >> 8) != get_next_byte()) sector->has_header_crc_error = true;
|
||||
if((header_crc & 0xff) != get_next_byte()) sector->has_header_crc_error = true;
|
||||
|
||||
// look for data mark
|
||||
bool data_found = false;
|
||||
while(!data_found) {
|
||||
run_for(Cycles(1));
|
||||
if(is_mfm_) {
|
||||
while(shift_register_ == MFMSync) {
|
||||
uint8_t mark = get_next_byte();
|
||||
if(mark == DataAddressByte) {
|
||||
crc_generator_.set_value(MFMPostSyncCRCValue);
|
||||
data_found = true;
|
||||
break;
|
||||
}
|
||||
if(mark == IDAddressByte) return nullptr;
|
||||
}
|
||||
} else {
|
||||
if(shift_register_ == FMDataAddressMark) {
|
||||
crc_generator_.reset();
|
||||
data_found = true;
|
||||
}
|
||||
if(shift_register_ == FMIDAddressMark) return nullptr;
|
||||
}
|
||||
if(index_count_ >= 2) return nullptr;
|
||||
}
|
||||
crc_generator_.add(DataAddressByte);
|
||||
|
||||
size_t data_size = (size_t)(128 << sector->size);
|
||||
sector->data.reserve(data_size);
|
||||
for(size_t c = 0; c < data_size; c++) {
|
||||
sector->data.push_back(get_next_byte());
|
||||
}
|
||||
uint16_t data_crc = crc_generator_.get_value();
|
||||
if((data_crc >> 8) != get_next_byte()) sector->has_data_crc_error = true;
|
||||
if((data_crc & 0xff) != get_next_byte()) sector->has_data_crc_error = true;
|
||||
|
||||
// Put this sector into the cache.
|
||||
int index = get_index(head_, track_, sector->sector);
|
||||
sectors_by_index_[index] = sector;
|
||||
|
||||
return sector;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<Sector> Parser::get_sector(uint8_t sector) {
|
||||
std::shared_ptr<Sector> first_sector;
|
||||
index_count_ = 0;
|
||||
while(!first_sector && index_count_ < 2) first_sector = get_next_sector();
|
||||
if(!first_sector) return nullptr;
|
||||
if(first_sector->sector == sector) return first_sector;
|
||||
|
||||
while(1) {
|
||||
std::shared_ptr<Sector> next_sector = get_next_sector();
|
||||
if(!next_sector) continue;
|
||||
if(next_sector->sector == first_sector->sector) return nullptr;
|
||||
if(next_sector->sector == sector) return next_sector;
|
||||
}
|
||||
}
|
||||
|
||||
int Parser::get_index(uint8_t head, uint8_t track, uint8_t sector) {
|
||||
return head | (track << 8) | (sector << 16);
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
//
|
||||
// MFM.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/09/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Storage_Disk_Encodings_MFM_hpp
|
||||
#define Storage_Disk_Encodings_MFM_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include "../Disk.hpp"
|
||||
#include "../Controller/DiskController.hpp"
|
||||
#include "../../../NumberTheory/CRC.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Encodings {
|
||||
namespace MFM {
|
||||
|
||||
const uint8_t IndexAddressByte = 0xfc;
|
||||
const uint8_t IDAddressByte = 0xfe;
|
||||
const uint8_t DataAddressByte = 0xfb;
|
||||
const uint8_t DeletedDataAddressByte = 0xf8;
|
||||
|
||||
const uint16_t FMIndexAddressMark = 0xf77a; // data 0xfc, with clock 0xd7 => 1111 1100 with clock 1101 0111 => 1111 0111 0111 1010
|
||||
const uint16_t FMIDAddressMark = 0xf57e; // data 0xfe, with clock 0xc7 => 1111 1110 with clock 1100 0111 => 1111 0101 0111 1110
|
||||
const uint16_t FMDataAddressMark = 0xf56f; // data 0xfb, with clock 0xc7 => 1111 1011 with clock 1100 0111 => 1111 0101 0110 1111
|
||||
const uint16_t FMDeletedDataAddressMark = 0xf56a; // data 0xf8, with clock 0xc7 => 1111 1000 with clock 1100 0111 => 1111 0101 0110 1010
|
||||
|
||||
const uint16_t MFMIndexSync = 0x5224; // data 0xc2, with a missing clock at 0x0080 => 0101 0010 1010 0100 without 1000 0000
|
||||
const uint16_t MFMSync = 0x4489; // data 0xa1, with a missing clock at 0x0020 => 0100 0100 1010 1001 without 0010 0000
|
||||
const uint16_t MFMPostSyncCRCValue = 0xcdb4; // the value the CRC generator should have after encountering three 0xa1s
|
||||
|
||||
const uint8_t MFMIndexSyncByteValue = 0xc2;
|
||||
const uint8_t MFMSyncByteValue = 0xa1;
|
||||
|
||||
/*!
|
||||
Represents a single [M]FM sector, identified by its track, side and sector records, a blob of data
|
||||
and a few extra flags of metadata.
|
||||
*/
|
||||
struct Sector {
|
||||
uint8_t track, side, sector, size;
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
bool has_data_crc_error;
|
||||
bool has_header_crc_error;
|
||||
bool is_deleted;
|
||||
|
||||
Sector() : track(0), side(0), sector(0), size(0), has_data_crc_error(false), has_header_crc_error(false), is_deleted(false) {}
|
||||
};
|
||||
|
||||
extern const size_t DefaultSectorGapLength;
|
||||
/*!
|
||||
Converts a vector of sectors into a properly-encoded MFM track.
|
||||
|
||||
@param sectors The sectors to write.
|
||||
@param sector_gap_length If specified, sets the distance in whole bytes between each ID and its data.
|
||||
@param sector_gap_filler_byte If specified, sets the value (unencoded) that is used to populate the gap between each ID and its data.
|
||||
*/
|
||||
std::shared_ptr<Storage::Disk::Track> GetMFMTrackWithSectors(const std::vector<Sector> §ors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e);
|
||||
|
||||
/*!
|
||||
Converts a vector of sectors into a properly-encoded FM track.
|
||||
|
||||
@param sectors The sectors to write.
|
||||
@param sector_gap_length If specified, sets the distance in whole bytes between each ID and its data.
|
||||
@param sector_gap_filler_byte If specified, sets the value (unencoded) that is used to populate the gap between each ID and its data.
|
||||
*/
|
||||
std::shared_ptr<Storage::Disk::Track> GetFMTrackWithSectors(const std::vector<Sector> §ors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e);
|
||||
|
||||
class Encoder {
|
||||
public:
|
||||
Encoder(std::vector<uint8_t> &target);
|
||||
virtual void add_byte(uint8_t input) = 0;
|
||||
virtual void add_index_address_mark() = 0;
|
||||
virtual void add_ID_address_mark() = 0;
|
||||
virtual void add_data_address_mark() = 0;
|
||||
virtual void add_deleted_data_address_mark() = 0;
|
||||
virtual void output_short(uint16_t value);
|
||||
|
||||
/// Outputs the CRC for all data since the last address mask; if @c incorrectly is @c true then outputs an incorrect CRC.
|
||||
void add_crc(bool incorrectly);
|
||||
|
||||
protected:
|
||||
NumberTheory::CRC16 crc_generator_;
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> &target_;
|
||||
};
|
||||
|
||||
std::unique_ptr<Encoder> GetMFMEncoder(std::vector<uint8_t> &target);
|
||||
std::unique_ptr<Encoder> GetFMEncoder(std::vector<uint8_t> &target);
|
||||
|
||||
class Parser: public Storage::Disk::Controller {
|
||||
public:
|
||||
Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Track> &track);
|
||||
|
||||
/*!
|
||||
Attempts to read the sector located at @c track and @c sector.
|
||||
|
||||
@returns a sector if one was found; @c nullptr otherwise.
|
||||
*/
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> get_sector(uint8_t head, uint8_t track, uint8_t sector);
|
||||
|
||||
/*!
|
||||
Attempts to read the track at @c track, starting from the index hole.
|
||||
|
||||
Decodes data bits only; clocks are omitted. Synchronisation values begin a new
|
||||
byte. If a synchronisation value begins partway through a byte then
|
||||
synchronisation-contributing bits will appear both in the preceding byte and
|
||||
in the next.
|
||||
|
||||
@returns a vector of data found.
|
||||
*/
|
||||
std::vector<uint8_t> get_track(uint8_t track);
|
||||
|
||||
private:
|
||||
Parser(bool is_mfm);
|
||||
|
||||
std::shared_ptr<Storage::Disk::Drive> drive_;
|
||||
unsigned int shift_register_;
|
||||
int index_count_;
|
||||
uint8_t track_, head_;
|
||||
int bit_count_;
|
||||
NumberTheory::CRC16 crc_generator_;
|
||||
bool is_mfm_;
|
||||
|
||||
void seek_to_track(uint8_t track);
|
||||
void process_input_bit(int value);
|
||||
void process_index_hole();
|
||||
uint8_t get_next_byte();
|
||||
|
||||
uint8_t get_byte_for_shift_value(uint16_t value);
|
||||
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> get_next_sector();
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> get_sector(uint8_t sector);
|
||||
std::vector<uint8_t> get_track();
|
||||
|
||||
std::map<int, std::shared_ptr<Storage::Encodings::MFM::Sector>> sectors_by_index_;
|
||||
std::set<int> decoded_tracks_;
|
||||
int get_index(uint8_t head, uint8_t track, uint8_t sector);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MFM_hpp */
|
42
Storage/Disk/Encodings/MFM/Constants.hpp
Normal file
42
Storage/Disk/Encodings/MFM/Constants.hpp
Normal file
@ -0,0 +1,42 @@
|
||||
//
|
||||
// Constants.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/09/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Constants_h
|
||||
#define Constants_h
|
||||
|
||||
#include "../../../Storage.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Encodings {
|
||||
namespace MFM {
|
||||
|
||||
const uint8_t IndexAddressByte = 0xfc;
|
||||
const uint8_t IDAddressByte = 0xfe;
|
||||
const uint8_t DataAddressByte = 0xfb;
|
||||
const uint8_t DeletedDataAddressByte = 0xf8;
|
||||
|
||||
const uint16_t FMIndexAddressMark = 0xf77a; // data 0xfc, with clock 0xd7 => 1111 1100 with clock 1101 0111 => 1111 0111 0111 1010
|
||||
const uint16_t FMIDAddressMark = 0xf57e; // data 0xfe, with clock 0xc7 => 1111 1110 with clock 1100 0111 => 1111 0101 0111 1110
|
||||
const uint16_t FMDataAddressMark = 0xf56f; // data 0xfb, with clock 0xc7 => 1111 1011 with clock 1100 0111 => 1111 0101 0110 1111
|
||||
const uint16_t FMDeletedDataAddressMark = 0xf56a; // data 0xf8, with clock 0xc7 => 1111 1000 with clock 1100 0111 => 1111 0101 0110 1010
|
||||
|
||||
const uint16_t MFMIndexSync = 0x5224; // data 0xc2, with a missing clock at 0x0080 => 0101 0010 1010 0100 without 1000 0000
|
||||
const uint16_t MFMSync = 0x4489; // data 0xa1, with a missing clock at 0x0020 => 0100 0100 1010 1001 without 0010 0000
|
||||
const uint16_t MFMPostSyncCRCValue = 0xcdb4; // the value the CRC generator should have after encountering three 0xa1s
|
||||
|
||||
const uint8_t MFMIndexSyncByteValue = 0xc2;
|
||||
const uint8_t MFMSyncByteValue = 0xa1;
|
||||
|
||||
const Time MFMBitLength = Time(1, 100000);
|
||||
const Time FMBitLength = Time(1, 50000);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Constants_h */
|
233
Storage/Disk/Encodings/MFM/Encoder.cpp
Normal file
233
Storage/Disk/Encodings/MFM/Encoder.cpp
Normal file
@ -0,0 +1,233 @@
|
||||
//
|
||||
// MFM.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/09/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Encoder.hpp"
|
||||
|
||||
#include "Constants.hpp"
|
||||
#include "../../Track/PCMTrack.hpp"
|
||||
#include "../../../../NumberTheory/CRC.hpp"
|
||||
|
||||
#include <set>
|
||||
|
||||
using namespace Storage::Encodings::MFM;
|
||||
|
||||
class MFMEncoder: public Encoder {
|
||||
public:
|
||||
MFMEncoder(std::vector<uint8_t> &target) : Encoder(target) {}
|
||||
|
||||
void add_byte(uint8_t input) {
|
||||
crc_generator_.add(input);
|
||||
uint16_t spread_value =
|
||||
(uint16_t)(
|
||||
((input & 0x01) << 0) |
|
||||
((input & 0x02) << 1) |
|
||||
((input & 0x04) << 2) |
|
||||
((input & 0x08) << 3) |
|
||||
((input & 0x10) << 4) |
|
||||
((input & 0x20) << 5) |
|
||||
((input & 0x40) << 6) |
|
||||
((input & 0x80) << 7)
|
||||
);
|
||||
uint16_t or_bits = (uint16_t)((spread_value << 1) | (spread_value >> 1) | (last_output_ << 15));
|
||||
uint16_t output = spread_value | ((~or_bits) & 0xaaaa);
|
||||
output_short(output);
|
||||
}
|
||||
|
||||
void add_index_address_mark() {
|
||||
for(int c = 0; c < 3; c++) output_short(MFMIndexSync);
|
||||
add_byte(IndexAddressByte);
|
||||
}
|
||||
|
||||
void add_ID_address_mark() {
|
||||
output_sync();
|
||||
add_byte(IDAddressByte);
|
||||
}
|
||||
|
||||
void add_data_address_mark() {
|
||||
output_sync();
|
||||
add_byte(DataAddressByte);
|
||||
}
|
||||
|
||||
void add_deleted_data_address_mark() {
|
||||
output_sync();
|
||||
add_byte(DeletedDataAddressByte);
|
||||
}
|
||||
|
||||
private:
|
||||
uint16_t last_output_;
|
||||
void output_short(uint16_t value) {
|
||||
last_output_ = value;
|
||||
Encoder::output_short(value);
|
||||
}
|
||||
|
||||
void output_sync() {
|
||||
for(int c = 0; c < 3; c++) output_short(MFMSync);
|
||||
crc_generator_.set_value(MFMPostSyncCRCValue);
|
||||
}
|
||||
};
|
||||
|
||||
class FMEncoder: public Encoder {
|
||||
// encodes each 16-bit part as clock, data, clock, data [...]
|
||||
public:
|
||||
FMEncoder(std::vector<uint8_t> &target) : Encoder(target) {}
|
||||
|
||||
void add_byte(uint8_t input) {
|
||||
crc_generator_.add(input);
|
||||
output_short(
|
||||
(uint16_t)(
|
||||
((input & 0x01) << 0) |
|
||||
((input & 0x02) << 1) |
|
||||
((input & 0x04) << 2) |
|
||||
((input & 0x08) << 3) |
|
||||
((input & 0x10) << 4) |
|
||||
((input & 0x20) << 5) |
|
||||
((input & 0x40) << 6) |
|
||||
((input & 0x80) << 7) |
|
||||
0xaaaa
|
||||
));
|
||||
}
|
||||
|
||||
void add_index_address_mark() {
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(IndexAddressByte);
|
||||
output_short(FMIndexAddressMark);
|
||||
}
|
||||
|
||||
void add_ID_address_mark() {
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(IDAddressByte);
|
||||
output_short(FMIDAddressMark);
|
||||
}
|
||||
|
||||
void add_data_address_mark() {
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(DataAddressByte);
|
||||
output_short(FMDataAddressMark);
|
||||
}
|
||||
|
||||
void add_deleted_data_address_mark() {
|
||||
crc_generator_.reset();
|
||||
crc_generator_.add(DeletedDataAddressByte);
|
||||
output_short(FMDeletedDataAddressMark);
|
||||
}
|
||||
};
|
||||
|
||||
template<class T> std::shared_ptr<Storage::Disk::Track>
|
||||
GetTrackWithSectors(
|
||||
const std::vector<Sector> §ors,
|
||||
size_t post_index_address_mark_bytes, uint8_t post_index_address_mark_value,
|
||||
size_t pre_address_mark_bytes,
|
||||
size_t post_address_mark_bytes, uint8_t post_address_mark_value,
|
||||
size_t pre_data_mark_bytes,
|
||||
size_t post_data_bytes, uint8_t post_data_value,
|
||||
size_t expected_track_bytes) {
|
||||
Storage::Disk::PCMSegment segment;
|
||||
segment.data.reserve(expected_track_bytes);
|
||||
T shifter(segment.data);
|
||||
|
||||
// output the index mark
|
||||
shifter.add_index_address_mark();
|
||||
|
||||
// add the post-index mark
|
||||
for(size_t c = 0; c < post_index_address_mark_bytes; c++) shifter.add_byte(post_index_address_mark_value);
|
||||
|
||||
// add sectors
|
||||
for(const Sector §or : sectors) {
|
||||
// gap
|
||||
for(size_t c = 0; c < pre_address_mark_bytes; c++) shifter.add_byte(0x00);
|
||||
|
||||
// sector header
|
||||
shifter.add_ID_address_mark();
|
||||
shifter.add_byte(sector.address.track);
|
||||
shifter.add_byte(sector.address.side);
|
||||
shifter.add_byte(sector.address.sector);
|
||||
shifter.add_byte(sector.size);
|
||||
shifter.add_crc(sector.has_header_crc_error);
|
||||
|
||||
// gap
|
||||
for(size_t c = 0; c < post_address_mark_bytes; c++) shifter.add_byte(post_address_mark_value);
|
||||
for(size_t c = 0; c < pre_data_mark_bytes; c++) shifter.add_byte(0x00);
|
||||
|
||||
// data, if attached
|
||||
if(!sector.data.empty()) {
|
||||
if(sector.is_deleted)
|
||||
shifter.add_deleted_data_address_mark();
|
||||
else
|
||||
shifter.add_data_address_mark();
|
||||
|
||||
size_t c = 0;
|
||||
size_t declared_length = (size_t)(128 << sector.size);
|
||||
for(c = 0; c < sector.data.size() && c < declared_length; c++) {
|
||||
shifter.add_byte(sector.data[c]);
|
||||
}
|
||||
for(; c < declared_length; c++) {
|
||||
shifter.add_byte(0x00);
|
||||
}
|
||||
shifter.add_crc(sector.has_data_crc_error);
|
||||
}
|
||||
|
||||
// gap
|
||||
for(size_t c = 0; c < post_data_bytes; c++) shifter.add_byte(post_data_value);
|
||||
}
|
||||
|
||||
while(segment.data.size() < expected_track_bytes) shifter.add_byte(0x00);
|
||||
|
||||
// Allow the amount of data written to be up to 10% more than the expected size. Which is generous.
|
||||
size_t max_size = expected_track_bytes + (expected_track_bytes / 10);
|
||||
if(segment.data.size() > max_size) segment.data.resize(max_size);
|
||||
|
||||
segment.number_of_bits = (unsigned int)(segment.data.size() * 8);
|
||||
return std::shared_ptr<Storage::Disk::Track>(new Storage::Disk::PCMTrack(std::move(segment)));
|
||||
}
|
||||
|
||||
Encoder::Encoder(std::vector<uint8_t> &target) :
|
||||
crc_generator_(0x1021, 0xffff),
|
||||
target_(target) {}
|
||||
|
||||
void Encoder::output_short(uint16_t value) {
|
||||
target_.push_back(value >> 8);
|
||||
target_.push_back(value & 0xff);
|
||||
}
|
||||
|
||||
void Encoder::add_crc(bool incorrectly) {
|
||||
uint16_t crc_value = crc_generator_.get_value();
|
||||
add_byte(crc_value >> 8);
|
||||
add_byte((crc_value & 0xff) ^ (incorrectly ? 1 : 0));
|
||||
}
|
||||
|
||||
const size_t Storage::Encodings::MFM::DefaultSectorGapLength = (size_t)~0;
|
||||
|
||||
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector<Sector> §ors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
|
||||
return GetTrackWithSectors<FMEncoder>(
|
||||
sectors,
|
||||
26, 0xff,
|
||||
6,
|
||||
11, 0xff,
|
||||
6,
|
||||
(sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 27, 0xff,
|
||||
6250); // i.e. 250kbps (including clocks) * 60 = 15000kpm, at 300 rpm => 50 kbits/rotation => 6250 bytes/rotation
|
||||
}
|
||||
|
||||
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector<Sector> §ors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
|
||||
return GetTrackWithSectors<MFMEncoder>(
|
||||
sectors,
|
||||
50, 0x4e,
|
||||
12,
|
||||
22, 0x4e,
|
||||
12,
|
||||
(sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 54, 0xff,
|
||||
12500); // unintelligently: double the single-density bytes/rotation (or: 500kbps @ 300 rpm)
|
||||
}
|
||||
|
||||
std::unique_ptr<Encoder> Storage::Encodings::MFM::GetMFMEncoder(std::vector<uint8_t> &target) {
|
||||
return std::unique_ptr<Encoder>(new MFMEncoder(target));
|
||||
}
|
||||
|
||||
std::unique_ptr<Encoder> Storage::Encodings::MFM::GetFMEncoder(std::vector<uint8_t> &target) {
|
||||
return std::unique_ptr<Encoder>(new FMEncoder(target));
|
||||
}
|
68
Storage/Disk/Encodings/MFM/Encoder.hpp
Normal file
68
Storage/Disk/Encodings/MFM/Encoder.hpp
Normal file
@ -0,0 +1,68 @@
|
||||
//
|
||||
// MFM.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/09/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Storage_Disk_Encodings_MFM_hpp
|
||||
#define Storage_Disk_Encodings_MFM_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include "Sector.hpp"
|
||||
#include "../../Track/Track.hpp"
|
||||
#include "../../../../NumberTheory/CRC.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Encodings {
|
||||
namespace MFM {
|
||||
|
||||
extern const size_t DefaultSectorGapLength;
|
||||
/*!
|
||||
Converts a vector of sectors into a properly-encoded MFM track.
|
||||
|
||||
@param sectors The sectors to write.
|
||||
@param sector_gap_length If specified, sets the distance in whole bytes between each ID and its data.
|
||||
@param sector_gap_filler_byte If specified, sets the value (unencoded) that is used to populate the gap between each ID and its data.
|
||||
*/
|
||||
std::shared_ptr<Storage::Disk::Track> GetMFMTrackWithSectors(const std::vector<Sector> §ors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e);
|
||||
|
||||
/*!
|
||||
Converts a vector of sectors into a properly-encoded FM track.
|
||||
|
||||
@param sectors The sectors to write.
|
||||
@param sector_gap_length If specified, sets the distance in whole bytes between each ID and its data.
|
||||
@param sector_gap_filler_byte If specified, sets the value (unencoded) that is used to populate the gap between each ID and its data.
|
||||
*/
|
||||
std::shared_ptr<Storage::Disk::Track> GetFMTrackWithSectors(const std::vector<Sector> §ors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e);
|
||||
|
||||
class Encoder {
|
||||
public:
|
||||
Encoder(std::vector<uint8_t> &target);
|
||||
virtual void add_byte(uint8_t input) = 0;
|
||||
virtual void add_index_address_mark() = 0;
|
||||
virtual void add_ID_address_mark() = 0;
|
||||
virtual void add_data_address_mark() = 0;
|
||||
virtual void add_deleted_data_address_mark() = 0;
|
||||
virtual void output_short(uint16_t value);
|
||||
|
||||
/// Outputs the CRC for all data since the last address mask; if @c incorrectly is @c true then outputs an incorrect CRC.
|
||||
void add_crc(bool incorrectly);
|
||||
|
||||
protected:
|
||||
NumberTheory::CRC16 crc_generator_;
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> &target_;
|
||||
};
|
||||
|
||||
std::unique_ptr<Encoder> GetMFMEncoder(std::vector<uint8_t> &target);
|
||||
std::unique_ptr<Encoder> GetFMEncoder(std::vector<uint8_t> &target);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MFM_hpp */
|
58
Storage/Disk/Encodings/MFM/Parser.cpp
Normal file
58
Storage/Disk/Encodings/MFM/Parser.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
//
|
||||
// Parser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/09/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Parser.hpp"
|
||||
|
||||
#include "Constants.hpp"
|
||||
#include "../../Track/TrackSerialiser.hpp"
|
||||
#include "SegmentParser.hpp"
|
||||
|
||||
using namespace Storage::Encodings::MFM;
|
||||
|
||||
Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Disk> &disk) :
|
||||
is_mfm_(is_mfm), disk_(disk) {}
|
||||
|
||||
void Parser::install_sectors_from_track(const Storage::Disk::Track::Address &address) {
|
||||
if(sectors_by_address_by_track_.find(address) != sectors_by_address_by_track_.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<Storage::Disk::Track> track = disk_->get_track_at_position((unsigned int)address.head, (unsigned int)address.position);
|
||||
if(!track) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::map<size_t, Sector> sectors = sectors_from_segment(
|
||||
Storage::Disk::track_serialisation(*track, is_mfm_ ? MFMBitLength : FMBitLength),
|
||||
is_mfm_);
|
||||
|
||||
std::map<int, Storage::Encodings::MFM::Sector> sectors_by_id;
|
||||
for(auto §or : sectors) {
|
||||
sectors_by_id.insert(std::make_pair(sector.second.address.sector, std::move(sector.second)));
|
||||
}
|
||||
sectors_by_address_by_track_.insert(std::make_pair(address, std::move(sectors_by_id)));
|
||||
}
|
||||
|
||||
Sector *Parser::get_sector(int head, int track, uint8_t sector) {
|
||||
Disk::Track::Address address;
|
||||
address.position = track;
|
||||
address.head = head;
|
||||
install_sectors_from_track(address);
|
||||
|
||||
auto sectors = sectors_by_address_by_track_.find(address);
|
||||
if(sectors == sectors_by_address_by_track_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto stored_sector = sectors->second.find(sector);
|
||||
if(stored_sector == sectors->second.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &stored_sector->second;
|
||||
}
|
47
Storage/Disk/Encodings/MFM/Parser.hpp
Normal file
47
Storage/Disk/Encodings/MFM/Parser.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
//
|
||||
// Parser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/09/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Parser_hpp
|
||||
#define Parser_hpp
|
||||
|
||||
#include "Sector.hpp"
|
||||
#include "../../Track/Track.hpp"
|
||||
#include "../../Drive.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Encodings {
|
||||
namespace MFM {
|
||||
|
||||
/*!
|
||||
Provides a mechanism for collecting sectors from a disk.
|
||||
*/
|
||||
class Parser {
|
||||
public:
|
||||
Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
|
||||
/*!
|
||||
Seeks to the physical track at @c head and @c track. Searches on it for a sector
|
||||
with logical address @c sector.
|
||||
|
||||
@returns a sector if one was found; @c nullptr otherwise.
|
||||
*/
|
||||
Storage::Encodings::MFM::Sector *get_sector(int head, int track, uint8_t sector);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Storage::Disk::Disk> disk_;
|
||||
bool is_mfm_ = true;
|
||||
|
||||
void install_sectors_from_track(const Storage::Disk::Track::Address &address);
|
||||
std::map<Storage::Disk::Track::Address, std::map<int, Storage::Encodings::MFM::Sector>> sectors_by_address_by_track_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Parser_hpp */
|
58
Storage/Disk/Encodings/MFM/Sector.hpp
Normal file
58
Storage/Disk/Encodings/MFM/Sector.hpp
Normal file
@ -0,0 +1,58 @@
|
||||
//
|
||||
// Sector.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/09/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Sector_h
|
||||
#define Sector_h
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Storage {
|
||||
namespace Encodings {
|
||||
namespace MFM {
|
||||
|
||||
/*!
|
||||
Represents a single [M]FM sector, identified by its track, side and sector records, a blob of data
|
||||
and a few extra flags of metadata.
|
||||
*/
|
||||
struct Sector {
|
||||
/*!
|
||||
Describes the location of a sector, implementing < to allow for use as a set key.
|
||||
*/
|
||||
struct Address {
|
||||
uint8_t track = 0, side = 0, sector = 0;
|
||||
|
||||
bool operator < (const Address &rhs) const {
|
||||
return ((track << 24) | (side << 8) | sector) < ((rhs.track << 24) | (rhs.side << 8) | rhs.sector);
|
||||
}
|
||||
};
|
||||
|
||||
Address address;
|
||||
uint8_t size = 0;
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
bool has_data_crc_error = false;
|
||||
bool has_header_crc_error = false;
|
||||
bool is_deleted = false;
|
||||
|
||||
Sector() noexcept {}
|
||||
|
||||
Sector(const Sector &&rhs) noexcept :
|
||||
address(rhs.address),
|
||||
size(rhs.size),
|
||||
data(std::move(rhs.data)),
|
||||
has_data_crc_error(rhs.has_data_crc_error),
|
||||
has_header_crc_error(rhs.has_header_crc_error),
|
||||
is_deleted(rhs.is_deleted ){}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Sector_h */
|
81
Storage/Disk/Encodings/MFM/SegmentParser.cpp
Normal file
81
Storage/Disk/Encodings/MFM/SegmentParser.cpp
Normal file
@ -0,0 +1,81 @@
|
||||
//
|
||||
// SegmentParser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/09/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "SegmentParser.hpp"
|
||||
#include "Shifter.hpp"
|
||||
|
||||
using namespace Storage::Encodings::MFM;
|
||||
|
||||
std::map<size_t, Storage::Encodings::MFM::Sector> Storage::Encodings::MFM::sectors_from_segment(const Storage::Disk::PCMSegment &&segment, bool is_double_density) {
|
||||
std::map<size_t, Sector> result;
|
||||
Shifter shifter;
|
||||
shifter.set_is_double_density(is_double_density);
|
||||
shifter.set_should_obey_syncs(true);
|
||||
|
||||
std::unique_ptr<Storage::Encodings::MFM::Sector> new_sector;
|
||||
bool is_reading = false;
|
||||
size_t position = 0;
|
||||
size_t size = 0;
|
||||
size_t start_location = 0;
|
||||
|
||||
for(unsigned int bit = 0; bit < segment.number_of_bits; ++bit) {
|
||||
shifter.add_input_bit(segment.bit(bit));
|
||||
switch(shifter.get_token()) {
|
||||
case Shifter::Token::None:
|
||||
case Shifter::Token::Sync:
|
||||
case Shifter::Token::Index:
|
||||
break;
|
||||
|
||||
case Shifter::Token::ID:
|
||||
new_sector.reset(new Storage::Encodings::MFM::Sector);
|
||||
is_reading = true;
|
||||
start_location = bit;
|
||||
position = 0;
|
||||
shifter.set_should_obey_syncs(false);
|
||||
break;
|
||||
|
||||
case Shifter::Token::Data:
|
||||
case Shifter::Token::DeletedData:
|
||||
if(new_sector) {
|
||||
is_reading = true;
|
||||
shifter.set_should_obey_syncs(false);
|
||||
new_sector->is_deleted = (shifter.get_token() == Shifter::Token::DeletedData);
|
||||
}
|
||||
break;
|
||||
|
||||
case Shifter::Token::Byte:
|
||||
if(is_reading) {
|
||||
switch(position) {
|
||||
case 0: new_sector->address.track = shifter.get_byte(); ++position; break;
|
||||
case 1: new_sector->address.side = shifter.get_byte(); ++position; break;
|
||||
case 2: new_sector->address.sector = shifter.get_byte(); ++position; break;
|
||||
case 3:
|
||||
new_sector->size = shifter.get_byte();
|
||||
size = (size_t)(128 << new_sector->size);
|
||||
++position;
|
||||
is_reading = false;
|
||||
shifter.set_should_obey_syncs(true);
|
||||
break;
|
||||
default:
|
||||
new_sector->data.push_back(shifter.get_byte());
|
||||
++position;
|
||||
if(position == size + 4) {
|
||||
result.insert(std::make_pair(start_location, std::move(*new_sector)));
|
||||
is_reading = false;
|
||||
shifter.set_should_obey_syncs(true);
|
||||
new_sector.reset();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
30
Storage/Disk/Encodings/MFM/SegmentParser.hpp
Normal file
30
Storage/Disk/Encodings/MFM/SegmentParser.hpp
Normal file
@ -0,0 +1,30 @@
|
||||
//
|
||||
// SegmentParser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/09/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef SegmentParser_hpp
|
||||
#define SegmentParser_hpp
|
||||
|
||||
#include "Sector.hpp"
|
||||
#include "../../Track/PCMSegment.hpp"
|
||||
#include <map>
|
||||
|
||||
namespace Storage {
|
||||
namespace Encodings {
|
||||
namespace MFM {
|
||||
|
||||
/*!
|
||||
Scans @c segment for all included sectors, returning a set that maps from location within
|
||||
the segment (counted in bits from the beginning) to sector.
|
||||
*/
|
||||
std::map<size_t, Sector> sectors_from_segment(const Disk::PCMSegment &&segment, bool is_double_density);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* SegmentParser_hpp */
|
120
Storage/Disk/Encodings/MFM/Shifter.cpp
Normal file
120
Storage/Disk/Encodings/MFM/Shifter.cpp
Normal file
@ -0,0 +1,120 @@
|
||||
//
|
||||
// Shifter.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/09/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Shifter.hpp"
|
||||
#include "Constants.hpp"
|
||||
|
||||
using namespace Storage::Encodings::MFM;
|
||||
|
||||
Shifter::Shifter() : owned_crc_generator_(new NumberTheory::CRC16(0x1021, 0xffff)), crc_generator_(owned_crc_generator_.get()) {}
|
||||
Shifter::Shifter(NumberTheory::CRC16 *crc_generator) : crc_generator_(crc_generator) {}
|
||||
|
||||
void Shifter::set_is_double_density(bool is_double_density) {
|
||||
is_double_density_ = is_double_density;
|
||||
if(!is_double_density) is_awaiting_marker_value_ = false;
|
||||
}
|
||||
|
||||
void Shifter::set_should_obey_syncs(bool should_obey_syncs) {
|
||||
should_obey_syncs_ = should_obey_syncs;
|
||||
}
|
||||
|
||||
void Shifter::add_input_bit(int value) {
|
||||
shift_register_ = (shift_register_ << 1) | value;
|
||||
bits_since_token_++;
|
||||
|
||||
token_ = Token::None;
|
||||
if(should_obey_syncs_) {
|
||||
if(!is_double_density_) {
|
||||
switch(shift_register_ & 0xffff) {
|
||||
case Storage::Encodings::MFM::FMIndexAddressMark:
|
||||
token_ = Token::Index;
|
||||
crc_generator_->reset();
|
||||
crc_generator_->add(Storage::Encodings::MFM::IndexAddressByte);
|
||||
break;
|
||||
case Storage::Encodings::MFM::FMIDAddressMark:
|
||||
token_ = Token::ID;
|
||||
crc_generator_->reset();
|
||||
crc_generator_->add(Storage::Encodings::MFM::IDAddressByte);
|
||||
break;
|
||||
case Storage::Encodings::MFM::FMDataAddressMark:
|
||||
token_ = Token::Data;
|
||||
crc_generator_->reset();
|
||||
crc_generator_->add(Storage::Encodings::MFM::DataAddressByte);
|
||||
break;
|
||||
case Storage::Encodings::MFM::FMDeletedDataAddressMark:
|
||||
token_ = Token::DeletedData;
|
||||
crc_generator_->reset();
|
||||
crc_generator_->add(Storage::Encodings::MFM::DeletedDataAddressByte);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch(shift_register_ & 0xffff) {
|
||||
case Storage::Encodings::MFM::MFMIndexSync:
|
||||
bits_since_token_ = 0;
|
||||
is_awaiting_marker_value_ = true;
|
||||
|
||||
token_ = Token::Sync;
|
||||
break;
|
||||
case Storage::Encodings::MFM::MFMSync:
|
||||
bits_since_token_ = 0;
|
||||
is_awaiting_marker_value_ = true;
|
||||
crc_generator_->set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
|
||||
|
||||
token_ = Token::Sync;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(token_ != Token::None) {
|
||||
bits_since_token_ = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(bits_since_token_ == 16) {
|
||||
token_ = Token::Byte;
|
||||
bits_since_token_ = 0;
|
||||
|
||||
if(is_awaiting_marker_value_ && is_double_density_) {
|
||||
is_awaiting_marker_value_ = false;
|
||||
switch(get_byte()) {
|
||||
case Storage::Encodings::MFM::IndexAddressByte:
|
||||
token_ = Token::Index;
|
||||
break;
|
||||
case Storage::Encodings::MFM::IDAddressByte:
|
||||
token_ = Token::ID;
|
||||
break;
|
||||
case Storage::Encodings::MFM::DataAddressByte:
|
||||
token_ = Token::Data;
|
||||
break;
|
||||
case Storage::Encodings::MFM::DeletedDataAddressByte:
|
||||
token_ = Token::DeletedData;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
crc_generator_->add(get_byte());
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Shifter::get_byte() const {
|
||||
return (uint8_t)(
|
||||
((shift_register_ & 0x0001) >> 0) |
|
||||
((shift_register_ & 0x0004) >> 1) |
|
||||
((shift_register_ & 0x0010) >> 2) |
|
||||
((shift_register_ & 0x0040) >> 3) |
|
||||
((shift_register_ & 0x0100) >> 4) |
|
||||
((shift_register_ & 0x0400) >> 5) |
|
||||
((shift_register_ & 0x1000) >> 6) |
|
||||
((shift_register_ & 0x4000) >> 7));
|
||||
}
|
59
Storage/Disk/Encodings/MFM/Shifter.hpp
Normal file
59
Storage/Disk/Encodings/MFM/Shifter.hpp
Normal file
@ -0,0 +1,59 @@
|
||||
//
|
||||
// Shifter.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/09/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Shifter_hpp
|
||||
#define Shifter_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include "../../../../NumberTheory/CRC.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Encodings {
|
||||
namespace MFM {
|
||||
|
||||
class Shifter {
|
||||
public:
|
||||
Shifter();
|
||||
Shifter(NumberTheory::CRC16 *crc_generator);
|
||||
|
||||
void set_is_double_density(bool is_double_density);
|
||||
void set_should_obey_syncs(bool should_obey_syncs);
|
||||
void add_input_bit(int bit);
|
||||
|
||||
enum Token {
|
||||
Index, ID, Data, DeletedData, Sync, Byte, None
|
||||
};
|
||||
uint8_t get_byte() const;
|
||||
Token get_token() const {
|
||||
return token_;
|
||||
}
|
||||
NumberTheory::CRC16 &get_crc_generator() {
|
||||
return *crc_generator_;
|
||||
}
|
||||
|
||||
private:
|
||||
// Bit stream input state
|
||||
int bits_since_token_ = 0;
|
||||
int shift_register_ = 0;
|
||||
bool is_awaiting_marker_value_ = false;
|
||||
bool should_obey_syncs_;
|
||||
Token token_;
|
||||
|
||||
// input configuration
|
||||
bool is_double_density_ = false;
|
||||
|
||||
std::unique_ptr<NumberTheory::CRC16> owned_crc_generator_;
|
||||
NumberTheory::CRC16 *crc_generator_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Shifter_hpp */
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include "CPM.hpp"
|
||||
|
||||
#include "../Encodings/MFM.hpp"
|
||||
#include "../Encodings/MFM/Parser.hpp"
|
||||
|
||||
using namespace Storage::Disk::CPM;
|
||||
|
||||
@ -26,7 +26,7 @@ std::unique_ptr<Storage::Disk::CPM::Catalogue> Storage::Disk::CPM::GetCatalogue(
|
||||
if(catalogue_allocation_bitmap & 0x8000) {
|
||||
size_t size_read = 0;
|
||||
do {
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> sector_contents = parser.get_sector(0, (uint8_t)track, (uint8_t)(parameters.first_sector + sector));
|
||||
Storage::Encodings::MFM::Sector *sector_contents = parser.get_sector(0, (uint8_t)track, (uint8_t)(parameters.first_sector + sector));
|
||||
if(!sector_contents) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -46,77 +46,109 @@ std::unique_ptr<Storage::Disk::CPM::Catalogue> Storage::Disk::CPM::GetCatalogue(
|
||||
catalogue_allocation_bitmap <<= 1;
|
||||
}
|
||||
|
||||
std::unique_ptr<Catalogue> result(new Catalogue);
|
||||
bool has_long_allocation_units = (parameters.tracks * parameters.sectors_per_track * (int)sector_size / parameters.block_size) >= 256;
|
||||
size_t bytes_per_catalogue_entry = (has_long_allocation_units ? 16 : 8) * (size_t)parameters.block_size;
|
||||
struct CatalogueEntry {
|
||||
uint8_t user_number;
|
||||
std::string name;
|
||||
std::string type;
|
||||
bool read_only;
|
||||
bool system;
|
||||
size_t extent;
|
||||
uint8_t number_of_records;
|
||||
size_t catalogue_index;
|
||||
|
||||
// From the catalogue, create files.
|
||||
std::map<std::vector<uint8_t>, size_t> indices_by_name;
|
||||
File empty_file;
|
||||
bool operator < (const CatalogueEntry &rhs) const {
|
||||
return std::tie(user_number, name, type, extent) < std::tie(rhs.user_number, rhs.name, rhs.type, rhs.extent);
|
||||
}
|
||||
bool is_same_file(const CatalogueEntry &rhs) const {
|
||||
return std::tie(user_number, name, type) == std::tie(rhs.user_number, rhs.name, rhs.type);
|
||||
}
|
||||
};
|
||||
|
||||
// From the catalogue, get catalogue entries.
|
||||
std::vector<CatalogueEntry> catalogue_entries;
|
||||
for(size_t c = 0; c < catalogue.size(); c += 32) {
|
||||
// Skip this file if it's deleted; this is marked by it having 0xe5 as its user number
|
||||
if(catalogue[c] == 0xe5) continue;
|
||||
|
||||
// Check whether this file has yet been seen; if not then add it to the list
|
||||
std::vector<uint8_t> descriptor;
|
||||
size_t index;
|
||||
descriptor.insert(descriptor.begin(), &catalogue[c], &catalogue[c + 12]);
|
||||
auto iterator = indices_by_name.find(descriptor);
|
||||
if(iterator != indices_by_name.end()) {
|
||||
index = iterator->second;
|
||||
} else {
|
||||
File new_file;
|
||||
new_file.user_number = catalogue[c];
|
||||
for(size_t s = 0; s < 8; s++) new_file.name.push_back((char)catalogue[c + s + 1]);
|
||||
for(size_t s = 0; s < 3; s++) new_file.type.push_back((char)catalogue[c + s + 9] & 0x7f);
|
||||
new_file.read_only = catalogue[c + 9] & 0x80;
|
||||
new_file.system = catalogue[c + 10] & 0x80;
|
||||
index = result->files.size();
|
||||
result->files.push_back(new_file);
|
||||
indices_by_name[descriptor] = index;
|
||||
catalogue_entries.emplace_back();
|
||||
CatalogueEntry &entry = catalogue_entries.back();
|
||||
entry.user_number = catalogue[c];
|
||||
entry.name.insert(entry.name.begin(), &catalogue[c+1], &catalogue[c+9]);
|
||||
for(size_t s = 0; s < 3; s++) entry.type.push_back((char)catalogue[c + s + 9] & 0x7f);
|
||||
entry.read_only = catalogue[c + 9] & 0x80;
|
||||
entry.system = catalogue[c + 10] & 0x80;
|
||||
entry.extent = (size_t)(catalogue[c + 12] + (catalogue[c + 14] << 5));
|
||||
entry.number_of_records = catalogue[c + 15];
|
||||
entry.catalogue_index = c;
|
||||
}
|
||||
|
||||
// Sort the catalogue entries and then map to files.
|
||||
std::sort(catalogue_entries.begin(), catalogue_entries.end());
|
||||
|
||||
std::unique_ptr<Catalogue> result(new Catalogue);
|
||||
|
||||
bool has_long_allocation_units = (parameters.tracks * parameters.sectors_per_track * (int)sector_size / parameters.block_size) >= 256;
|
||||
size_t bytes_per_catalogue_entry = (has_long_allocation_units ? 16 : 8) * (size_t)parameters.block_size;
|
||||
int sectors_per_block = parameters.block_size / (int)sector_size;
|
||||
int records_per_sector = (int)sector_size / 128;
|
||||
|
||||
auto entry = catalogue_entries.begin();
|
||||
while(entry != catalogue_entries.end()) {
|
||||
// Find final catalogue entry that relates to the same file.
|
||||
auto final_entry = entry + 1;
|
||||
while(final_entry != catalogue_entries.end() && final_entry->is_same_file(*entry)) {
|
||||
final_entry++;
|
||||
}
|
||||
final_entry--;
|
||||
|
||||
// figure out where this data needs to be pasted in
|
||||
size_t extent = (size_t)(catalogue[c + 12] + (catalogue[c + 14] << 5));
|
||||
int number_of_records = catalogue[c + 15];
|
||||
// Create file.
|
||||
result->files.emplace_back();
|
||||
File &new_file = result->files.back();
|
||||
new_file.user_number = entry->user_number;
|
||||
new_file.name = std::move(entry->name);
|
||||
new_file.type = std::move(entry->type);
|
||||
new_file.read_only = entry->read_only;
|
||||
new_file.system = entry->system;
|
||||
|
||||
size_t required_size = extent * bytes_per_catalogue_entry + (size_t)number_of_records * 128;
|
||||
if(result->files[index].data.size() < required_size) {
|
||||
result->files[index].data.resize(required_size);
|
||||
}
|
||||
// Create storage for data.
|
||||
size_t required_size = final_entry->extent * bytes_per_catalogue_entry + (size_t)final_entry->number_of_records * 128;
|
||||
new_file.data.resize(required_size);
|
||||
|
||||
int sectors_per_block = parameters.block_size / (int)sector_size;
|
||||
int records_per_sector = (int)sector_size / 128;
|
||||
int record = 0;
|
||||
for(size_t block = 0; block < (has_long_allocation_units ? 8 : 16) && record < number_of_records; block++) {
|
||||
int block_number;
|
||||
if(has_long_allocation_units) {
|
||||
block_number = catalogue[c + 16 + (block << 1)] + (catalogue[c + 16 + (block << 1) + 1] << 8);
|
||||
} else {
|
||||
block_number = catalogue[c + 16 + block];
|
||||
}
|
||||
if(!block_number) {
|
||||
record += parameters.block_size / 128;
|
||||
continue;
|
||||
}
|
||||
int first_sector = block_number * sectors_per_block;
|
||||
|
||||
sector = first_sector % parameters.sectors_per_track;
|
||||
track = first_sector / parameters.sectors_per_track;
|
||||
|
||||
for(int s = 0; s < sectors_per_block && record < number_of_records; s++) {
|
||||
std::shared_ptr<Storage::Encodings::MFM::Sector> sector_contents = parser.get_sector(0, (uint8_t)track, (uint8_t)(parameters.first_sector + sector));
|
||||
if(!sector_contents) break;
|
||||
sector++;
|
||||
if(sector == parameters.sectors_per_track) {
|
||||
sector = 0;
|
||||
track++;
|
||||
// Accumulate all data.
|
||||
while(entry <= final_entry) {
|
||||
int record = 0;
|
||||
for(size_t block = 0; block < (has_long_allocation_units ? 8 : 16) && record < entry->number_of_records; block++) {
|
||||
int block_number;
|
||||
if(has_long_allocation_units) {
|
||||
block_number = catalogue[entry->catalogue_index + 16 + (block << 1)] + (catalogue[entry->catalogue_index + 16 + (block << 1) + 1] << 8);
|
||||
} else {
|
||||
block_number = catalogue[entry->catalogue_index + 16 + block];
|
||||
}
|
||||
if(!block_number) {
|
||||
record += parameters.block_size / 128;
|
||||
continue;
|
||||
}
|
||||
int first_sector = block_number * sectors_per_block;
|
||||
|
||||
int records_to_copy = std::min(number_of_records - record, records_per_sector);
|
||||
memcpy(&result->files[index].data[extent * bytes_per_catalogue_entry + (size_t)record * 128], sector_contents->data.data(), (size_t)records_to_copy * 128);
|
||||
record += records_to_copy;
|
||||
sector = first_sector % parameters.sectors_per_track;
|
||||
track = first_sector / parameters.sectors_per_track;
|
||||
|
||||
for(int s = 0; s < sectors_per_block && record < entry->number_of_records; s++) {
|
||||
Storage::Encodings::MFM::Sector *sector_contents = parser.get_sector(0, (uint8_t)track, (uint8_t)(parameters.first_sector + sector));
|
||||
if(!sector_contents) break;
|
||||
sector++;
|
||||
if(sector == parameters.sectors_per_track) {
|
||||
sector = 0;
|
||||
track++;
|
||||
}
|
||||
|
||||
int records_to_copy = std::min(entry->number_of_records - record, records_per_sector);
|
||||
memcpy(&new_file.data[entry->extent * bytes_per_catalogue_entry + (size_t)record * 128], sector_contents->data.data(), (size_t)records_to_copy * 128);
|
||||
record += records_to_copy;
|
||||
}
|
||||
}
|
||||
|
||||
entry++;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,10 @@ struct PCMSegment {
|
||||
PCMSegment(Time length_of_a_bit, unsigned int number_of_bits, std::vector<uint8_t> data)
|
||||
: length_of_a_bit(length_of_a_bit), number_of_bits(number_of_bits), data(data) {}
|
||||
PCMSegment() {}
|
||||
|
||||
int bit(size_t index) const {
|
||||
return (data[index >> 3] >> (7 ^ (index & 7)))&1;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@ -22,6 +22,23 @@ namespace Disk {
|
||||
*/
|
||||
class Track {
|
||||
public:
|
||||
/*!
|
||||
Describes the location of a track, implementing < to allow for use as a set key.
|
||||
*/
|
||||
struct Address {
|
||||
int head, position;
|
||||
|
||||
bool operator < (const Address &rhs) const {
|
||||
return (head < rhs.head) || (position < rhs.position);
|
||||
}
|
||||
// Address(const Address &rhs) noexcept : head(rhs.head), position(rhs.position) {}
|
||||
// const Address &operator =(const Address &rhs) {
|
||||
// head = rhs.head;
|
||||
// position = rhs.position;
|
||||
// return *this;
|
||||
// }
|
||||
};
|
||||
|
||||
/*!
|
||||
Describes a detectable track event — either a flux transition or the passing of the index hole,
|
||||
along with the length of time between the previous event and its occurance.
|
||||
|
58
Storage/Disk/Track/TrackSerialiser.cpp
Normal file
58
Storage/Disk/Track/TrackSerialiser.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
//
|
||||
// TrackSerialiser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 26/09/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "TrackSerialiser.hpp"
|
||||
|
||||
// TODO: if this is a PCMTrack with only one segment and that segment's bit rate is within tolerance,
|
||||
// just return a copy of that segment.
|
||||
Storage::Disk::PCMSegment Storage::Disk::track_serialisation(Track &track, Time length_of_a_bit) {
|
||||
unsigned int history_size = 16;
|
||||
DigitalPhaseLockedLoop pll(100, history_size);
|
||||
|
||||
struct ResultAccumulator: public DigitalPhaseLockedLoop::Delegate {
|
||||
PCMSegment result;
|
||||
void digital_phase_locked_loop_output_bit(int value) {
|
||||
result.data.resize(1 + (result.number_of_bits >> 3));
|
||||
if(value) result.data[result.number_of_bits >> 3] |= 0x80 >> (result.number_of_bits & 7);
|
||||
result.number_of_bits++;
|
||||
}
|
||||
} result_accumulator;
|
||||
result_accumulator.result.number_of_bits = 0;
|
||||
result_accumulator.result.length_of_a_bit = length_of_a_bit;
|
||||
|
||||
Time length_multiplier = Time(100*length_of_a_bit.clock_rate, length_of_a_bit.length);
|
||||
length_multiplier.simplify();
|
||||
|
||||
// start at the index hole
|
||||
track.seek_to(Time(0));
|
||||
|
||||
// grab events until the next index hole
|
||||
Time time_error = Time(0);
|
||||
while(true) {
|
||||
Track::Event next_event = track.get_next_event();
|
||||
if(next_event.type == Track::Event::IndexHole) break;
|
||||
|
||||
Time extended_length = next_event.length * length_multiplier + time_error;
|
||||
time_error.clock_rate = extended_length.clock_rate;
|
||||
time_error.length = extended_length.length % extended_length.clock_rate;
|
||||
pll.run_for(Cycles((int)extended_length.get_unsigned_int()));
|
||||
pll.add_pulse();
|
||||
|
||||
// If the PLL is now sufficiently primed, restart, and start recording bits this time.
|
||||
if(history_size) {
|
||||
history_size--;
|
||||
if(!history_size) {
|
||||
track.seek_to(Time(0));
|
||||
time_error.set_zero();
|
||||
pll.set_delegate(&result_accumulator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result_accumulator.result;
|
||||
}
|
38
Storage/Disk/Track/TrackSerialiser.hpp
Normal file
38
Storage/Disk/Track/TrackSerialiser.hpp
Normal file
@ -0,0 +1,38 @@
|
||||
//
|
||||
// TrackSerialiser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 23/09/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef TrackSerialiser_h
|
||||
#define TrackSerialiser_h
|
||||
|
||||
#include "../DPLL/DigitalPhaseLockedLoop.hpp"
|
||||
#include "PCMSegment.hpp"
|
||||
#include "Track.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
|
||||
/*!
|
||||
Instantiates a PLL with a target bit length of @c length_of_a_bit and provides a complete
|
||||
serialisation of @c track, starting from the index hole.
|
||||
|
||||
This feature is offered for the benefit of various parts of the code that need to make
|
||||
sense of a track **other than emulation**, as it renders a one-off image of the track,
|
||||
which can be inaccurate. However there are many occasions where a single rendering is
|
||||
desireable — e.g. file formats that apply that constraint, or static analysis prior to
|
||||
emulation launch, which works with broad strokes.
|
||||
|
||||
@param track The track to serialise.
|
||||
@param length_of_a_bit The expected length of a single bit, as a proportion of the
|
||||
track length.
|
||||
*/
|
||||
PCMSegment track_serialisation(Track &track, Time length_of_a_bit);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* TrackSerialiser_h */
|
Loading…
x
Reference in New Issue
Block a user