1
0
mirror of https://github.com/soarqin/DSP_Mods.git synced 2025-12-09 00:53:39 +08:00

WIP: CompressSave 1.2.0

This commit is contained in:
2022-11-20 16:36:02 +08:00
parent f4457c5fd2
commit aec98cda34
33 changed files with 714 additions and 334 deletions

View File

@@ -3,25 +3,56 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Reflection.Emit; using System.Reflection.Emit;
using BepInEx; using BepInEx;
using BepInEx.Configuration;
using HarmonyLib; using HarmonyLib;
using CompressSave.LZ4Wrap; using CompressSave.Wrapper;
namespace CompressSave; namespace CompressSave;
[BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
public class CompressSave : BaseUnityPlugin 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() public void Awake()
{ {
SaveUtil.logger = Logger; 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<string>("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<int>(-1, 22), new {}))
.Value;
PatchSave.CreateCompressBuffer();
if (GameConfig.gameVersion != SaveUtil.VerifiedVersion) if (GameConfig.gameVersion != SaveUtil.VerifiedVersion)
{ {
SaveUtil.logger.LogWarning($"Save version mismatch. Expect:{SaveUtil.VerifiedVersion}, Current:{GameConfig.gameVersion}. MOD may not work as expected."); SaveUtil.logger.LogWarning($"Save version mismatch. Expect:{SaveUtil.VerifiedVersion}, Current:{GameConfig.gameVersion}. MOD may not work as expected.");
} }
Harmony.CreateAndPatchAll(typeof(PatchSave)); Harmony.CreateAndPatchAll(typeof(PatchSave));
if (PatchSave.EnableCompress) if (PatchSave.EnableCompress && PatchSave.CompressionTypeForSaves != CompressionType.None)
Harmony.CreateAndPatchAll(typeof(PatchUISaveGame)); Harmony.CreateAndPatchAll(typeof(PatchUISaveGame));
Harmony.CreateAndPatchAll(typeof(PatchUILoadGame)); Harmony.CreateAndPatchAll(typeof(PatchUILoadGame));
} }
@@ -39,18 +70,36 @@ public class CompressSave : BaseUnityPlugin
class PatchSave class PatchSave
{ {
public static WrapperDefines lz4Wrapper = new LZ4API(), zstdWrapper = new ZstdAPI();
const long MB = 1024 * 1024; 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 UseCompressSave = false;
public static bool IsCompressedSave; public static CompressionType CompressedType = CompressionType.None;
static Stream lzstream = null; public static CompressionType CompressionTypeForSaves = CompressionType.LZ4;
public static int CompressionLevelForSaves = 0;
static Stream compressionStream = null;
public static bool EnableCompress; public static bool EnableCompress;
public static bool EnableDecompress; 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) private static void WriteHeader(FileStream fileStream)
{ {
for (int i = 0; i < 4; i++) for (int i = 0; i < 3; i++)
fileStream.WriteByte(0xCC); fileStream.WriteByte(0xCC);
switch (CompressionTypeForSaves)
{
case CompressionType.Zstd:
fileStream.WriteByte(0xCD);
break;
case CompressionType.LZ4:
default:
fileStream.WriteByte(0xCC);
break;
}
} }
[HarmonyPrefix] [HarmonyPrefix]
@@ -58,17 +107,17 @@ class PatchSave
[HarmonyPatch(typeof(GameSave), "SaveAsLastExit")] [HarmonyPatch(typeof(GameSave), "SaveAsLastExit")]
static void BeforeAutoSave() static void BeforeAutoSave()
{ {
UseCompressSave = EnableCompress; UseCompressSave = EnableCompress && CompressionTypeForSaves != CompressionType.None;
} }
[HarmonyTranspiler] [HarmonyTranspiler]
[HarmonyPatch(typeof(GameSave), "SaveCurrentGame")] [HarmonyPatch(typeof(GameSave), "SaveCurrentGame")]
static IEnumerable<CodeInstruction> SaveCurrentGame_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator) static IEnumerable<CodeInstruction> SaveCurrentGame_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{ {
/* BinaryWriter binaryWriter = new BinaryWriter(fileStream); => Create lzstream and replace binaryWriter. /* BinaryWriter binaryWriter = new BinaryWriter(fileStream); => Create compressionStream and replace binaryWriter.
* set PerformanceMonitor.BeginStream to lzstream. * set PerformanceMonitor.BeginStream to compressionStream.
* fileStream.Seek(6L, SeekOrigin.Begin); binaryWriter.Write(position); => Disable seek&write function. * 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 try
{ {
@@ -83,7 +132,7 @@ class PatchSave
.Set(OpCodes.Call, AccessTools.Method(typeof(PatchSave), "FileLengthWrite1")) .Set(OpCodes.Call, AccessTools.Method(typeof(PatchSave), "FileLengthWrite1"))
.MatchForward(false, new CodeMatch(OpCodes.Callvirt, AccessTools.Method(typeof(System.IDisposable), "Dispose"))) .MatchForward(false, new CodeMatch(OpCodes.Callvirt, AccessTools.Method(typeof(System.IDisposable), "Dispose")))
.Advance(1) .Advance(1)
.Insert(new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(PatchSave), "DisposeLzstream"))); .Insert(new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(PatchSave), "DisposecompressionStream")));
EnableCompress = true; EnableCompress = true;
return matcher.InstructionEnumeration(); return matcher.InstructionEnumeration();
} }
@@ -97,7 +146,7 @@ class PatchSave
public static void MonitorStream(Stream fileStream) public static void MonitorStream(Stream fileStream)
{ {
PerformanceMonitor.BeginStream(UseCompressSave ? lzstream : fileStream); PerformanceMonitor.BeginStream(UseCompressSave ? compressionStream : fileStream);
} }
public static BinaryWriter CreateBinaryWriter(FileStream fileStream) public static BinaryWriter CreateBinaryWriter(FileStream fileStream)
@@ -106,8 +155,8 @@ class PatchSave
{ {
SaveUtil.logger.LogDebug("Begin compress save"); SaveUtil.logger.LogDebug("Begin compress save");
WriteHeader(fileStream); WriteHeader(fileStream);
lzstream = new LZ4CompressionStream(fileStream, compressBuffer, true); //need to dispose after use compressionStream = new CompressionStream(CompressionTypeForSaves == CompressionType.LZ4 ? lz4Wrapper : zstdWrapper, CompressionLevelForSaves, fileStream, compressBuffer, true); //need to dispose after use
return ((LZ4CompressionStream)lzstream).BufferWriter; return ((CompressionStream)compressionStream).BufferWriter;
} }
SaveUtil.logger.LogDebug("Begin normal save"); SaveUtil.logger.LogDebug("Begin normal save");
return new BinaryWriter(fileStream); return new BinaryWriter(fileStream);
@@ -126,17 +175,16 @@ class PatchSave
binaryWriter.Write(value); binaryWriter.Write(value);
} }
public static void DisposeLzstream() public static void DisposecompressionStream()
{ {
if (!UseCompressSave) return; if (!UseCompressSave) return;
var writeflag = lzstream.CanWrite; var writeflag = compressionStream.CanWrite;
lzstream?.Dispose(); //Dispose need to be done before fstream closed. compressionStream?.Dispose(); //Dispose need to be done before fstream closed.
lzstream = null; compressionStream = null;
if (writeflag) //Reset UseCompressSave after writing to file if (writeflag) //Reset UseCompressSave after writing to file
UseCompressSave = false; UseCompressSave = false;
} }
[HarmonyTranspiler] [HarmonyTranspiler]
[HarmonyPatch(typeof(GameSave), "LoadCurrentGame")] [HarmonyPatch(typeof(GameSave), "LoadCurrentGame")]
[HarmonyPatch(typeof(GameSave), "LoadGameDesc")] [HarmonyPatch(typeof(GameSave), "LoadGameDesc")]
@@ -145,11 +193,11 @@ class PatchSave
[HarmonyPatch(typeof(GameSave), "ReadModes")] [HarmonyPatch(typeof(GameSave), "ReadModes")]
static IEnumerable<CodeInstruction> LoadCurrentGame_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator iLGenerator) static IEnumerable<CodeInstruction> LoadCurrentGame_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator iLGenerator)
{ {
/* using (BinaryReader binaryReader = new BinaryReader(fileStream)) => Create lzstream and replace binaryReader. /* using (BinaryReader binaryReader = new BinaryReader(fileStream)) => Create decompressionStream and replace binaryReader.
* set PerformanceMonitor.BeginStream to lzstream. * set PerformanceMonitor.BeginStream to decompressionStream.
* if (fileStream.Length != binaryReader.ReadInt64()) => Replace binaryReader.ReadInt64() to pass file length check. * 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 * fileStream.Seek((long)num2, SeekOrigin.Current); => Use decompressionStream.Read to seek forward
* binaryReader.Dispose(); => Dispose lzstream before fileStream close. * binaryReader.Dispose(); => Dispose decompressionStream before fileStream close.
*/ */
try try
{ {
@@ -165,7 +213,7 @@ class PatchSave
.Set(OpCodes.Call, AccessTools.Method(typeof(PatchSave), "FileLengthRead")) .Set(OpCodes.Call, AccessTools.Method(typeof(PatchSave), "FileLengthRead"))
.MatchForward(false, new CodeMatch(OpCodes.Callvirt, AccessTools.Method(typeof(System.IDisposable), "Dispose"))) .MatchForward(false, new CodeMatch(OpCodes.Callvirt, AccessTools.Method(typeof(System.IDisposable), "Dispose")))
.Advance(1) .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"))); .MatchBack(false, new CodeMatch(OpCodes.Callvirt, AccessTools.Method(typeof(System.IO.Stream), "Seek")));
if (matcher.IsValid) if (matcher.IsValid)
matcher.Set(OpCodes.Call, AccessTools.Method(typeof(PatchSave), "ReadSeek")); matcher.Set(OpCodes.Call, AccessTools.Method(typeof(PatchSave), "ReadSeek"));
@@ -191,45 +239,53 @@ class PatchSave
static void ReadHeader_Postfix(ref GameSaveHeader header) static void ReadHeader_Postfix(ref GameSaveHeader header)
{ {
if (header != null) if (header != null)
((CompressionGameSaveHeader)header).IsCompressed = IsCompressedSave; ((CompressionGameSaveHeader)header).CompressionType = CompressedType;
} }
public static BinaryReader CreateBinaryReader(FileStream fileStream) public static BinaryReader CreateBinaryReader(FileStream fileStream)
{ {
if ((IsCompressedSave = SaveUtil.IsCompressedSave(fileStream))) switch (CompressedType = SaveUtil.SaveGetCompressType(fileStream))
{ {
UseCompressSave = true; case CompressionType.LZ4:
lzstream = new LZ4DecompressionStream(fileStream); case CompressionType.Zstd:
return new PeekableReader((LZ4DecompressionStream)lzstream); UseCompressSave = true;
} compressionStream = new DecompressionStream(CompressedType == CompressionType.LZ4 ? lz4Wrapper : zstdWrapper, fileStream);
else return new PeekableReader((DecompressionStream)compressionStream);
{ case CompressionType.None:
UseCompressSave = false; UseCompressSave = false;
fileStream.Seek(0, SeekOrigin.Begin); fileStream.Seek(0, SeekOrigin.Begin);
return new BinaryReader(fileStream); return new BinaryReader(fileStream);
default:
throw new ArgumentOutOfRangeException();
} }
} }
public static long FileLengthRead(BinaryReader binaryReader) public static long FileLengthRead(BinaryReader binaryReader)
{ {
if (UseCompressSave) switch (CompressedType)
{ {
binaryReader.ReadInt64(); case CompressionType.LZ4:
return lzstream.Length; 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) public static long ReadSeek(FileStream fileStream, long offset, SeekOrigin origin)
{ {
if (UseCompressSave) switch (CompressedType)
{ {
while (offset > 0) case CompressionType.LZ4:
offset -= lzstream.Read(compressBuffer.outBuffer, 0, (int)offset); case CompressionType.Zstd:
return lzstream.Position; 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);
} }
} }

View File

@@ -1,20 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net472</TargetFramework>
<AssemblyName>CompressSave</AssemblyName> <AssemblyName>CompressSave</AssemblyName>
<BepInExPluginGuid>org.soardev.compresssave</BepInExPluginGuid> <BepInExPluginGuid>org.soardev.compresssave</BepInExPluginGuid>
<Description>DSP MOD - CompressSave</Description> <Description>DSP MOD - CompressSave</Description>
<Version>1.1.14</Version> <Version>1.2.0</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<TargetFramework>net472</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BepInEx.Core" Version="5.*" /> <PackageReference Include="BepInEx.Core" Version="5.*" />
<PackageReference Include="BepInEx.PluginInfoProps" Version="1.*" /> <PackageReference Include="BepInEx.PluginInfoProps" Version="1.*" />
<PackageReference Include="DysonSphereProgram.GameLibs" Version="*-r.*" /> <PackageReference Include="DysonSphereProgram.GameLibs" Version="*-r.*" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" /> <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.*" />
<PackageReference Include="UnityEngine.Modules" Version="2018.4.12" IncludeAssets="compile" /> <PackageReference Include="UnityEngine.Modules" Version="2018.4.12" IncludeAssets="compile" />
</ItemGroup> </ItemGroup>
@@ -22,11 +22,7 @@
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2" PrivateAssets="all" /> <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2" PrivateAssets="all" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="LZ4Wrap" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="del /F /Q package\$(ProjectName)-$(Version).zip&#xA;zip -9 -j package/$(ProjectName)-$(Version).zip $(TargetPath) $(TargetDir)/System.Runtime.CompilerServices.Unsafe.dll package/LZ4.dll package/icon.png package/manifest.json README.md" /> <Exec Command="del /F /Q package\$(ProjectName)-$(Version).zip&#xA;zip -9 -j package/$(ProjectName)-$(Version).zip $(TargetPath) $(TargetDir)/System.Runtime.CompilerServices.Unsafe.dll package/lz4wrap.dll package/zstdwrap.dll package/icon.png package/manifest.json README.md" />
</Target> </Target>
</Project> </Project>

View File

@@ -1,7 +1,13 @@
namespace CompressSave namespace CompressSave;
public enum CompressionType
{ {
internal class CompressionGameSaveHeader: GameSaveHeader None = 0,
{ LZ4 = 1,
public bool IsCompressed = false; Zstd = 2,
} }
internal class CompressionGameSaveHeader: GameSaveHeader
{
public CompressionType CompressionType = CompressionType.None;
} }

View File

@@ -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<string, List<DynDllMapping>>
{
{
"LZ4.dll", new List<DynDllMapping>
{
"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;
}

View File

@@ -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();
}
}

View File

@@ -1,16 +1,16 @@
cmake_minimum_required(VERSION 3.2) 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/lz4.c lz4/lz4.h
lz4/lz4frame.c lz4/lz4frame.h lz4/lz4frame_static.h lz4/lz4frame.c lz4/lz4frame.h lz4/lz4frame_static.h
lz4/lz4hc.c lz4/lz4hc.h lz4/lz4hc.c lz4/lz4hc.h
lz4/xxhash.c lz4/xxhash.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_compile_definitions(lz4wrap PRIVATE LZ4WRAP_EXPORTS)
target_include_directories(LZ4 PRIVATE lz4) target_include_directories(lz4wrap PRIVATE lz4)
if(WIN32) if(WIN32)
set_target_properties(LZ4 PROPERTIES PREFIX "") set_target_properties(lz4wrap PROPERTIES PREFIX "")
endif() endif()

View File

@@ -14,7 +14,7 @@ static CContext* CreateCompressContext()
return (CContext*)malloc(sizeof(CContext)); return (CContext*)malloc(sizeof(CContext));
} }
void FreeCompressContext(CContext* ctx) void __stdcall CompressContextFree(CContext* ctx)
{ {
if (ctx != NULL) 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, { 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, /* compression level; 0 == default */
0, /* autoflush */ 0, /* autoflush */
0, /* favor decompression speed */ 0, /* favor decompression speed */
{ 0, 0, 0 }, /* reserved, must be set to 0 */ { 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; 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(); CContext* ctx = CreateCompressContext();
if (dictBuffer) if (dict)
ctx->dict = LZ4F_createCDict(dictBuffer, dictSize); ctx->dict = LZ4F_createCDict(dict, dictSize);
else else
ctx->dict = NULL; ctx->dict = NULL;
if (ctx == NULL) return NULL; if (ctx == NULL) return NULL;
@@ -51,7 +51,7 @@ CContext* CreateCompressContextFromBuffer(void* dictBuffer, size_t dictSize) {
if (LZ4F_isError(ctxCreation)) if (LZ4F_isError(ctxCreation))
{ {
LZ4F_freeCompressionContext(innerctx); LZ4F_freeCompressionContext(innerctx);
FreeCompressContext(ctx); CompressContextFree(ctx);
return NULL; return NULL;
} }
ctx->cctx = innerctx; ctx->cctx = innerctx;
@@ -59,14 +59,14 @@ CContext* CreateCompressContextFromBuffer(void* dictBuffer, size_t dictSize) {
return ctx; 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 (ctx == NULL) return -1;
if (outCapacity < LZ4F_HEADER_SIZE_MAX || outCapacity < LZ4F_compressBound(0, &kPrefs)) return LZ4F_ERROR_dstMaxSize_tooSmall; if (outCapacity < LZ4F_HEADER_SIZE_MAX || outCapacity < LZ4F_compressBound(0, &kPrefs)) return LZ4F_ERROR_dstMaxSize_tooSmall;
kPrefs.compressionLevel = compressionLevel;
/* write frame header */ /* write frame header */
size_t const headerSize = ctx->dict == NULL size_t const headerSize = ctx->dict == NULL
? LZ4F_compressBegin(ctx->cctx, outBuff, outCapacity, &kPrefs) ? 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 size_t result = ctx->dict == NULL
? LZ4F_compressUpdate(ctx->cctx, dstBuffer, dstCapacity, srcBuffer, srcSize, 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; 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); size_t writeSize = LZ4F_compressEnd(ctx->cctx, dstBuffer, dstCapacity, NULL);
return writeSize; return writeSize;
@@ -112,39 +112,43 @@ static size_t get_block_size(const LZ4F_frameInfo_t* info) {
} }
} }
//return: input bytes expects for next call //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; DContext* dctx = (DContext*)malloc(sizeof(DContext));
LZ4F_dctx** _pdctx = &dctx; size_t const dctxStatus = LZ4F_createDecompressionContext(&dctx->dctx, LZ4F_VERSION);
size_t const dctxStatus = LZ4F_createDecompressionContext(_pdctx, LZ4F_VERSION);
Check(!LZ4F_isError(dctxStatus), dctxStatus); Check(!LZ4F_isError(dctxStatus), dctxStatus);
Check(*inBufferSize >= LZ4F_HEADER_SIZE_MAX, LZ4F_ERROR_dstMaxSize_tooSmall); Check(*inBufferSize >= LZ4F_HEADER_SIZE_MAX, LZ4F_ERROR_dstMaxSize_tooSmall);
LZ4F_frameInfo_t info; 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); Check(!LZ4F_isError(fires), fires);
*blockSize = get_block_size(&info); *blockSize = get_block_size(&info);
*pdctx = *_pdctx; dctx->dict = dict;
dctx->dictSize = dictSize;
*pdctx = dctx;
return fires; 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 size_t ret = dctx->dict == NULL
? LZ4F_decompress(dctx, outBuffer, outBufferSize, inBuffer, inBufferSize, NULL) ? LZ4F_decompress(dctx->dctx, outBuffer, outBufferSize, inBuffer, inBufferSize, NULL)
: LZ4F_decompress_usingDict(dctx, outBuffer, outBufferSize, inBuffer, inBufferSize, dict, dictSize,NULL); : LZ4F_decompress_usingDict(dctx->dctx, outBuffer, outBufferSize, inBuffer, inBufferSize, dctx->dict, dctx->dictSize, NULL);
Check(!LZ4F_isError(ret), ret); Check(!LZ4F_isError(ret), ret);
return ret; return ret;
} }
size_t DecompressEnd(LZ4F_dctx* ctx) size_t __stdcall DecompressEnd(DContext* dctx)
{ {
return LZ4F_freeDecompressionContext(ctx); if (!dctx) return 0;
size_t r = LZ4F_freeDecompressionContext(dctx->dctx);
free(dctx);
return r;
} }

View File

@@ -20,20 +20,27 @@ typedef struct
LZ4F_CDict* dict; LZ4F_CDict* dict;
} CContext; } 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);

View File

@@ -26,16 +26,24 @@ class PatchUILoadGame
if (code.opcode == OpCodes.Ldstr && code.OperandIs("#,##0")) if (code.opcode == OpCodes.Ldstr && code.OperandIs("#,##0"))
{ {
var iffalse = generator.DefineLabel(); var iffalse = generator.DefineLabel();
var ifzstd = generator.DefineLabel();
var callLabel = generator.DefineLabel(); var callLabel = generator.DefineLabel();
code.WithLabels(iffalse) code.WithLabels(iffalse)
.operand = "(N)#,##0"; .operand = "(N)#,##0";
codes[i + 1].WithLabels(callLabel); codes[i + 1].WithLabels(callLabel);
var IL = new List<CodeInstruction> { var IL = new List<CodeInstruction> {
new CodeInstruction(OpCodes.Ldloc_0), new CodeInstruction(OpCodes.Ldloc_0),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(CompressionGameSaveHeader),"IsCompressed")), new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(CompressionGameSaveHeader),"CompressionType")),
new CodeInstruction(OpCodes.Brfalse_S,iffalse), 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.Ldstr,"(LZ4)#,##0"),
new CodeInstruction(OpCodes.Br_S,callLabel), new CodeInstruction(OpCodes.Br_S,callLabel),
new CodeInstruction(OpCodes.Ldstr,"(ZSTD)#,##0").WithLabels(ifzstd),
new CodeInstruction(OpCodes.Br_S,callLabel),
}; };
codes.InsertRange(i, IL); codes.InsertRange(i, IL);
break; break;
@@ -48,7 +56,7 @@ class PatchUILoadGame
[HarmonyPatch(typeof(UILoadGameWindow), "OnSelectedChange"), HarmonyPostfix] [HarmonyPatch(typeof(UILoadGameWindow), "OnSelectedChange"), HarmonyPostfix]
static void OnSelectedChange(UILoadGameWindow __instance, UIButton ___loadButton, Text ___prop3Text) 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) if (decompressButton)
decompressButton.button.interactable = compressedSave; decompressButton.button.interactable = compressedSave;
} }

View File

@@ -7,6 +7,14 @@
## Updates ## 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 ### 1.1.14
* Fix Sandbox info on Save/Load Panel. * Fix Sandbox info on Save/Load Panel.
* Fix DLL version info. * Fix DLL version info.

View File

@@ -1,11 +1,11 @@
using System; using System;
using System.IO; using System.IO;
using BepInEx.Logging; using BepInEx.Logging;
using CompressSave.LZ4Wrap; using CompressSave.Wrapper;
namespace CompressSave; namespace CompressSave;
class SaveUtil public static class SaveUtil
{ {
public static ManualLogSource logger; public static ManualLogSource logger;
@@ -14,10 +14,10 @@ class SaveUtil
{ {
Major = 0, Major = 0,
Minor = 9, Minor = 9,
Release = 26, Release = 27,
}; };
public static string UnzipToFile(LZ4DecompressionStream lzStream, string fullPath) public static string UnzipToFile(DecompressionStream lzStream, string fullPath)
{ {
lzStream.ResetStream(); lzStream.ResetStream();
string dir = Path.GetDirectoryName(fullPath); string dir = Path.GetDirectoryName(fullPath);
@@ -51,13 +51,22 @@ class SaveUtil
{ {
using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read)) using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read))
{ {
if (!IsCompressedSave(fileStream)) return false; var compressType = SaveGetCompressType(fileStream);
using (var lzstream = new LZ4DecompressionStream(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) catch (Exception e)
{ {
@@ -65,28 +74,34 @@ class SaveUtil
return false; 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()) 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 try
{ {
using (FileStream fileStream = new FileStream(GetFullSavePath(saveName), FileMode.Open)) using (FileStream fileStream = new FileStream(GetFullSavePath(saveName), FileMode.Open))
return IsCompressedSave(fileStream); return SaveGetCompressType(fileStream);
} }
catch (Exception e) catch (Exception e)
{ {
logger.LogWarning(e); logger.LogWarning(e);
return false; return CompressionType.None;
} }
} }

View File

@@ -1,7 +1,7 @@
using System; using System;
using System.IO; using System.IO;
namespace CompressSave.LZ4Wrap; namespace CompressSave.Wrapper;
class BlackHoleStream : Stream class BlackHoleStream : Stream
{ {

View File

@@ -3,7 +3,7 @@ using System.IO;
using System.Text; using System.Text;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace CompressSave.LZ4Wrap; namespace CompressSave.Wrapper;
public unsafe class BufferWriter : BinaryWriter public unsafe class BufferWriter : BinaryWriter
{ {
@@ -40,13 +40,13 @@ public unsafe class BufferWriter : BinaryWriter
byte* startPos; byte* startPos;
private Stream _baseStream; private Stream _baseStream;
public BufferWriter(DoubleBuffer doubleBuffer, LZ4CompressionStream outStream) public BufferWriter(DoubleBuffer doubleBuffer, CompressionStream outStream)
: this(doubleBuffer, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true), 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; _baseStream = outStream;
swapedBytes = 0; swapedBytes = 0;

View File

@@ -1,4 +1,4 @@
namespace CompressSave.LZ4Wrap; namespace CompressSave.Wrapper;
//public class BufferedFileStream : FileStream //public class BufferedFileStream : FileStream
//{ //{

View File

@@ -2,10 +2,12 @@ using System;
using System.IO; using System.IO;
using System.Threading; 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 const int MB = 1024 * 1024;
public override bool CanRead => false; public override bool CanRead => false;
@@ -40,7 +42,7 @@ public class LZ4CompressionStream : Stream, IDisposable
{ {
if (errorCode < 0) if (errorCode < 0)
{ {
LZ4API.FreeCompressContext(cctx); wrapper.CompressContextFree(cctx);
cctx = IntPtr.Zero; cctx = IntPtr.Zero;
lastError = errorCode; lastError = errorCode;
throw new Exception(errorCode.ToString()); throw new Exception(errorCode.ToString());
@@ -54,13 +56,13 @@ public class LZ4CompressionStream : Stream, IDisposable
public byte[] outBuffer; public byte[] outBuffer;
} }
public static CompressBuffer CreateBuffer(int ExBufferSize = 4 * MB) public static CompressBuffer CreateBuffer(WrapperDefines wrapper, int ExBufferSize = 4 * MB)
{ {
try try
{ {
return new CompressBuffer return new CompressBuffer
{ {
outBuffer = new byte[LZ4API.CalCompressOutBufferSize(ExBufferSize) + 1], outBuffer = new byte[wrapper.CompressBufferBound(ExBufferSize) + 1],
readBuffer = new byte[ExBufferSize], readBuffer = new byte[ExBufferSize],
writeBuffer = new byte[ExBufferSize], writeBuffer = new byte[ExBufferSize],
}; };
@@ -75,11 +77,12 @@ public class LZ4CompressionStream : Stream, IDisposable
public BufferWriter BufferWriter => bfferWriter; public BufferWriter BufferWriter => bfferWriter;
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; this.outStream = outStream;
InitBuffer(compressBuffer.readBuffer, compressBuffer.writeBuffer, compressBuffer.outBuffer); 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); HandleError(writeSize);
outStream.Write(outBuffer, 0, (int)writeSize); outStream.Write(outBuffer, 0, (int)writeSize);
this.useMultiThread = useMultiThread; this.useMultiThread = useMultiThread;
@@ -89,13 +92,12 @@ public class LZ4CompressionStream : Stream, IDisposable
compressThread = new Thread(() => CompressAsync()); compressThread = new Thread(() => CompressAsync());
compressThread.Start(); compressThread.Start();
} }
} }
void InitBuffer(byte[] readBuffer, byte[] writeBuffer, byte[] outBuffer) void InitBuffer(byte[] readBuffer, byte[] writeBuffer, byte[] outBuffer)
{ {
doubleBuffer = new DoubleBuffer(readBuffer ?? new byte[4 * MB], writeBuffer ?? new byte[4 * MB], Compress); 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); bfferWriter = new BufferWriter(doubleBuffer,this);
} }
@@ -130,7 +132,7 @@ public class LZ4CompressionStream : Stream, IDisposable
long writeSize = 0; long writeSize = 0;
try 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); HandleError(writeSize);
} }
finally finally
@@ -188,7 +190,7 @@ public class LZ4CompressionStream : Stream, IDisposable
protected void FreeContext() protected void FreeContext()
{ {
LZ4API.FreeCompressContext(cctx); wrapper.CompressContextFree(cctx);
cctx = IntPtr.Zero; cctx = IntPtr.Zero;
} }
@@ -207,7 +209,7 @@ public class LZ4CompressionStream : Stream, IDisposable
stopWorker = true; stopWorker = true;
doubleBuffer.SwapBuffer(); doubleBuffer.SwapBuffer();
long size = LZ4API.CompressEnd(cctx, outBuffer, outBuffer.Length); long size = wrapper.CompressEnd(cctx, outBuffer, outBuffer.Length);
//Debug.Log($"End"); //Debug.Log($"End");
outStream.Write(outBuffer, 0, (int)size); outStream.Write(outBuffer, 0, (int)size);
base.Close(); base.Close();

View File

@@ -1,10 +1,12 @@
using System; using System;
using System.IO; 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 CanRead => true;
public override bool CanSeek => false; public override bool CanSeek => false;
@@ -39,13 +41,14 @@ class LZ4DecompressionStream : Stream
readonly long startPos = 0; readonly long startPos = 0;
long readPos = 0; //sum of readlen 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; this.inStream = inStream;
startPos = inStream.Position; startPos = inStream.Position;
srcBuffer = new ByteSpan(new byte[extraBufferSize]); srcBuffer = new ByteSpan(new byte[extraBufferSize]);
int len = Fill(); 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; srcBuffer.Position += len;
if (expect < 0) throw new Exception(expect.ToString()); if (expect < 0) throw new Exception(expect.ToString());
dcmpBuffer = new ByteSpan(new byte[blockSize]); dcmpBuffer = new ByteSpan(new byte[blockSize]);
@@ -57,7 +60,7 @@ class LZ4DecompressionStream : Stream
decompressFinish = false; decompressFinish = false;
srcBuffer.Clear(); srcBuffer.Clear();
dcmpBuffer.Clear(); dcmpBuffer.Clear();
LZ4API.ResetDecompresssCTX(dctx); wrapper.DecompressContextReset(dctx);
readPos = 0; readPos = 0;
} }
@@ -85,7 +88,7 @@ class LZ4DecompressionStream : Stream
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
LZ4API.DecompressEnd(dctx); wrapper.DecompressEnd(dctx);
dctx = IntPtr.Zero; dctx = IntPtr.Zero;
base.Dispose(disposing); base.Dispose(disposing);
} }
@@ -98,7 +101,7 @@ class LZ4DecompressionStream : Stream
var buffSize = Fill(); var buffSize = Fill();
if (buffSize <= 0) return readlen; 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) throw new Exception(rt.Expect.ToString());
if (rt.Expect == 0) decompressFinish = true; if (rt.Expect == 0) decompressFinish = true;
@@ -117,7 +120,7 @@ class LZ4DecompressionStream : Stream
var buffSize = Fill(); var buffSize = Fill();
if (buffSize <= 0) return -1; 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) throw new Exception(rt.Expect.ToString());
if (rt.Expect == 0) decompressFinish = true; if (rt.Expect == 0) decompressFinish = true;

