Decompiled source of PoolTrim v1.2.0

PoolTrim.dll

Decompiled 2 weeks ago
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.");
			}
		}
	}
}