diff --git a/SabreTools.Serialization.Test/Readers/QDTests.cs b/SabreTools.Serialization.Test/Readers/QDTests.cs new file mode 100644 index 00000000..1a56cce4 --- /dev/null +++ b/SabreTools.Serialization.Test/Readers/QDTests.cs @@ -0,0 +1,73 @@ +using System.IO; +using System.Linq; +using SabreTools.Serialization.Readers; +using Xunit; + +namespace SabreTools.Serialization.Test.Readers +{ + public class QDTests + { + [Fact] + public void NullArray_Null() + { + byte[]? data = null; + int offset = 0; + var deserializer = new QD(); + + var actual = deserializer.Deserialize(data, offset); + Assert.Null(actual); + } + + [Fact] + public void EmptyArray_Null() + { + byte[]? data = []; + int offset = 0; + var deserializer = new QD(); + + var actual = deserializer.Deserialize(data, offset); + Assert.Null(actual); + } + + [Fact] + public void InvalidArray_Null() + { + byte[]? data = [.. Enumerable.Repeat(0xFF, 1024)]; + int offset = 0; + var deserializer = new QD(); + + var actual = deserializer.Deserialize(data, offset); + Assert.Null(actual); + } + + [Fact] + public void NullStream_Null() + { + Stream? data = null; + var deserializer = new QD(); + + var actual = deserializer.Deserialize(data); + Assert.Null(actual); + } + + [Fact] + public void EmptyStream_Null() + { + Stream? data = new MemoryStream([]); + var deserializer = new QD(); + + var actual = deserializer.Deserialize(data); + Assert.Null(actual); + } + + [Fact] + public void InvalidStream_Null() + { + Stream? data = new MemoryStream([.. Enumerable.Repeat(0xFF, 1024)]); + var deserializer = new QD(); + + var actual = deserializer.Deserialize(data); + Assert.Null(actual); + } + } +} diff --git a/SabreTools.Serialization.Test/Wrappers/QDTests.cs b/SabreTools.Serialization.Test/Wrappers/QDTests.cs new file mode 100644 index 00000000..7342f6ff --- /dev/null +++ b/SabreTools.Serialization.Test/Wrappers/QDTests.cs @@ -0,0 +1,61 @@ +using System.IO; +using System.Linq; +using SabreTools.Serialization.Wrappers; +using Xunit; + +namespace SabreTools.Serialization.Test.Wrappers +{ + public class QDTests + { + [Fact] + public void NullArray_Null() + { + byte[]? data = null; + int offset = 0; + var actual = QD.Create(data, offset); + Assert.Null(actual); + } + + [Fact] + public void EmptyArray_Null() + { + byte[]? data = []; + int offset = 0; + var actual = QD.Create(data, offset); + Assert.Null(actual); + } + + [Fact] + public void InvalidArray_Null() + { + byte[]? data = [.. Enumerable.Repeat(0xFF, 1024)]; + int offset = 0; + var actual = QD.Create(data, offset); + Assert.Null(actual); + } + + [Fact] + public void NullStream_Null() + { + Stream? data = null; + var actual = QD.Create(data); + Assert.Null(actual); + } + + [Fact] + public void EmptyStream_Null() + { + Stream? data = new MemoryStream([]); + var actual = QD.Create(data); + Assert.Null(actual); + } + + [Fact] + public void InvalidStream_Null() + { + Stream? data = new MemoryStream([.. Enumerable.Repeat(0xFF, 1024)]); + var actual = QD.Create(data); + Assert.Null(actual); + } + } +} diff --git a/SabreTools.Serialization/Models/NES/QD.cs b/SabreTools.Serialization/Models/NES/QD.cs new file mode 100644 index 00000000..61524f2e --- /dev/null +++ b/SabreTools.Serialization/Models/NES/QD.cs @@ -0,0 +1,14 @@ +namespace SabreTools.Data.Models.NES +{ + /// + /// Quick Disk Famicom Disk System image + /// + /// + public class QD + { + /// + /// Disk data (65536 * x bytes) + /// + public byte[] Data { get; set; } = []; + } +} diff --git a/SabreTools.Serialization/Models/README.MD b/SabreTools.Serialization/Models/README.MD index 0eaa482f..54acc200 100644 --- a/SabreTools.Serialization/Models/README.MD +++ b/SabreTools.Serialization/Models/README.MD @@ -38,7 +38,7 @@ Not all of this information was able to be gathered directly from the files in q | [Matthew Russotto](http://www.russotto.net/quantumcomp.html) | Compression/Quantum | | [Microsoft Learn](https://learn.microsoft.com/en-us/) | BMP, CFB, Compression/LZX, Compression/MSZIP, MicrosoftCabinet, OLE, PortableExecutable, SecuROM, WiseInstaller | | [msitools](https://github.com/GNOME/msitools/) | CFB | -| [Nesdev Wiki](https://www.nesdev.org/wiki/Nesdev_Wiki) | FDS, NESCart | +| [Nesdev Wiki](https://www.nesdev.org/wiki/Nesdev_Wiki) | FDS, NESCart, QD | | [OSDev.org](https://wiki.osdev.org/Expanded_Main_Page) | MSDOS, NewExecutable | | [PInvoke.net](http://www.pinvoke.net/index.aspx) | MSDOS | | [PKWARE(?)](https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT) | PKZIP | diff --git a/SabreTools.Serialization/Readers/QD.cs b/SabreTools.Serialization/Readers/QD.cs new file mode 100644 index 00000000..f898ba35 --- /dev/null +++ b/SabreTools.Serialization/Readers/QD.cs @@ -0,0 +1,39 @@ +using System.IO; +using SabreTools.IO.Extensions; + +namespace SabreTools.Serialization.Readers +{ + public class QD : BaseBinaryReader + { + /// + public override Data.Models.NES.QD? Deserialize(Stream? data) + { + // If the data is invalid + if (data is null || !data.CanRead) + return null; + + try + { + // Cache the current offset + long initialOffset = data.Position; + + // Create a new QD file to fill + var QD = new Data.Models.NES.QD(); + + // If the size is invalid + if ((data.Length - data.Position) == 0 || (data.Length - data.Position) % 65536 != 0) + return null; + + // Read the disk data + QD.Data = data.ReadBytes((int)(data.Length - data.Position)); + + return QD; + } + catch + { + // Ignore the actual error + return null; + } + } + } +} diff --git a/SabreTools.Serialization/WrapperFactory.cs b/SabreTools.Serialization/WrapperFactory.cs index 57ed47d6..1d0e36ba 100644 --- a/SabreTools.Serialization/WrapperFactory.cs +++ b/SabreTools.Serialization/WrapperFactory.cs @@ -50,6 +50,7 @@ namespace SabreTools.Serialization WrapperType.PKZIP => PKZIP.Create(data), WrapperType.PlayJAudioFile => PlayJAudioFile.Create(data), WrapperType.PlayJPlaylist => PlayJPlaylist.Create(data), + WrapperType.QD => QD.Create(data), WrapperType.Quantum => Quantum.Create(data), WrapperType.RAR => RAR.Create(data), WrapperType.RealArcadeInstaller => RealArcadeInstaller.Create(data), @@ -673,6 +674,13 @@ namespace SabreTools.Serialization #endregion + #region QD + + if (extension.Equals("qd", StringComparison.OrdinalIgnoreCase)) + return WrapperType.QD; + + #endregion + #region Quantum if (magic.StartsWith(Data.Models.Quantum.Constants.SignatureBytes)) diff --git a/SabreTools.Serialization/Wrappers/FDS.Printing.cs b/SabreTools.Serialization/Wrappers/FDS.Printing.cs index f9cbe94a..fbfd653f 100644 --- a/SabreTools.Serialization/Wrappers/FDS.Printing.cs +++ b/SabreTools.Serialization/Wrappers/FDS.Printing.cs @@ -14,7 +14,7 @@ namespace SabreTools.Serialization.Wrappers /// public void PrintInformation(StringBuilder builder) { - builder.AppendLine("Nintendo Entertainment System Cart Information:"); + builder.AppendLine("fwNES FDS File Information:"); builder.AppendLine("-------------------------"); builder.AppendLine(); diff --git a/SabreTools.Serialization/Wrappers/QD.Printing.cs b/SabreTools.Serialization/Wrappers/QD.Printing.cs new file mode 100644 index 00000000..e288e24d --- /dev/null +++ b/SabreTools.Serialization/Wrappers/QD.Printing.cs @@ -0,0 +1,24 @@ +using System.Text; +using SabreTools.Data.Extensions; + +namespace SabreTools.Serialization.Wrappers +{ + public partial class QD : IPrintable + { +#if NETCOREAPP + /// + public string ExportJSON() => System.Text.Json.JsonSerializer.Serialize(Model, _jsonSerializerOptions); +#endif + + /// + public void PrintInformation(StringBuilder builder) + { + builder.AppendLine("Quick Disk Image Information:"); + builder.AppendLine("-------------------------"); + builder.AppendLine(); + + //builder.AppendLine(Model.Data, "Disk Data"); + builder.AppendLine(Model.Data.Length, "Disk Data Length"); + } + } +} diff --git a/SabreTools.Serialization/Wrappers/QD.cs b/SabreTools.Serialization/Wrappers/QD.cs new file mode 100644 index 00000000..0a06a3a3 --- /dev/null +++ b/SabreTools.Serialization/Wrappers/QD.cs @@ -0,0 +1,89 @@ +using System.IO; + +namespace SabreTools.Serialization.Wrappers +{ + public partial class QD : WrapperBase + { + #region Descriptive Properties + + /// + public override string DescriptionString => "Quick Disk Famicom Disk System image"; + + #endregion + + #region Constructors + + /// + public QD(Data.Models.NES.QD model, byte[] data) : base(model, data) { } + + /// + public QD(Data.Models.NES.QD model, byte[] data, int offset) : base(model, data, offset) { } + + /// + public QD(Data.Models.NES.QD model, byte[] data, int offset, int length) : base(model, data, offset, length) { } + + /// + public QD(Data.Models.NES.QD model, Stream data) : base(model, data) { } + + /// + public QD(Data.Models.NES.QD model, Stream data, long offset) : base(model, data, offset) { } + + /// + public QD(Data.Models.NES.QD model, Stream data, long offset, long length) : base(model, data, offset, length) { } + + #endregion + + #region Static Constructors + + /// + /// Create an NES cart image from a byte array and offset + /// + /// Byte array representing the archive + /// Offset within the array to parse + /// An NES cart image wrapper on success, null on failure + public static QD? Create(byte[]? data, int offset) + { + // If the data is invalid + if (data is null || data.Length == 0) + return null; + + // If the offset is out of bounds + if (offset < 0 || offset >= data.Length) + return null; + + // Create a memory stream and use that + var dataStream = new MemoryStream(data, offset, data.Length - offset); + return Create(dataStream); + } + + /// + /// Create an NES cart image from a Stream + /// + /// Stream representing the archive + /// An NES cart image wrapper on success, null on failure + public static QD? Create(Stream? data) + { + // If the data is invalid + if (data is null || !data.CanRead) + return null; + + try + { + // Cache the current offset + long currentOffset = data.Position; + + var model = new Readers.QD().Deserialize(data); + if (model is null) + return null; + + return new QD(model, data, currentOffset); + } + catch + { + return null; + } + } + + #endregion + } +} diff --git a/SabreTools.Serialization/Wrappers/WrapperType.cs b/SabreTools.Serialization/Wrappers/WrapperType.cs index 567cb4c1..802a483f 100644 --- a/SabreTools.Serialization/Wrappers/WrapperType.cs +++ b/SabreTools.Serialization/Wrappers/WrapperType.cs @@ -197,6 +197,11 @@ namespace SabreTools.Serialization.Wrappers /// Quantum, + /// + /// Quick Disk Famicom Disk System image + /// + QD, + /// /// RAR archive ///