diff --git a/Aaru.CommonTypes/Enums/Images.cs b/Aaru.CommonTypes/Enums/Images.cs index 5981ccfe0..e71ed9ac4 100644 --- a/Aaru.CommonTypes/Enums/Images.cs +++ b/Aaru.CommonTypes/Enums/Images.cs @@ -387,7 +387,10 @@ public enum MediaTagType DVD_DiscKey_Decrypted = 73, /// Physical Format Information for the 2nd layer of dual-layer DVDs [Description("Physical Format Information (2nd Layer)")] - DVD_PFI_2ndLayer = 74 + DVD_PFI_2ndLayer = 74, + /// Floppy write protection status + [Description("Write protection status")] + Floppy_WriteProtection = 75 } /// Enumeration of media types defined in metadata diff --git a/Aaru.Images/A2R/A2R.cs b/Aaru.Images/A2R/A2R.cs index 0a0cafc93..e2e1d2f1a 100644 --- a/Aaru.Images/A2R/A2R.cs +++ b/Aaru.Images/A2R/A2R.cs @@ -40,6 +40,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; using Aaru.CommonTypes.Structs; @@ -67,12 +68,13 @@ public sealed partial class A2R : IWritableFluxImage InfoChunkV2 _infoChunkV2; InfoChunkV3 _infoChunkV3; Dictionary _meta; + Dictionary _mediaTags; FileStream _writingStream; public A2R() => _imageInfo = new ImageInfo { ReadableSectorTags = [], - ReadableMediaTags = [], + ReadableMediaTags = [MediaTagType.Floppy_WriteProtection], HasPartitions = false, HasSessions = false, Version = null, diff --git a/Aaru.Images/A2R/Properties.cs b/Aaru.Images/A2R/Properties.cs index 907658ef9..4d70da231 100644 --- a/Aaru.Images/A2R/Properties.cs +++ b/Aaru.Images/A2R/Properties.cs @@ -78,14 +78,72 @@ public sealed partial class A2R public IEnumerable KnownExtensions => [".a2r"]; /// - public IEnumerable SupportedMediaTags => null; + public IEnumerable SupportedMediaTags => [MediaTagType.Floppy_WriteProtection]; /// public IEnumerable SupportedMediaTypes => [ - // TODO: A2R supports a lot more formats, please add more whence tested. - MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_HD, MediaType.DOS_525_DS_DD_9, MediaType.DOS_525_HD, - MediaType.Apple32SS, MediaType.AppleSonyDS, MediaType.Unknown + // Apple formats + MediaType.Apple32SS, MediaType.Apple32DS, MediaType.Apple33SS, MediaType.Apple33DS, + MediaType.AppleSonySS, MediaType.AppleSonyDS, MediaType.AppleFileWare, + + // IBM PC/DOS formats - 5.25" + MediaType.DOS_525_SS_DD_8, MediaType.DOS_525_SS_DD_9, + MediaType.DOS_525_DS_DD_8, MediaType.DOS_525_DS_DD_9, MediaType.DOS_525_HD, + + // IBM PC/DOS formats - 3.5" + MediaType.DOS_35_SS_DD_8, MediaType.DOS_35_SS_DD_9, + MediaType.DOS_35_DS_DD_8, MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_HD, MediaType.DOS_35_ED, + + // Microsoft formats + MediaType.DMF, MediaType.DMF_82, MediaType.XDF_525, MediaType.XDF_35, + + // Atari formats + MediaType.ATARI_525_SD, MediaType.ATARI_525_DD, MediaType.ATARI_525_ED, + MediaType.ATARI_35_SS_DD, MediaType.ATARI_35_DS_DD, + MediaType.ATARI_35_SS_DD_11, MediaType.ATARI_35_DS_DD_11, + + // Commodore/Amiga formats + MediaType.CBM_35_DD, MediaType.CBM_AMIGA_35_DD, MediaType.CBM_AMIGA_35_HD, + MediaType.CBM_1540, MediaType.CBM_1540_Ext, MediaType.CBM_1571, + + // NEC/Sharp formats + MediaType.NEC_525_SS, MediaType.NEC_525_DS, MediaType.NEC_525_HD, + MediaType.NEC_35_HD_8, MediaType.NEC_35_HD_15, MediaType.NEC_35_TD, + MediaType.SHARP_525, MediaType.SHARP_525_9, MediaType.SHARP_35, MediaType.SHARP_35_9, + + // 8" formats + MediaType.NEC_8_SD, MediaType.NEC_8_DD, + MediaType.ECMA_99_8, MediaType.ECMA_69_8, + + // IBM formats + MediaType.IBM23FD, MediaType.IBM33FD_128, MediaType.IBM33FD_256, MediaType.IBM33FD_512, + MediaType.IBM43FD_128, MediaType.IBM43FD_256, + MediaType.IBM53FD_256, MediaType.IBM53FD_512, MediaType.IBM53FD_1024, + + // DEC formats + MediaType.RX01, MediaType.RX02, MediaType.RX03, MediaType.RX50, + + // Acorn formats + MediaType.ACORN_525_SS_SD_40, MediaType.ACORN_525_SS_SD_80, + MediaType.ACORN_525_SS_DD_40, MediaType.ACORN_525_SS_DD_80, MediaType.ACORN_525_DS_DD, + MediaType.ACORN_35_DS_DD, MediaType.ACORN_35_DS_HD, + + // ECMA standard formats + MediaType.ECMA_54, MediaType.ECMA_59, MediaType.ECMA_66, MediaType.ECMA_69_8, + MediaType.ECMA_69_15, MediaType.ECMA_69_26, MediaType.ECMA_70, MediaType.ECMA_78, + MediaType.ECMA_78_2, MediaType.ECMA_99_15, MediaType.ECMA_99_26, MediaType.ECMA_100, + MediaType.ECMA_125, MediaType.ECMA_147, + + // FDFORMAT formats + MediaType.FDFORMAT_525_DD, MediaType.FDFORMAT_525_HD, + MediaType.FDFORMAT_35_DD, MediaType.FDFORMAT_35_HD, + + // Other formats + MediaType.Apricot_35, MediaType.MetaFloppy_Mod_I, MediaType.MetaFloppy_Mod_II, + + // Unknown/fallback + MediaType.Unknown ]; /// diff --git a/Aaru.Images/A2R/Read.cs b/Aaru.Images/A2R/Read.cs index af1d21821..79e660562 100644 --- a/Aaru.Images/A2R/Read.cs +++ b/Aaru.Images/A2R/Read.cs @@ -57,6 +57,7 @@ public sealed partial class A2R _a2RStream.Seek(0, SeekOrigin.Begin); _a2RFilter = imageFilter; + _mediaTags = []; // Per A2R spec: Read 8-byte header // Bytes 0-2: Signature ("A2R") @@ -118,6 +119,11 @@ public sealed partial class A2R _imageInfo.Creator = Encoding.ASCII.GetString(_infoChunkV2.creator).TrimEnd(); + // Store write protection status as media tag + // Boolean value: 1 byte where 0 = false (not write protected), 1 = true (write protected) + _mediaTags[MediaTagType.Floppy_WriteProtection] = _infoChunkV2.writeProtected == 1 ? [1] : [0]; + _imageInfo.ReadableMediaTags.Add(MediaTagType.Floppy_WriteProtection); + // The disk type actually only denotes the form factor of the media, and not the media type. switch(_infoChunkV2.diskType) { @@ -576,7 +582,16 @@ public sealed partial class A2R } /// - public ErrorNumber ReadMediaTag(MediaTagType tag, out byte[] buffer) => throw new NotImplementedException(); + public ErrorNumber ReadMediaTag(MediaTagType tag, out byte[] buffer) + { + buffer = null; + + if(!_mediaTags.TryGetValue(tag, out byte[] data)) return ErrorNumber.NoData; + + buffer = data?.Clone() as byte[]; + + return buffer == null ? ErrorNumber.NoData : ErrorNumber.NoError; + } /// public ErrorNumber diff --git a/Aaru.Images/A2R/Write.cs b/Aaru.Images/A2R/Write.cs index 46d6e7324..895159341 100644 --- a/Aaru.Images/A2R/Write.cs +++ b/Aaru.Images/A2R/Write.cs @@ -235,8 +235,20 @@ public sealed partial class A2R Encoding.UTF8.GetBytes($"Aaru v{typeof(A2R).Assembly.GetName().Version?.ToString()}".PadRight(32, ' ')); // Per A2R 3.x spec: writeProtected indicates if floppy is write protected (1 = protected) - // Default to 1 (write protected) as a safe default for archival images - _infoChunkV3.writeProtected = 1; + // Check if Floppy_WriteProtection media tag is available, otherwise default to 1 (write protected) + // as a safe default for archival images + // Initialize _mediaTags if not already initialized + _mediaTags ??= []; + + if(_mediaTags.TryGetValue(MediaTagType.Floppy_WriteProtection, out byte[] writeProtectionTag)) + { + // Boolean value: non-zero byte = true (write protected), 0 = false (not write protected) + _infoChunkV3.writeProtected = writeProtectionTag is { Length: > 0 } && writeProtectionTag[0] != 0 ? (byte)1 : (byte)0; + } + else + { + _infoChunkV3.writeProtected = 1; // Default to write protected + } // Per A2R 3.x spec: synchronized indicates if cross-track sync/index was used during imaging (1 = synchronized) // Will be set based on first capture's index signals in WriteFluxCapture @@ -285,7 +297,30 @@ public sealed partial class A2R public bool SetMetadata(Metadata metadata) => false; /// - public bool WriteMediaTag(byte[] data, MediaTagType tag) => false; + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + if(!SupportedMediaTags.Contains(tag)) + { + ErrorMessage = $"Tried to write unsupported media tag {tag}."; + + return false; + } + + _mediaTags ??= []; + + if(_mediaTags.ContainsKey(tag)) _mediaTags.Remove(tag); + + _mediaTags.Add(tag, data); + + // If this is the write protection tag, update the INFO chunk value + if(tag == MediaTagType.Floppy_WriteProtection) + { + // Boolean value: non-zero byte = true (write protected), 0 = false (not write protected) + _infoChunkV3.writeProtected = data is { Length: > 0 } && data[0] != 0 ? (byte)1 : (byte)0; + } + + return true; + } /// public bool WriteSector(byte[] data, ulong sectorAddress, bool negative, SectorStatus sectorStatus) => diff --git a/Aaru.Images/HxCStream/HxCStream.cs b/Aaru.Images/HxCStream/HxCStream.cs index c1f47cdbb..80429938d 100644 --- a/Aaru.Images/HxCStream/HxCStream.cs +++ b/Aaru.Images/HxCStream/HxCStream.cs @@ -31,7 +31,7 @@ // ****************************************************************************/ using System.Collections.Generic; -using System.IO; +using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; using Aaru.CommonTypes.Structs; @@ -49,10 +49,12 @@ public sealed partial class HxCStream : IFluxImage, IVerifiableImage List _trackFilePaths; + Dictionary _mediaTags; + public HxCStream() => _imageInfo = new ImageInfo { ReadableSectorTags = [], - ReadableMediaTags = [], + ReadableMediaTags = [MediaTagType.Floppy_WriteProtection], HasPartitions = false, HasSessions = false, Version = null, diff --git a/Aaru.Images/HxCStream/Properties.cs b/Aaru.Images/HxCStream/Properties.cs index 6689f9188..d62b7f022 100644 --- a/Aaru.Images/HxCStream/Properties.cs +++ b/Aaru.Images/HxCStream/Properties.cs @@ -80,13 +80,74 @@ public sealed partial class HxCStream }; /// - public IEnumerable SupportedMediaTags => null; + public IEnumerable SupportedMediaTags => new[] + { + MediaTagType.Floppy_WriteProtection + }; /// public IEnumerable SupportedMediaTypes => new[] { - // TODO: HxCStream supports a lot more formats, please add more whence tested. - MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_HD, MediaType.DOS_525_DS_DD_9, MediaType.DOS_525_HD, + // Apple formats + MediaType.Apple32SS, MediaType.Apple32DS, MediaType.Apple33SS, MediaType.Apple33DS, + MediaType.AppleSonySS, MediaType.AppleSonyDS, MediaType.AppleFileWare, + + // IBM PC/DOS formats - 5.25" + MediaType.DOS_525_SS_DD_8, MediaType.DOS_525_SS_DD_9, + MediaType.DOS_525_DS_DD_8, MediaType.DOS_525_DS_DD_9, MediaType.DOS_525_HD, + + // IBM PC/DOS formats - 3.5" + MediaType.DOS_35_SS_DD_8, MediaType.DOS_35_SS_DD_9, + MediaType.DOS_35_DS_DD_8, MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_HD, MediaType.DOS_35_ED, + + // Microsoft formats + MediaType.DMF, MediaType.DMF_82, MediaType.XDF_525, MediaType.XDF_35, + + // Atari formats + MediaType.ATARI_525_SD, MediaType.ATARI_525_DD, MediaType.ATARI_525_ED, + MediaType.ATARI_35_SS_DD, MediaType.ATARI_35_DS_DD, + MediaType.ATARI_35_SS_DD_11, MediaType.ATARI_35_DS_DD_11, + + // Commodore/Amiga formats + MediaType.CBM_35_DD, MediaType.CBM_AMIGA_35_DD, MediaType.CBM_AMIGA_35_HD, + MediaType.CBM_1540, MediaType.CBM_1540_Ext, MediaType.CBM_1571, + + // NEC/Sharp formats + MediaType.NEC_525_SS, MediaType.NEC_525_DS, MediaType.NEC_525_HD, + MediaType.NEC_35_HD_8, MediaType.NEC_35_HD_15, MediaType.NEC_35_TD, + MediaType.SHARP_525, MediaType.SHARP_525_9, MediaType.SHARP_35, MediaType.SHARP_35_9, + + // 8" formats + MediaType.NEC_8_SD, MediaType.NEC_8_DD, + MediaType.ECMA_99_8, MediaType.ECMA_69_8, + + // IBM formats + MediaType.IBM23FD, MediaType.IBM33FD_128, MediaType.IBM33FD_256, MediaType.IBM33FD_512, + MediaType.IBM43FD_128, MediaType.IBM43FD_256, + MediaType.IBM53FD_256, MediaType.IBM53FD_512, MediaType.IBM53FD_1024, + + // DEC formats + MediaType.RX01, MediaType.RX02, MediaType.RX03, MediaType.RX50, + + // Acorn formats + MediaType.ACORN_525_SS_SD_40, MediaType.ACORN_525_SS_SD_80, + MediaType.ACORN_525_SS_DD_40, MediaType.ACORN_525_SS_DD_80, MediaType.ACORN_525_DS_DD, + MediaType.ACORN_35_DS_DD, MediaType.ACORN_35_DS_HD, + + // ECMA standard formats + MediaType.ECMA_54, MediaType.ECMA_59, MediaType.ECMA_66, MediaType.ECMA_69_8, + MediaType.ECMA_69_15, MediaType.ECMA_69_26, MediaType.ECMA_70, MediaType.ECMA_78, + MediaType.ECMA_78_2, MediaType.ECMA_99_15, MediaType.ECMA_99_26, MediaType.ECMA_100, + MediaType.ECMA_125, MediaType.ECMA_147, + + // FDFORMAT formats + MediaType.FDFORMAT_525_DD, MediaType.FDFORMAT_525_HD, + MediaType.FDFORMAT_35_DD, MediaType.FDFORMAT_35_HD, + + // Other formats + MediaType.Apricot_35, MediaType.MetaFloppy_Mod_I, MediaType.MetaFloppy_Mod_II, + + // Unknown/fallback MediaType.Unknown }; diff --git a/Aaru.Images/HxCStream/Read.cs b/Aaru.Images/HxCStream/Read.cs index b64b6d211..dad5b36c3 100644 --- a/Aaru.Images/HxCStream/Read.cs +++ b/Aaru.Images/HxCStream/Read.cs @@ -53,6 +53,7 @@ public sealed partial class HxCStream { _trackCaptures = []; _trackFilePaths = []; + _mediaTags = []; _imageInfo.Heads = 0; _imageInfo.Cylinders = 0; @@ -109,7 +110,7 @@ public sealed partial class HxCStream // Process each track file int trackIndex = 0; int totalTracks = trackFiles.Count; - + AaruLogging.Debug(MODULE_NAME, "Processing {0} track files...", totalTracks); foreach((int cylinder, int head) key in trackFiles.Keys.OrderBy(k => k.cylinder).ThenBy(k => k.head).ToList()) @@ -117,15 +118,15 @@ public sealed partial class HxCStream trackIndex++; string trackfile = trackFiles[key]; _trackFilePaths.Add(trackfile); - + AaruLogging.Debug(MODULE_NAME, "Processing track {0}/{1}: cylinder {2}, head {3}", trackIndex, totalTracks, key.cylinder, key.head); - + ErrorNumber error = ProcessTrackFile(trackfile, (uint)key.head, (ushort)key.cylinder); if(error != ErrorNumber.NoError) return error; } - + AaruLogging.Debug(MODULE_NAME, "Successfully processed all {0} track files", totalTracks); _imageInfo.MetadataMediaType = MetadataMediaType.BlockMedia; @@ -173,7 +174,7 @@ public sealed partial class HxCStream // Verify CRC32 - calculate CRC of chunk data (excluding the CRC itself) byte[] chunkData = new byte[chunkHeader.size - 4]; Array.Copy(fileData, (int)fileOffset, chunkData, 0, (int)(chunkHeader.size - 4)); - + uint storedCrc = BitConverter.ToUInt32(fileData, (int)(fileOffset + chunkHeader.size - 4)); if(!VerifyChunkCrc32(chunkData, storedCrc)) @@ -360,6 +361,19 @@ public sealed partial class HxCStream AaruLogging.Debug(MODULE_NAME, "Finished processing chunks. Total flux pulses: {0}, IO stream values: {1}", fluxPulses.Count, ioStream.Count); + // Extract write protect status from IO stream + // The write protect flag should be constant for a capture, so we only need to check the first value + if(ioStream.Count > 0 && !_mediaTags.ContainsKey(MediaTagType.Floppy_WriteProtection)) + { + IoStreamState firstState = DecodeIoStreamValue(ioStream[0]); + bool writeProtected = firstState.WriteProtect; + + // Store as boolean: 1 byte where 0 = false (not write protected), 1 = true (write protected) + _mediaTags[MediaTagType.Floppy_WriteProtection] = writeProtected ? [1] : [0]; + + AaruLogging.Debug(MODULE_NAME, "Write protect status from IO stream: {0}", writeProtected ? "write protected" : "not write protected"); + } + // Extract index signals from IO stream var indexPositions = new List(); @@ -646,7 +660,21 @@ public sealed partial class HxCStream #region IMediaImage Members /// - public ErrorNumber ReadMediaTag(MediaTagType tag, out byte[] buffer) => throw new NotImplementedException(); + public ErrorNumber ReadMediaTag(MediaTagType tag, out byte[] buffer) + { + buffer = null; + + if(_mediaTags == null) return ErrorNumber.NotOpened; + + if(_mediaTags.TryGetValue(tag, out byte[] tagData)) + { + buffer = new byte[tagData.Length]; + Array.Copy(tagData, buffer, tagData.Length); + return ErrorNumber.NoError; + } + + return ErrorNumber.NoData; + } /// public ErrorNumber ReadSector(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus)