using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using Il2CppSystem.IO;
using Microsoft.CodeAnalysis;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("SoDLoadFix")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0")]
[assembly: AssemblyProduct("SoDLoadFix")]
[assembly: AssemblyTitle("SoDLoadFix")]
[assembly: AssemblyVersion("0.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
internal sealed class NullableAttribute : Attribute
{
public readonly byte[] NullableFlags;
public NullableAttribute(byte P_0)
{
NullableFlags = new byte[1] { P_0 };
}
public NullableAttribute(byte[] P_0)
{
NullableFlags = P_0;
}
}
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
internal sealed class NullableContextAttribute : Attribute
{
public readonly byte Flag;
public NullableContextAttribute(byte P_0)
{
Flag = P_0;
}
}
[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 SoDLoadFix
{
internal static class Breakers
{
internal readonly struct Swap
{
public readonly List<Interactable>? Back;
public Swap(List<Interactable> back)
{
Back = back;
}
}
[HarmonyPatch(typeof(NewAddress), "GetBreakerSecurity")]
internal static class AddressSecurity
{
private static void Prefix(NewAddress __instance, out Swap __state)
{
__state = Before(__instance.breakerSecurity == null, __instance.breakerSecurityID);
}
private static void Finalizer(Swap __state)
{
After(__state);
}
}
[HarmonyPatch(typeof(NewAddress), "GetBreakerLights")]
internal static class AddressLights
{
private static void Prefix(NewAddress __instance, out Swap __state)
{
__state = Before(__instance.breakerLights == null, __instance.breakerLightsID);
}
private static void Finalizer(Swap __state)
{
After(__state);
}
}
[HarmonyPatch(typeof(NewAddress), "GetBreakerDoors")]
internal static class AddressDoors
{
private static void Prefix(NewAddress __instance, out Swap __state)
{
__state = Before(__instance.breakerDoors == null, __instance.breakerDoorsID);
}
private static void Finalizer(Swap __state)
{
After(__state);
}
}
[HarmonyPatch(typeof(NewFloor), "GetBreakerSecurity")]
internal static class FloorSecurity
{
private static void Prefix(NewFloor __instance, out Swap __state)
{
__state = Before(__instance.breakerSecurity == null, __instance.breakerSecurityID);
}
private static void Finalizer(Swap __state)
{
After(__state);
}
}
[HarmonyPatch(typeof(NewFloor), "GetBreakerLights")]
internal static class FloorLights
{
private static void Prefix(NewFloor __instance, out Swap __state)
{
__state = Before(__instance.breakerLights == null, __instance.breakerLightsID);
}
private static void Finalizer(Swap __state)
{
After(__state);
}
}
[HarmonyPatch(typeof(NewFloor), "GetBreakerDoors")]
internal static class FloorDoors
{
private static void Prefix(NewFloor __instance, out Swap __state)
{
__state = Before(__instance.breakerDoors == null, __instance.breakerDoorsID);
}
private static void Finalizer(Swap __state)
{
After(__state);
}
}
private static readonly HashSet<int> ids = new HashSet<int>();
private static readonly List<Interactable> nothing = new List<Interactable>();
private static List<Interactable>? real;
private static IntPtr listSeen;
private static IntPtr lastItem;
private static int read;
private static long skipped;
private static long searched;
private static long rereads;
private static long audited;
private static long wrong;
internal static bool Audit;
private const int ListItems = 16;
private const int ListSize = 24;
private const int ArrayFirst = 32;
private const int ObjectId = 16;
private static bool layoutChecked;
private static bool layoutOk;
private static IntPtr ItemAt(IntPtr array, int i)
{
return Marshal.ReadIntPtr(array, 32 + i * IntPtr.Size);
}
private static bool Layout(List<Interactable> list)
{
if (layoutChecked)
{
return layoutOk;
}
int count = list.Count;
if (count == 0)
{
return false;
}
layoutChecked = true;
int num = Marshal.ReadInt32(((Il2CppObjectBase)list).Pointer, 24);
IntPtr array = Marshal.ReadIntPtr(((Il2CppObjectBase)list).Pointer, 16);
Interactable val = list[count - 1];
layoutOk = num == count && val != null && ItemAt(array, count - 1) == ((Il2CppObjectBase)val).Pointer && Marshal.ReadInt32(((Il2CppObjectBase)val).Pointer, 16) == val.id;
if (!layoutOk)
{
Plugin.L.LogWarning((object)"[LOADFIX] this game version lays out its lists differently, breaker searches left as they are");
}
return layoutOk;
}
private static void Catch(List<Interactable> list)
{
int num = Marshal.ReadInt32(((Il2CppObjectBase)list).Pointer, 24);
IntPtr array = Marshal.ReadIntPtr(((Il2CppObjectBase)list).Pointer, 16);
if (((Il2CppObjectBase)list).Pointer != listSeen || num < read || (read > 0 && ItemAt(array, read - 1) != lastItem))
{
ids.Clear();
read = 0;
listSeen = ((Il2CppObjectBase)list).Pointer;
rereads++;
}
while (read < num)
{
IntPtr intPtr = ItemAt(array, read);
if (intPtr != IntPtr.Zero)
{
ids.Add(Marshal.ReadInt32(intPtr, 16));
}
read++;
}
lastItem = ((num > 0) ? ItemAt(array, num - 1) : IntPtr.Zero);
}
internal static Swap Before(bool unset, int id)
{
if (!unset || Environment.CurrentManagedThreadId != Plugin.MainThread)
{
return default(Swap);
}
CityData instance = CityData.Instance;
List<Interactable> val = ((instance != null) ? instance.interactableDirectory : null);
if (val == null)
{
return default(Swap);
}
bool flag = ((Il2CppObjectBase)val).Pointer == ((Il2CppObjectBase)nothing).Pointer;
List<Interactable> val2 = (real = (List<Interactable>?)(flag ? ((object)real) : ((object)val)));
if (!Layout(val2))
{
return default(Swap);
}
Catch(val2);
if (ids.Contains(id))
{
searched++;
if (!flag)
{
return default(Swap);
}
instance.interactableDirectory = val2;
return new Swap(val);
}
skipped++;
if (Audit && skipped % 200 == 0L)
{
Check(val2, id);
}
if (flag)
{
return default(Swap);
}
instance.interactableDirectory = nothing;
return new Swap(val);
}
internal static void After(Swap s)
{
if (s.Back != null)
{
CityData.Instance.interactableDirectory = s.Back;
}
}
private static void Check(List<Interactable> list, int id)
{
audited++;
int num = Marshal.ReadInt32(((Il2CppObjectBase)list).Pointer, 24);
IntPtr array = Marshal.ReadIntPtr(((Il2CppObjectBase)list).Pointer, 16);
for (int i = 0; i < num; i++)
{
IntPtr intPtr = ItemAt(array, i);
if (intPtr != IntPtr.Zero && Marshal.ReadInt32(intPtr, 16) == id)
{
wrong++;
break;
}
}
}
internal static string Summary()
{
return $"breaker searches: {skipped} skipped, {searched} searched, list read {rereads} times, {ids.Count} ids; checked {audited} skips, {wrong} wrong";
}
internal static void Apply(Harmony h)
{
h.PatchAll(typeof(AddressSecurity));
h.PatchAll(typeof(AddressLights));
h.PatchAll(typeof(AddressDoors));
h.PatchAll(typeof(FloorSecurity));
h.PatchAll(typeof(FloorLights));
h.PatchAll(typeof(FloorDoors));
}
}
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInPlugin("sm3gm.sod.loadfix", "Faster Loading", "1.1.0")]
public sealed class Plugin : BasePlugin
{
public const string VER = "1.1.0";
internal static ManualLogSource L;
internal static int MainThread;
public override void Load()
{
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_0020: Expected O, but got Unknown
//IL_0166: Unknown result type (might be due to invalid IL or missing references)
//IL_016c: Expected O, but got Unknown
L = ((BasePlugin)this).Log;
MainThread = Environment.CurrentManagedThreadId;
Harmony val = new Harmony("sm3gm.sod.loadfix");
if (((BasePlugin)this).Config.Bind<bool>("Fixes", "FastFactLookup", true, "Find existing facts with an index instead of searching the whole list for every new fact.").Value)
{
val.PatchAll(typeof(FactLookup));
}
val.PatchAll(typeof(LoadReport));
if (((BasePlugin)this).Config.Bind<bool>("Fixes", "RememberRoomData", true, "Keep the room visibility data the game works out on the first load of a city, and reuse it on later loads of that same city file.").Value)
{
val.PatchAll(typeof(RoomCache));
}
TreeCheck.Enabled = ((BasePlugin)this).Config.Bind<bool>("Debug", "CheckRoomData", false, "After a load, check the fact list, write a fingerprint of every room's visibility data and every camera's view so that two loads can be compared, and log how far the game's background work on rooms has come. Adds about 5 seconds to a load.").Value;
if (((BasePlugin)this).Config.Bind<bool>("Fixes", "DensityModOnlyWhenNeeded", true, "When DensityMod is installed, let its per-frame city tile search run only while a new city's density is being decided.").Value)
{
DensitySkip.Apply(val);
}
if (((BasePlugin)this).Config.Bind<bool>("Fixes", "FastBreakerSearch", true, "Skip the search through every object in the city when an address or floor looks for a breaker that does not exist yet.").Value)
{
Breakers.Apply(val);
}
Breakers.Audit = TreeCheck.Enabled;
RepeatChecks.Verify = TreeCheck.Enabled;
if (((BasePlugin)this).Config.Bind<bool>("Fixes", "RememberRepeatChecks", true, "Right after you arrive in a city, answer the questions the game repeats many times from memory, so the game settles sooner. The memory is cleared after every pass.").Value)
{
RepeatChecks.Apply(val);
}
if (TreeCheck.Enabled)
{
val.PatchAll(typeof(BackgroundWatch));
}
ManualLogSource l = L;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(22, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Faster Loading ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>("1.1.0");
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" loaded");
}
l.LogInfo(val2);
}
}
internal static class DensitySkip
{
internal static long Skipped;
internal static long Allowed;
internal static void Apply(Harmony h)
{
//IL_0097: Unknown result type (might be due to invalid IL or missing references)
//IL_00a5: Expected O, but got Unknown
try
{
Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetName().Name == "DensityMod");
if (assembly == null)
{
Plugin.L.LogInfo((object)"[LOADFIX] DensityMod not installed, nothing to do");
return;
}
MethodInfo methodInfo = assembly.GetType("DensityMod.DensityModHook+Citydata_PopulationMultiplier")?.GetMethod("Postfix", BindingFlags.Static | BindingFlags.Public);
if (methodInfo == null)
{
Plugin.L.LogWarning((object)"[LOADFIX] DensityMod's hook not found, left alone");
return;
}
h.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(DensitySkip), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
Plugin.L.LogInfo((object)"[LOADFIX] DensityMod's search now runs only while a new city's density is being decided");
}
catch (Exception ex)
{
Plugin.L.LogWarning((object)("[LOADFIX] DensityMod: " + ex.Message));
}
}
private static bool Prefix()
{
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_001e: Invalid comparison between Unknown and I4
try
{
CityConstructor instance = CityConstructor.Instance;
int num;
if ((Object)(object)instance != (Object)null && instance.generateNew)
{
num = (((int)instance.loadState <= 7) ? 1 : 0);
if (num != 0)
{
Allowed++;
goto IL_0045;
}
}
else
{
num = 0;
}
Skipped++;
goto IL_0045;
IL_0045:
return (byte)num != 0;
}
catch
{
Allowed++;
return true;
}
}
}
[HarmonyPatch(typeof(EvidenceCreator), "CreateFact")]
internal static class FactLookup
{
private readonly struct Key : IEquatable<Key>
{
private readonly int preset;
private readonly IntPtr from;
private readonly IntPtr to;
public Key(int preset, IntPtr from, IntPtr to)
{
this.preset = preset;
this.from = from;
this.to = to;
}
public bool Equals(Key o)
{
if (preset == o.preset && from == o.from)
{
return to == o.to;
}
return false;
}
public override bool Equals(object? o)
{
if (o is Key o2)
{
return Equals(o2);
}
return false;
}
public override int GetHashCode()
{
return (preset * 397) ^ from.GetHashCode() ^ (to.GetHashCode() * 31);
}
}
private sealed class PresetInfo
{
public int Id;
public bool Duplicates;
public bool Reverse;
public bool Valid;
}
private readonly struct State
{
public readonly List<Fact>? Real;
public readonly List<Fact>? Scratch;
public readonly int Placed;
public State(List<Fact> real, List<Fact> scratch, int placed)
{
Real = real;
Scratch = scratch;
Placed = placed;
}
}
private static readonly Dictionary<Key, IntPtr> index = new Dictionary<Key, IntPtr>();
private static readonly Dictionary<string, PresetInfo> byName = new Dictionary<string, PresetInfo>();
private static readonly Dictionary<string, int> idByPresetName = new Dictionary<string, int>();
private static readonly Dictionary<IntPtr, int> idByPointer = new Dictionary<IntPtr, int>();
private static readonly List<List<Fact>> pool = new List<List<Fact>>();
private static IntPtr indexedList;
private static int indexed;
private static int depth;
private static bool playedSinceIndex;
internal static long Calls;
internal static long Swapped;
internal static long Hits;
internal static long Ticks;
internal static long Indexed;
private static int PresetId(FactPreset p)
{
if (idByPointer.TryGetValue(((Il2CppObjectBase)p).Pointer, out var value))
{
return value;
}
string key = ((SoCustomComparison)p).presetName ?? "";
if (!idByPresetName.TryGetValue(key, out value))
{
value = (idByPresetName[key] = idByPresetName.Count + 1);
}
idByPointer[((Il2CppObjectBase)p).Pointer] = value;
return value;
}
private static PresetInfo Lookup(string name)
{
if (byName.TryGetValue(name, out PresetInfo value))
{
return value;
}
value = new PresetInfo();
Toolbox instance = Toolbox.Instance;
Dictionary<string, FactPreset> val = ((instance != null) ? instance.factPresetDictionary : null);
FactPreset val2 = default(FactPreset);
if (val != null && val.TryGetValue(name.ToLower(), ref val2) && (SoCustomComparison)(object)val2 != (SoCustomComparison)null)
{
value.Valid = true;
value.Id = PresetId(val2);
value.Duplicates = val2.allowDuplicates;
value.Reverse = val2.allowReverseDuplicates;
byName[name] = value;
}
return value;
}
private static void CatchUp(List<Fact> real)
{
if (playedSinceIndex || ((Il2CppObjectBase)real).Pointer != indexedList || real.Count < indexed)
{
index.Clear();
indexed = 0;
indexedList = ((Il2CppObjectBase)real).Pointer;
playedSinceIndex = false;
}
int count = real.Count;
while (indexed < count)
{
Fact val = real[indexed];
if (val != null)
{
FactPreset preset = val.preset;
List<Evidence> fromEvidence = val.fromEvidence;
List<Evidence> toEvidence = val.toEvidence;
if (!((SoCustomComparison)(object)preset == (SoCustomComparison)null) && fromEvidence != null && toEvidence != null && fromEvidence.Count != 0 && toEvidence.Count != 0)
{
Evidence val2 = fromEvidence[0];
Evidence val3 = toEvidence[0];
if (val2 != null && val3 != null)
{
index.TryAdd(new Key(PresetId(preset), ((Il2CppObjectBase)val2).Pointer, ((Il2CppObjectBase)val3).Pointer), ((Il2CppObjectBase)val).Pointer);
Indexed++;
}
}
}
indexed++;
}
}
private static void Prefix(string presetName, Evidence fromEvidenceSingular, Evidence toEvidenceSingular, out State __state)
{
//IL_0150: Unknown result type (might be due to invalid IL or missing references)
//IL_015a: Expected O, but got Unknown
//IL_0192: Unknown result type (might be due to invalid IL or missing references)
//IL_019c: Expected O, but got Unknown
__state = default(State);
long timestamp = Stopwatch.GetTimestamp();
Calls++;
try
{
if (Environment.CurrentManagedThreadId != Plugin.MainThread || string.IsNullOrEmpty(presetName))
{
return;
}
SessionData instance = SessionData.Instance;
if ((Object)(object)instance == (Object)null || instance.startedGame)
{
playedSinceIndex = true;
return;
}
GameplayController instance2 = GameplayController.Instance;
List<Fact> val = ((instance2 != null) ? instance2.factList : null);
if (val == null)
{
return;
}
PresetInfo presetInfo = Lookup(presetName);
if (!presetInfo.Valid || (presetInfo.Duplicates && presetInfo.Reverse))
{
return;
}
if (depth == 0)
{
CatchUp(val);
}
while (pool.Count <= depth)
{
pool.Add(new List<Fact>());
}
List<Fact> val2 = pool[depth];
val2.Clear();
IntPtr intPtr = ((fromEvidenceSingular != null) ? ((Il2CppObjectBase)fromEvidenceSingular).Pointer : IntPtr.Zero);
IntPtr intPtr2 = ((toEvidenceSingular != null) ? ((Il2CppObjectBase)toEvidenceSingular).Pointer : IntPtr.Zero);
if (intPtr != IntPtr.Zero && intPtr2 != IntPtr.Zero)
{
if (!presetInfo.Duplicates && index.TryGetValue(new Key(presetInfo.Id, intPtr, intPtr2), out var value))
{
val2.Add(new Fact(value));
Hits++;
}
if (!presetInfo.Reverse && index.TryGetValue(new Key(presetInfo.Id, intPtr2, intPtr), out var value2))
{
val2.Add(new Fact(value2));
Hits++;
}
}
__state = new State(val, val2, val2.Count);
instance2.factList = val2;
depth++;
Swapped++;
}
catch (Exception ex)
{
Plugin.L.LogWarning((object)("[LOADFIX] fact lookup: " + ex.Message));
}
finally
{
Ticks += Stopwatch.GetTimestamp() - timestamp;
}
}
private static void Finalizer(State __state)
{
if (__state.Real == null)
{
return;
}
long timestamp = Stopwatch.GetTimestamp();
depth--;
try
{
GameplayController.Instance.factList = __state.Real;
List<Fact> scratch = __state.Scratch;
for (int i = __state.Placed; i < scratch.Count; i++)
{
__state.Real.Add(scratch[i]);
}
scratch.Clear();
}
catch (Exception ex)
{
Plugin.L.LogError((object)("[LOADFIX] could not restore the fact list: " + ex));
}
finally
{
Ticks += Stopwatch.GetTimestamp() - timestamp;
}
}
internal static string Check()
{
long timestamp = Stopwatch.GetTimestamp();
GameplayController instance = GameplayController.Instance;
List<Fact> val = ((instance != null) ? instance.factList : null);
if (val == null)
{
return "fact check: no list";
}
HashSet<Key> hashSet = new HashSet<Key>();
int num = 0;
int num2 = 0;
int num3 = 0;
for (int i = 0; i < val.Count; i++)
{
Fact val2 = val[i];
if (val2 == null)
{
continue;
}
FactPreset preset = val2.preset;
List<Evidence> fromEvidence = val2.fromEvidence;
List<Evidence> toEvidence = val2.toEvidence;
if ((SoCustomComparison)(object)preset == (SoCustomComparison)null || fromEvidence == null || toEvidence == null || fromEvidence.Count == 0 || toEvidence.Count == 0)
{
continue;
}
Evidence val3 = fromEvidence[0];
Evidence val4 = toEvidence[0];
if (val3 != null && val4 != null)
{
int preset2 = PresetId(preset);
num++;
if (!preset.allowDuplicates && hashSet.Contains(new Key(preset2, ((Il2CppObjectBase)val3).Pointer, ((Il2CppObjectBase)val4).Pointer)))
{
num2++;
}
if (!preset.allowReverseDuplicates && ((Il2CppObjectBase)val3).Pointer != ((Il2CppObjectBase)val4).Pointer && hashSet.Contains(new Key(preset2, ((Il2CppObjectBase)val4).Pointer, ((Il2CppObjectBase)val3).Pointer)))
{
num3++;
}
hashSet.Add(new Key(preset2, ((Il2CppObjectBase)val3).Pointer, ((Il2CppObjectBase)val4).Pointer));
}
}
return $"fact check: {num} facts, {num2} on the list twice, {num3} twice in reverse, {(double)(Stopwatch.GetTimestamp() - timestamp) * 1000.0 / (double)Stopwatch.Frequency:0} ms";
}
internal static string Summary()
{
return $"{Calls} facts asked for, {Swapped} through the index, {Hits} duplicates found, {Indexed} facts indexed, {(double)Ticks * 1000.0 / (double)Stopwatch.Frequency:0} ms spent";
}
}
[HarmonyPatch(typeof(CityConstructor), "Finalized")]
internal static class RoomCache
{
private sealed class RoomRecord
{
public int Index;
public int Id;
public readonly List<(int index, int id, int[]?[] entries)> Others = new List<(int, int, int[][])>();
}
private const int Format = 1;
internal static string Status = "not used";
internal static bool Restored;
private static string? path;
private static string? key;
private static void Prefix()
{
//IL_010d: Unknown result type (might be due to invalid IL or missing references)
//IL_0112: Unknown result type (might be due to invalid IL or missing references)
Restored = false;
path = (key = null);
long timestamp = Stopwatch.GetTimestamp();
try
{
CityConstructor instance = CityConstructor.Instance;
if ((Object)(object)instance == (Object)null || instance.generateNew)
{
Status = "new city, nothing to read";
return;
}
RestartSafeController instance2 = RestartSafeController.Instance;
FileInfo val = ((instance2 != null) ? instance2.loadCityFileInfo : null);
if (val == null)
{
Status = "no city file";
return;
}
string text = string.Join(";", (from p in ((BaseChainloader<BasePlugin>)(object)IL2CPPChainloader.Instance).Plugins
where p.Key != "sm3gm.sod.loadfix" && !p.Key.StartsWith("sm3gm.dev.")
select p.Key + "=" + new FileInfo(p.Value.Location).Length).OrderBy<string, string>((string x) => x, StringComparer.Ordinal));
object[] obj = new object[5]
{
((FileSystemInfo)val).Name,
val.Length,
null,
null,
null
};
DateTime lastWriteTimeUtc = ((FileSystemInfo)val).LastWriteTimeUtc;
obj[2] = ((DateTime)(ref lastWriteTimeUtc)).Ticks;
obj[3] = Game.Instance.buildID;
obj[4] = text;
key = string.Format("{0}|{1}|{2}|{3}|{4}", obj);
string text2 = string.Concat(((FileSystemInfo)val).Name.Select((char c) => (!Path.GetInvalidFileNameChars().Contains(c)) ? c : '_'));
path = Path.Combine(Paths.CachePath, "sm3gm.loadfix", text2 + ".rooms");
if (!File.Exists(path))
{
Status = "first load of this city";
return;
}
var (num, num2, text3) = Restore(path, key);
if (text3 != null)
{
Status = "kept data not used: " + text3;
return;
}
Restored = true;
Status = $"read back {num} rooms, {num2} entries, {(double)(Stopwatch.GetTimestamp() - timestamp) * 1000.0 / (double)Stopwatch.Frequency:0} ms";
}
catch (Exception ex)
{
Status = "could not read: " + ex.Message;
}
}
private static void Postfix()
{
if (Restored || path == null || key == null)
{
return;
}
long timestamp = Stopwatch.GetTimestamp();
try
{
Status = Status + "; " + Save(path, key) + $", {(double)(Stopwatch.GetTimestamp() - timestamp) * 1000.0 / (double)Stopwatch.Frequency:0} ms";
}
catch (Exception ex)
{
Status = Status + "; could not keep: " + ex.Message;
}
}
private static (int rooms, long entries, string? why) Restore(string file, string wanted)
{
//IL_0368: Unknown result type (might be due to invalid IL or missing references)
//IL_0372: Expected O, but got Unknown
List<NewRoom> roomDirectory = CityData.Instance.roomDirectory;
using BinaryReader binaryReader = new BinaryReader(new MemoryStream(File.ReadAllBytes(file)));
if (binaryReader.ReadInt32() != 1)
{
return (rooms: 0, entries: 0L, why: "older format");
}
if (binaryReader.ReadString() != wanted)
{
return (rooms: 0, entries: 0L, why: "the city, the game or the mods changed");
}
if (binaryReader.ReadInt32() != roomDirectory.Count)
{
return (rooms: 0, entries: 0L, why: "different number of rooms");
}
RoomRecord[] array = new RoomRecord[binaryReader.ReadInt32()];
long num = 0L;
for (int i = 0; i < array.Length; i++)
{
RoomRecord roomRecord = (array[i] = new RoomRecord
{
Index = binaryReader.ReadInt32(),
Id = binaryReader.ReadInt32()
});
int num2 = binaryReader.ReadInt32();
for (int j = 0; j < num2; j++)
{
int item = binaryReader.ReadInt32();
int item2 = binaryReader.ReadInt32();
int[][] array2 = new int[binaryReader.ReadInt32()][];
for (int k = 0; k < array2.Length; k++)
{
int num3 = binaryReader.ReadInt32();
if (num3 >= 0)
{
int[] array3 = (array2[k] = new int[num3]);
for (int l = 0; l < num3; l++)
{
array3[l] = binaryReader.ReadInt32();
}
}
}
num += array2.Length;
roomRecord.Others.Add((item, item2, array2));
}
}
NewRoom[] array4 = (NewRoom[])(object)new NewRoom[roomDirectory.Count];
for (int m = 0; m < array4.Length; m++)
{
array4[m] = roomDirectory[m];
}
RoomRecord[] array5 = array;
foreach (RoomRecord roomRecord2 in array5)
{
if (roomRecord2.Index < 0 || roomRecord2.Index >= array4.Length || (Object)(object)array4[roomRecord2.Index] == (Object)null || array4[roomRecord2.Index].roomID != roomRecord2.Id)
{
return (rooms: 0, entries: 0L, why: "rooms do not line up");
}
foreach (var other in roomRecord2.Others)
{
if (other.index < 0 || other.index >= array4.Length || (Object)(object)array4[other.index] == (Object)null || array4[other.index].roomID != other.id)
{
return (rooms: 0, entries: 0L, why: "rooms do not line up");
}
}
}
int num4 = 0;
array5 = array;
foreach (RoomRecord roomRecord3 in array5)
{
NewRoom val = array4[roomRecord3.Index];
if (val.completedTreeCull || val.loadedCullTreeFromSave)
{
continue;
}
Dictionary<NewRoom, List<CullTreeEntry>> val2 = val.cullingTree;
if (val2 == null)
{
val2 = (val.cullingTree = new Dictionary<NewRoom, List<CullTreeEntry>>());
}
foreach (var other2 in roomRecord3.Others)
{
List<CullTreeEntry> val4 = new List<CullTreeEntry>();
int[][] item3 = other2.entries;
foreach (int[] array6 in item3)
{
List<int> val5 = null;
if (array6 != null)
{
val5 = new List<int>();
int[] array7 = array6;
foreach (int num7 in array7)
{
val5.Add(num7);
}
}
val4.Add(new CullTreeEntry(val5));
}
val2[array4[other2.index]] = val4;
}
val.completedTreeCull = true;
num4++;
}
return (rooms: num4, entries: num, why: null);
}
private static string Save(string file, string keyText)
{
List<NewRoom> roomDirectory = CityData.Instance.roomDirectory;
Dictionary<IntPtr, int> dictionary = new Dictionary<IntPtr, int>();
for (int i = 0; i < roomDirectory.Count; i++)
{
if ((Object)(object)roomDirectory[i] != (Object)null)
{
dictionary[((Il2CppObjectBase)roomDirectory[i]).Pointer] = i;
}
}
using MemoryStream memoryStream = new MemoryStream();
using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
MemoryStream memoryStream2 = new MemoryStream();
BinaryWriter binaryWriter2 = new BinaryWriter(memoryStream2);
int num = 0;
int num2 = 0;
long num3 = 0L;
for (int j = 0; j < roomDirectory.Count; j++)
{
NewRoom val = roomDirectory[j];
if ((Object)(object)val == (Object)null || !val.completedTreeCull || val.loadedCullTreeFromSave)
{
continue;
}
Dictionary<NewRoom, List<CullTreeEntry>> cullingTree = val.cullingTree;
if (cullingTree == null)
{
continue;
}
List<(int, int, List<CullTreeEntry>)> list = new List<(int, int, List<CullTreeEntry>)>();
Enumerator<NewRoom, List<CullTreeEntry>> enumerator = cullingTree.GetEnumerator();
while (enumerator.MoveNext())
{
KeyValuePair<NewRoom, List<CullTreeEntry>> current = enumerator.Current;
if (current.Value != null)
{
if ((Object)(object)current.Key == (Object)null || !dictionary.TryGetValue(((Il2CppObjectBase)current.Key).Pointer, out var value))
{
num2++;
}
else
{
list.Add((value, current.Key.roomID, current.Value));
}
}
}
binaryWriter2.Write(j);
binaryWriter2.Write(val.roomID);
binaryWriter2.Write(list.Count);
foreach (var (value2, value3, val2) in list)
{
binaryWriter2.Write(value2);
binaryWriter2.Write(value3);
binaryWriter2.Write(val2.Count);
for (int k = 0; k < val2.Count; k++)
{
CullTreeEntry obj = val2[k];
List<int> val3 = ((obj != null) ? obj.requiredOpenDoors : null);
if (val3 == null)
{
binaryWriter2.Write(-1);
continue;
}
binaryWriter2.Write(val3.Count);
for (int l = 0; l < val3.Count; l++)
{
binaryWriter2.Write(val3[l]);
}
}
num3 += val2.Count;
}
num++;
}
binaryWriter2.Flush();
binaryWriter.Write(1);
binaryWriter.Write(keyText);
binaryWriter.Write(roomDirectory.Count);
binaryWriter.Write(num);
memoryStream2.Position = 0L;
memoryStream2.CopyTo(memoryStream);
binaryWriter.Flush();
Directory.CreateDirectory(Path.GetDirectoryName(file));
string sourceFileName = file + ".new";
File.WriteAllBytes(sourceFileName, memoryStream.ToArray());
if (File.Exists(file))
{
File.Delete(file);
}
File.Move(sourceFileName, file);
return $"kept {num} rooms, {num3} entries, {num2} entries for replaced rooms left out, {memoryStream.Length / 1024} KB";
}
}
internal static class TreeCheck
{
internal static bool Enabled;
private static string Doors(List<int>? doors)
{
if (doors == null || doors.Count == 0)
{
return "";
}
int[] array = new int[doors.Count];
for (int i = 0; i < array.Length; i++)
{
array[i] = doors[i];
}
Array.Sort(array);
return string.Join(",", array);
}
private static ulong Fingerprint(IEnumerable<string> lines)
{
ulong num = 14695981039346656037uL;
foreach (string item in lines.OrderBy<string, string>((string k) => k, StringComparer.Ordinal))
{
foreach (char c in item)
{
num ^= c;
num *= 1099511628211L;
}
num ^= 0xA;
num *= 1099511628211L;
}
return num;
}
internal static void Run()
{
//IL_038a: Unknown result type (might be due to invalid IL or missing references)
//IL_0391: Expected O, but got Unknown
//IL_024a: Unknown result type (might be due to invalid IL or missing references)
//IL_024f: Unknown result type (might be due to invalid IL or missing references)
long timestamp = Stopwatch.GetTimestamp();
CityData instance = CityData.Instance;
List<NewRoom> val = ((instance != null) ? instance.roomDirectory : null);
if (val == null)
{
Plugin.L.LogInfo((object)"[LOADFIX] fingerprints: no rooms");
return;
}
int num = 0;
int num2 = 0;
long num3 = 0L;
long num4 = 0L;
List<string> list = new List<string>();
for (int i = 0; i < val.Count; i++)
{
NewRoom val2 = val[i];
if ((Object)(object)val2 == (Object)null)
{
continue;
}
if (!val2.completedTreeCull && !val2.loadedCullTreeFromSave)
{
num2++;
continue;
}
num++;
List<string> list2 = new List<string>();
List<string> list3 = new List<string>();
Dictionary<NewRoom, List<CullTreeEntry>> cullingTree = val2.cullingTree;
if (cullingTree != null)
{
Enumerator<NewRoom, List<CullTreeEntry>> enumerator = cullingTree.GetEnumerator();
while (enumerator.MoveNext())
{
KeyValuePair<NewRoom, List<CullTreeEntry>> current = enumerator.Current;
int num5 = (((Object)(object)current.Key != (Object)null) ? current.Key.roomID : (-2));
List<CullTreeEntry> value = current.Value;
if (value == null)
{
continue;
}
for (int j = 0; j < value.Count; j++)
{
string text = num5.ToString();
CullTreeEntry obj = value[j];
string item = text + ":" + Doors((obj != null) ? obj.requiredOpenDoors : null);
list2.Add(item);
if (num5 >= 0)
{
list3.Add(item);
}
else
{
num4++;
}
}
}
}
num3 += list2.Count;
list.Add($"{val2.roomID} {list2.Count} {Fingerprint(list2):x16} {list3.Count} {Fingerprint(list3):x16}");
}
List<string> list4 = new List<string>();
long num6 = 0L;
CitizenBehaviour instance2 = CitizenBehaviour.Instance;
List<SceneRecorder> val3 = ((instance2 != null) ? instance2.sceneRecorders : null);
if (val3 != null)
{
for (int k = 0; k < val3.Count; k++)
{
SceneRecorder val4 = val3[k];
if (val4 == null)
{
continue;
}
List<string> list5 = new List<string>();
Dictionary<NewNode, List<int>> coveredNodes = val4.coveredNodes;
if (coveredNodes != null)
{
Enumerator<NewNode, List<int>> enumerator2 = coveredNodes.GetEnumerator();
while (enumerator2.MoveNext())
{
KeyValuePair<NewNode, List<int>> current2 = enumerator2.Current;
if (current2.Key != null)
{
Vector3Int nodeCoord = current2.Key.nodeCoord;
list5.Add($"{((Vector3Int)(ref nodeCoord)).x},{((Vector3Int)(ref nodeCoord)).y},{((Vector3Int)(ref nodeCoord)).z}:{Doors(current2.Value)}");
}
}
}
num6 += list5.Count;
Interactable interactable = val4.interactable;
list4.Add($"{((interactable != null) ? interactable.id : (-1))} {list5.Count} {Fingerprint(list5):x16}");
}
}
list.Sort(StringComparer.Ordinal);
list4.Sort(StringComparer.Ordinal);
string text2 = (RoomCache.Restored ? "cache" : "game");
File.WriteAllLines(Path.Combine(Paths.BepInExRootPath, "loadfix-rooms-" + text2 + ".txt"), list);
File.WriteAllLines(Path.Combine(Paths.BepInExRootPath, "loadfix-cameras-" + text2 + ".txt"), list4);
ManualLogSource l = Plugin.L;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val5 = new BepInExInfoLogInterpolatedStringHandler(121, 8, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val5).AppendLiteral("[LOADFIX] fingerprints (");
((BepInExLogInterpolatedStringHandler)val5).AppendFormatted<string>(text2);
((BepInExLogInterpolatedStringHandler)val5).AppendLiteral("): ");
((BepInExLogInterpolatedStringHandler)val5).AppendFormatted<int>(num);
((BepInExLogInterpolatedStringHandler)val5).AppendLiteral(" rooms finished, ");
((BepInExLogInterpolatedStringHandler)val5).AppendFormatted<int>(num2);
((BepInExLogInterpolatedStringHandler)val5).AppendLiteral(" not needed yet, ");
((BepInExLogInterpolatedStringHandler)val5).AppendFormatted<long>(num3);
((BepInExLogInterpolatedStringHandler)val5).AppendLiteral(" entries, ");
((BepInExLogInterpolatedStringHandler)val5).AppendFormatted<long>(num4);
((BepInExLogInterpolatedStringHandler)val5).AppendLiteral(" for replaced rooms; ");
((BepInExLogInterpolatedStringHandler)val5).AppendFormatted<int>(list4.Count);
((BepInExLogInterpolatedStringHandler)val5).AppendLiteral(" cameras, ");
((BepInExLogInterpolatedStringHandler)val5).AppendFormatted<long>(num6);
((BepInExLogInterpolatedStringHandler)val5).AppendLiteral(" nodes covered, ");
((BepInExLogInterpolatedStringHandler)val5).AppendFormatted<double>((double)(Stopwatch.GetTimestamp() - timestamp) * 1000.0 / (double)Stopwatch.Frequency, "0");
((BepInExLogInterpolatedStringHandler)val5).AppendLiteral(" ms");
}
l.LogInfo(val5);
}
}
[HarmonyPatch(typeof(GeometryCullingController), "Update")]
internal static class BackgroundWatch
{
private static float next;
private static int last = -1;
private static void Postfix(GeometryCullingController __instance)
{
//IL_0045: Unknown result type (might be due to invalid IL or missing references)
//IL_004b: Expected O, but got Unknown
try
{
if (Time.unscaledTime < next)
{
return;
}
next = Time.unscaledTime + 60f;
int roomsCalculated = __instance.roomsCalculated;
if (roomsCalculated != last)
{
last = roomsCalculated;
ManualLogSource l = Plugin.L;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(53, 4, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[LOADFIX] room queue: ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(roomsCalculated);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" done, ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(__instance.toCalculateList?.Count ?? 0);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" on the list, working ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(__instance.asyncCullingTreeActive);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(Breakers.Summary());
}
l.LogInfo(val);
}
}
catch
{
}
}
}
[HarmonyPatch(typeof(CityConstructor), "TriggerStartEvent")]
internal static class LoadReport
{
private static void Postfix()
{
//IL_0094: Unknown result type (might be due to invalid IL or missing references)
//IL_009a: Expected O, but got Unknown
Plugin.L.LogInfo((object)("[LOADFIX] room data: " + RoomCache.Status));
Plugin.L.LogInfo((object)("[LOADFIX] " + FactLookup.Summary()));
Plugin.L.LogInfo((object)("[LOADFIX] " + Breakers.Summary()));
if (TreeCheck.Enabled)
{
try
{
Plugin.L.LogInfo((object)("[LOADFIX] " + FactLookup.Check()));
}
catch (Exception ex)
{
Plugin.L.LogWarning((object)("[LOADFIX] fact check: " + ex.Message));
}
}
ManualLogSource l = Plugin.L;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(55, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[LOADFIX] DensityMod's search: skipped ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<long>(DensitySkip.Skipped);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" times, allowed ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<long>(DensitySkip.Allowed);
}
l.LogInfo(val);
if (TreeCheck.Enabled)
{
try
{
TreeCheck.Run();
}
catch (Exception ex2)
{
Plugin.L.LogWarning((object)("[LOADFIX] fingerprints: " + ex2));
}
}
}
}
internal static class RepeatChecks
{
private static readonly Dictionary<(IntPtr, IntPtr), (bool, int)> answers = new Dictionary<(IntPtr, IntPtr), (bool, int)>();
internal static bool Verify;
private static long started;
private static long totalTicks;
private static int passes;
private static int asked;
private static int remembered;
private static int differed;
private static long allAsked;
private static long allRemembered;
private static long allDiffered;
private static bool OffMain => Environment.CurrentManagedThreadId != Plugin.MainThread;
internal static void Apply(Harmony h)
{
//IL_0027: Unknown result type (might be due to invalid IL or missing references)
//IL_003c: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Expected O, but got Unknown
//IL_0049: Expected O, but got Unknown
//IL_0071: Unknown result type (might be due to invalid IL or missing references)
//IL_0086: Unknown result type (might be due to invalid IL or missing references)
//IL_0093: Expected O, but got Unknown
//IL_0093: Expected O, but got Unknown
try
{
h.Patch((MethodBase)AccessTools.Method(typeof(SideJobController), "JobCreationCheck", (Type[])null, (Type[])null), new HarmonyMethod(typeof(RepeatChecks), "Begin", (Type[])null), new HarmonyMethod(typeof(RepeatChecks), "End", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
h.Patch((MethodBase)AccessTools.Method(typeof(SideJobController), "MotivePass", (Type[])null, (Type[])null), new HarmonyMethod(typeof(RepeatChecks), "Before", (Type[])null), new HarmonyMethod(typeof(RepeatChecks), "After", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
}
catch (Exception ex)
{
Plugin.L.LogWarning((object)("[LOADFIX] repeat checks not attached: " + ex.GetType().Name + " " + ex.Message));
}
}
private static void Begin()
{
if (!OffMain)
{
answers.Clear();
asked = (remembered = (differed = 0));
started = Stopwatch.GetTimestamp();
}
}
private static void End()
{
//IL_0094: Unknown result type (might be due to invalid IL or missing references)
//IL_009a: Expected O, but got Unknown
if (OffMain)
{
return;
}
long num = Stopwatch.GetTimestamp() - started;
answers.Clear();
totalTicks += num;
passes++;
allAsked += asked;
allRemembered += remembered;
allDiffered += differed;
double num2 = (double)num * 1000.0 / (double)Stopwatch.Frequency;
if (!(num2 < 1000.0))
{
ManualLogSource l = Plugin.L;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(57, 5, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[LOADFIX] repeat checks, pass ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(passes);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<double>(num2, "0");
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ms, ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(asked);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" asked, ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(remembered);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" from memory");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(Verify ? $", {differed} would have differed" : "");
}
l.LogInfo(val);
}
}
private static bool Before(ref List<ModifierRule> rules, Citizen cit, ref int score, ref bool __result)
{
if (Verify || rules == null || (Object)(object)cit == (Object)null || OffMain)
{
return true;
}
asked++;
if (answers.TryGetValue((((Il2CppObjectBase)rules).Pointer, ((Il2CppObjectBase)cit).Pointer), out var value))
{
remembered++;
(__result, score) = value;
return false;
}
return true;
}
private static void After(ref List<ModifierRule> rules, Citizen cit, ref int score, bool __result, bool __runOriginal)
{
if (!__runOriginal || rules == null || (Object)(object)cit == (Object)null || OffMain)
{
return;
}
(IntPtr, IntPtr) key = (((Il2CppObjectBase)rules).Pointer, ((Il2CppObjectBase)cit).Pointer);
if (Verify)
{
asked++;
if (answers.TryGetValue(key, out var value))
{
remembered++;
if (value.Item1 != __result || value.Item2 != score)
{
differed++;
}
return;
}
}
answers[key] = (__result, score);
}
}
}