From 37c484411b0be982706c5dda71a2f34e0b2dfe21 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Fri, 29 Aug 2025 13:37:54 -0400 Subject: [PATCH] Reimplment MPQ extraction via StormLib --- SabreTools.Serialization/Wrappers/MoPaQ.cs | 87 ++++++++++++++++++++-- 1 file changed, 80 insertions(+), 7 deletions(-) diff --git a/SabreTools.Serialization/Wrappers/MoPaQ.cs b/SabreTools.Serialization/Wrappers/MoPaQ.cs index 7ba5eb42..9dd04130 100644 --- a/SabreTools.Serialization/Wrappers/MoPaQ.cs +++ b/SabreTools.Serialization/Wrappers/MoPaQ.cs @@ -1,10 +1,14 @@ using System; using System.IO; using SabreTools.Serialization.Interfaces; +using SabreTools.Models.MoPaQ; +#if (NET452_OR_GREATER || NETCOREAPP) && (WINX86 || WINX64) +using StormLibSharp; +#endif namespace SabreTools.Serialization.Wrappers { - public partial class MoPaQ : WrapperBase, IExtractable + public partial class MoPaQ : WrapperBase, IExtractable { #region Descriptive Properties @@ -16,14 +20,30 @@ namespace SabreTools.Serialization.Wrappers #region Constructors /// - public MoPaQ(Models.MoPaQ.Archive? model, byte[]? data, int offset) + /// This should only be used for until MPQ parsing is fixed + public MoPaQ(byte[]? data, int offset) + : base(new Archive(), data, offset) + { + // All logic is handled by the base class + } + + /// + /// This should only be used for until MPQ parsing is fixed + public MoPaQ(Stream? data) + : base(new Archive(), data) + { + // All logic is handled by the base class + } + + /// + public MoPaQ(Archive? model, byte[]? data, int offset) : base(model, data, offset) { // All logic is handled by the base class } /// - public MoPaQ(Models.MoPaQ.Archive? model, Stream? data) + public MoPaQ(Archive? model, Stream? data) : base(model, data) { // All logic is handled by the base class @@ -84,7 +104,6 @@ namespace SabreTools.Serialization.Wrappers #region Extraction /// - /// TODO: Reimplement extraction based on StormLibSharp public bool Extract(string outputDirectory, bool includeDebug) { #if NET20 || NET35 || !(WINX86 || WINX64) @@ -92,9 +111,63 @@ namespace SabreTools.Serialization.Wrappers Console.WriteLine(); return false; #else - Console.WriteLine("Extraction needs to be reimplemented for this framework!"); - Console.WriteLine(); - return false; + try + { + if (Filename == null || !File.Exists(Filename)) + return false; + + // Try to open the archive and listfile + var mpqArchive = new MpqArchive(Filename, FileAccess.Read); + string? listfile = null; + MpqFileStream listStream = mpqArchive.OpenFile("(listfile)"); + + // If we can't read the listfile, we just return + if (!listStream.CanRead) + return false; + + // Read the listfile in for processing + using (var sr = new StreamReader(listStream)) + { + listfile = sr.ReadToEnd(); + } + + // Split the listfile by newlines + string[] listfileLines = listfile.Replace("\r\n", "\n").Split('\n'); + + // Loop over each entry + foreach (string sub in listfileLines) + { + // Ensure directory separators are consistent + string filename = sub; + if (Path.DirectorySeparatorChar == '\\') + filename = filename.Replace('/', '\\'); + else if (Path.DirectorySeparatorChar == '/') + filename = filename.Replace('\\', '/'); + + // Ensure the full output directory exists + filename = Path.Combine(outDir, filename); + var directoryName = Path.GetDirectoryName(filename); + if (directoryName != null && !Directory.Exists(directoryName)) + Directory.CreateDirectory(directoryName); + + // Try to write the data + try + { + mpqArchive.ExtractFile(sub, filename); + } + catch (System.Exception ex) + { + if (includeDebug) System.Console.WriteLine(ex); + } + } + + return true; + } + catch (System.Exception ex) + { + if (includeDebug) System.Console.WriteLine(ex); + return false; + } #endif }