diff --git a/CompressSave/CompressSave.cs b/CompressSave/CompressSave.cs index 21e9562..aed69cf 100644 --- a/CompressSave/CompressSave.cs +++ b/CompressSave/CompressSave.cs @@ -3,25 +3,56 @@ using System.Collections.Generic; using System.IO; using System.Reflection.Emit; using BepInEx; +using BepInEx.Configuration; using HarmonyLib; -using CompressSave.LZ4Wrap; +using CompressSave.Wrapper; namespace CompressSave; [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] public class CompressSave : BaseUnityPlugin { + string StringFromCompresstionType(CompressionType type) + { + switch (type) + { + case CompressionType.LZ4: return "lz4"; + case CompressionType.Zstd: return "zstd"; + case CompressionType.None: default: return "none"; + } + } + + CompressionType CompressionTypeFromString(string str) + { + switch (str) + { + case "lz4": return CompressionType.LZ4; + case "zstd": return CompressionType.Zstd; + default: return CompressionType.None; + } + } + public void Awake() { SaveUtil.logger = Logger; - if (LZ4API.Avaliable) + if (LZ4API.Avaliable && ZstdAPI.Avaliable) { + PatchSave.CompressionTypeForSaves = CompressionTypeFromString( + Config.Bind("Compression", "Type", StringFromCompresstionType(PatchSave.CompressionTypeForSaves), + new ConfigDescription("Set default compression type.", + new AcceptableValueList("lz4", "zstd", "none"), new {})) + .Value); + PatchSave.CompressionLevelForSaves = Config.Bind("Compression", "Level", PatchSave.CompressionLevelForSaves, + new ConfigDescription("Set default compression level. -1 for fastest level, 0 for default level. And positive levels for lz4: 3-12, for zstd: 1-22.", + new AcceptableValueRange(-1, 22), new {})) + .Value; + PatchSave.CreateCompressBuffer(); if (GameConfig.gameVersion != SaveUtil.VerifiedVersion) { SaveUtil.logger.LogWarning($"Save version mismatch. Expect:{SaveUtil.VerifiedVersion}, Current:{GameConfig.gameVersion}. MOD may not work as expected."); } Harmony.CreateAndPatchAll(typeof(PatchSave)); - if (PatchSave.EnableCompress) + if (PatchSave.EnableCompress && PatchSave.CompressionTypeForSaves != CompressionType.None) Harmony.CreateAndPatchAll(typeof(PatchUISaveGame)); Harmony.CreateAndPatchAll(typeof(PatchUILoadGame)); } @@ -39,18 +70,36 @@ public class CompressSave : BaseUnityPlugin class PatchSave { + public static WrapperDefines lz4Wrapper = new LZ4API(), zstdWrapper = new ZstdAPI(); const long MB = 1024 * 1024; - static LZ4CompressionStream.CompressBuffer compressBuffer = LZ4CompressionStream.CreateBuffer((int)MB); //Bigger buffer for GS2 compatible + private static CompressionStream.CompressBuffer compressBuffer; public static bool UseCompressSave = false; - public static bool IsCompressedSave; - static Stream lzstream = null; + public static CompressionType CompressedType = CompressionType.None; + public static CompressionType CompressionTypeForSaves = CompressionType.LZ4; + public static int CompressionLevelForSaves = 0; + static Stream compressionStream = null; public static bool EnableCompress; public static bool EnableDecompress; + public static void CreateCompressBuffer() + { + compressBuffer = CompressionStream.CreateBuffer(CompressionTypeForSaves == CompressionType.LZ4 ? lz4Wrapper : zstdWrapper, (int)MB); //Bigger buffer for GS2 compatible + } + private static void WriteHeader(FileStream fileStream) { - for (int i = 0; i < 4; i++) + for (int i = 0; i < 3; i++) fileStream.WriteByte(0xCC); + switch (CompressionTypeForSaves) + { + case CompressionType.Zstd: + fileStream.WriteByte(0xCD); + break; + case CompressionType.LZ4: + default: + fileStream.WriteByte(0xCC); + break; + } } [HarmonyPrefix] @@ -58,17 +107,17 @@ class PatchSave [HarmonyPatch(typeof(GameSave), "SaveAsLastExit")] static void BeforeAutoSave() { - UseCompressSave = EnableCompress; + UseCompressSave = EnableCompress && CompressionTypeForSaves != CompressionType.None; } [HarmonyTranspiler] [HarmonyPatch(typeof(GameSave), "SaveCurrentGame")] static IEnumerable SaveCurrentGame_Transpiler(IEnumerable instructions, ILGenerator generator) { - /* BinaryWriter binaryWriter = new BinaryWriter(fileStream); => Create lzstream and replace binaryWriter. - * set PerformanceMonitor.BeginStream to lzstream. + /* BinaryWriter binaryWriter = new BinaryWriter(fileStream); => Create compressionStream and replace binaryWriter. + * set PerformanceMonitor.BeginStream to compressionStream. * fileStream.Seek(6L, SeekOrigin.Begin); binaryWriter.Write(position); => Disable seek&write function. - * binaryWriter.Dispose(); => Dispose lzstream before fileStream close. + * binaryWriter.Dispose(); => Dispose compressionStream before fileStream close. */ try { @@ -83,7 +132,7 @@ class PatchSave .Set(OpCodes.Call, AccessTools.Method(typeof(PatchSave), "FileLengthWrite1")) .MatchForward(false, new CodeMatch(OpCodes.Callvirt, AccessTools.Method(typeof(System.IDisposable), "Dispose"))) .Advance(1) - .Insert(new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(PatchSave), "DisposeLzstream"))); + .Insert(new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(PatchSave), "DisposecompressionStream"))); EnableCompress = true; return matcher.InstructionEnumeration(); } @@ -97,7 +146,7 @@ class PatchSave public static void MonitorStream(Stream fileStream) { - PerformanceMonitor.BeginStream(UseCompressSave ? lzstream : fileStream); + PerformanceMonitor.BeginStream(UseCompressSave ? compressionStream : fileStream); } public static BinaryWriter CreateBinaryWriter(FileStream fileStream) @@ -106,8 +155,8 @@ class PatchSave { SaveUtil.logger.LogDebug("Begin compress save"); WriteHeader(fileStream); - lzstream = new LZ4CompressionStream(fileStream, compressBuffer, true); //need to dispose after use - return ((LZ4CompressionStream)lzstream).BufferWriter; + compressionStream = new CompressionStream(CompressionTypeForSaves == CompressionType.LZ4 ? lz4Wrapper : zstdWrapper, CompressionLevelForSaves, fileStream, compressBuffer, true); //need to dispose after use + return ((CompressionStream)compressionStream).BufferWriter; } SaveUtil.logger.LogDebug("Begin normal save"); return new BinaryWriter(fileStream); @@ -126,17 +175,16 @@ class PatchSave binaryWriter.Write(value); } - public static void DisposeLzstream() + public static void DisposecompressionStream() { if (!UseCompressSave) return; - var writeflag = lzstream.CanWrite; - lzstream?.Dispose(); //Dispose need to be done before fstream closed. - lzstream = null; + var writeflag = compressionStream.CanWrite; + compressionStream?.Dispose(); //Dispose need to be done before fstream closed. + compressionStream = null; if (writeflag) //Reset UseCompressSave after writing to file UseCompressSave = false; } - [HarmonyTranspiler] [HarmonyPatch(typeof(GameSave), "LoadCurrentGame")] [HarmonyPatch(typeof(GameSave), "LoadGameDesc")] @@ -145,11 +193,11 @@ class PatchSave [HarmonyPatch(typeof(GameSave), "ReadModes")] static IEnumerable LoadCurrentGame_Transpiler(IEnumerable instructions, ILGenerator iLGenerator) { - /* using (BinaryReader binaryReader = new BinaryReader(fileStream)) => Create lzstream and replace binaryReader. - * set PerformanceMonitor.BeginStream to lzstream. + /* using (BinaryReader binaryReader = new BinaryReader(fileStream)) => Create decompressionStream and replace binaryReader. + * set PerformanceMonitor.BeginStream to decompressionStream. * if (fileStream.Length != binaryReader.ReadInt64()) => Replace binaryReader.ReadInt64() to pass file length check. - * fileStream.Seek((long)num2, SeekOrigin.Current); => Use lzstream.Read to seek forward - * binaryReader.Dispose(); => Dispose lzstream before fileStream close. + * fileStream.Seek((long)num2, SeekOrigin.Current); => Use decompressionStream.Read to seek forward + * binaryReader.Dispose(); => Dispose decompressionStream before fileStream close. */ try { @@ -165,7 +213,7 @@ class PatchSave .Set(OpCodes.Call, AccessTools.Method(typeof(PatchSave), "FileLengthRead")) .MatchForward(false, new CodeMatch(OpCodes.Callvirt, AccessTools.Method(typeof(System.IDisposable), "Dispose"))) .Advance(1) - .Insert(new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(PatchSave), "DisposeLzstream"))) + .Insert(new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(PatchSave), "DisposecompressionStream"))) .MatchBack(false, new CodeMatch(OpCodes.Callvirt, AccessTools.Method(typeof(System.IO.Stream), "Seek"))); if (matcher.IsValid) matcher.Set(OpCodes.Call, AccessTools.Method(typeof(PatchSave), "ReadSeek")); @@ -191,45 +239,53 @@ class PatchSave static void ReadHeader_Postfix(ref GameSaveHeader header) { if (header != null) - ((CompressionGameSaveHeader)header).IsCompressed = IsCompressedSave; + ((CompressionGameSaveHeader)header).CompressionType = CompressedType; } public static BinaryReader CreateBinaryReader(FileStream fileStream) { - if ((IsCompressedSave = SaveUtil.IsCompressedSave(fileStream))) + switch (CompressedType = SaveUtil.SaveGetCompressType(fileStream)) { - UseCompressSave = true; - lzstream = new LZ4DecompressionStream(fileStream); - return new PeekableReader((LZ4DecompressionStream)lzstream); - } - else - { - UseCompressSave = false; - fileStream.Seek(0, SeekOrigin.Begin); - return new BinaryReader(fileStream); + case CompressionType.LZ4: + case CompressionType.Zstd: + UseCompressSave = true; + compressionStream = new DecompressionStream(CompressedType == CompressionType.LZ4 ? lz4Wrapper : zstdWrapper, fileStream); + return new PeekableReader((DecompressionStream)compressionStream); + case CompressionType.None: + UseCompressSave = false; + fileStream.Seek(0, SeekOrigin.Begin); + return new BinaryReader(fileStream); + default: + throw new ArgumentOutOfRangeException(); } } public static long FileLengthRead(BinaryReader binaryReader) { - if (UseCompressSave) + switch (CompressedType) { - binaryReader.ReadInt64(); - return lzstream.Length; + case CompressionType.LZ4: + case CompressionType.Zstd: + binaryReader.ReadInt64(); + return compressionStream.Length; + case CompressionType.None: + default: + return binaryReader.ReadInt64(); } - else - return binaryReader.ReadInt64(); } public static long ReadSeek(FileStream fileStream, long offset, SeekOrigin origin) { - if (UseCompressSave) + switch (CompressedType) { - while (offset > 0) - offset -= lzstream.Read(compressBuffer.outBuffer, 0, (int)offset); - return lzstream.Position; + case CompressionType.LZ4: + case CompressionType.Zstd: + while (offset > 0) + offset -= compressionStream.Read(compressBuffer.outBuffer, 0, (int)offset); + return compressionStream.Position; + case CompressionType.None: + default: + return fileStream.Seek(offset, origin); } - else - return fileStream.Seek(offset, origin); } } \ No newline at end of file diff --git a/CompressSave/CompressSave.csproj b/CompressSave/CompressSave.csproj index b897000..427a191 100644 --- a/CompressSave/CompressSave.csproj +++ b/CompressSave/CompressSave.csproj @@ -1,32 +1,28 @@ - net472 CompressSave org.soardev.compresssave DSP MOD - CompressSave - 1.1.14 + 1.2.0 true latest + net472 - + - + - - - - - + - + diff --git a/CompressSave/CompressionGameSaveHeader.cs b/CompressSave/CompressionGameSaveHeader.cs index a5092b9..b90abb5 100644 --- a/CompressSave/CompressionGameSaveHeader.cs +++ b/CompressSave/CompressionGameSaveHeader.cs @@ -1,7 +1,13 @@ -namespace CompressSave +namespace CompressSave; + +public enum CompressionType { - internal class CompressionGameSaveHeader: GameSaveHeader - { - public bool IsCompressed = false; - } + None = 0, + LZ4 = 1, + Zstd = 2, +} + +internal class CompressionGameSaveHeader: GameSaveHeader +{ + public CompressionType CompressionType = CompressionType.None; } diff --git a/CompressSave/LZ4Wrap/LZ4Wrap.cs b/CompressSave/LZ4Wrap/LZ4Wrap.cs deleted file mode 100644 index fceeeb4..0000000 --- a/CompressSave/LZ4Wrap/LZ4Wrap.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using MonoMod.Utils; - -namespace CompressSave.LZ4Wrap; - -public struct DecompressStatus -{ - public long WriteLen; - public long ReadLen; - public long Expect; -} - -public static class LZ4API -{ - public static readonly bool Avaliable; - - static LZ4API() - { - Avaliable = true; - string assemblyPath = System.Reflection.Assembly.GetAssembly(typeof(LZ4API)).Location; - string root = string.Empty; - try - { - if (!string.IsNullOrEmpty(assemblyPath)) - { - root = Path.GetDirectoryName(assemblyPath) ?? string.Empty; - } - - var map = new Dictionary> - { - { - "LZ4.dll", new List - { - "LZ4.dll", - "X64/LZ4.dll", - "BepInEx/scripts/x64/LZ4.dll", - Path.Combine(root, "X64/LZ4.dll"), - Path.Combine(root, "LZ4.dll") - } - }, - }; - typeof(LZ4API).ResolveDynDllImports(map); - } - catch (Exception e) - { - Avaliable = false; - Console.WriteLine($"Error: {e}"); - } - } - - public delegate long CalCompressOutBufferSizeFunc(long inBufferSize); - - [DynDllImport(libraryName: "LZ4.dll")] public static CalCompressOutBufferSizeFunc CalCompressOutBufferSize; - - [DynDllImport(libraryName: "LZ4.dll")] public static CompressBeginFunc CompressBegin; - - public delegate long CompressBeginFunc(out IntPtr ctx, byte[] outBuff, long outCapacity, byte[] dictBuffer = null, - long dictSize = 0); - - [DynDllImport(libraryName: "LZ4.dll")] private static CompressUpdateFunc CompressUpdate = null; - - private unsafe delegate long CompressUpdateFunc(IntPtr ctx, byte* dstBuffer, long dstCapacity, byte* srcBuffer, - long srcSize); - - public static unsafe long CompressUpdateEx(IntPtr ctx, byte[] dstBuffer, long dstOffset, byte[] srcBuffer, - long srcOffset, long srcLen) - { - fixed (byte* pdst = dstBuffer, psrc = srcBuffer) - { - return CompressUpdate(ctx, pdst + dstOffset, dstBuffer.Length - dstOffset, psrc + srcOffset, - srcLen - srcOffset); - } - } - - [DynDllImport(libraryName: "LZ4.dll")] public static FreeCompressContextFunc FreeCompressContext; - - public delegate void FreeCompressContextFunc(IntPtr ctx); - - [DynDllImport(libraryName: "LZ4.dll")] public static CompressEndFunc CompressEnd; - - public delegate long CompressEndFunc(IntPtr ctx, byte[] dstBuffer, long dstCapacity); - - [DynDllImport(libraryName: "LZ4.dll")] public static DecompressEndFunc DecompressEnd; - - public delegate long DecompressEndFunc(IntPtr dctx); - - [DynDllImport(libraryName: "LZ4.dll")] private static DecompressUpdateFunc DecompressUpdate = null; - - private unsafe delegate long DecompressUpdateFunc(IntPtr dctx, byte* dstBuffer, ref long dstCapacity, byte* srcBuffer, - ref long srcSize, byte* dict, long dictSize); - - public static unsafe DecompressStatus DecompressUpdateEx(IntPtr dctx, byte[] dstBuffer, int dstOffset, int dstCount, - byte[] srcBuffer, long srcOffset, long count, byte[] dict) - { - long dstLen = Math.Min(dstCount, dstBuffer.Length - dstOffset); - long errCode; - fixed (byte* pdst = dstBuffer, psrc = srcBuffer, pdict = dict) - { - errCode = DecompressUpdate(dctx, pdst + dstOffset, ref dstLen, psrc + srcOffset, ref count, pdict, - dict?.Length ?? 0); - } - - return new DecompressStatus - { - Expect = errCode, - ReadLen = count, - WriteLen = dstLen, - }; - } - - [DynDllImport(libraryName: "LZ4.dll")] public static DecompressBeginFunc DecompressBegin; - - public delegate long DecompressBeginFunc(ref IntPtr pdctx, byte[] inBuffer, ref int inBufferSize, out int blockSize); - - public delegate void ResetDecompresssCtxFunc(IntPtr dctx); - - [DynDllImport(libraryName: "LZ4.dll")] public static ResetDecompresssCtxFunc ResetDecompresssCTX; -} \ No newline at end of file diff --git a/CompressSave/LZ4Wrap/PeekableReader.cs b/CompressSave/LZ4Wrap/PeekableReader.cs deleted file mode 100644 index b74bf66..0000000 --- a/CompressSave/LZ4Wrap/PeekableReader.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.IO; - -namespace CompressSave.LZ4Wrap; - -class PeekableReader : BinaryReader -{ - LZ4DecompressionStream lzstream; - public PeekableReader(LZ4DecompressionStream input) : base (input) - { - lzstream = input; - } - - public override int PeekChar() - { - return lzstream.PeekByte(); - } -} \ No newline at end of file diff --git a/CompressSave/LZ4WrapC/CMakeLists.txt b/CompressSave/LZ4WrapC/CMakeLists.txt index e65d39e..a464024 100644 --- a/CompressSave/LZ4WrapC/CMakeLists.txt +++ b/CompressSave/LZ4WrapC/CMakeLists.txt @@ -1,16 +1,16 @@ cmake_minimum_required(VERSION 3.2) -project(LZ4Wrap) +project(lz4wrap) -add_library(LZ4 SHARED +add_library(lz4wrap SHARED lz4/lz4.c lz4/lz4.h lz4/lz4frame.c lz4/lz4frame.h lz4/lz4frame_static.h lz4/lz4hc.c lz4/lz4hc.h lz4/xxhash.c lz4/xxhash.h - dllmain.c LZ4Wrap.c LZ4Wrap.h) + dllmain.c lz4wrap.c lz4wrap.h) -target_compile_definitions(LZ4 PRIVATE LZ4WRAP_EXPORTS) -target_include_directories(LZ4 PRIVATE lz4) +target_compile_definitions(lz4wrap PRIVATE LZ4WRAP_EXPORTS) +target_include_directories(lz4wrap PRIVATE lz4) if(WIN32) - set_target_properties(LZ4 PROPERTIES PREFIX "") + set_target_properties(lz4wrap PROPERTIES PREFIX "") endif() diff --git a/CompressSave/LZ4WrapC/LZ4Wrap.c b/CompressSave/LZ4WrapC/LZ4Wrap.c index 92861de..542b318 100644 --- a/CompressSave/LZ4WrapC/LZ4Wrap.c +++ b/CompressSave/LZ4WrapC/LZ4Wrap.c @@ -14,7 +14,7 @@ static CContext* CreateCompressContext() return (CContext*)malloc(sizeof(CContext)); } -void FreeCompressContext(CContext* ctx) +void __stdcall CompressContextFree(CContext* ctx) { if (ctx != NULL) { @@ -24,24 +24,24 @@ void FreeCompressContext(CContext* ctx) } } -const LZ4F_preferences_t kPrefs = { +static LZ4F_preferences_t kPrefs = { { LZ4F_max4MB, LZ4F_blockLinked, LZ4F_contentChecksumEnabled, LZ4F_frame, - 0 /* unknown content size */, 0/* no dictID */ , LZ4F_blockChecksumEnabled }, + 0 /* unknown content size */, 0/* no dictID */ , LZ4F_blockChecksumEnabled }, 0, /* compression level; 0 == default */ 0, /* autoflush */ 0, /* favor decompression speed */ { 0, 0, 0 }, /* reserved, must be set to 0 */ }; -size_t CalCompressOutBufferSize(size_t inBufferSize) +size_t __stdcall CompressBufferBound(size_t inBufferSize) { return LZ4F_compressBound(inBufferSize, &kPrefs) + LZ4F_HEADER_SIZE_MAX; } -CContext* CreateCompressContextFromBuffer(void* dictBuffer, size_t dictSize) { +CContext* CreateCompressContextFromBuffer(void* dict, size_t dictSize) { CContext* ctx = CreateCompressContext(); - if (dictBuffer) - ctx->dict = LZ4F_createCDict(dictBuffer, dictSize); + if (dict) + ctx->dict = LZ4F_createCDict(dict, dictSize); else ctx->dict = NULL; if (ctx == NULL) return NULL; @@ -51,7 +51,7 @@ CContext* CreateCompressContextFromBuffer(void* dictBuffer, size_t dictSize) { if (LZ4F_isError(ctxCreation)) { LZ4F_freeCompressionContext(innerctx); - FreeCompressContext(ctx); + CompressContextFree(ctx); return NULL; } ctx->cctx = innerctx; @@ -59,14 +59,14 @@ CContext* CreateCompressContextFromBuffer(void* dictBuffer, size_t dictSize) { return ctx; } -size_t CompressBegin(CContext** pctx, void* outBuff , size_t outCapacity, void* dictBuffer, size_t dictSize) +size_t __stdcall CompressBegin(CContext** pctx, int compressionLevel, void* outBuff , size_t outCapacity, void* dict, size_t dictSize) { - CContext* ctx = CreateCompressContextFromBuffer(dictBuffer, dictSize); + CContext* ctx = CreateCompressContextFromBuffer(dict, dictSize); if (ctx == NULL) return -1; if (outCapacity < LZ4F_HEADER_SIZE_MAX || outCapacity < LZ4F_compressBound(0, &kPrefs)) return LZ4F_ERROR_dstMaxSize_tooSmall; - + kPrefs.compressionLevel = compressionLevel; /* write frame header */ size_t const headerSize = ctx->dict == NULL ? LZ4F_compressBegin(ctx->cctx, outBuff, outCapacity, &kPrefs) @@ -81,7 +81,7 @@ size_t CompressBegin(CContext** pctx, void* outBuff , size_t outCapacity, void* } -size_t CompressUpdate(CContext* ctx,void* dstBuffer, size_t dstCapacity,const void* srcBuffer, size_t srcSize) +size_t __stdcall CompressUpdate(CContext* ctx,void* dstBuffer, size_t dstCapacity,const void* srcBuffer, size_t srcSize) { size_t result = ctx->dict == NULL ? LZ4F_compressUpdate(ctx->cctx, dstBuffer, dstCapacity, srcBuffer, srcSize, NULL) @@ -94,7 +94,7 @@ size_t CompressUpdate(CContext* ctx,void* dstBuffer, size_t dstCapacity,const vo return result; } -size_t CompressEnd(CContext* ctx, void* dstBuffer, size_t dstCapacity) +size_t __stdcall CompressEnd(CContext* ctx, void* dstBuffer, size_t dstCapacity) { size_t writeSize = LZ4F_compressEnd(ctx->cctx, dstBuffer, dstCapacity, NULL); return writeSize; @@ -112,39 +112,43 @@ static size_t get_block_size(const LZ4F_frameInfo_t* info) { } } //return: input bytes expects for next call -size_t DecompressBegin(LZ4F_dctx **pdctx,void *inBuffer,size_t *inBufferSize, size_t *blockSize) +size_t __stdcall DecompressBegin(DContext **pdctx,void *inBuffer,size_t *inBufferSize, size_t *blockSize, void* dict, size_t dictSize) { - LZ4F_dctx* dctx; - LZ4F_dctx** _pdctx = &dctx; - size_t const dctxStatus = LZ4F_createDecompressionContext(_pdctx, LZ4F_VERSION); + DContext* dctx = (DContext*)malloc(sizeof(DContext)); + size_t const dctxStatus = LZ4F_createDecompressionContext(&dctx->dctx, LZ4F_VERSION); Check(!LZ4F_isError(dctxStatus), dctxStatus); Check(*inBufferSize >= LZ4F_HEADER_SIZE_MAX, LZ4F_ERROR_dstMaxSize_tooSmall); LZ4F_frameInfo_t info; - size_t const fires = LZ4F_getFrameInfo(*_pdctx, &info, inBuffer, inBufferSize); + size_t const fires = LZ4F_getFrameInfo(dctx->dctx, &info, inBuffer, inBufferSize); Check(!LZ4F_isError(fires), fires); *blockSize = get_block_size(&info); - *pdctx = *_pdctx; + dctx->dict = dict; + dctx->dictSize = dictSize; + *pdctx = dctx; return fires; } -void ResetDecompresssCTX(LZ4F_dctx* dctx) +void __stdcall DecompressContextReset(DContext* dctx) { - LZ4F_resetDecompressionContext(dctx); + LZ4F_resetDecompressionContext(dctx->dctx); } -size_t DecompressUpdate(LZ4F_dctx* dctx, void* outBuffer, size_t * outBufferSize, void* inBuffer, size_t * inBufferSize,void* dict,size_t dictSize) +size_t __stdcall DecompressUpdate(DContext* dctx, void* outBuffer, size_t * outBufferSize, void* inBuffer, size_t * inBufferSize) { - size_t ret = dict == NULL - ? LZ4F_decompress(dctx, outBuffer, outBufferSize, inBuffer, inBufferSize, NULL) - : LZ4F_decompress_usingDict(dctx, outBuffer, outBufferSize, inBuffer, inBufferSize, dict, dictSize,NULL); + size_t ret = dctx->dict == NULL + ? LZ4F_decompress(dctx->dctx, outBuffer, outBufferSize, inBuffer, inBufferSize, NULL) + : LZ4F_decompress_usingDict(dctx->dctx, outBuffer, outBufferSize, inBuffer, inBufferSize, dctx->dict, dctx->dictSize, NULL); Check(!LZ4F_isError(ret), ret); return ret; } -size_t DecompressEnd(LZ4F_dctx* ctx) +size_t __stdcall DecompressEnd(DContext* dctx) { - return LZ4F_freeDecompressionContext(ctx); -} \ No newline at end of file + if (!dctx) return 0; + size_t r = LZ4F_freeDecompressionContext(dctx->dctx); + free(dctx); + return r; +} diff --git a/CompressSave/LZ4WrapC/LZ4Wrap.h b/CompressSave/LZ4WrapC/LZ4Wrap.h index 0e87927..6a76374 100644 --- a/CompressSave/LZ4WrapC/LZ4Wrap.h +++ b/CompressSave/LZ4WrapC/LZ4Wrap.h @@ -20,20 +20,27 @@ typedef struct LZ4F_CDict* dict; } CContext; -LZ4API void FreeCompressContext(CContext* ctx); +typedef struct +{ + LZ4F_dctx* dctx; + void* dict; + size_t dictSize; +} DContext; -LZ4API size_t CalCompressOutBufferSize(size_t inBufferSize); +LZ4API void __stdcall CompressContextFree(CContext* ctx); -LZ4API size_t CompressBegin(CContext** ctx, void* outBuff, size_t outCapacity, void* dictBuffer, size_t dictSize); +LZ4API size_t __stdcall CompressBufferBound(size_t inBufferSize); -LZ4API size_t CompressUpdate(CContext* ctx, void* dstBuffer, size_t dstCapacity, const void* srcBuffer, size_t srcSize); +LZ4API size_t __stdcall CompressBegin(CContext** ctx, int compressionLevel, void* outBuff, size_t outCapacity, void* dict, size_t dictSize); -LZ4API size_t CompressEnd(CContext* ctx, void* dstBuffer, size_t dstCapacity); +LZ4API size_t __stdcall CompressUpdate(CContext* ctx, void* dstBuffer, size_t dstCapacity, const void* srcBuffer, size_t srcSize); -LZ4API size_t DecompressBegin(LZ4F_dctx** pdctx, void* inBuffer, size_t* inBufferSize, size_t* blockSize); +LZ4API size_t __stdcall CompressEnd(CContext* ctx, void* dstBuffer, size_t dstCapacity); -LZ4API void ResetDecompresssCTX(LZ4F_dctx* dctx); +LZ4API size_t __stdcall DecompressBegin(DContext** pdctx, void* inBuffer, size_t* inBufferSize, size_t* blockSize, void* dict, size_t dictSize); -LZ4API size_t DecompressUpdate(LZ4F_dctx* dctx, void* outBuffer, size_t* outBufferSize, void* inBuffer, size_t* inBufferSize, void* dict, size_t dictSize); +LZ4API void __stdcall DecompressContextReset(DContext* dctx); -LZ4API size_t DecompressEnd(LZ4F_dctx* dctx); +LZ4API size_t __stdcall DecompressUpdate(DContext* dctx, void* outBuffer, size_t* outBufferSize, void* inBuffer, size_t* inBufferSize); + +LZ4API size_t __stdcall DecompressEnd(DContext* dctx); diff --git a/CompressSave/PatchUILoadGame.cs b/CompressSave/PatchUILoadGame.cs index 0402235..dad5d47 100644 --- a/CompressSave/PatchUILoadGame.cs +++ b/CompressSave/PatchUILoadGame.cs @@ -26,16 +26,24 @@ class PatchUILoadGame if (code.opcode == OpCodes.Ldstr && code.OperandIs("#,##0")) { var iffalse = generator.DefineLabel(); + var ifzstd = generator.DefineLabel(); var callLabel = generator.DefineLabel(); code.WithLabels(iffalse) .operand = "(N)#,##0"; codes[i + 1].WithLabels(callLabel); var IL = new List { new CodeInstruction(OpCodes.Ldloc_0), - new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(CompressionGameSaveHeader),"IsCompressed")), - new CodeInstruction(OpCodes.Brfalse_S,iffalse), + new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(CompressionGameSaveHeader),"CompressionType")), + new CodeInstruction(OpCodes.Ldc_I4_S, 0), + new CodeInstruction(OpCodes.Beq_S, iffalse), + new CodeInstruction(OpCodes.Ldloc_0), + new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(CompressionGameSaveHeader),"CompressionType")), + new CodeInstruction(OpCodes.Ldc_I4_S, 2), + new CodeInstruction(OpCodes.Beq_S, ifzstd), new CodeInstruction(OpCodes.Ldstr,"(LZ4)#,##0"), new CodeInstruction(OpCodes.Br_S,callLabel), + new CodeInstruction(OpCodes.Ldstr,"(ZSTD)#,##0").WithLabels(ifzstd), + new CodeInstruction(OpCodes.Br_S,callLabel), }; codes.InsertRange(i, IL); break; @@ -48,7 +56,7 @@ class PatchUILoadGame [HarmonyPatch(typeof(UILoadGameWindow), "OnSelectedChange"), HarmonyPostfix] static void OnSelectedChange(UILoadGameWindow __instance, UIButton ___loadButton, Text ___prop3Text) { - bool compressedSave = (___prop3Text != null &&___prop3Text.text.Contains("LZ4")) || (___loadButton.button.interactable == false && SaveUtil.IsCompressedSave(__instance.selected?.saveName)); + bool compressedSave = (___prop3Text != null && (___prop3Text.text.Contains("(LZ4)") || ___prop3Text.text.Contains("(ZSTD)"))) || (___loadButton.button.interactable == false && SaveUtil.SaveGetCompressType(__instance.selected?.saveName) != CompressionType.None); if (decompressButton) decompressButton.button.interactable = compressedSave; } diff --git a/CompressSave/README.md b/CompressSave/README.md index 594ddad..99515a3 100644 --- a/CompressSave/README.md +++ b/CompressSave/README.md @@ -7,6 +7,14 @@ ## Updates +### 1.2.0 +* Match game version 0.9.27.15033. +* Add new compression type: zstd (a bit slower but get better compression ratio than lz4). +* Add config to set compression type and level(Don't use high compression levels for zstd as they are very slow). +* Optimize native dlls for other compression library support: + * Unified naming rules for filenames and export functions. + * Add compression level support. + ### 1.1.14 * Fix Sandbox info on Save/Load Panel. * Fix DLL version info. diff --git a/CompressSave/SaveUtil.cs b/CompressSave/SaveUtil.cs index 5fb2207..494e26d 100644 --- a/CompressSave/SaveUtil.cs +++ b/CompressSave/SaveUtil.cs @@ -1,11 +1,11 @@ using System; using System.IO; using BepInEx.Logging; -using CompressSave.LZ4Wrap; +using CompressSave.Wrapper; namespace CompressSave; -class SaveUtil +public static class SaveUtil { public static ManualLogSource logger; @@ -14,10 +14,10 @@ class SaveUtil { Major = 0, Minor = 9, - Release = 26, + Release = 27, }; - public static string UnzipToFile(LZ4DecompressionStream lzStream, string fullPath) + public static string UnzipToFile(DecompressionStream lzStream, string fullPath) { lzStream.ResetStream(); string dir = Path.GetDirectoryName(fullPath); @@ -51,13 +51,22 @@ class SaveUtil { using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read)) { - if (!IsCompressedSave(fileStream)) return false; - using (var lzstream = new LZ4DecompressionStream(fileStream)) + var compressType = SaveGetCompressType(fileStream); + switch (compressType) { - newSaveName = UnzipToFile(lzstream, path); + case CompressionType.LZ4: + case CompressionType.Zstd: + using (var lzstream = new DecompressionStream(compressType == CompressionType.LZ4 ? PatchSave.lz4Wrapper : PatchSave.zstdWrapper, fileStream)) + { + newSaveName = UnzipToFile(lzstream, path); + } + return true; + case CompressionType.None: + return false; + default: + throw new ArgumentOutOfRangeException(); } } - return true; } catch (Exception e) { @@ -65,28 +74,34 @@ class SaveUtil return false; } } - public static bool IsCompressedSave(FileStream fs) + public static CompressionType SaveGetCompressType(FileStream fs) { - for (int i = 0; i < 4; i++) + for (int i = 0; i < 3; i++) { if (0xCC != fs.ReadByte()) - return false; + return CompressionType.None; } - return true; + + return fs.ReadByte() switch + { + 0xCC => CompressionType.LZ4, + 0xCD => CompressionType.Zstd, + _ => CompressionType.None + }; } - internal static bool IsCompressedSave(string saveName) + internal static CompressionType SaveGetCompressType(string saveName) { - if (string.IsNullOrEmpty(saveName)) return false; + if (string.IsNullOrEmpty(saveName)) return CompressionType.None; try { using (FileStream fileStream = new FileStream(GetFullSavePath(saveName), FileMode.Open)) - return IsCompressedSave(fileStream); + return SaveGetCompressType(fileStream); } catch (Exception e) { logger.LogWarning(e); - return false; + return CompressionType.None; } } diff --git a/CompressSave/LZ4Wrap/BlackHoleStream.cs b/CompressSave/Wrapper/BlackHoleStream.cs similarity index 96% rename from CompressSave/LZ4Wrap/BlackHoleStream.cs rename to CompressSave/Wrapper/BlackHoleStream.cs index 8715bc7..ed77d94 100644 --- a/CompressSave/LZ4Wrap/BlackHoleStream.cs +++ b/CompressSave/Wrapper/BlackHoleStream.cs @@ -1,7 +1,7 @@ using System; using System.IO; -namespace CompressSave.LZ4Wrap; +namespace CompressSave.Wrapper; class BlackHoleStream : Stream { diff --git a/CompressSave/LZ4Wrap/BufferWriter.cs b/CompressSave/Wrapper/BufferWriter.cs similarity index 97% rename from CompressSave/LZ4Wrap/BufferWriter.cs rename to CompressSave/Wrapper/BufferWriter.cs index 5acac36..0f5bdf5 100644 --- a/CompressSave/LZ4Wrap/BufferWriter.cs +++ b/CompressSave/Wrapper/BufferWriter.cs @@ -3,7 +3,7 @@ using System.IO; using System.Text; using System.Runtime.CompilerServices; -namespace CompressSave.LZ4Wrap; +namespace CompressSave.Wrapper; public unsafe class BufferWriter : BinaryWriter { @@ -40,13 +40,13 @@ public unsafe class BufferWriter : BinaryWriter byte* startPos; private Stream _baseStream; - public BufferWriter(DoubleBuffer doubleBuffer, LZ4CompressionStream outStream) + public BufferWriter(DoubleBuffer doubleBuffer, CompressionStream outStream) : this(doubleBuffer, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true), outStream) { } - BufferWriter(DoubleBuffer buffer , UTF8Encoding encoding, LZ4CompressionStream outStream) : base(Stream.Null, encoding) + BufferWriter(DoubleBuffer buffer , UTF8Encoding encoding, CompressionStream outStream) : base(Stream.Null, encoding) { _baseStream = outStream; swapedBytes = 0; diff --git a/CompressSave/LZ4Wrap/BufferedStream.cs b/CompressSave/Wrapper/BufferedStream.cs similarity index 98% rename from CompressSave/LZ4Wrap/BufferedStream.cs rename to CompressSave/Wrapper/BufferedStream.cs index a39ac30..301049b 100644 --- a/CompressSave/LZ4Wrap/BufferedStream.cs +++ b/CompressSave/Wrapper/BufferedStream.cs @@ -1,4 +1,4 @@ -namespace CompressSave.LZ4Wrap; +namespace CompressSave.Wrapper; //public class BufferedFileStream : FileStream //{ diff --git a/CompressSave/LZ4Wrap/LZ4CompressionStream.cs b/CompressSave/Wrapper/CompressionStream.cs similarity index 83% rename from CompressSave/LZ4Wrap/LZ4CompressionStream.cs rename to CompressSave/Wrapper/CompressionStream.cs index bf95019..351705f 100644 --- a/CompressSave/LZ4Wrap/LZ4CompressionStream.cs +++ b/CompressSave/Wrapper/CompressionStream.cs @@ -2,10 +2,12 @@ using System; using System.IO; using System.Threading; -namespace CompressSave.LZ4Wrap; +namespace CompressSave.Wrapper; -public class LZ4CompressionStream : Stream, IDisposable +public class CompressionStream : Stream { + public WrapperDefines wrapper; + public const int MB = 1024 * 1024; public override bool CanRead => false; @@ -40,7 +42,7 @@ public class LZ4CompressionStream : Stream, IDisposable { if (errorCode < 0) { - LZ4API.FreeCompressContext(cctx); + wrapper.CompressContextFree(cctx); cctx = IntPtr.Zero; lastError = errorCode; throw new Exception(errorCode.ToString()); @@ -54,13 +56,13 @@ public class LZ4CompressionStream : Stream, IDisposable public byte[] outBuffer; } - public static CompressBuffer CreateBuffer(int ExBufferSize = 4 * MB) + public static CompressBuffer CreateBuffer(WrapperDefines wrapper, int ExBufferSize = 4 * MB) { try { return new CompressBuffer { - outBuffer = new byte[LZ4API.CalCompressOutBufferSize(ExBufferSize) + 1], + outBuffer = new byte[wrapper.CompressBufferBound(ExBufferSize) + 1], readBuffer = new byte[ExBufferSize], writeBuffer = new byte[ExBufferSize], }; @@ -75,11 +77,12 @@ public class LZ4CompressionStream : Stream, IDisposable public BufferWriter BufferWriter => bfferWriter; BufferWriter bfferWriter; - public LZ4CompressionStream(Stream outStream, CompressBuffer compressBuffer,bool useMultiThread) + public CompressionStream(WrapperDefines wrap, int compressionLevel, Stream outStream, CompressBuffer compressBuffer, bool useMultiThread) { + this.wrapper = wrap; this.outStream = outStream; InitBuffer(compressBuffer.readBuffer, compressBuffer.writeBuffer, compressBuffer.outBuffer); - long writeSize = LZ4API.CompressBegin(out cctx, outBuffer, outBuffer.Length); + long writeSize = wrapper.CompressBegin(out cctx, compressionLevel, outBuffer, outBuffer.Length); HandleError(writeSize); outStream.Write(outBuffer, 0, (int)writeSize); this.useMultiThread = useMultiThread; @@ -89,13 +92,12 @@ public class LZ4CompressionStream : Stream, IDisposable compressThread = new Thread(() => CompressAsync()); compressThread.Start(); } - } void InitBuffer(byte[] readBuffer, byte[] writeBuffer, byte[] outBuffer) { doubleBuffer = new DoubleBuffer(readBuffer ?? new byte[4 * MB], writeBuffer ?? new byte[4 * MB], Compress); - this.outBuffer = outBuffer ?? new byte[LZ4API.CalCompressOutBufferSize(writeBuffer.Length)]; + this.outBuffer = outBuffer ?? new byte[wrapper.CompressBufferBound(writeBuffer.Length)]; bfferWriter = new BufferWriter(doubleBuffer,this); } @@ -130,7 +132,7 @@ public class LZ4CompressionStream : Stream, IDisposable long writeSize = 0; try { - writeSize = LZ4API.CompressUpdateEx(cctx, outBuffer, 0, consumeBuffer.Buffer, 0, consumeBuffer.Length); + writeSize = wrapper.CompressUpdateEx(cctx, outBuffer, 0, consumeBuffer.Buffer, 0, consumeBuffer.Length); HandleError(writeSize); } finally @@ -188,7 +190,7 @@ public class LZ4CompressionStream : Stream, IDisposable protected void FreeContext() { - LZ4API.FreeCompressContext(cctx); + wrapper.CompressContextFree(cctx); cctx = IntPtr.Zero; } @@ -207,7 +209,7 @@ public class LZ4CompressionStream : Stream, IDisposable stopWorker = true; doubleBuffer.SwapBuffer(); - long size = LZ4API.CompressEnd(cctx, outBuffer, outBuffer.Length); + long size = wrapper.CompressEnd(cctx, outBuffer, outBuffer.Length); //Debug.Log($"End"); outStream.Write(outBuffer, 0, (int)size); base.Close(); diff --git a/CompressSave/LZ4Wrap/LZ4DecompressionStream.cs b/CompressSave/Wrapper/DecompressionStream.cs similarity index 84% rename from CompressSave/LZ4Wrap/LZ4DecompressionStream.cs rename to CompressSave/Wrapper/DecompressionStream.cs index d5b3d9f..3c44b73 100644 --- a/CompressSave/LZ4Wrap/LZ4DecompressionStream.cs +++ b/CompressSave/Wrapper/DecompressionStream.cs @@ -1,10 +1,12 @@ using System; using System.IO; -namespace CompressSave.LZ4Wrap; +namespace CompressSave.Wrapper; -class LZ4DecompressionStream : Stream +public class DecompressionStream : Stream { + public WrapperDefines wrapper; + public override bool CanRead => true; public override bool CanSeek => false; @@ -39,13 +41,14 @@ class LZ4DecompressionStream : Stream readonly long startPos = 0; long readPos = 0; //sum of readlen - public LZ4DecompressionStream(Stream inStream,int extraBufferSize = 512*1024) + public DecompressionStream(WrapperDefines wrap, Stream inStream,int extraBufferSize = 512*1024) { + this.wrapper = wrap; this.inStream = inStream; startPos = inStream.Position; srcBuffer = new ByteSpan(new byte[extraBufferSize]); int len = Fill(); - long expect = LZ4API.DecompressBegin(ref dctx, srcBuffer.Buffer, ref len, out var blockSize); + long expect = wrapper.DecompressBegin(ref dctx, srcBuffer.Buffer, ref len, out var blockSize); srcBuffer.Position += len; if (expect < 0) throw new Exception(expect.ToString()); dcmpBuffer = new ByteSpan(new byte[blockSize]); @@ -57,7 +60,7 @@ class LZ4DecompressionStream : Stream decompressFinish = false; srcBuffer.Clear(); dcmpBuffer.Clear(); - LZ4API.ResetDecompresssCTX(dctx); + wrapper.DecompressContextReset(dctx); readPos = 0; } @@ -85,7 +88,7 @@ class LZ4DecompressionStream : Stream protected override void Dispose(bool disposing) { - LZ4API.DecompressEnd(dctx); + wrapper.DecompressEnd(dctx); dctx = IntPtr.Zero; base.Dispose(disposing); } @@ -98,7 +101,7 @@ class LZ4DecompressionStream : Stream var buffSize = Fill(); if (buffSize <= 0) return readlen; - var rt = LZ4API.DecompressUpdateEx(dctx, dcmpBuffer, 0, dcmpBuffer.Capacity, srcBuffer, srcBuffer.Position,buffSize, null); + var rt = wrapper.DecompressUpdateEx(dctx, dcmpBuffer, 0, dcmpBuffer.Capacity, srcBuffer, srcBuffer.Position,buffSize); if (rt.Expect < 0) throw new Exception(rt.Expect.ToString()); if (rt.Expect == 0) decompressFinish = true; @@ -117,7 +120,7 @@ class LZ4DecompressionStream : Stream var buffSize = Fill(); if (buffSize <= 0) return -1; - var rt = LZ4API.DecompressUpdateEx(dctx, dcmpBuffer, 0, dcmpBuffer.Capacity, srcBuffer, srcBuffer.Position, buffSize, null); + var rt = wrapper.DecompressUpdateEx(dctx, dcmpBuffer, 0, dcmpBuffer.Capacity, srcBuffer, srcBuffer.Position, buffSize); if (rt.Expect < 0) throw new Exception(rt.Expect.ToString()); if (rt.Expect == 0) decompressFinish = true; diff --git a/CompressSave/LZ4Wrap/DoubleBuffer.cs b/CompressSave/Wrapper/DoubleBuffer.cs similarity index 98% rename from CompressSave/LZ4Wrap/DoubleBuffer.cs rename to CompressSave/Wrapper/DoubleBuffer.cs index d64f010..f91de2f 100644 --- a/CompressSave/LZ4Wrap/DoubleBuffer.cs +++ b/CompressSave/Wrapper/DoubleBuffer.cs @@ -1,7 +1,7 @@ using System; using System.Threading; -namespace CompressSave.LZ4Wrap; +namespace CompressSave.Wrapper; public class ByteSpan { diff --git a/CompressSave/Wrapper/LZ4Wrapper.cs b/CompressSave/Wrapper/LZ4Wrapper.cs new file mode 100644 index 0000000..3c6a087 --- /dev/null +++ b/CompressSave/Wrapper/LZ4Wrapper.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.IO; +using MonoMod.Utils; + +namespace CompressSave.Wrapper; + +public class LZ4API: WrapperDefines +{ + public static readonly bool Avaliable; + + static LZ4API() + { + Avaliable = true; + string assemblyPath = System.Reflection.Assembly.GetAssembly(typeof(LZ4API)).Location; + string root = string.Empty; + try + { + if (!string.IsNullOrEmpty(assemblyPath)) + { + root = Path.GetDirectoryName(assemblyPath) ?? string.Empty; + } + + var map = new Dictionary> + { + { + "lz4wrap.dll", new List + { + "lz4wrap.dll", + "X64/lz4wrap.dll", + "BepInEx/scripts/x64/lz4wrap.dll", + Path.Combine(root, "X64/lz4wrap.dll"), + Path.Combine(root, "lz4wrap.dll") + } + }, + }; + typeof(LZ4API).ResolveDynDllImports(map); + } + catch (Exception e) + { + Avaliable = false; + Console.WriteLine($"Error: {e}"); + } + } + + public LZ4API() + { + CompressBufferBound = CompressBufferBound_; + CompressBegin = CompressBegin_; + CompressEnd = CompressEnd_; + CompressUpdate = CompressUpdate_; + CompressContextFree = CompressContextFree_; + DecompressBegin = DecompressBegin_; + DecompressEnd = DecompressEnd_; + DecompressUpdate = DecompressUpdate_; + DecompressContextReset = DecompressContextReset_; + } + + [DynDllImport(libraryName: "lz4wrap.dll", "CompressBufferBound")] protected static CompressBufferBoundFunc CompressBufferBound_; + [DynDllImport(libraryName: "lz4wrap.dll", "CompressBegin")] protected static CompressBeginFunc CompressBegin_; + [DynDllImport(libraryName: "lz4wrap.dll", "CompressEnd")] protected static CompressEndFunc CompressEnd_; + [DynDllImport(libraryName: "lz4wrap.dll", "CompressUpdate")] protected static CompressUpdateFunc CompressUpdate_; + [DynDllImport(libraryName: "lz4wrap.dll", "CompressContextFree")] protected static CompressContextFreeFunc CompressContextFree_; + [DynDllImport(libraryName: "lz4wrap.dll", "DecompressBegin")] protected static DecompressBeginFunc DecompressBegin_; + [DynDllImport(libraryName: "lz4wrap.dll", "DecompressEnd")] protected static DecompressEndFunc DecompressEnd_; + [DynDllImport(libraryName: "lz4wrap.dll", "DecompressUpdate")] protected static DecompressUpdateFunc DecompressUpdate_; + [DynDllImport(libraryName: "lz4wrap.dll", "DecompressContextReset")] protected static DecompressContextResetFunc DecompressContextReset_; +} diff --git a/CompressSave/Wrapper/PeekableReader.cs b/CompressSave/Wrapper/PeekableReader.cs new file mode 100644 index 0000000..daf577e --- /dev/null +++ b/CompressSave/Wrapper/PeekableReader.cs @@ -0,0 +1,17 @@ +using System.IO; + +namespace CompressSave.Wrapper; + +class PeekableReader : BinaryReader +{ + DecompressionStream decompressionStream; + public PeekableReader(DecompressionStream input) : base (input) + { + decompressionStream = input; + } + + public override int PeekChar() + { + return decompressionStream.PeekByte(); + } +} \ No newline at end of file diff --git a/CompressSave/Wrapper/WrapperDefines.cs b/CompressSave/Wrapper/WrapperDefines.cs new file mode 100644 index 0000000..652064a --- /dev/null +++ b/CompressSave/Wrapper/WrapperDefines.cs @@ -0,0 +1,64 @@ +using System; + +namespace CompressSave.Wrapper; + +public struct DecompressStatus +{ + public long WriteLen; + public long ReadLen; + public long Expect; +} + +public class WrapperDefines +{ + public delegate long CompressBufferBoundFunc(long inBufferSize); + public delegate long CompressBeginFunc(out IntPtr ctx, int compressionLevel, byte[] outBuff, long outCapacity, byte[] dictBuffer = null, + long dictSize = 0); + public delegate long CompressEndFunc(IntPtr ctx, byte[] dstBuffer, long dstCapacity); + public delegate void CompressContextFreeFunc(IntPtr ctx); + public delegate long DecompressBeginFunc(ref IntPtr pdctx, byte[] inBuffer, ref int inBufferSize, out int blockSize, byte[] dict = null, long dictSize = 0); + public delegate long DecompressEndFunc(IntPtr dctx); + public delegate void DecompressContextResetFunc(IntPtr dctx); + protected unsafe delegate long CompressUpdateFunc(IntPtr ctx, byte* dstBuffer, long dstCapacity, byte* srcBuffer, + long srcSize); + protected unsafe delegate long DecompressUpdateFunc(IntPtr dctx, byte* dstBuffer, ref long dstCapacity, byte* srcBuffer, + ref long srcSize); + + public CompressBufferBoundFunc CompressBufferBound; + public CompressBeginFunc CompressBegin; + public CompressEndFunc CompressEnd; + public CompressContextFreeFunc CompressContextFree; + public DecompressBeginFunc DecompressBegin; + public DecompressEndFunc DecompressEnd; + public DecompressContextResetFunc DecompressContextReset; + protected CompressUpdateFunc CompressUpdate; + protected DecompressUpdateFunc DecompressUpdate; + + public unsafe long CompressUpdateEx(IntPtr ctx, byte[] dstBuffer, long dstOffset, byte[] srcBuffer, + long srcOffset, long srcLen) + { + fixed (byte* pdst = dstBuffer, psrc = srcBuffer) + { + return CompressUpdate(ctx, pdst + dstOffset, dstBuffer.Length - dstOffset, psrc + srcOffset, + srcLen - srcOffset); + } + } + + public unsafe DecompressStatus DecompressUpdateEx(IntPtr dctx, byte[] dstBuffer, int dstOffset, int dstCount, + byte[] srcBuffer, long srcOffset, long count) + { + long dstLen = Math.Min(dstCount, dstBuffer.Length - dstOffset); + long errCode; + fixed (byte* pdst = dstBuffer, psrc = srcBuffer) + { + errCode = DecompressUpdate(dctx, pdst + dstOffset, ref dstLen, psrc + srcOffset, ref count); + } + + return new DecompressStatus + { + Expect = errCode, + ReadLen = count, + WriteLen = dstLen, + }; + } +} diff --git a/CompressSave/Wrapper/ZstdWrapper.cs b/CompressSave/Wrapper/ZstdWrapper.cs new file mode 100644 index 0000000..2f6a766 --- /dev/null +++ b/CompressSave/Wrapper/ZstdWrapper.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.IO; +using MonoMod.Utils; + +namespace CompressSave.Wrapper; + +public class ZstdAPI: WrapperDefines +{ + public static readonly bool Avaliable; + + static ZstdAPI() + { + Avaliable = true; + string assemblyPath = System.Reflection.Assembly.GetAssembly(typeof(ZstdAPI)).Location; + string root = string.Empty; + try + { + if (!string.IsNullOrEmpty(assemblyPath)) + { + root = Path.GetDirectoryName(assemblyPath) ?? string.Empty; + } + + var map = new Dictionary> + { + { + "zstdwrap.dll", new List + { + "zstdwrap.dll", + "X64/zstdwrap.dll", + "BepInEx/scripts/x64/zstdwrap.dll", + Path.Combine(root, "X64/zstdwrap.dll"), + Path.Combine(root, "zstdwrap.dll") + } + }, + }; + typeof(ZstdAPI).ResolveDynDllImports(map); + } + catch (Exception e) + { + Avaliable = false; + Console.WriteLine($"Error: {e}"); + } + } + + public ZstdAPI() + { + CompressBufferBound = CompressBufferBound_; + CompressBegin = CompressBegin_; + CompressEnd = CompressEnd_; + CompressUpdate = CompressUpdate_; + CompressContextFree = CompressContextFree_; + DecompressBegin = DecompressBegin_; + DecompressEnd = DecompressEnd_; + DecompressUpdate = DecompressUpdate_; + DecompressContextReset = DecompressContextReset_; + } + + [DynDllImport(libraryName: "zstdwrap.dll", "CompressBufferBound")] protected static CompressBufferBoundFunc CompressBufferBound_; + [DynDllImport(libraryName: "zstdwrap.dll", "CompressBegin")] protected static CompressBeginFunc CompressBegin_; + [DynDllImport(libraryName: "zstdwrap.dll", "CompressEnd")] protected static CompressEndFunc CompressEnd_; + [DynDllImport(libraryName: "zstdwrap.dll", "CompressUpdate")] protected static CompressUpdateFunc CompressUpdate_; + [DynDllImport(libraryName: "zstdwrap.dll", "CompressContextFree")] protected static CompressContextFreeFunc CompressContextFree_; + [DynDllImport(libraryName: "zstdwrap.dll", "DecompressBegin")] protected static DecompressBeginFunc DecompressBegin_; + [DynDllImport(libraryName: "zstdwrap.dll", "DecompressEnd")] protected static DecompressEndFunc DecompressEnd_; + [DynDllImport(libraryName: "zstdwrap.dll", "DecompressUpdate")] protected static DecompressUpdateFunc DecompressUpdate_; + [DynDllImport(libraryName: "zstdwrap.dll", "DecompressContextReset")] protected static DecompressContextResetFunc DecompressContextReset_; +} diff --git a/CompressSave/ZstdWrapC/CMakeLists.txt b/CompressSave/ZstdWrapC/CMakeLists.txt new file mode 100644 index 0000000..b5c5c27 --- /dev/null +++ b/CompressSave/ZstdWrapC/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.2) + +project(zstdwrap) + +add_library(LZ4 SHARED + dllmain.c zstdwrap.c zstdwrap.h) + +target_compile_definitions(zstdwrap PRIVATE ZSTDWRAP_EXPORTS ZSTDLIB_STATIC_API) +target_link_libraries(zstdwrap PRIVATE zstd) +if(WIN32) + set_target_properties(zstdwrap PROPERTIES PREFIX "") +endif() diff --git a/CompressSave/ZstdWrapC/dllmain.c b/CompressSave/ZstdWrapC/dllmain.c new file mode 100644 index 0000000..52d4ad2 --- /dev/null +++ b/CompressSave/ZstdWrapC/dllmain.c @@ -0,0 +1,19 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/CompressSave/ZstdWrapC/zstdwrap.c b/CompressSave/ZstdWrapC/zstdwrap.c new file mode 100644 index 0000000..c28c539 --- /dev/null +++ b/CompressSave/ZstdWrapC/zstdwrap.c @@ -0,0 +1,84 @@ +#include "zstdwrap.h" + +#include +#include +#include +#include + +size_t __stdcall CompressBufferBound(size_t inBufferSize) +{ + return ZSTD_COMPRESSBOUND(inBufferSize); +} + +size_t __stdcall CompressBegin(ZSTD_CStream** pctx, int compressionLevel, void* outBuff, size_t outCapacity, void* dict, size_t dictSize) +{ + ZSTD_CStream *ctx = ZSTD_createCStream(); + if (ctx == NULL) return -1; + ZSTD_CCtx_setParameter(ctx, ZSTD_c_compressionLevel, compressionLevel); + if (dict) + { + ZSTD_CCtx_loadDictionary(ctx, dict, dictSize); + } + *pctx = ctx; + return 0; +} + + +size_t __stdcall CompressUpdate(ZSTD_CStream* ctx,void* dstBuffer, size_t dstCapacity,const void* srcBuffer, size_t srcSize) +{ + ZSTD_outBuffer obuf = {dstBuffer, dstCapacity, 0}; + ZSTD_inBuffer ibuf = {srcBuffer, srcSize, 0}; + do + { + ZSTD_compressStream2(ctx, &obuf, &ibuf, ZSTD_e_continue); + } + while (ibuf.pos < ibuf.size); + return obuf.pos; +} + +size_t __stdcall CompressEnd(ZSTD_CStream* ctx, void* dstBuffer, size_t dstCapacity) +{ + ZSTD_outBuffer obuf = {dstBuffer, dstCapacity, 0}; + ZSTD_inBuffer ibuf = {NULL, 0, 0}; + while (ZSTD_compressStream2(ctx, &obuf, &ibuf, ZSTD_e_end) > 0) {} + return obuf.pos; +} + +void __stdcall CompressContextFree(ZSTD_CStream* ctx) +{ + ZSTD_freeCStream(ctx); +} + +size_t __stdcall DecompressBegin(ZSTD_DStream **pdctx,void *inBuffer,size_t *inBufferSize, size_t *blockSize, void* dict, size_t dictSize) +{ + ZSTD_DStream *ctx = ZSTD_createDStream(); + if (ctx == NULL) return -1; + if (dict) + { + ZSTD_DCtx_loadDictionary(ctx, dict, dictSize); + } + *pdctx = ctx; + *inBufferSize = 0; + *blockSize = ZSTD_DStreamOutSize() << 2; + return 0; +} + +void __stdcall DecompressContextReset(ZSTD_DStream* dctx) +{ + ZSTD_DCtx_reset(dctx, ZSTD_reset_session_only); +} + +size_t __stdcall DecompressUpdate(ZSTD_DStream* dctx, void* outBuffer, size_t * outBufferSize, void* inBuffer, size_t * inBufferSize) +{ + ZSTD_outBuffer obuf = {outBuffer, *outBufferSize, 0}; + ZSTD_inBuffer ibuf = {inBuffer, *inBufferSize, 0}; + size_t r = ZSTD_decompressStream(dctx, &obuf, &ibuf); + *outBufferSize = obuf.pos; + *inBufferSize = ibuf.pos; + return r; +} + +size_t __stdcall DecompressEnd(ZSTD_DStream* ctx) +{ + return ZSTD_freeDStream(ctx); +} \ No newline at end of file diff --git a/CompressSave/ZstdWrapC/zstdwrap.h b/CompressSave/ZstdWrapC/zstdwrap.h new file mode 100644 index 0000000..2f36b1a --- /dev/null +++ b/CompressSave/ZstdWrapC/zstdwrap.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include + +#if defined(__cplusplus) +#define API_EXTERN_C extern "C" +#else +#define API_EXTERN_C +#endif +#ifdef ZSTDWRAP_EXPORTS +#define ZSTDAPI API_EXTERN_C __declspec(dllexport) +#else +#define ZSTDAPI API_EXTERN_C __declspec(dllimport) +#endif + +ZSTDAPI void __stdcall CompressContextFree(ZSTD_CStream* ctx); + +ZSTDAPI size_t __stdcall CompressBufferBound(size_t inBufferSize); + +ZSTDAPI size_t __stdcall CompressBegin(ZSTD_CStream** ctx, int compressionLevel, void* outBuff, size_t outCapacity, void* dict, size_t dictSize); + +ZSTDAPI size_t __stdcall CompressUpdate(ZSTD_CStream* ctx, void* dstBuffer, size_t dstCapacity, const void* srcBuffer, size_t srcSize); + +ZSTDAPI size_t __stdcall CompressEnd(ZSTD_CStream* ctx, void* dstBuffer, size_t dstCapacity); + +ZSTDAPI size_t __stdcall DecompressBegin(ZSTD_DStream** pdctx, void* inBuffer, size_t* inBufferSize, size_t* blockSize, void* dict, size_t dictSize); + +ZSTDAPI void __stdcall DecompressContextReset(ZSTD_DStream* dctx); + +ZSTDAPI size_t __stdcall DecompressUpdate(ZSTD_DStream* dctx, void* outBuffer, size_t* outBufferSize, void* inBuffer, size_t* inBufferSize); + +ZSTDAPI size_t __stdcall DecompressEnd(ZSTD_DStream* dctx); diff --git a/CompressSave/package/LZ4.dll b/CompressSave/package/LZ4.dll deleted file mode 100644 index 58a3f61..0000000 Binary files a/CompressSave/package/LZ4.dll and /dev/null differ diff --git a/CompressSave/package/lz4wrap.dll b/CompressSave/package/lz4wrap.dll new file mode 100644 index 0000000..8d7d800 Binary files /dev/null and b/CompressSave/package/lz4wrap.dll differ diff --git a/CompressSave/package/manifest.json b/CompressSave/package/manifest.json index 450c407..c555dbf 100644 --- a/CompressSave/package/manifest.json +++ b/CompressSave/package/manifest.json @@ -1,6 +1,6 @@ { "name": "CompressSave", - "version_number": "1.1.14", + "version_number": "1.2.0", "website_url": "https://github.com/soarqin/DSP_Mods/tree/master/CompressSave", "description": "Compress game saves to reduce space use and boost save speed / 压缩游戏存档以降低空间使用并提升保存速度", "dependencies": ["xiaoye97-BepInEx-5.4.17"] diff --git a/CompressSave/package/zstdwrap.dll b/CompressSave/package/zstdwrap.dll new file mode 100644 index 0000000..0f28c2d Binary files /dev/null and b/CompressSave/package/zstdwrap.dll differ diff --git a/UniverseGenTweaks/README.md b/UniverseGenTweaks/README.md index 385cc5d..d51d620 100644 --- a/UniverseGenTweaks/README.md +++ b/UniverseGenTweaks/README.md @@ -5,8 +5,10 @@ ## Usage * More options on universe creation -* Can set maximum star count in config file. +* Can set maximum star count(128 by default, up to 1024) in config file. + * Note: there is performance issue on galaxy view with large amount of stars. ## 使用说明 * 生成宇宙时提供更多选项 -* 可以在配置文件中设置最大恒星数 +* 可以在配置文件中设置最大恒星数(默认128, 最多1024) + * 注意: 大量恒星会导致宇宙视图出现性能问题 \ No newline at end of file diff --git a/UniverseGenTweaks/UniverseGenTweaks.cs b/UniverseGenTweaks/UniverseGenTweaks.cs index 0137eab..9a907ec 100644 --- a/UniverseGenTweaks/UniverseGenTweaks.cs +++ b/UniverseGenTweaks/UniverseGenTweaks.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Reflection.Emit; using BepInEx; using BepInEx.Configuration; @@ -15,19 +16,23 @@ public class UniverseGenTweaks : BaseUnityPlugin BepInEx.Logging.Logger.CreateLogSource(PluginInfo.PLUGIN_NAME); private bool _cfgEnabled = true; - private static int _maxStarCount = 64; + private static int _maxStarCount = 128; private static float _minDist = 2f; - private static float _maxDist = 3.2f; + private static float _minStep = 2f; + private static float _maxStep = 3.2f; private static float _flatten = 0.18f; private static Text _minDistTitle; - private static Text _maxDistTitle; + private static Text _minStepTitle; + private static Text _maxStepTitle; private static Text _flattenTitle; private static Slider _minDistSlider; - private static Slider _maxDistSlider; + private static Slider _minStepSlider; + private static Slider _maxStepSlider; private static Slider _flattenSlider; private static Text _minDistText; - private static Text _maxDistText; + private static Text _minStepText; + private static Text _maxStepText; private static Text _flattenText; private void Awake() @@ -62,18 +67,24 @@ public class UniverseGenTweaks : BaseUnityPlugin __instance.starCountSlider.maxValue = _maxStarCount; createSliderWithText(__instance.starCountSlider, out _minDistTitle, out _minDistSlider, out _minDistText); - createSliderWithText(__instance.starCountSlider, out _maxDistTitle, out _maxDistSlider, out _maxDistText); + createSliderWithText(__instance.starCountSlider, out _minStepTitle, out _minStepSlider, out _minStepText); + createSliderWithText(__instance.starCountSlider, out _maxStepTitle, out _maxStepSlider, out _maxStepText); createSliderWithText(__instance.starCountSlider, out _flattenTitle, out _flattenSlider, out _flattenText); _minDistTitle.name = "min-dist"; _minDistSlider.minValue = 10f; - _minDistSlider.maxValue = _maxDist * 10f; + _minDistSlider.maxValue = 50f; _minDistSlider.value = _minDist * 10f; - _maxDistTitle.name = "max-dist"; - _maxDistSlider.minValue = _minDist * 10f; - _maxDistSlider.maxValue = 100f; - _maxDistSlider.value = _maxDist * 10f; + _minStepTitle.name = "min-step"; + _minStepSlider.minValue = 10f; + _minStepSlider.maxValue = _maxStep * 10f - 1f; + _minStepSlider.value = _minStep * 10f; + + _maxStepTitle.name = "max-step"; + _maxStepSlider.minValue = _minStep * 10f + 1f; + _maxStepSlider.maxValue = 100f; + _maxStepSlider.value = _maxStep * 10f; _flattenTitle.name = "flatten"; _flattenSlider.minValue = 1f; @@ -81,12 +92,13 @@ public class UniverseGenTweaks : BaseUnityPlugin _flattenSlider.value = _flatten * 50f; transformDeltaY(_minDistTitle.transform, -0.3573f); - transformDeltaY(_maxDistTitle.transform, -0.3573f * 2); - transformDeltaY(_flattenTitle.transform, -0.3573f * 3); - transformDeltaY(__instance.resourceMultiplierSlider.transform.parent, -0.3573f * 3); - transformDeltaY(__instance.sandboxToggle.transform.parent, -0.3573f * 3); - transformDeltaY(__instance.propertyMultiplierText.transform, -0.3573f * 3); - transformDeltaY(__instance.addrText.transform.parent, -0.3573f * 3); + transformDeltaY(_minStepTitle.transform, -0.3573f * 2); + transformDeltaY(_maxStepTitle.transform, -0.3573f * 3); + transformDeltaY(_flattenTitle.transform, -0.3573f * 4); + transformDeltaY(__instance.resourceMultiplierSlider.transform.parent, -0.3573f * 4); + transformDeltaY(__instance.sandboxToggle.transform.parent, -0.3573f * 4); + transformDeltaY(__instance.propertyMultiplierText.transform, -0.3573f * 4); + transformDeltaY(__instance.addrText.transform.parent, -0.3573f * 4); } [HarmonyPrefix] @@ -95,18 +107,21 @@ public class UniverseGenTweaks : BaseUnityPlugin { if (Localization.language == Language.zhCN) { - _minDistTitle.text = "恒星/步进距离"; - _maxDistTitle.text = "步进最大距离"; + _minDistTitle.text = "恒星最小距离"; + _minStepTitle.text = "步进最小距离"; + _maxStepTitle.text = "步进最大距离"; _flattenTitle.text = "扁平度"; } else { - _minDistTitle.text = "Star/Step Distance"; - _maxDistTitle.text = "Step Distance Max"; + _minDistTitle.text = "Star Distance Min"; + _minStepTitle.text = "Step Distance Min"; + _maxStepTitle.text = "Step Distance Max"; _flattenTitle.text = "Flatten"; } _minDistText.text = _minDist.ToString(); - _maxDistText.text = _maxDist.ToString(); + _minStepText.text = _minStep.ToString(); + _maxStepText.text = _maxStep.ToString(); _flattenText.text = _flatten.ToString(); } @@ -117,34 +132,60 @@ public class UniverseGenTweaks : BaseUnityPlugin _minDistSlider.onValueChanged.RemoveAllListeners(); _minDistSlider.onValueChanged.AddListener(val => { - var newVal = val / 10f; + var newVal = Mathf.Round(val) / 10f; if (newVal.Equals(_minDist)) return; _minDist = newVal; - _maxDistSlider.minValue = newVal * 10f; _minDistText.text = _minDist.ToString(); __instance.SetStarmapGalaxy(); }); - _maxDistSlider.onValueChanged.RemoveAllListeners(); - _maxDistSlider.onValueChanged.AddListener(val => + _minStepSlider.onValueChanged.RemoveAllListeners(); + _minStepSlider.onValueChanged.AddListener(val => { - var newVal = val / 10f; - if (newVal.Equals(_maxDist)) return; - _maxDist = newVal; - _minDistSlider.maxValue = newVal * 10f; - _maxDistText.text = _maxDist.ToString(); + var newVal = Mathf.Round(val) / 10f; + if (newVal.Equals(_minStep)) return; + _minStep = newVal; + _maxStepSlider.minValue = newVal * 10f; + _minStepText.text = _minStep.ToString(); + __instance.SetStarmapGalaxy(); + }); + _maxStepSlider.onValueChanged.RemoveAllListeners(); + _maxStepSlider.onValueChanged.AddListener(val => + { + var newVal = Mathf.Round(val) / 10f; + if (newVal.Equals(_maxStep)) return; + _maxStep = newVal; + _minStepSlider.maxValue = newVal * 10f; + _maxStepText.text = _maxStep.ToString(); __instance.SetStarmapGalaxy(); }); _flattenSlider.onValueChanged.RemoveAllListeners(); _flattenSlider.onValueChanged.AddListener(val => { - var newVal = val / 50f; - if (newVal.Equals(_maxDist)) return; + var newVal = Mathf.Round(val) / 50f; + if (newVal.Equals(_flatten)) return; _flatten = newVal; _flattenText.text = _flatten.ToString(); __instance.SetStarmapGalaxy(); }); } + [HarmonyTranspiler] + [HarmonyPatch(typeof(UIGalaxySelect), "OnStarCountSliderValueChange")] + static IEnumerable PatchStarCountOnValueChange(IEnumerable instructions) + { + foreach (var instruction in instructions) + { + if (instruction.opcode == OpCodes.Ldc_I4_S && instruction.OperandIs(80)) + { + yield return new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(UniverseGenTweaks), "_maxStarCount")); + } + else + { + yield return instruction; + } + } + } + [HarmonyPrefix] [HarmonyPatch(typeof(GalaxyData), MethodType.Constructor)] static bool PatchGalaxyData(GalaxyData __instance) @@ -168,28 +209,31 @@ public class UniverseGenTweaks : BaseUnityPlugin yield return pop; yield return pop; yield return new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(UniverseGenTweaks), "_minDist")); - yield return new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(UniverseGenTweaks), "_minDist")); - yield return new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(UniverseGenTweaks), "_maxDist")); + yield return new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(UniverseGenTweaks), "_minStep")); + yield return new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(UniverseGenTweaks), "_maxStep")); yield return new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(UniverseGenTweaks), "_flatten")); } yield return instruction; } } + /* Patch `rand() * (maxStepLen - minStepLen) + minDist` to `rand() * (maxStepLen - minStepLen) + minStepLen`, + this should be a bugged line in original game code. */ [HarmonyTranspiler] - [HarmonyPatch(typeof(UIGalaxySelect), "OnStarCountSliderValueChange")] - static IEnumerable PatchStarCountOnValueChange(IEnumerable instructions) + [HarmonyPatch(typeof(UniverseGen), "RandomPoses")] + static IEnumerable PatchUniverGenRandomPoses(IEnumerable instructions) { + var lastIsMul = false; foreach (var instruction in instructions) { - if (instruction.opcode == OpCodes.Ldc_I4_S && instruction.OperandIs(80)) + if (lastIsMul && instruction.opcode == OpCodes.Ldarg_2) { - yield return new CodeInstruction(OpCodes.Ldc_I4, _maxStarCount); - } - else - { - yield return instruction; + lastIsMul = false; + yield return new CodeInstruction(OpCodes.Ldarg_3); + continue; } + lastIsMul = instruction.opcode == OpCodes.Mul; + yield return instruction; } } } \ No newline at end of file diff --git a/UniverseGenTweaks/package/icon.png b/UniverseGenTweaks/package/icon.png index 645e026..14b795d 100644 Binary files a/UniverseGenTweaks/package/icon.png and b/UniverseGenTweaks/package/icon.png differ diff --git a/UniverseGenTweaks/package/manifest.json b/UniverseGenTweaks/package/manifest.json index f3febba..00e4ce0 100644 --- a/UniverseGenTweaks/package/manifest.json +++ b/UniverseGenTweaks/package/manifest.json @@ -2,7 +2,7 @@ "name": "UniverseGenTweaks", "version_number": "1.0.0", "website_url": "https://github.com/soarqin/DSP_Mods/tree/master/UniverseGenTweaks", - "description": "#### Universe Generator Tweaks / 宇宙生成参数调节", + "description": "Universe Generator Tweaks / 宇宙生成参数调节", "dependencies": [ "xiaoye97-BepInEx-5.4.17" ]