using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; 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.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("SmoothSave")] [assembly: AssemblyDescription("https://valheim.thunderstore.io/package/Smoothbrain/SmoothSave")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("SmoothSave")] [assembly: AssemblyCopyright("Copyright © 2022")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("15C530A2-65CF-42AA-83C3-AC862F358667")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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 SmoothSave { [BepInPlugin("org.bepinex.plugins.smoothsave", "Smooth Save", "1.0.5")] [BepInIncompatibility("org.bepinex.plugins.valheim_plus")] public class SmoothSave : BaseUnityPlugin { private enum Logging { Off, Simple, Detailed } [HarmonyPatch(typeof(ZNet), "SaveWorldThread")] public class SplitSaveWorld { private static bool Prefix() { Task task = customZDOCopyTask.Task; task.Wait(); return task.Result; } } [HarmonyPatch(typeof(ZDO), "IncreaseDataRevision")] public class TrackZDOChanges { private static void CloneUpdatedZDO(ZDO zdo) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) if (copiedZdoIndices.TryGetValue(zdo.m_uid, out var value)) { copiedZdos[value] = zdo.Clone(); UpdateZDOExtraData(zdo.m_uid); } } private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator ilg) { foreach (CodeInstruction instruction in instructions) { if (instruction.opcode == OpCodes.Ret) { Label endLabel = ilg.DefineLabel(); yield return new CodeInstruction(OpCodes.Ldsfld, (object)AccessTools.DeclaredField(typeof(SmoothSave), "copiedZdoIndices")); yield return new CodeInstruction(OpCodes.Brtrue, (object)endLabel); yield return new CodeInstruction(OpCodes.Ret, (object)null); yield return new CodeInstruction(OpCodes.Ldarg_0, (object)null) { labels = { endLabel } }; yield return new CodeInstruction(OpCodes.Call, (object)AccessTools.DeclaredMethod(typeof(TrackZDOChanges), "CloneUpdatedZDO", (Type[])null, (Type[])null)); } yield return instruction; } } } [HarmonyPatch(typeof(ZDOMan), "RPC_ZDOData")] public class TrackZDORPCChanges { private static ZDO CloneUpdatedZDO(ZDO zdo) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) if (copiedZdoIndices.TryGetValue(zdo.m_uid, out var value)) { copiedZdos[value] = zdo.Clone(); UpdateZDOExtraData(zdo.m_uid); } return zdo; } private static IEnumerable Transpiler(IEnumerable _instructions, ILGenerator ilg) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Expected O, but got Unknown //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Expected O, but got Unknown //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Expected O, but got Unknown List list = _instructions.ToList(); MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZDO), "Deserialize", (Type[])null, (Type[])null); for (int i = 0; i < list.Count; i++) { if (list[i].opcode == OpCodes.Callvirt && CodeInstructionExtensions.OperandIs(list[i], (MemberInfo)methodInfo)) { Label label = ilg.DefineLabel(); list.InsertRange(i + 1, (IEnumerable)(object)new CodeInstruction[4] { new CodeInstruction(OpCodes.Ldsfld, (object)AccessTools.DeclaredField(typeof(SmoothSave), "copiedZdoIndices")), new CodeInstruction(OpCodes.Brfalse, (object)label), new CodeInstruction(OpCodes.Call, (object)AccessTools.DeclaredMethod(typeof(TrackZDOChanges), "CloneUpdatedZDO", (Type[])null, (Type[])null)), new CodeInstruction(OpCodes.Pop, (object)null) { labels = { label } } }); list.Insert(i - 1, new CodeInstruction(OpCodes.Dup, (object)null)); i += 6; } } return list; } } [HarmonyPatch(typeof(ZDOMan), "RemoveFromSector")] public class TrackRemovedZDOs { private static bool RemoveZDO(List zdos, ZDO zdo, int index) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) int num = zdos.IndexOf(zdo); if (num < 0) { return false; } zdos.RemoveAt(num); Dictionary copiedZdoIndices = SmoothSave.copiedZdoIndices; if (copiedZdoIndices != null) { if (copyingSectorId == index && num <= copyingSectorOffset) { copyingSectorOffset--; } else if (copyingSectorId <= index) { return true; } if (copiedZdoIndices.TryGetValue(zdo.m_uid, out var value)) { ZDOID uid = zdo.m_uid; removedIndices.Add(value); copiedZdoIndices.Remove(uid); copiedZdo_floats.Remove(uid); copiedZdo_vec3.Remove(uid); copiedZdo_quats.Remove(uid); copiedZdo_ints.Remove(uid); copiedZdo_longs.Remove(uid); copiedZdo_strings.Remove(uid); copiedZdo_byteArrays.Remove(uid); } } return true; } private static IEnumerable Transpiler(IEnumerable instructions) { MethodInfo listRemove = AccessTools.DeclaredMethod(typeof(List), "Remove", new Type[1] { typeof(ZDO) }, (Type[])null); bool first = true; foreach (CodeInstruction instruction in instructions) { if (first && instruction.opcode == OpCodes.Callvirt && CodeInstructionExtensions.OperandIs(instruction, (MemberInfo)listRemove)) { first = false; yield return new CodeInstruction(OpCodes.Ldloc_0, (object)null); yield return new CodeInstruction(OpCodes.Call, (object)AccessTools.DeclaredMethod(typeof(TrackRemovedZDOs), "RemoveZDO", (Type[])null, (Type[])null)); } else { yield return instruction; } } } } [HarmonyPatch(typeof(ZDOMan), "AddToSector")] public class TrackAddedZDOs { private static void AddZdo(ZDO zdo, int index) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) if (copiedZdoIndices != null && index < copyingSectorId && zdo.Persistent) { copiedZdoIndices.Add(zdo.m_uid, copiedZdos.Count); copiedZdos.Add(zdo); UpdateZDOExtraData(zdo.m_uid); } } private static IEnumerable Transpiler(IEnumerable instructions) { MethodInfo listAdd = AccessTools.DeclaredMethod(typeof(List), "Add", new Type[1] { typeof(ZDO) }, (Type[])null); int firsts = 2; foreach (CodeInstruction instruction in instructions) { yield return instruction; if (firsts > 0 && instruction.opcode == OpCodes.Callvirt && CodeInstructionExtensions.OperandIs(instruction, (MemberInfo)listAdd)) { int num = firsts - 1; firsts = num; yield return new CodeInstruction(OpCodes.Ldarg_1, (object)null); yield return new CodeInstruction(OpCodes.Ldloc_0, (object)null); yield return new CodeInstruction(OpCodes.Call, (object)AccessTools.DeclaredMethod(typeof(TrackAddedZDOs), "AddZdo", (Type[])null, (Type[])null)); } } } } [HarmonyPatch(typeof(ZNet), "SaveWorld")] public class ReplaceZDOCopying { private static void CustomZDOCopy(ZDOMan zdoMan) { ZDOMan zdoMan2 = zdoMan; TaskCompletionSource task = new TaskCompletionSource(); customZDOCopyTask = task; IEnumerator saveCoroutine = save(); ((MonoBehaviour)ZNet.instance).StartCoroutine(saveCoroutine); customZDOCopyTask.Task.ContinueWith(delegate { ((MonoBehaviour)ZNet.instance).StopCoroutine(saveCoroutine); }); IEnumerator save() { yield return null; Dictionary zdoIndex = new Dictionary(); List saveZDOs = new List(); Dictionary> zdo_floats = new Dictionary>(); Dictionary> zdo_vec3 = new Dictionary>(); Dictionary> zdo_quats = new Dictionary>(); Dictionary> zdo_ints = new Dictionary>(); Dictionary> zdo_longs = new Dictionary>(); Dictionary> zdo_strings = new Dictionary>(); Dictionary> zdo_byteArrays = new Dictionary>(); zdoMan2.m_saveData = new SaveData { m_sessionID = zdoMan2.m_sessionID, m_zdos = saveZDOs }; yield return null; copiedZdos = saveZDOs; copiedZdoIndices = zdoIndex; copiedZdo_floats = zdo_floats; copiedZdo_vec3 = zdo_vec3; copiedZdo_quats = zdo_quats; copiedZdo_ints = zdo_ints; copiedZdo_longs = zdo_longs; copiedZdo_strings = zdo_strings; copiedZdo_byteArrays = zdo_byteArrays; Stopwatch stopwatch = Stopwatch.StartNew(); long longestBlockingTime = 0L; long lastElapsed = 0L; int currentBatch = zdoBatchSize.Value; List?[] objectsBySector = zdoMan2.m_objectsBySector; int index = 0; int end = objectsBySector.Length; while (index < end) { List zdos = objectsBySector[index]; int num; if (zdos != null) { for (int j = 0; j < zdos.Count; j = num) { ZDO zdo = zdos[j]; if (zdo.Persistent) { ZDOID zdoid = zdo.m_uid; zdoIndex.Add(zdoid, saveZDOs.Count); saveZDOs.Add(zdo.Clone()); if (ZDOExtraData.s_floats.TryGetValue(zdoid, out var value_floats)) { zdo_floats.Add(zdoid, (BinarySearchDictionary)value_floats.Clone()); } if (ZDOExtraData.s_vec3.TryGetValue(zdoid, out var value_vec3)) { zdo_vec3.Add(zdoid, (BinarySearchDictionary)value_vec3.Clone()); } if (ZDOExtraData.s_quats.TryGetValue(zdoid, out var value_quats)) { zdo_quats.Add(zdoid, (BinarySearchDictionary)value_quats.Clone()); } if (ZDOExtraData.s_ints.TryGetValue(zdoid, out var value_ints)) { zdo_ints.Add(zdoid, (BinarySearchDictionary)value_ints.Clone()); } if (ZDOExtraData.s_longs.TryGetValue(zdoid, out var value_longs)) { zdo_longs.Add(zdoid, (BinarySearchDictionary)value_longs.Clone()); } if (ZDOExtraData.s_strings.TryGetValue(zdoid, out BinarySearchDictionary value_strings)) { zdo_strings.Add(zdoid, (BinarySearchDictionary)value_strings.Clone()); } if (ZDOExtraData.s_byteArrays.TryGetValue(zdoid, out BinarySearchDictionary value_byteArrays)) { zdo_byteArrays.Add(zdoid, (BinarySearchDictionary)value_byteArrays.Clone()); } num = currentBatch - 1; currentBatch = num; if (num <= 0) { currentBatch = zdoBatchSize.Value; copyingSectorId = index; copyingSectorOffset = j; stopwatch.Stop(); longestBlockingTime = Math.Max(longestBlockingTime, stopwatch.ElapsedMilliseconds - lastElapsed); lastElapsed = stopwatch.ElapsedMilliseconds; yield return null; stopwatch.Start(); j = copyingSectorOffset; } value_floats = null; value_vec3 = null; value_quats = null; value_ints = null; value_longs = null; value_strings = null; value_byteArrays = null; } num = j + 1; } } num = index + 1; index = num; } foreach (int removedIndex in removedIndices.OrderByDescending((int i) => i)) { int lastIndex = saveZDOs.Count - 1; saveZDOs[removedIndex] = saveZDOs[lastIndex]; saveZDOs.RemoveAt(lastIndex); } removedIndices.Clear(); long outsideSectorStart = stopwatch.ElapsedMilliseconds; foreach (List zdoList in zdoMan2.m_objectsByOutsideSector.Values) { foreach (ZDO zdo2 in zdoList) { if (zdo2.Persistent) { saveZDOs.Add(zdo2.Clone()); } } } stopwatch.Stop(); zdoMan2.m_saveData.m_nextUid = zdoMan2.m_nextUid; ZDOExtraData.s_saveFloats = zdo_floats; ZDOExtraData.s_saveVec3s = zdo_vec3; ZDOExtraData.s_saveQuats = zdo_quats; ZDOExtraData.s_saveInts = zdo_ints; ZDOExtraData.s_saveLongs = zdo_longs; ZDOExtraData.s_saveStrings = zdo_strings; ZDOExtraData.s_saveByteArrays = zdo_byteArrays; long outsideSectorTime = stopwatch.ElapsedMilliseconds - outsideSectorStart; long connectionHashDataStart = stopwatch.ElapsedMilliseconds; ZDOExtraData.RegenerateConnectionHashData(); ZDOExtraData.s_saveConnections = ZDOHelper.Clone(ZDOExtraData.s_connectionsHashData); long connectionHashDataTime = stopwatch.ElapsedMilliseconds - connectionHashDataStart; if (saveLoggingOutput.Value == Logging.Simple) { Log($"World saved: Estimated blocking time without the mod: {stopwatch.ElapsedMilliseconds} ms. Blocking time now: {longestBlockingTime} ms."); } else if (saveLoggingOutput.Value == Logging.Detailed) { Log($"Copying ZDOs into internal buffer took {stopwatch.ElapsedMilliseconds} ms. (Longest blocking time: {longestBlockingTime} ms, copying ZDOs outside sector time: {outsideSectorTime} ms, saving connections: {connectionHashDataTime} ms)."); } ZoneSystem.instance.PrepareSave(); RandEventSystem.instance.PrepareSave(); copiedZdos = null; copiedZdoIndices = null; ZNet.instance.m_saveThreadStartTime = Time.realtimeSinceStartup; task.SetResult(result: true); } } public static bool Prefix(bool sync) { if (customZDOCopyTask.Task.IsCompleted) { return true; } if (sync) { AbortSave(); return true; } return false; } public static IEnumerable Transpiler(IEnumerable instructions, ILGenerator ilg) { MethodInfo PrepareSave = AccessTools.DeclaredMethod(typeof(ZDOMan), "PrepareSave", (Type[])null, (Type[])null); bool insertCustomSave = false; Label doCustomSaveLabel = ilg.DefineLabel(); foreach (CodeInstruction instruction in instructions) { if (insertCustomSave) { insertCustomSave = false; Label endCustomSaveLabel = ilg.DefineLabel(); yield return new CodeInstruction(OpCodes.Br, (object)endCustomSaveLabel); yield return new CodeInstruction(OpCodes.Call, (object)AccessTools.DeclaredMethod(typeof(ReplaceZDOCopying), "CustomZDOCopy", (Type[])null, (Type[])null)) { labels = { doCustomSaveLabel } }; instruction.labels.Add(endCustomSaveLabel); } if (instruction.opcode == OpCodes.Callvirt && CodeInstructionExtensions.OperandIs(instruction, (MemberInfo)PrepareSave)) { yield return new CodeInstruction(OpCodes.Ldarg_1, (object)null); yield return new CodeInstruction(OpCodes.Brfalse, (object)doCustomSaveLabel); insertCustomSave = true; } yield return instruction; } } } [HarmonyPatch(typeof(ZNet), "StopAll")] private class InterceptSaveThreadJoinOnStop { public static void Prefix(ZNet __instance) { if (!__instance.m_haveStoped && !customZDOCopyTask.Task.IsCompleted) { __instance.SaveWorld(true); } } } private const string ModName = "Smooth Save"; private const string ModVersion = "1.0.5"; private const string ModGUID = "org.bepinex.plugins.smoothsave"; private static SmoothSave selfReference; private static ConfigEntry zdoBatchSize; private static ConfigEntry saveLoggingOutput; private static TaskCompletionSource customZDOCopyTask; private static readonly List removedIndices; private static Dictionary? copiedZdoIndices; private static List copiedZdos; private static Dictionary> copiedZdo_floats; private static Dictionary> copiedZdo_vec3; private static Dictionary> copiedZdo_quats; private static Dictionary> copiedZdo_ints; private static Dictionary> copiedZdo_longs; private static Dictionary> copiedZdo_strings; private static Dictionary> copiedZdo_byteArrays; private static int copyingSectorId; private static int copyingSectorOffset; private static ManualLogSource logger => ((BaseUnityPlugin)selfReference).Logger; public void Awake() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown selfReference = this; Assembly executingAssembly = Assembly.GetExecutingAssembly(); Harmony val = new Harmony("org.bepinex.plugins.smoothsave"); val.PatchAll(executingAssembly); zdoBatchSize = ((BaseUnityPlugin)this).Config.Bind("1 - General", "ZDOs to copy at once", 3000, "The number of ZDOs that are copied at the same time. You can increase this, if you have powerful hardware."); saveLoggingOutput = ((BaseUnityPlugin)this).Config.Bind("1 - General", "World save output", Logging.Simple, "Log level for world saves."); } static SmoothSave() { selfReference = null; zdoBatchSize = null; saveLoggingOutput = null; customZDOCopyTask = new TaskCompletionSource(); removedIndices = new List(); copiedZdos = null; copiedZdo_floats = new Dictionary>(); copiedZdo_vec3 = new Dictionary>(); copiedZdo_quats = new Dictionary>(); copiedZdo_ints = new Dictionary>(); copiedZdo_longs = new Dictionary>(); copiedZdo_strings = new Dictionary>(); copiedZdo_byteArrays = new Dictionary>(); customZDOCopyTask.SetResult(result: true); } private static void Log(string message) { logger.LogMessage((object)message); } private static void UpdateZDOExtraData(ZDOID zdoid) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) if (ZDOExtraData.s_floats.TryGetValue(zdoid, out var value)) { copiedZdo_floats[zdoid] = (BinarySearchDictionary)value.Clone(); } if (ZDOExtraData.s_vec3.TryGetValue(zdoid, out var value2)) { copiedZdo_vec3[zdoid] = (BinarySearchDictionary)value2.Clone(); } if (ZDOExtraData.s_quats.TryGetValue(zdoid, out var value3)) { copiedZdo_quats[zdoid] = (BinarySearchDictionary)value3.Clone(); } if (ZDOExtraData.s_ints.TryGetValue(zdoid, out var value4)) { copiedZdo_ints[zdoid] = (BinarySearchDictionary)value4.Clone(); } if (ZDOExtraData.s_longs.TryGetValue(zdoid, out var value5)) { copiedZdo_longs[zdoid] = (BinarySearchDictionary)value5.Clone(); } if (ZDOExtraData.s_strings.TryGetValue(zdoid, out var value6)) { copiedZdo_strings[zdoid] = (BinarySearchDictionary)value6.Clone(); } if (ZDOExtraData.s_byteArrays.TryGetValue(zdoid, out var value7)) { copiedZdo_byteArrays[zdoid] = (BinarySearchDictionary)value7.Clone(); } } private static void AbortSave() { customZDOCopyTask.SetResult(result: false); Log("Aborted saving due to new save request while ZDOs for saving are being collected."); } } }