1
0
mirror of https://github.com/soarqin/DSP_Mods.git synced 2025-12-09 02:53:29 +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.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);
}
}

View File

@@ -1,32 +1,28 @@
<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>
<ItemGroup Condition="'$(TargetFramework.TrimEnd(`0123456789`))' == 'net'">
<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&#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>
</Project>

View File

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

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

View File

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

View File

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

View File

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

View File

@@ -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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
using System;
using System.Threading;
namespace CompressSave.LZ4Wrap;
namespace CompressSave.Wrapper;
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",
"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"]

Binary file not shown.

View File

@@ -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)
* 注意: 大量恒星会导致宇宙视图出现性能问题

View File

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

View File

@@ -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"
]