diff --git a/Analyser/Static/StaticAnalyser.cpp b/Analyser/Static/StaticAnalyser.cpp index fdda45eaf..0990ebfaa 100644 --- a/Analyser/Static/StaticAnalyser.cpp +++ b/Analyser/Static/StaticAnalyser.cpp @@ -62,6 +62,7 @@ #include "Storage/Disk/DiskImage/Formats/NIB.hpp" #include "Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp" #include "Storage/Disk/DiskImage/Formats/PCBooter.hpp" +#include "Storage/Disk/DiskImage/Formats/SAP.hpp" #include "Storage/Disk/DiskImage/Formats/SSD.hpp" #include "Storage/Disk/DiskImage/Formats/STX.hpp" #include "Storage/Disk/DiskImage/Formats/WOZ.hpp" @@ -311,6 +312,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: TargetPlatform::AcornElectron | TargetPlatform::Coleco | TargetPlatform::MSX, "rom"); + accumulator.try_standard>(TargetPlatform::ThomsonMO, "sap"); accumulator.try_standard(TargetPlatform::Sega, "sg"); accumulator.try_standard(TargetPlatform::Sega, "sms"); accumulator.try_standard>(TargetPlatform::Acorn, "ssd"); diff --git a/Numeric/CRC.hpp b/Numeric/CRC.hpp index 43f1c2718..80601a2d1 100644 --- a/Numeric/CRC.hpp +++ b/Numeric/CRC.hpp @@ -27,10 +27,6 @@ template < > class Generator { public: - /*! - Instantiates a CRC16 that will compute the CRC16 specified by the supplied - @c polynomial and @c reset_value. - */ constexpr Generator() noexcept: value_(reset_value) {} /// Resets the CRC to the reset value. diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 46f3fc391..7e5ebcdd3 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -58,6 +58,9 @@ 4B03E8422F9528D4008AF203 /* LEP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B03E8412F9528D4008AF203 /* LEP.cpp */; }; 4B03E8432F9528D4008AF203 /* LEP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B03E8412F9528D4008AF203 /* LEP.cpp */; }; 4B03E8442F9528D4008AF203 /* LEP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B03E8412F9528D4008AF203 /* LEP.cpp */; }; + 4B03E8472F965E17008AF203 /* SAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B03E8462F965E17008AF203 /* SAP.cpp */; }; + 4B03E8482F965E17008AF203 /* SAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B03E8462F965E17008AF203 /* SAP.cpp */; }; + 4B03E8492F965E17008AF203 /* SAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B03E8462F965E17008AF203 /* SAP.cpp */; }; 4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B049CDC1DA3C82F00322067 /* BCDTest.swift */; }; 4B04C899285E3DC800AA8FD6 /* 65816ComparativeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B04C898285E3DC800AA8FD6 /* 65816ComparativeTests.mm */; }; 4B051C912669C90B00CA44E8 /* ROMCatalogue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051C5826670A9300CA44E8 /* ROMCatalogue.cpp */; }; @@ -1396,6 +1399,8 @@ 4B03E8352F8D914C008AF203 /* Video.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Video.cpp; sourceTree = ""; }; 4B03E8402F9528D4008AF203 /* LEP.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LEP.hpp; sourceTree = ""; }; 4B03E8412F9528D4008AF203 /* LEP.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = LEP.cpp; sourceTree = ""; }; + 4B03E8452F965E17008AF203 /* SAP.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SAP.hpp; sourceTree = ""; }; + 4B03E8462F965E17008AF203 /* SAP.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SAP.cpp; sourceTree = ""; }; 4B046DC31CFE651500E9E45E /* ScanProducer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ScanProducer.hpp; sourceTree = ""; }; 4B047075201ABC180047AB0D /* Cartridge.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = ""; }; 4B049CDC1DA3C82F00322067 /* BCDTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BCDTest.swift; sourceTree = ""; }; @@ -3457,6 +3462,7 @@ 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */, 4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */, 423820422B1A90BE00964EFE /* PCBooter.cpp */, + 4B03E8462F965E17008AF203 /* SAP.cpp */, 4B4518991F75FD1B00926311 /* SSD.cpp */, 4B7BA03323C58B1E00B98D9E /* STX.cpp */, 4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */, @@ -3481,6 +3487,7 @@ 4B0F94FD208C1A1600FE41D9 /* NIB.hpp */, 4B4518981F75FD1B00926311 /* OricMFMDSK.hpp */, 423820432B1A90BE00964EFE /* PCBooter.hpp */, + 4B03E8452F965E17008AF203 /* SAP.hpp */, 4B45189A1F75FD1B00926311 /* SSD.hpp */, 4B7BA03223C58B1E00B98D9E /* STX.hpp */, 4B6ED2EF208E2F8A0047B343 /* WOZ.hpp */, @@ -6675,6 +6682,7 @@ 4BD235CC2F32A4C80094AFAE /* KernelShaders.cpp in Sources */, 4B055AC21FAE9AE30060FFFF /* KeyboardMachine.cpp in Sources */, 4B89453B201967B4007DE474 /* StaticAnalyser.cpp in Sources */, + 4B03E8482F965E17008AF203 /* SAP.cpp in Sources */, 4B055AEB1FAE9BA20060FFFF /* PartialMachineCycle.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -6776,6 +6784,7 @@ 4B6AAEA4230E3E1D0078E864 /* MassStorageDevice.cpp in Sources */, 4B89452E201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */, + 4B03E8492F965E17008AF203 /* SAP.cpp in Sources */, 4B7BA03723CEB86000B98D9E /* BD500.cpp in Sources */, 4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */, 4B8FE2221DA19FB20090D3CE /* MachineController.swift in Sources */, @@ -7050,6 +7059,7 @@ 423BDC4A2AB24699008E37B6 /* 8088Tests.mm in Sources */, 4B778F2723A5EEF60000D260 /* BinaryDump.cpp in Sources */, 4BFCA1241ECBDCB400AC40C1 /* AllRAMProcessor.cpp in Sources */, + 4B03E8472F965E17008AF203 /* SAP.cpp in Sources */, 4B06AB042C6460D60034D014 /* 6560.cpp in Sources */, 4B06AB002C6460B70034D014 /* Keyboard.cpp in Sources */, 4B778F5223A5F22F0000D260 /* StaticAnalyser.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index 606b53e0c..23b8a7e98 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -880,6 +880,7 @@ CFBundleTypeExtensions fd + sap CFBundleTypeIconFile floppy525 diff --git a/Storage/Disk/DiskImage/Formats/SAP.cpp b/Storage/Disk/DiskImage/Formats/SAP.cpp new file mode 100644 index 000000000..c9c9367f8 --- /dev/null +++ b/Storage/Disk/DiskImage/Formats/SAP.cpp @@ -0,0 +1,98 @@ +// +// SAP.cpp +// Clock Signal +// +// Created by Thomas Harte on 20/04/2026. +// Copyright © 2026 Thomas Harte. All rights reserved. +// + +#include "SAP.hpp" + +#include "Numeric/CRC.hpp" +#include "Storage/Disk/Encodings/MFM/Encoder.hpp" + +#include + +using namespace Storage::Disk; + +SAP::SAP(const std::string &file_name) : file_(file_name) { + // Header: + // + // 1 byte: disk geometry. + // format 0 => 80 tracks, 256-byte sectors, 16 sectors/track; + // format 1 => 40 tracks, 128-byte sectors, 16 sectors/track. + // 65 bytes: text signature. + // + sector_size_ = file_.get(); + if(sector_size_ != 1 && sector_size_ != 2) { + throw Error::InvalidFormat; + } + + // Convert to regularised IBM-style floppy disk form. + sector_size_ = sector_size_ == 2 ? 0 : 1; + + // Test only the start of the signature. + if(!file_.check_signature("SYSTEME D'ARCHIVAGE PUKALL S.A.P.")) { + throw Error::InvalidFormat; + } +} + +HeadPosition SAP::maximum_head_position() const { + // In the SAP file format, this is coupled to sector size. + return HeadPosition(sector_size_ ? 80 : 40); +} + +bool SAP::is_read_only() const { + return true; +} + +bool SAP::represents(const std::string &name) const { + return name == file_.name(); +} + +Track::Address SAP::canonical_address(const Track::Address address) const { + return Track::Address( + address.head, + HeadPosition(address.position.as_int()) + ); +} + +std::unique_ptr SAP::track_at_position(const Track::Address address) const { + static constexpr int sectors_per_track = 16; + const auto track_size = sectors_per_track * ((128 << sector_size_) + 6); + const auto header_size = 66; + file_.seek(address.position.as_int() * track_size + header_size, Whence::SET); + + std::vector sectors; + sectors.reserve(sectors_per_track); + + for(int s = 0; s < sectors_per_track; s++) { + auto §or = sectors.emplace_back(); + + [[maybe_unused]] const auto format = file_.get(); + [[maybe_unused]] const auto protection = file_.get(); + sector.address.track = file_.get(); + sector.address.sector = file_.get(); + sector.size = sector_size_; + + // SAP uses almost-but-not-quite an ordinary CCITT CRC. + file_.seek(-4, Whence::CUR); + auto contents = file_.read(256 + 4); + if(file_.eof()) break; + + // On-disk bytes are XORd with B3. For some reason. + for(size_t c = 4; c < contents.size(); c++) { + contents[c] ^= 0xb3; + } + const auto calculated = CRC::Generator::crc_of(contents); + const auto crc = file_.get_be(); + + contents.erase(contents.begin(), contents.begin() + 4); + sector.samples.push_back(contents); + sector.has_data_crc_error = crc != calculated; + } + + return Encodings::MFM::TrackWithSectors(Encodings::MFM::Density::Double, sectors); +} + +void SAP::set_tracks(const std::map> &) {} diff --git a/Storage/Disk/DiskImage/Formats/SAP.hpp b/Storage/Disk/DiskImage/Formats/SAP.hpp new file mode 100644 index 000000000..bd255ef41 --- /dev/null +++ b/Storage/Disk/DiskImage/Formats/SAP.hpp @@ -0,0 +1,34 @@ +// +// SAP.hpp +// Clock Signal +// +// Created by Thomas Harte on 20/04/2026. +// Copyright © 2026 Thomas Harte. All rights reserved. +// + +#pragma once + +#include "Storage/Disk/DiskImage/DiskImage.hpp" +#include "Storage/Disk/Track/PCMTrack.hpp" +#include "Storage/FileHolder.hpp" +#include + +namespace Storage::Disk { + +class SAP: public DiskImage { +public: + SAP(const std::string &file_name); + + HeadPosition maximum_head_position() const; + Track::Address canonical_address(Track::Address) const; + std::unique_ptr track_at_position(Track::Address) const; + void set_tracks(const std::map> &tracks); + bool is_read_only() const; + bool represents(const std::string &) const; + +private: + mutable FileHolder file_; + uint8_t sector_size_; +}; + +} diff --git a/cmake/CLK_SOURCES.cmake b/cmake/CLK_SOURCES.cmake index 25a8cbf23..0a32638ce 100644 --- a/cmake/CLK_SOURCES.cmake +++ b/cmake/CLK_SOURCES.cmake @@ -220,6 +220,7 @@ set(CLK_SOURCES Storage/Disk/DiskImage/Formats/NIB.cpp Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp Storage/Disk/DiskImage/Formats/PCBooter.cpp + Storage/Disk/DiskImage/Formats/SAP.cpp Storage/Disk/DiskImage/Formats/SSD.cpp Storage/Disk/DiskImage/Formats/STX.cpp Storage/Disk/DiskImage/Formats/Utility/ImplicitSectors.cpp