From b6561719a78189b065fd0de8063b6bd85159885b Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Thu, 24 Jul 2025 10:40:31 -0400 Subject: [PATCH] Add SecuROM DFA --- .../Deserializers/SecuROMDFA.cs | 80 +++++++++++++++++++ SabreTools.Serialization/Printer.cs | 12 +++ .../Printers/SecuROMDFA.cs | 59 ++++++++++++++ .../Wrappers/SecuROMDFA.cs | 79 ++++++++++++++++++ .../Wrappers/WrapperFactory.cs | 8 ++ .../Wrappers/WrapperType.cs | 5 ++ 6 files changed, 243 insertions(+) create mode 100644 SabreTools.Serialization/Deserializers/SecuROMDFA.cs create mode 100644 SabreTools.Serialization/Printers/SecuROMDFA.cs create mode 100644 SabreTools.Serialization/Wrappers/SecuROMDFA.cs diff --git a/SabreTools.Serialization/Deserializers/SecuROMDFA.cs b/SabreTools.Serialization/Deserializers/SecuROMDFA.cs new file mode 100644 index 00000000..04a4f3d4 --- /dev/null +++ b/SabreTools.Serialization/Deserializers/SecuROMDFA.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using SabreTools.IO.Extensions; +using SabreTools.Matching; +using SabreTools.Models.SecuROM; +using static SabreTools.Models.SecuROM.Constants; + +namespace SabreTools.Serialization.Deserializers +{ + public class SecuROMDFA : BaseBinaryDeserializer + { + /// + public override DFAFile? Deserialize(Stream? data) + { + // If the data is invalid + if (data == null || !data.CanRead) + return null; + + try + { + // Create a new file to fill + var dfa = new DFAFile(); + + #region Header + + // Try to parse the header + dfa.Signature = data.ReadBytes(8); + if (!dfa.Signature.EqualsExactly(DFAMagicBytes)) + return null; + + dfa.BlockOrHeaderSize = data.ReadUInt32(); + + #endregion + + #region Entries + + // If we have any entries + List entries = []; + + // Read entries while there is data + while (data.Position < data.Length) + { + var entry = ParseDFAEntry(data); + entries.Add(entry); + } + + // Set the entries list + dfa.Entries = [.. entries]; + + #endregion + + return dfa; + } + catch + { + // Ignore the actual error + return null; + } + } + + /// + /// Parse a Stream into a DFAEntry + /// + /// Stream to parse + /// Filled DFAEntry on success, null on error + public static DFAEntry ParseDFAEntry(Stream data) + { + var obj = new DFAEntry(); + + byte[] name = data.ReadBytes(4); + obj.Name = Encoding.ASCII.GetString(name); + obj.Length = data.ReadUInt32(); + if (obj.Length > 0) + obj.Value = data.ReadBytes((int)obj.Length); + + return obj; + } + } +} diff --git a/SabreTools.Serialization/Printer.cs b/SabreTools.Serialization/Printer.cs index 4c16a807..3e9167f8 100644 --- a/SabreTools.Serialization/Printer.cs +++ b/SabreTools.Serialization/Printer.cs @@ -64,6 +64,7 @@ namespace SabreTools.Serialization Wrapper.PlayJPlaylist item => item.PrettyPrint(), Wrapper.PortableExecutable item => item.PrettyPrint(), Wrapper.Quantum item => item.PrettyPrint(), + Wrapper.SecuROMDFA item => item.PrettyPrint(), Wrapper.SGA item => item.PrettyPrint(), Wrapper.VBSP item => item.PrettyPrint(), Wrapper.VPK item => item.PrettyPrint(), @@ -113,6 +114,7 @@ namespace SabreTools.Serialization Wrapper.PlayJPlaylist item => item.ExportJSON(), Wrapper.PortableExecutable item => item.ExportJSON(), Wrapper.Quantum item => item.ExportJSON(), + Wrapper.SecuROMDFA item => item.ExportJSON(), Wrapper.SGA item => item.ExportJSON(), Wrapper.VBSP item => item.ExportJSON(), Wrapper.VPK item => item.ExportJSON(), @@ -427,6 +429,16 @@ namespace SabreTools.Serialization return builder; } + /// + /// Export the item information as pretty-printed text + /// + private static StringBuilder PrettyPrint(this Wrapper.SecuROMDFA item) + { + var builder = new StringBuilder(); + SecuROMDFA.Print(builder, item.Model); + return builder; + } + /// /// Export the item information as pretty-printed text /// diff --git a/SabreTools.Serialization/Printers/SecuROMDFA.cs b/SabreTools.Serialization/Printers/SecuROMDFA.cs new file mode 100644 index 00000000..0b565d30 --- /dev/null +++ b/SabreTools.Serialization/Printers/SecuROMDFA.cs @@ -0,0 +1,59 @@ +using System.Text; +using SabreTools.Models.SecuROM; +using SabreTools.Serialization.Interfaces; + +namespace SabreTools.Serialization.Printers +{ + public class SecuROMDFA : IPrinter + { + /// + public void PrintInformation(StringBuilder builder, DFAFile model) + => Print(builder, model); + + public static void Print(StringBuilder builder, DFAFile dfaFile) + { + builder.AppendLine("SecuROM DFA File Information:"); + builder.AppendLine("-------------------------"); + builder.AppendLine(dfaFile.Signature, "Signature"); + builder.AppendLine(dfaFile.BlockOrHeaderSize, "Block or header size"); + builder.AppendLine(); + + + Print(builder, dfaFile.Entries); + } + + private static void Print(StringBuilder builder, DFAEntry[]? entries) + { + builder.AppendLine(" Entries Information:"); + builder.AppendLine(" -------------------------"); + if (entries == null || entries.Length == 0) + { + builder.AppendLine(" No entries"); + builder.AppendLine(); + return; + } + + for (int i = 0; i < entries.Length; i++) + { + var entry = entries[i]; + Print(builder, entry, i); + } + + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, DFAEntry? entry, int index) + { + builder.AppendLine($" Entry {index}"); + if (entry == null) + { + builder.AppendLine(" [NULL]"); + return; + } + + builder.AppendLine(entry.Name, " Name"); + builder.AppendLine(entry.Length, " Length"); + builder.AppendLine(entry.Value, " Value"); + } + } +} diff --git a/SabreTools.Serialization/Wrappers/SecuROMDFA.cs b/SabreTools.Serialization/Wrappers/SecuROMDFA.cs new file mode 100644 index 00000000..51ae5039 --- /dev/null +++ b/SabreTools.Serialization/Wrappers/SecuROMDFA.cs @@ -0,0 +1,79 @@ +using System.IO; +using SabreTools.Models.SecuROM; + +namespace SabreTools.Serialization.Wrappers +{ + public class SecuROMDFA : WrapperBase + { + #region Descriptive Properties + + /// + public override string DescriptionString => "SecuROM DFA File"; + + #endregion + + #region Constructors + + /// + public SecuROMDFA(DFAFile? model, byte[]? data, int offset) + : base(model, data, offset) + { + // All logic is handled by the base class + } + + /// + public SecuROMDFA(DFAFile? model, Stream? data) + : base(model, data) + { + // All logic is handled by the base class + } + + /// + /// Create a SecuROM DFA file from a byte array and offset + /// + /// Byte array representing the archive + /// Offset within the array to parse + /// A SecuROM DFA file wrapper on success, null on failure + public static SecuROMDFA? Create(byte[]? data, int offset) + { + // If the data is invalid + if (data == 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 a SecuROM DFA file from a Stream + /// + /// Stream representing the archive + /// A SecuROM DFA file wrapper on success, null on failure + public static SecuROMDFA? Create(Stream? data) + { + // If the data is invalid + if (data == null || !data.CanRead) + return null; + + try + { + var file = Deserializers.SecuROMDFA.DeserializeStream(data); + if (file == null) + return null; + + return new SecuROMDFA(file, data); + } + catch + { + return null; + } + } + + #endregion + } +} diff --git a/SabreTools.Serialization/Wrappers/WrapperFactory.cs b/SabreTools.Serialization/Wrappers/WrapperFactory.cs index c561c5d7..cc4641bc 100644 --- a/SabreTools.Serialization/Wrappers/WrapperFactory.cs +++ b/SabreTools.Serialization/Wrappers/WrapperFactory.cs @@ -49,6 +49,7 @@ namespace SabreTools.Serialization.Wrappers WrapperType.RAR => null,// TODO: Implement wrapper WrapperType.RealArcadeInstaller => null,// TODO: Implement wrapper WrapperType.RealArcadeMezzanine => null,// TODO: Implement wrapper + WrapperType.SecuROMDFA => SecuROMDFA.Create(data), WrapperType.SevenZip => null,// TODO: Implement wrapper WrapperType.SFFS => null,// TODO: Implement wrapper WrapperType.SGA => SGA.Create(data), @@ -616,6 +617,13 @@ namespace SabreTools.Serialization.Wrappers #endregion + #region SecuROM DFA + + if (magic.StartsWith(Models.SecuROM.Constants.DFAMagicBytes)) + return WrapperType.SecuROMDFA; + + #endregion + // TODO: Use constants from Models here #region SevenZip diff --git a/SabreTools.Serialization/Wrappers/WrapperType.cs b/SabreTools.Serialization/Wrappers/WrapperType.cs index ea595904..0d50724e 100644 --- a/SabreTools.Serialization/Wrappers/WrapperType.cs +++ b/SabreTools.Serialization/Wrappers/WrapperType.cs @@ -185,6 +185,11 @@ namespace SabreTools.Serialization.Wrappers /// Currently has no IWrapper implementation RealArcadeMezzanine, + /// + /// SecuROM DFA File + /// + SecuROMDFA, + /// /// 7-zip archive ///