diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj index 39b86ba7..a17d4c40 100644 --- a/FModel/FModel.csproj +++ b/FModel/FModel.csproj @@ -136,6 +136,7 @@ + diff --git a/FModel/PakReader/Parsers/OodleStream.cs b/FModel/PakReader/Parsers/OodleStream.cs index 191dcbce..b7e345bf 100644 --- a/FModel/PakReader/Parsers/OodleStream.cs +++ b/FModel/PakReader/Parsers/OodleStream.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Runtime.InteropServices; +using FModel.Utils; namespace PakReader.Parsers { @@ -14,17 +15,13 @@ namespace PakReader.Parsers public OodleStream(byte[] input, long decompressedLength) { + Oodle.LoadOodleDll(); _baseStream = new MemoryStream(Decompress(input, decompressedLength), false) { Position = 0 }; } - /// - /// Oodle Library Path - /// - private const string OodleLibraryPath = "oo2core_5_win64"; - protected override void Dispose(bool disposing) { try @@ -45,7 +42,7 @@ namespace PakReader.Parsers /// /// Oodle64 Decompression Method /// - [DllImport(OodleLibraryPath, CallingConvention = CallingConvention.Cdecl)] + [DllImport(Oodle.OODLE_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] private static extern long OodleLZ_Decompress(byte[] buffer, long bufferSize, byte[] result, long outputBufferSize, int a, int b, int c, long d, long e, long f, long g, long h, long i, int ThreadModule); /// diff --git a/FModel/Utils/Oodle.cs b/FModel/Utils/Oodle.cs new file mode 100644 index 00000000..69d5436a --- /dev/null +++ b/FModel/Utils/Oodle.cs @@ -0,0 +1,74 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; +using FModel.Logger; + +namespace FModel.Utils +{ + public static class Oodle + { + private const string WARFRAME_CDN_HOST = "https://origin.warframe.com"; + private const string WARFRAME_INDEX_PATH = "/origin/E926E926/index.txt.lzma"; + private const string WARFRAME_INDEX_URL = WARFRAME_CDN_HOST + WARFRAME_INDEX_PATH; + public const string OODLE_DLL_NAME = "oo2core_8_win64.dll"; + + public static bool LoadOodleDll() + { + if (File.Exists(OODLE_DLL_NAME)) + { + return true; + } + return DownloadOodleDll().Result; + } + + private static async Task DownloadOodleDll() + { + using var client = new HttpClient {Timeout = TimeSpan.FromSeconds(2)}; + using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, + new Uri(WARFRAME_INDEX_URL)); + try + { + using var httpResponseMessage = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); + var lzma = await httpResponseMessage.Content.ReadAsByteArrayAsync().ConfigureAwait(false); + var input = new MemoryStream(lzma); + var output = new MemoryStream(); + LZMA.Decompress(input, output); + output.Position = 0; + using var reader = new StreamReader(output); + string line, dllUrl = null; + while ((line = reader.ReadLine()) != null) + { + if (line.Contains(OODLE_DLL_NAME)) + { + dllUrl = WARFRAME_CDN_HOST + line.Substring(0, line.IndexOf(',')); + break; + } + } + if (dllUrl == null) + { + DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Oodle]", "Warframe index did not contain oodle dll"); + return default; + } + + using var dllRequest = new HttpRequestMessage(HttpMethod.Get, new Uri(dllUrl)); + using var dllResponse = await client.SendAsync(dllRequest, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); + var dllLzma = await dllResponse.Content.ReadAsByteArrayAsync().ConfigureAwait(false); + input = new MemoryStream(dllLzma); + output = new MemoryStream(); + LZMA.Decompress(input, output); + output.Position = 0; + await File.WriteAllBytesAsync(OODLE_DLL_NAME, output.ToArray()).ConfigureAwait(false); + DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Oodle]", "Successfully downloaded oodle dll"); + return true; + } + catch (Exception e) + { + DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Oodle]", $"Uncaught exception while downloading oodle dll: {e.GetType()}: {e.Message}"); + /* TaskCanceledException + * HttpRequestException */ + } + return default; + } + } +} \ No newline at end of file diff --git a/FModel/Utils/SevenZipHelper.cs b/FModel/Utils/SevenZipHelper.cs new file mode 100644 index 00000000..8ad31393 --- /dev/null +++ b/FModel/Utils/SevenZipHelper.cs @@ -0,0 +1,119 @@ +using System.IO; + + +// https://gist.github.com/ststeiger/cb9750664952f775a341#gistcomment-2912797 +namespace FModel.Utils +{ + using System; + using SevenZip; + using SevenZip.Compression.LZMA; + + public enum LzmaSpeed + { + Fastest = 5, + VeryFast = 8, + Fast = 16, + Medium = 32, + Slow = 64, + VerySlow = 128, + } + public enum DictionarySize + { + ///64 KiB + VerySmall = 1 << 16, + ///1 MiB + Small = 1 << 20, + ///4 MiB + Medium = 1 << 22, + ///8 MiB + Large = 1 << 23, + ///16 MiB + Larger = 1 << 24, + ///64 MiB + VeryLarge = 1 << 26, + } + public static class LZMA + { + public static void Compress(Stream input, Stream output, LzmaSpeed speed = LzmaSpeed.Fastest, DictionarySize dictionarySize = DictionarySize.VerySmall, Action onProgress = null) + { + int posStateBits = 2; // default: 2 + int litContextBits = 3; // 3 for normal files, 0; for 32-bit data + int litPosBits = 0; // 0 for 64-bit data, 2 for 32-bit. + var numFastBytes = (int)speed; + string matchFinder = "BT4"; // default: BT4 + bool endMarker = true; + + CoderPropID[] propIDs = + { + CoderPropID.DictionarySize, + CoderPropID.PosStateBits, // (0 <= x <= 4). + CoderPropID.LitContextBits, // (0 <= x <= 8). + CoderPropID.LitPosBits, // (0 <= x <= 4). + CoderPropID.NumFastBytes, + CoderPropID.MatchFinder, // "BT2", "BT4". + CoderPropID.EndMarker + }; + + object[] properties = + { + (int)dictionarySize, + posStateBits, + litContextBits, + litPosBits, + numFastBytes, + matchFinder, + endMarker + }; + + var lzmaEncoder = new Encoder(); + + lzmaEncoder.SetCoderProperties(propIDs, properties); + lzmaEncoder.WriteCoderProperties(output); + var fileSize = input.Length; + for (int i = 0; i < 8; i++) output.WriteByte((byte)(fileSize >> (8 * i))); + + ICodeProgress prg = null; + if (onProgress != null) + { + prg = new DelegateCodeProgress(onProgress); + } + lzmaEncoder.Code(input, output, -1, -1, prg); + } + + public static void Decompress(Stream input, Stream output, Action? onProgress = null) + { + var decoder = new Decoder(); + + byte[] properties = new byte[5]; + if (input.Read(properties, 0, 5) != 5) + { + throw new Exception("input .lzma is too short"); + } + decoder.SetDecoderProperties(properties); + + long fileLength = 0; + for (int i = 0; i < 8; i++) + { + int v = input.ReadByte(); + if (v < 0) throw new Exception("Can't Read 1"); + fileLength |= ((long)(byte)v) << (8 * i); + } + + ICodeProgress prg = null; + if (onProgress != null) + { + prg = new DelegateCodeProgress(onProgress); + } + long compressedSize = input.Length - input.Position; + + decoder.Code(input, output, compressedSize, fileLength, prg); + } + + private class DelegateCodeProgress : ICodeProgress + { + private readonly Action _handler; + public DelegateCodeProgress(Action handler) => this._handler = handler; + public void SetProgress(long inSize, long outSize) => _handler(inSize, outSize); + } + } +} \ No newline at end of file