using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Threading;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Microsoft.Win32.SafeHandles;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: InternalsVisibleTo("Checks")]
[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("PoolTrim")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.2.0.0")]
[assembly: AssemblyInformationalVersion("1.2.0+d5fff59ee5d554d821333d37ebb211da86ea7b13")]
[assembly: AssemblyProduct("PoolTrim")]
[assembly: AssemblyTitle("PoolTrim")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.2.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
internal sealed class IsUnmanagedAttribute : Attribute
{
}
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace PoolTrim
{
[BepInPlugin("fyyy.dsp.pooltrim", "PoolTrim", "1.2.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class PoolTrimPlugin : BaseUnityPlugin
{
public const string GUID = "fyyy.dsp.pooltrim";
public const string NAME = "PoolTrim";
public const string VERSION = "1.2.0";
internal static ManualLogSource Log;
internal static bool ColdGeometry;
private void Awake()
{
//IL_0041: Unknown result type (might be due to invalid IL or missing references)
//IL_004b: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
ColdGeometry = ((BaseUnityPlugin)this).Config.Bind<bool>("Performance", "ColdGeometry", false, "Losslessly compress belt-path geometry in RAM. Reduces memory use but increases load time. Requires game restart.\n在内存中无损压缩传送带路径几何,降低内存占用,但增加读档耗时。修改后需重启游戏。").Value;
if (ColdGeometry)
{
PathGeometry.Initialize();
GeometryPatches.Install(new Harmony("fyyy.dsp.pooltrim"));
}
Harmony.CreateAndPatchAll(typeof(Patches), "fyyy.dsp.pooltrim");
Log.LogInfo((object)("PoolTrim ready. ColdGeometry:" + ColdGeometry));
}
}
internal static class Patches
{
private static readonly FieldRef<CargoPath, int> BufferLength = AccessTools.FieldRefAccess<CargoPath, int>("bufferLength");
private static readonly FieldRef<PlanetFactory, int> EntityCapacity = AccessTools.FieldRefAccess<PlanetFactory, int>("entityCapacity");
private static readonly FieldRef<PlanetFactory, int[]> EntityRecycle = AccessTools.FieldRefAccess<PlanetFactory, int[]>("entityRecycle");
private static readonly FieldRef<PlanetFactory, int> EntityRecycleCursor = AccessTools.FieldRefAccess<PlanetFactory, int>("entityRecycleCursor");
private static bool _errorLogged;
private static volatile bool _loading;
internal static long TrimmedPoints;
private static long _trimmedEntities;
private static int _trimmedFactories;
[HarmonyPrefix]
[HarmonyPatch(typeof(GameSave), "LoadCurrentGame")]
private static void LoadBegin()
{
TrimmedPoints = (_trimmedEntities = 0L);
PathGeometry.ResetTotals();
_trimmedFactories = 0;
_errorLogged = false;
_loading = true;
}
[HarmonyPostfix]
[HarmonyPriority(0)]
[HarmonyPatch(typeof(CargoPath), "Import")]
private static void TrimAfterImport(CargoPath __instance)
{
try
{
int num = Math.Max(1, BufferLength.Invoke(__instance));
if (__instance.buffer != null && __instance.buffer.Length > num)
{
int num2 = __instance.buffer.Length - num;
__instance.SetCapacity(num);
TrimmedPoints += num2;
}
if (_loading && PoolTrimPlugin.ColdGeometry)
{
PathGeometry.Store(__instance);
}
}
catch (Exception e)
{
Warn(e);
}
}
[HarmonyPostfix]
[HarmonyPriority(0)]
[HarmonyPatch(typeof(PlanetFactory), "Import")]
private static void TrimFactoryAfterImport(PlanetFactory __instance)
{
if (!_loading)
{
return;
}
try
{
int num = TrimEntities(__instance);
if (num != 0)
{
_trimmedEntities += num;
_trimmedFactories++;
}
}
catch (Exception e)
{
Warn(e);
}
}
internal static int TrimEntities(PlanetFactory factory)
{
int num = EntityCapacity.Invoke(factory);
int entityCursor = factory.entityCursor;
long num2 = entityCursor + Math.Max(1024L, ((long)entityCursor + 7L) / 8);
if ((num - num2) * 8 < num)
{
return 0;
}
int num3 = checked((int)num2);
int num4 = EntityRecycleCursor.Invoke(factory);
int[] array = EntityRecycle.Invoke(factory);
if (entityCursor >= 1 && entityCursor <= num && num4 >= 0 && num4 < entityCursor)
{
EntityData[] entityPool = factory.entityPool;
if (entityPool != null && entityPool.Length == num)
{
AnimData[] entityAnimPool = factory.entityAnimPool;
if (entityAnimPool != null && entityAnimPool.Length == num)
{
SignData[] entitySignPool = factory.entitySignPool;
if (entitySignPool != null && entitySignPool.Length == num && factory.entityConnPool?.LongLength == (long)num * 16L)
{
Mutex[] entityMutexs = factory.entityMutexs;
if (entityMutexs != null && entityMutexs.Length == num)
{
int[][] entityNeeds = factory.entityNeeds;
if (entityNeeds != null && entityNeeds.Length == num && array != null && array.Length == num)
{
EntityData[] entityPool2 = CopyPrefix(factory.entityPool, num3, entityCursor);
AnimData[] entityAnimPool2 = CopyPrefix(factory.entityAnimPool, num3, entityCursor);
SignData[] entitySignPool2 = CopyPrefix(factory.entitySignPool, num3, entityCursor);
int[] entityConnPool = checked(CopyPrefix(factory.entityConnPool, num3 * 16, entityCursor * 16));
Mutex[] entityMutexs2 = CopyPrefix(factory.entityMutexs, num3, entityCursor);
int[][] entityNeeds2 = CopyPrefix(factory.entityNeeds, num3, entityCursor);
int[] array2 = CopyPrefix(array, num3, num4);
factory.entityPool = entityPool2;
factory.entityAnimPool = entityAnimPool2;
factory.entitySignPool = entitySignPool2;
factory.entityConnPool = entityConnPool;
factory.entityMutexs = entityMutexs2;
factory.entityNeeds = entityNeeds2;
EntityRecycle.Invoke(factory) = array2;
EntityCapacity.Invoke(factory) = num3;
return num - num3;
}
}
}
}
}
}
throw new InvalidOperationException("Unexpected entity pool layout; factory " + factory.planetId + " left unchanged.");
}
private static T[] CopyPrefix<T>(T[] source, int length, int count)
{
T[] array = new T[length];
Array.Copy(source, array, count);
return array;
}
[HarmonyFinalizer]
[HarmonyPatch(typeof(GameSave), "LoadCurrentGame")]
private static void ReportAfterLoad(bool __result, Exception __exception)
{
_loading = false;
if (__result && __exception == null)
{
if (TrimmedPoints > 0)
{
PoolTrimPlugin.Log.LogInfo((object)$"PoolTrim: trimmed {TrimmedPoints} cargo path points, ~{(double)TrimmedPoints * 29.0 / 1000000000.0:F2} GB saved");
}
if (_trimmedEntities > 0)
{
PoolTrimPlugin.Log.LogInfo((object)$"PoolTrim: trimmed {_trimmedEntities} entity slots across {_trimmedFactories} factories (including companion arrays)");
}
if (PoolTrimPlugin.ColdGeometry)
{
PoolTrimPlugin.Log.LogInfo((object)PathGeometry.Totals());
}
}
}
private static void Warn(Exception e)
{
if (!_errorLogged)
{
_errorLogged = true;
PoolTrimPlugin.Log.LogWarning((object)("PoolTrim failed (further errors muted): " + e));
}
}
}
internal static class PathGeometry
{
private sealed class Packed
{
internal int Count;
internal byte[] Positions;
internal byte[] Rotations;
}
private sealed class Workspace
{
internal readonly CodecHandle Encoder = new CodecHandle(decode: false);
internal readonly CodecHandle Decoder = new CodecHandle(decode: true);
private readonly byte[] _input = new byte[65536];
private readonly byte[] _output = new byte[65536];
internal byte[] Input(int length)
{
if (length > _input.Length)
{
return new byte[length];
}
return _input;
}
internal byte[] Output(int length)
{
if (length > _output.Length)
{
return new byte[length];
}
return _output;
}
}
private sealed class CodecHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private readonly bool _decode;
internal CodecHandle(bool decode)
: base(ownsHandle: true)
{
_decode = decode;
if (!(decode ? CreateDecompressor(4u, IntPtr.Zero, out var intPtr) : CreateCompressor(4u, IntPtr.Zero, out intPtr)))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
SetHandle(intPtr);
}
protected override bool ReleaseHandle()
{
if (!_decode)
{
return CloseCompressor(handle);
}
return CloseDecompressor(handle);
}
}
private static readonly ConditionalWeakTable<CargoPath, Packed> Cold = new ConditionalWeakTable<CargoPath, Packed>();
private const int BlockPoints = 1024;
[ThreadStatic]
private static Workspace _workspace;
private static long _paths;
private static long _rawBytes;
private static long _packedBytes;
private static long _encodeTicks;
internal static void Initialize()
{
WorkspaceForThread();
}
private static Workspace WorkspaceForThread()
{
return _workspace ?? (_workspace = new Workspace());
}
internal static void ResetTotals()
{
_paths = (_rawBytes = (_packedBytes = (_encodeTicks = 0L)));
}
internal static string Totals()
{
return $"PoolTrim: cold-stored {_paths} paths during import; geometry payload {(double)_rawBytes / 1000000000.0:F3} -> {(double)_packedBytes / 1000000000.0:F3} GB; encode {(double)_encodeTicks / (double)Stopwatch.Frequency:F3} s (before on-demand restores, excludes object overhead)";
}
internal static void Store(CargoPath path)
{
int pathLength = path.pathLength;
if (pathLength >= 16 && path.pointPos != null)
{
if (pathLength > path.pointPos.Length || pathLength > path.pointRot.Length || pathLength > path.buffer.Length)
{
throw new InvalidOperationException("Unexpected CargoPath geometry length.");
}
long timestamp = Stopwatch.GetTimestamp();
Packed packed = new Packed
{
Count = pathLength,
Positions = PathGeometry.Encode<Vector3>(path.pointPos, pathLength),
Rotations = PathGeometry.Encode<Quaternion>(path.pointRot, pathLength)
};
Interlocked.Add(ref _encodeTicks, Stopwatch.GetTimestamp() - timestamp);
long num = (long)pathLength * 28L;
long num2 = packed.Positions.LongLength + packed.Rotations.LongLength;
if (num - num2 > 128)
{
Cold.Add(path, packed);
path.pointPos = null;
path.pointRot = null;
Interlocked.Increment(ref _paths);
Interlocked.Add(ref _rawBytes, num);
Interlocked.Add(ref _packedBytes, num2);
}
}
}
internal static void Restore(CargoPath path)
{
if (Volatile.Read(in path.pointPos) != null)
{
return;
}
lock (path)
{
if (Cold.TryGetValue(path, out var value))
{
Vector3[] value2 = PathGeometry.Decode<Vector3>(value.Positions, value.Count, path.buffer.Length);
Quaternion[] pointRot = PathGeometry.Decode<Quaternion>(value.Rotations, value.Count, path.buffer.Length);
path.pointRot = pointRot;
Volatile.Write(ref path.pointPos, value2);
Cold.Remove(path);
}
}
}
internal static Vector3[] Positions(CargoPath path)
{
Restore(path);
return path.pointPos;
}
internal static Quaternion[] Rotations(CargoPath path)
{
Restore(path);
return path.pointRot;
}
internal static Vector3[] ExportPositions(CargoPath path)
{
lock (path)
{
Packed value;
return Cold.TryGetValue(path, out value) ? PathGeometry.Decode<Vector3>(value.Positions, value.Count, value.Count) : path.pointPos;
}
}
internal static Quaternion[] ExportRotations(CargoPath path)
{
lock (path)
{
Packed value;
return Cold.TryGetValue(path, out value) ? PathGeometry.Decode<Quaternion>(value.Rotations, value.Count, value.Count) : path.pointRot;
}
}
internal static void Discard(CargoPath path)
{
if (Cold.Remove(path))
{
path.pointPos = Array.Empty<Vector3>();
path.pointRot = Array.Empty<Quaternion>();
}
}
internal unsafe static byte[] Encode<T>(T[] values, int count) where T : unmanaged
{
if (count < 0 || count > values.Length)
{
throw new ArgumentOutOfRangeException("count");
}
if (count == 0)
{
return Array.Empty<byte>();
}
int num = sizeof(T) / 4;
int num2 = checked(count * sizeof(T));
Workspace workspace = WorkspaceForThread();
byte[] array = workspace.Input(num2);
fixed (T* ptr = values)
{
uint* ptr2 = (uint*)ptr;
for (int i = 0; i < count; i += 1024)
{
int num3 = Math.Min(1024, count - i) * num;
int num4 = i * num;
int num5 = i * sizeof(T);
for (int j = 0; j < num3; j++)
{
int num6 = num4 + j;
uint num7 = ptr2[num6] - ((num6 >= num) ? ptr2[num6 - num] : 0);
array[num5 + j] = (byte)num7;
array[num5 + num3 + j] = (byte)(num7 >> 8);
array[num5 + num3 * 2 + j] = (byte)(num7 >> 16);
array[num5 + num3 * 3 + j] = (byte)(num7 >> 24);
}
}
}
byte[] array2 = workspace.Output(num2);
UIntPtr written;
while (true)
{
fixed (byte* input = array)
{
fixed (byte* output = array2)
{
if (Compress(workspace.Encoder, input, (UIntPtr)(uint)num2, output, (UIntPtr)(uint)array2.Length, out written))
{
break;
}
}
}
int lastWin32Error = Marshal.GetLastWin32Error();
if (lastWin32Error != 122)
{
throw new Win32Exception(lastWin32Error);
}
array2 = new byte[checked((int)written.ToUInt64())];
}
byte[] array3 = new byte[checked((int)written.ToUInt64())];
Buffer.BlockCopy(array2, 0, array3, 0, array3.Length);
return array3;
}
internal unsafe static T[] Decode<T>(byte[] bytes, int count, int capacity) where T : unmanaged
{
if (count < 0 || count > capacity)
{
throw new InvalidDataException("Invalid cold path geometry capacity.");
}
T[] array = new T[capacity];
if (count == 0)
{
if (bytes.Length != 0)
{
throw new InvalidDataException("Unexpected empty path geometry.");
}
return array;
}
int num = sizeof(T) / 4;
int num2 = checked(count * sizeof(T));
Workspace workspace = WorkspaceForThread();
byte[] array2 = workspace.Input(num2);
fixed (byte* input = bytes)
{
fixed (byte* output = array2)
{
if (!Decompress(workspace.Decoder, input, (UIntPtr)(uint)bytes.Length, output, (UIntPtr)(uint)num2, out var written))
{
throw new InvalidDataException("Could not decode cold path geometry.", new Win32Exception(Marshal.GetLastWin32Error()));
}
if (written.ToUInt64() != (ulong)num2)
{
throw new InvalidDataException("Unexpected cold path geometry length.");
}
}
}
fixed (T* ptr = array)
{
uint* ptr2 = (uint*)ptr;
for (int i = 0; i < count; i += 1024)
{
int num3 = Math.Min(1024, count - i) * num;
int num4 = i * num;
int num5 = i * sizeof(T);
for (int j = 0; j < num3; j++)
{
uint num6 = (uint)(array2[num5 + j] | (array2[num5 + num3 + j] << 8) | (array2[num5 + num3 * 2 + j] << 16) | (array2[num5 + num3 * 3 + j] << 24));
int num7 = num4 + j;
ptr2[num7] = num6 + ((num7 >= num) ? ptr2[num7 - num] : 0);
}
}
}
return array;
}
[DllImport("cabinet.dll", SetLastError = true)]
private static extern bool CreateCompressor(uint algorithm, IntPtr allocator, out IntPtr handle);
[DllImport("cabinet.dll", SetLastError = true)]
private static extern bool CreateDecompressor(uint algorithm, IntPtr allocator, out IntPtr handle);
[DllImport("cabinet.dll", SetLastError = true)]
private static extern bool CloseCompressor(IntPtr handle);
[DllImport("cabinet.dll", SetLastError = true)]
private static extern bool CloseDecompressor(IntPtr handle);
[DllImport("cabinet.dll", SetLastError = true)]
private unsafe static extern bool Compress(CodecHandle handle, byte* input, UIntPtr length, byte* output, UIntPtr capacity, out UIntPtr written);
[DllImport("cabinet.dll", SetLastError = true)]
private unsafe static extern bool Decompress(CodecHandle handle, byte* input, UIntPtr length, byte* output, UIntPtr capacity, out UIntPtr written);
}
internal static class GeometryPatches
{
internal static readonly FieldInfo PositionField = AccessTools.Field(typeof(CargoPath), "pointPos");
internal static readonly FieldInfo RotationField = AccessTools.Field(typeof(CargoPath), "pointRot");
internal static void Install(Harmony harmony)
{
foreach (MethodBase item in Readers())
{
harmony.Patch(item, (HarmonyMethod)null, (HarmonyMethod)null, Patch("ReadGeometry"), (HarmonyMethod)null, (HarmonyMethod)null);
}
InstallPaths(harmony);
}
internal static void InstallPaths(Harmony harmony)
{
string[] array = new string[7] { "SetCapacity", "AddBuffer", "TruncBuffer", "PathClose", "PathOpen", "Clear", "PresentCargos" };
foreach (string text in array)
{
harmony.Patch((MethodBase)AccessTools.Method(typeof(CargoPath), text, (Type[])null, (Type[])null), Patch("Restore"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
}
harmony.Patch((MethodBase)AccessTools.Method(typeof(CargoPath), "PathConcat", (Type[])null, (Type[])null), Patch("RestorePair"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
harmony.Patch((MethodBase)AccessTools.Method(typeof(CargoPath), "PathCopy", (Type[])null, (Type[])null), Patch("RestorePair"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
harmony.Patch((MethodBase)AccessTools.Method(typeof(CargoPath), "Free", (Type[])null, (Type[])null), Patch("Discard"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
harmony.Patch((MethodBase)AccessTools.Method(typeof(CargoPath), "Export", (Type[])null, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, Patch("ReadExport"), (HarmonyMethod)null, (HarmonyMethod)null);
MethodInfo methodInfo = AccessTools.Method("LossyCompression.CargoPathCompress:Encode", (Type[])null, (Type[])null);
if (methodInfo != null)
{
harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, Patch("ReadExport"), (HarmonyMethod)null, (HarmonyMethod)null);
}
}
private static HarmonyMethod Patch(string name)
{
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
//IL_0012: Expected O, but got Unknown
return new HarmonyMethod(typeof(GeometryPatches), name, (Type[])null);
}
internal static IEnumerable<MethodBase> Readers()
{
yield return AccessTools.Method(typeof(CargoPath), "DrawDebugLine", (Type[])null, (Type[])null);
string[] array = new string[4] { "GeneratePathGeometry", "AlterBeltRenderer", "AlterPathRenderer", "SetBeltState" };
foreach (string text in array)
{
yield return AccessTools.Method(typeof(CargoTraffic), text, (Type[])null, (Type[])null);
}
yield return AccessTools.Method(typeof(PlanetFactory), "OnBeltBuilt", (Type[])null, (Type[])null);
yield return AccessTools.Method(typeof(BuildTool_Click), "MatchInserter", (Type[])null, (Type[])null);
yield return AccessTools.Method(typeof(BuildTool_BlueprintPaste), "MatchInserter", (Type[])null, (Type[])null);
yield return AccessTools.Method(typeof(BuildTool_Addon), "SnapToBelt", (Type[])null, (Type[])null);
yield return AccessTools.Method(typeof(BuildTool_Inserter), "DeterminePreviews", (Type[])null, (Type[])null);
yield return AccessTools.Method(typeof(UIBeltWindow), "_OnUpdate", (Type[])null, (Type[])null);
}
private static void Restore(CargoPath __instance)
{
PathGeometry.Restore(__instance);
}
private static void RestorePair(CargoPath __instance, CargoPath __0)
{
PathGeometry.Restore(__instance);
PathGeometry.Restore(__0);
}
private static void Discard(CargoPath __instance)
{
PathGeometry.Discard(__instance);
}
internal static IEnumerable<CodeInstruction> ReadGeometry(IEnumerable<CodeInstruction> instructions)
{
return ReplaceReads(instructions, export: false);
}
private static IEnumerable<CodeInstruction> ReadExport(IEnumerable<CodeInstruction> instructions)
{
return ReplaceReads(instructions, export: true);
}
private static IEnumerable<CodeInstruction> ReplaceReads(IEnumerable<CodeInstruction> instructions, bool export)
{
int replaced = 0;
foreach (CodeInstruction instruction in instructions)
{
string text = (CodeInstructionExtensions.LoadsField(instruction, PositionField, false) ? "Positions" : (CodeInstructionExtensions.LoadsField(instruction, RotationField, false) ? "Rotations" : null));
if (text != null)
{
instruction.opcode = OpCodes.Call;
instruction.operand = AccessTools.Method(typeof(PathGeometry), (export ? "Export" : "") + text, (Type[])null, (Type[])null);
replaced++;
}
yield return instruction;
}
if (replaced == 0)
{
throw new InvalidOperationException("CargoPath geometry reader changed; refusing to enable cold storage.");
}
}
}
}