mirror of
https://github.com/soarqin/DSP_Mods.git
synced 2025-12-08 22:13:30 +08:00
WIP: CompressSave 1.2.0
This commit is contained in:
@@ -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<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)
|
||||
{
|
||||
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<CodeInstruction> SaveCurrentGame_Transpiler(IEnumerable<CodeInstruction> 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<CodeInstruction> LoadCurrentGame_Transpiler(IEnumerable<CodeInstruction> 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);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<AssemblyName>CompressSave</AssemblyName>
|
||||
<BepInExPluginGuid>org.soardev.compresssave</BepInExPluginGuid>
|
||||
<Description>DSP MOD - CompressSave</Description>
|
||||
<Version>1.1.14</Version>
|
||||
<Version>1.2.0</Version>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BepInEx.Core" Version="5.*" />
|
||||
<PackageReference Include="BepInEx.PluginInfoProps" Version="1.*" />
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -22,11 +22,7 @@
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="LZ4Wrap" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="del /F /Q package\$(ProjectName)-$(Version).zip
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
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>
|
||||
</Project>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
if (!dctx) return 0;
|
||||
size_t r = LZ4F_freeDecompressionContext(dctx->dctx);
|
||||
free(dctx);
|
||||
return r;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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<CodeInstruction> {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace CompressSave.LZ4Wrap;
|
||||
namespace CompressSave.Wrapper;
|
||||
|
||||
class BlackHoleStream : Stream
|
||||
{
|
||||
@@ -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;
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace CompressSave.LZ4Wrap;
|
||||
namespace CompressSave.Wrapper;
|
||||
|
||||
//public class BufferedFileStream : FileStream
|
||||
//{
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace CompressSave.LZ4Wrap;
|
||||
namespace CompressSave.Wrapper;
|
||||
|
||||
public class ByteSpan
|
||||
{
|
||||
68
CompressSave/Wrapper/LZ4Wrapper.cs
Normal file
68
CompressSave/Wrapper/LZ4Wrapper.cs
Normal 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_;
|
||||
}
|
||||
17
CompressSave/Wrapper/PeekableReader.cs
Normal file
17
CompressSave/Wrapper/PeekableReader.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
64
CompressSave/Wrapper/WrapperDefines.cs
Normal file
64
CompressSave/Wrapper/WrapperDefines.cs
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
68
CompressSave/Wrapper/ZstdWrapper.cs
Normal file
68
CompressSave/Wrapper/ZstdWrapper.cs
Normal 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_;
|
||||
}
|
||||
12
CompressSave/ZstdWrapC/CMakeLists.txt
Normal file
12
CompressSave/ZstdWrapC/CMakeLists.txt
Normal 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()
|
||||
19
CompressSave/ZstdWrapC/dllmain.c
Normal file
19
CompressSave/ZstdWrapC/dllmain.c
Normal 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;
|
||||
}
|
||||
|
||||
84
CompressSave/ZstdWrapC/zstdwrap.c
Normal file
84
CompressSave/ZstdWrapC/zstdwrap.c
Normal 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);
|
||||
}
|
||||
34
CompressSave/ZstdWrapC/zstdwrap.h
Normal file
34
CompressSave/ZstdWrapC/zstdwrap.h
Normal 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.
BIN
CompressSave/package/lz4wrap.dll
Normal file
BIN
CompressSave/package/lz4wrap.dll
Normal file
Binary file not shown.
@@ -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"]
|
||||
|
||||
BIN
CompressSave/package/zstdwrap.dll
Normal file
BIN
CompressSave/package/zstdwrap.dll
Normal file
Binary file not shown.
@@ -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)
|
||||
* 注意: 大量恒星会导致宇宙视图出现性能问题
|
||||
@@ -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<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]
|
||||
[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<CodeInstruction> PatchStarCountOnValueChange(IEnumerable<CodeInstruction> instructions)
|
||||
[HarmonyPatch(typeof(UniverseGen), "RandomPoses")]
|
||||
static IEnumerable<CodeInstruction> PatchUniverGenRandomPoses(IEnumerable<CodeInstruction> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 88 KiB |
@@ -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"
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user