View File

@@ -1,7 +1,7 @@
using System; using System;
using System.Threading; using System.Threading;
namespace CompressSave.LZ4Wrap; namespace CompressSave.Wrapper;
public class ByteSpan public class ByteSpan
{ {

View File

@@ -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<string, List<DynDllMapping>>
{
{
"lz4wrap.dll", new List<DynDllMapping>
{
"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_;
}

View File

@@ -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();
}
}

View File

@@ -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,
};
}
}

View File

@@ -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<string, List<DynDllMapping>>
{
{
"zstdwrap.dll", new List<DynDllMapping>
{
"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_;
}

View File

@@ -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()

View File

@@ -0,0 +1,19 @@
// dllmain.cpp : Defines the entry point for the DLL application.
#include <windows.h>
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;
}

View File

@@ -0,0 +1,84 @@
#include "zstdwrap.h"
#include <windows.h>
#include <limits.h>
#include <assert.h>
#include <stdio.h>
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);
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include <zstd.h>
#include <stddef.h>
#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);

Binary file not shown.

Binary file not shown.

View File

@@ -1,6 +1,6 @@
{ {
"name": "CompressSave", "name": "CompressSave",
"version_number": "1.1.14", "version_number": "1.2.0",
"website_url": "https://github.com/soarqin/DSP_Mods/tree/master/CompressSave", "website_url": "https://github.com/soarqin/DSP_Mods/tree/master/CompressSave",
"description": "Compress game saves to reduce space use and boost save speed / 压缩游戏存档以降低空间使用并提升保存速度", "description": "Compress game saves to reduce space use and boost save speed / 压缩游戏存档以降低空间使用并提升保存速度",
"dependencies": ["xiaoye97-BepInEx-5.4.17"] "dependencies": ["xiaoye97-BepInEx-5.4.17"]

Binary file not shown.

View File

@@ -5,8 +5,10 @@
## Usage ## Usage
* More options on universe creation * 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)
* 注意: 大量恒星会导致宇宙视图出现性能问题

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit; using System.Reflection.Emit;
using BepInEx; using BepInEx;
using BepInEx.Configuration; using BepInEx.Configuration;
@@ -15,19 +16,23 @@ public class UniverseGenTweaks : BaseUnityPlugin
BepInEx.Logging.Logger.CreateLogSource(PluginInfo.PLUGIN_NAME); BepInEx.Logging.Logger.CreateLogSource(PluginInfo.PLUGIN_NAME);
private bool _cfgEnabled = true; private bool _cfgEnabled = true;
private static int _maxStarCount = 64; private static int _maxStarCount = 128;
private static float _minDist = 2f; 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 float _flatten = 0.18f;
private static Text _minDistTitle; private static Text _minDistTitle;
private static Text _maxDistTitle; private static Text _minStepTitle;
private static Text _maxStepTitle;
private static Text _flattenTitle; private static Text _flattenTitle;
private static Slider _minDistSlider; private static Slider _minDistSlider;
private static Slider _maxDistSlider; private static Slider _minStepSlider;
private static Slider _maxStepSlider;
private static Slider _flattenSlider; private static Slider _flattenSlider;
private static Text _minDistText; private static Text _minDistText;
private static Text _maxDistText; private static Text _minStepText;
private static Text _maxStepText;
private static Text _flattenText; private static Text _flattenText;
private void Awake() private void Awake()
@@ -62,18 +67,24 @@ public class UniverseGenTweaks : BaseUnityPlugin
__instance.starCountSlider.maxValue = _maxStarCount; __instance.starCountSlider.maxValue = _maxStarCount;
createSliderWithText(__instance.starCountSlider, out _minDistTitle, out _minDistSlider, out _minDistText); 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); createSliderWithText(__instance.starCountSlider, out _flattenTitle, out _flattenSlider, out _flattenText);
_minDistTitle.name = "min-dist"; _minDistTitle.name = "min-dist";
_minDistSlider.minValue = 10f; _minDistSlider.minValue = 10f;
_minDistSlider.maxValue = _maxDist * 10f; _minDistSlider.maxValue = 50f;
_minDistSlider.value = _minDist * 10f; _minDistSlider.value = _minDist * 10f;
_maxDistTitle.name = "max-dist"; _minStepTitle.name = "min-step";
_maxDistSlider.minValue = _minDist * 10f; _minStepSlider.minValue = 10f;
_maxDistSlider.maxValue = 100f; _minStepSlider.maxValue = _maxStep * 10f - 1f;
_maxDistSlider.value = _maxDist * 10f; _minStepSlider.value = _minStep * 10f;
_maxStepTitle.name = "max-step";
_maxStepSlider.minValue = _minStep * 10f + 1f;
_maxStepSlider.maxValue = 100f;
_maxStepSlider.value = _maxStep * 10f;
_flattenTitle.name = "flatten"; _flattenTitle.name = "flatten";
_flattenSlider.minValue = 1f; _flattenSlider.minValue = 1f;
@@ -81,12 +92,13 @@ public class UniverseGenTweaks : BaseUnityPlugin
_flattenSlider.value = _flatten * 50f; _flattenSlider.value = _flatten * 50f;
transformDeltaY(_minDistTitle.transform, -0.3573f); transformDeltaY(_minDistTitle.transform, -0.3573f);
transformDeltaY(_maxDistTitle.transform, -0.3573f * 2); transformDeltaY(_minStepTitle.transform, -0.3573f * 2);
transformDeltaY(_flattenTitle.transform, -0.3573f * 3); transformDeltaY(_maxStepTitle.transform, -0.3573f * 3);
transformDeltaY(__instance.resourceMultiplierSlider.transform.parent, -0.3573f * 3); transformDeltaY(_flattenTitle.transform, -0.3573f * 4);
transformDeltaY(__instance.sandboxToggle.transform.parent, -0.3573f * 3); transformDeltaY(__instance.resourceMultiplierSlider.transform.parent, -0.3573f * 4);
transformDeltaY(__instance.propertyMultiplierText.transform, -0.3573f * 3); transformDeltaY(__instance.sandboxToggle.transform.parent, -0.3573f * 4);
transformDeltaY(__instance.addrText.transform.parent, -0.3573f * 3); transformDeltaY(__instance.propertyMultiplierText.transform, -0.3573f * 4);
transformDeltaY(__instance.addrText.transform.parent, -0.3573f * 4);
} }
[HarmonyPrefix] [HarmonyPrefix]
@@ -95,18 +107,21 @@ public class UniverseGenTweaks : BaseUnityPlugin
{ {
if (Localization.language == Language.zhCN) if (Localization.language == Language.zhCN)
{ {
_minDistTitle.text = "恒星/步进距离"; _minDistTitle.text = "恒星最小距离";
_maxDistTitle.text = "步进最距离"; _minStepTitle.text = "步进最距离";
_maxStepTitle.text = "步进最大距离";
_flattenTitle.text = "扁平度"; _flattenTitle.text = "扁平度";
} }
else else
{ {
_minDistTitle.text = "Star/Step Distance"; _minDistTitle.text = "Star Distance Min";
_maxDistTitle.text = "Step Distance Max"; _minStepTitle.text = "Step Distance Min";
_maxStepTitle.text = "Step Distance Max";
_flattenTitle.text = "Flatten"; _flattenTitle.text = "Flatten";
} }
_minDistText.text = _minDist.ToString(); _minDistText.text = _minDist.ToString();
_maxDistText.text = _maxDist.ToString(); _minStepText.text = _minStep.ToString();
_maxStepText.text = _maxStep.ToString();
_flattenText.text = _flatten.ToString(); _flattenText.text = _flatten.ToString();
} }
@@ -117,34 +132,60 @@ public class UniverseGenTweaks : BaseUnityPlugin
_minDistSlider.onValueChanged.RemoveAllListeners(); _minDistSlider.onValueChanged.RemoveAllListeners();
_minDistSlider.onValueChanged.AddListener(val => _minDistSlider.onValueChanged.AddListener(val =>
{ {
var newVal = val / 10f; var newVal = Mathf.Round(val) / 10f;
if (newVal.Equals(_minDist)) return; if (newVal.Equals(_minDist)) return;
_minDist = newVal; _minDist = newVal;
_maxDistSlider.minValue = newVal * 10f;
_minDistText.text = _minDist.ToString(); _minDistText.text = _minDist.ToString();
__instance.SetStarmapGalaxy(); __instance.SetStarmapGalaxy();
}); });
_maxDistSlider.onValueChanged.RemoveAllListeners(); _minStepSlider.onValueChanged.RemoveAllListeners();
_maxDistSlider.onValueChanged.AddListener(val => _minStepSlider.onValueChanged.AddListener(val =>
{ {
var newVal = val / 10f; var newVal = Mathf.Round(val) / 10f;
if (newVal.Equals(_maxDist)) return; if (newVal.Equals(_minStep)) return;
_maxDist = newVal; _minStep = newVal;
_minDistSlider.maxValue = newVal * 10f; _maxStepSlider.minValue = newVal * 10f;
_maxDistText.text = _maxDist.ToString(); _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(); __instance.SetStarmapGalaxy();
}); });
_flattenSlider.onValueChanged.RemoveAllListeners(); _flattenSlider.onValueChanged.RemoveAllListeners();
_flattenSlider.onValueChanged.AddListener(val => _flattenSlider.onValueChanged.AddListener(val =>
{ {
var newVal = val / 50f; var newVal = Mathf.Round(val) / 50f;
if (newVal.Equals(_maxDist)) return; if (newVal.Equals(_flatten)) return;
_flatten = newVal; _flatten = newVal;
_flattenText.text = _flatten.ToString(); _flattenText.text = _flatten.ToString();
__instance.SetStarmapGalaxy(); __instance.SetStarmapGalaxy();
}); });
} }
[HarmonyTranspiler]
[HarmonyPatch(typeof(UIGalaxySelect), "OnStarCountSliderValueChange")]
static IEnumerable<CodeInstruction> PatchStarCountOnValueChange(IEnumerable<CodeInstruction> 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] [HarmonyPrefix]
[HarmonyPatch(typeof(GalaxyData), MethodType.Constructor)] [HarmonyPatch(typeof(GalaxyData), MethodType.Constructor)]
static bool PatchGalaxyData(GalaxyData __instance) static bool PatchGalaxyData(GalaxyData __instance)
@@ -168,28 +209,31 @@ public class UniverseGenTweaks : BaseUnityPlugin
yield return pop; yield return pop;
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), "_minDist")); yield return new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(UniverseGenTweaks), "_minStep"));
yield return new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(UniverseGenTweaks), "_maxDist")); yield return new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(UniverseGenTweaks), "_maxStep"));
yield return new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(UniverseGenTweaks), "_flatten")); yield return new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(UniverseGenTweaks), "_flatten"));
} }
yield return instruction; yield return instruction;
} }
} }
/* Patch `rand() * (maxStepLen - minStepLen) + minDist` to `rand() * (maxStepLen - minStepLen) + minStepLen`,
this should be a bugged line in original game code. */
[HarmonyTranspiler] [HarmonyTranspiler]
[HarmonyPatch(typeof(UIGalaxySelect), "OnStarCountSliderValueChange")] [HarmonyPatch(typeof(UniverseGen), "RandomPoses")]
static IEnumerable<CodeInstruction> PatchStarCountOnValueChange(IEnumerable<CodeInstruction> instructions) static IEnumerable<CodeInstruction> PatchUniverGenRandomPoses(IEnumerable<CodeInstruction> instructions)
{ {
var lastIsMul = false;
foreach (var instruction in instructions) 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); lastIsMul = false;
} yield return new CodeInstruction(OpCodes.Ldarg_3);
else continue;
{
yield return instruction;
} }
lastIsMul = instruction.opcode == OpCodes.Mul;
yield return instruction;
} }
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View File

@@ -2,7 +2,7 @@
"name": "UniverseGenTweaks", "name": "UniverseGenTweaks",
"version_number": "1.0.0", "version_number": "1.0.0",
"website_url": "https://github.com/soarqin/DSP_Mods/tree/master/UniverseGenTweaks", "website_url": "https://github.com/soarqin/DSP_Mods/tree/master/UniverseGenTweaks",
"description": "#### Universe Generator Tweaks / 宇宙生成参数调节", "description": "Universe Generator Tweaks / 宇宙生成参数调节",
"dependencies": [ "dependencies": [
"xiaoye97-BepInEx-5.4.17" "xiaoye97-BepInEx-5.4.17"
] ]