Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ChunkedSaveFix v0.2.0
plugins/FixSave.dll
Decompiled 3 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("FixSave")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.2.0.0")] [assembly: AssemblyInformationalVersion("0.2.0+20d46d95f64c44821203859faa8c8449a634cfc9")] [assembly: AssemblyProduct("FixSave")] [assembly: AssemblyTitle("FixSave")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.2.0.0")] [module: UnverifiableCode] namespace Balguito.Valheim.FixSave; [BepInPlugin("balguito.valheim.fixsave", "Balguito Fix Save", "0.2.0")] public class BalguitoFixSave : BaseUnityPlugin { internal static ManualLogSource Log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<bool> Verbose; internal static ConfigEntry<bool> RemoveSizeChangedChunks; internal static ConfigEntry<bool> LogManifestOverlaps; internal static ConfigEntry<int> TopChunksToLog; internal static ConfigEntry<string> RepairMode; internal static ConfigEntry<bool> RepairIncludePortals; internal static ConfigEntry<int> RepairPositionPrecision; internal static ConfigEntry<int> RepairRotationPrecision; internal static ConfigEntry<int> RepairMaxRemovalsPerSave; internal static ConfigEntry<int> RepairTopGroupsToLog; internal static ConfigEntry<bool> ForceFullRepairSave; internal static ConfigEntry<bool> PruneManifestToFullRepairChunks; private Harmony _harmony; private void Awake() { //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable chunked save diagnostics/fixes."); RemoveSizeChangedChunks = ((BaseUnityPlugin)this).Config.Bind<bool>("Fixes", "RemoveSizeChangedChunks", true, "Remove stale chunk mappings marked as sizeChanged before writing the .chunks manifest."); RepairMode = ((BaseUnityPlugin)this).Config.Bind<string>("Repair", "RepairMode", "Disabled", "Repair mode: Disabled, ScanOnly, FilterSaveExact. Disabled is the public-safe default. ScanOnly logs duplicate save clones. FilterSaveExact removes duplicate clones from the save output before chunks are written."); ForceFullRepairSave = ((BaseUnityPlugin)this).Config.Bind<bool>("Repair", "ForceFullRepairSave", false, "Force one full-world repair save by marking all possible chunks as dirty before GetSaveClonePerChunk. Use with RepairMode=FilterSaveExact, then turn it off after verifying the next load."); PruneManifestToFullRepairChunks = ((BaseUnityPlugin)this).Config.Bind<bool>("Repair", "PruneManifestToFullRepairChunks", true, "During ForceFullRepairSave, remove non-portal chunk mappings from the manifest if they were not part of the rewritten full repair output."); RepairIncludePortals = ((BaseUnityPlugin)this).Config.Bind<bool>("Repair", "RepairIncludePortals", false, "Include portal prefabs in duplicate filtering. Recommended false."); RepairPositionPrecision = ((BaseUnityPlugin)this).Config.Bind<int>("Repair", "PositionPrecision", 100, "Position quantization multiplier. 100 = centimeters. Higher is stricter."); RepairRotationPrecision = ((BaseUnityPlugin)this).Config.Bind<int>("Repair", "RotationPrecision", 10000, "Rotation quaternion quantization multiplier. Higher is stricter."); RepairMaxRemovalsPerSave = ((BaseUnityPlugin)this).Config.Bind<int>("Repair", "MaxRemovalsPerSave", 0, "Safety cap for FilterSaveExact. 0 = no cap. If cap is reached, extra duplicates are kept."); RepairTopGroupsToLog = ((BaseUnityPlugin)this).Config.Bind<int>("Repair", "TopGroupsToLog", 10, "How many duplicate groups to log when repair diagnostics find duplicates."); LogManifestOverlaps = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "LogManifestOverlaps", false, "Detect and log overlapping chunk mappings before writing the .chunks manifest. Disabled by default because the geometric detector is noisy."); TopChunksToLog = ((BaseUnityPlugin)this).Config.Bind<int>("Diagnostics", "TopChunksToLog", 0, "How many largest chunks to log during verbose save diagnostics."); Verbose = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "Verbose", false, "Enable verbose diagnostics. Public default is false to avoid log spam."); _harmony = new Harmony("balguito.valheim.fixsave"); _harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Balguito Fix Save 0.2.0 loaded - build 20260629-211115"); ((BaseUnityPlugin)this).Logger.LogWarning((object)"Balguito Fix Save is intended for Valheim public-test 0.221.13 chunked saves. Back up the full world folder before enabling RepairMode or ForceFullRepairSave."); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } [HarmonyPatch(typeof(ZDOMan), "PrepareSave")] internal static class Patch_ZDOMan_PrepareSave_Diagnostics { private static void Prefix(ZDOMan __instance) { if (BalguitoFixSave.Enabled.Value && BalguitoFixSave.Verbose.Value) { BalguitoFixSave.Log.LogInfo((object)$"[ChunkedSaveFix] PrepareSave START. MemoryZDOs={__instance.NrOfObjects():N0}"); } } private static void Postfix(ZDOMan __instance) { if (BalguitoFixSave.Enabled.Value && BalguitoFixSave.Verbose.Value) { BalguitoFixSave.Log.LogInfo((object)$"[ChunkedSaveFix] PrepareSave END. MemoryZDOs={__instance.NrOfObjects():N0}"); } } } [HarmonyPatch(typeof(ZDOMan), "GetSaveClonePerChunk")] internal static class Patch_ZDOMan_GetSaveClonePerChunk_DiagnosticsAndRepair { private static void Prefix(ZDOMan __instance) { FullRepairState.BeforeGetSaveClonePerChunk(__instance); } private static void Postfix(List<Tuple<ChunkIndex, List<ZDO>>> __result) { //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) if (!BalguitoFixSave.Enabled.Value) { return; } int num = __result?.Count ?? 0; int num2 = __result?.Sum((Tuple<ChunkIndex, List<ZDO>> x) => x.Item2?.Count ?? 0) ?? 0; if (BalguitoFixSave.Verbose.Value || FullRepairState.Active) { BalguitoFixSave.Log.LogInfo((object)$"[ChunkedSaveFix] GetSaveClonePerChunk BEFORE repair. DirtyChunksToWrite={num:N0}, ZDOsToWrite={num2:N0}, FullRepair={FullRepairState.Active}"); } if (__result == null) { return; } SaveCloneRepair.Run(__result); FullRepairState.AfterGetSaveClonePerChunk(__result); int count = __result.Count; int num3 = __result.Sum((Tuple<ChunkIndex, List<ZDO>> x) => x.Item2?.Count ?? 0); int num4 = num2 - num3; if (num4 > 0 || BalguitoFixSave.Verbose.Value || FullRepairState.Active) { BalguitoFixSave.Log.LogInfo((object)$"[ChunkedSaveFix] GetSaveClonePerChunk AFTER repair. DirtyChunksToWrite={count:N0}, ZDOsToWrite={num3:N0}, Removed={num4:N0}, FullRepair={FullRepairState.Active}"); } if (!BalguitoFixSave.Verbose.Value) { return; } int count2 = Math.Max(0, BalguitoFixSave.TopChunksToLog.Value); foreach (Tuple<ChunkIndex, List<ZDO>> item2 in __result.OrderByDescending((Tuple<ChunkIndex, List<ZDO>> x) => x.Item2?.Count ?? 0).Take(count2)) { ChunkIndex item = item2.Item1; int num5 = item2.Item2?.Count ?? 0; BalguitoFixSave.Log.LogInfo((object)$"[ChunkedSaveFix] write chunk={ChunkDebug.DescribeChunkIndex(item)}, zdos={num5:N0}"); } } } [HarmonyPatch(typeof(ChunkSaveMapping), "Save")] internal static class Patch_ChunkSaveMapping_Save { private static void Prefix(ChunkSaveMapping __instance) { if (!BalguitoFixSave.Enabled.Value) { return; } Dictionary<ChunkIndex, ChunkInfo> chunks = __instance.Chunks; if (chunks == null) { BalguitoFixSave.Log.LogWarning((object)"[ChunkedSaveFix] ChunkSaveMapping.Save START. chunks=null"); return; } LogManifest("BEFORE", chunks); if (BalguitoFixSave.LogManifestOverlaps.Value) { ChunkDebug.LogOverlaps(chunks, "BEFORE"); } if (BalguitoFixSave.RemoveSizeChangedChunks.Value) { RemoveSizeChanged(chunks); } FullRepairState.PruneManifest(chunks); LogManifest("AFTER", chunks); if (BalguitoFixSave.LogManifestOverlaps.Value) { ChunkDebug.LogOverlaps(chunks, "AFTER"); } } private static void LogManifest(string phase, Dictionary<ChunkIndex, ChunkInfo> chunks) { //IL_0111: Unknown result type (might be due to invalid IL or missing references) int num = chunks.Values.Sum((ChunkInfo x) => x?.m_numZDOs ?? 0); int num2 = chunks.Count((KeyValuePair<ChunkIndex, ChunkInfo> x) => x.Value != null && x.Value.m_sizeChanged); if (BalguitoFixSave.Verbose.Value || num2 > 0 || FullRepairState.Active) { BalguitoFixSave.Log.LogInfo((object)$"[ChunkedSaveFix] ChunkSaveMapping.Save {phase}. ManifestChunks={chunks.Count:N0}, ManifestZDOs={num:N0}, SizeChanged={num2:N0}"); } if (!BalguitoFixSave.Verbose.Value) { return; } int count = Math.Max(0, BalguitoFixSave.TopChunksToLog.Value); foreach (KeyValuePair<ChunkIndex, ChunkInfo> item in chunks.OrderByDescending((KeyValuePair<ChunkIndex, ChunkInfo> x) => x.Value?.m_numZDOs ?? 0).Take(count)) { BalguitoFixSave.Log.LogInfo((object)("[ChunkedSaveFix] manifest " + phase + " chunk=" + ChunkDebug.DescribeChunk(item.Key, item.Value))); } } private static void RemoveSizeChanged(Dictionary<ChunkIndex, ChunkInfo> chunks) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0075: 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) List<ChunkIndex> list = (from kv in chunks where kv.Value != null && kv.Value.m_sizeChanged select kv.Key).ToList(); if (list.Count == 0) { return; } foreach (ChunkIndex item in list) { if (chunks.TryGetValue(item, out var value)) { chunks.Remove(item); BalguitoFixSave.Log.LogWarning((object)("[ChunkedSaveFix] Removed stale resized chunk from manifest: " + ChunkDebug.DescribeChunk(item, value))); } } BalguitoFixSave.Log.LogWarning((object)$"[ChunkedSaveFix] Removed {list.Count:N0} stale resized chunk mapping(s) before writing .chunks manifest"); } } internal static class SaveCloneRepair { private struct ZdoRef { public readonly int ChunkListIndex; public readonly int ZdoIndex; public readonly ZDO Zdo; public ZdoRef(int chunkListIndex, int zdoIndex, ZDO zdo) { ChunkListIndex = chunkListIndex; ZdoIndex = zdoIndex; Zdo = zdo; } } private class DuplicateGroupStats { public readonly DedupeKey Key; public int Count; public ZdoRef Keeper; public DuplicateGroupStats(DedupeKey key, ZdoRef keeper) { Key = key; Keeper = keeper; Count = 1; } } private struct DedupeKey : IEquatable<DedupeKey> { public int Prefab; public int X; public int Y; public int Z; public int RX; public int RY; public int RZ; public int RW; public static DedupeKey FromZdo(ZDO zdo, int positionPrecision, int rotationPrecision) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_004b: 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_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) Vector3 position = zdo.GetPosition(); Quaternion rotation = zdo.GetRotation(); return new DedupeKey { Prefab = zdo.GetPrefab(), X = Quantize(position.x, positionPrecision), Y = Quantize(position.y, positionPrecision), Z = Quantize(position.z, positionPrecision), RX = Quantize(rotation.x, rotationPrecision), RY = Quantize(rotation.y, rotationPrecision), RZ = Quantize(rotation.z, rotationPrecision), RW = Quantize(rotation.w, rotationPrecision) }; } public Vector3 ToApproxPosition(int positionPrecision) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) int num = Math.Max(1, positionPrecision); return new Vector3((float)X / (float)num, (float)Y / (float)num, (float)Z / (float)num); } private static int Quantize(float value, int precision) { return Mathf.RoundToInt(value * (float)precision); } public bool Equals(DedupeKey other) { if (Prefab == other.Prefab && X == other.X && Y == other.Y && Z == other.Z && RX == other.RX && RY == other.RY && RZ == other.RZ) { return RW == other.RW; } return false; } public override bool Equals(object obj) { if (obj is DedupeKey other) { return Equals(other); } return false; } public override int GetHashCode() { return (((((((((((((Prefab * 397) ^ X) * 397) ^ Y) * 397) ^ Z) * 397) ^ RX) * 397) ^ RY) * 397) ^ RZ) * 397) ^ RW; } } public static void Run(List<Tuple<ChunkIndex, List<ZDO>>> objectsByChunk) { string text = NormalizeMode(BalguitoFixSave.RepairMode.Value); if (text == "disabled") { if (BalguitoFixSave.Verbose.Value) { BalguitoFixSave.Log.LogInfo((object)"[DedupeSave] RepairMode=Disabled"); } return; } bool flag = text == "filtersaveexact"; bool flag2 = text == "scanonly"; if (!flag && !flag2) { BalguitoFixSave.Log.LogError((object)("[DedupeSave] Unknown RepairMode='" + BalguitoFixSave.RepairMode.Value + "'. Valid: Disabled, ScanOnly, FilterSaveExact. Running as ScanOnly.")); flag2 = true; flag = false; } int positionPrecision = Math.Max(1, BalguitoFixSave.RepairPositionPrecision.Value); int rotationPrecision = Math.Max(1, BalguitoFixSave.RepairRotationPrecision.Value); int num = Math.Max(0, BalguitoFixSave.RepairMaxRemovalsPerSave.Value); bool value = BalguitoFixSave.RepairIncludePortals.Value; Dictionary<DedupeKey, ZdoRef> dictionary = new Dictionary<DedupeKey, ZdoRef>(); Dictionary<DedupeKey, DuplicateGroupStats> dictionary2 = new Dictionary<DedupeKey, DuplicateGroupStats>(); List<ZdoRef> list = new List<ZdoRef>(); for (int i = 0; i < objectsByChunk.Count; i++) { List<ZDO> item = objectsByChunk[i].Item2; if (item == null) { continue; } for (int j = 0; j < item.Count; j++) { ZDO val = item[j]; if (val == null || (!value && IsPortal(val))) { continue; } DedupeKey key = DedupeKey.FromZdo(val, positionPrecision, rotationPrecision); ZdoRef zdoRef = new ZdoRef(i, j, val); if (!dictionary.TryGetValue(key, out var value2)) { dictionary[key] = zdoRef; dictionary2[key] = new DuplicateGroupStats(key, zdoRef); continue; } DuplicateGroupStats duplicateGroupStats = dictionary2[key]; duplicateGroupStats.Count++; if (IsBetterKeeper(zdoRef.Zdo, value2.Zdo)) { list.Add(value2); dictionary[key] = zdoRef; duplicateGroupStats.Keeper = zdoRef; } else { list.Add(zdoRef); } } } List<DuplicateGroupStats> list2 = (from x in dictionary2.Values where x.Count > 1 orderby x.Count descending, x.Key.Prefab select x).ToList(); int num2 = list2.Sum((DuplicateGroupStats x) => x.Count - 1); if (num2 > 0 || BalguitoFixSave.Verbose.Value || FullRepairState.Active) { BalguitoFixSave.Log.LogInfo((object)string.Format("[DedupeSave] Mode={0}, DuplicateGroups={1:N0}, DuplicateZDOs={2:N0}, IncludePortals={3}", flag ? "FilterSaveExact" : "ScanOnly", list2.Count, num2, value)); } LogDuplicateGroups(list2); if (!flag) { return; } if (list.Count == 0) { if (BalguitoFixSave.Verbose.Value || FullRepairState.Active) { BalguitoFixSave.Log.LogInfo((object)"[DedupeSave] No duplicate save clones to remove."); } } else if (num > 0 && list.Count > num) { BalguitoFixSave.Log.LogError((object)$"[DedupeSave] Refusing to remove {list.Count:N0} duplicates because MaxRemovalsPerSave={num:N0}. Increase cap or set 0 for no cap."); } else { int num3 = RemoveFromSaveCloneLists(objectsByChunk, list); BalguitoFixSave.Log.LogWarning((object)$"[DedupeSave] Removed {num3:N0} duplicate ZDO save clone(s). This affects the saved .chunk output, not live memory."); } } private static string NormalizeMode(string mode) { return (mode ?? string.Empty).Trim().Replace("_", string.Empty).Replace("-", string.Empty) .ToLowerInvariant(); } private static bool IsPortal(ZDO zdo) { try { return (Object)(object)Game.instance != (Object)null && Game.instance.PortalPrefabHash != null && Game.instance.PortalPrefabHash.Contains(zdo.GetPrefab()); } catch { return false; } } private static bool IsBetterKeeper(ZDO candidate, ZDO current) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) if (candidate == null) { return false; } if (current == null) { return true; } if (candidate.DataRevision != current.DataRevision) { return candidate.DataRevision > current.DataRevision; } if (candidate.OwnerRevision != current.OwnerRevision) { return candidate.OwnerRevision > current.OwnerRevision; } return CompareZdoId(candidate.m_uid, current.m_uid) > 0; } private static int CompareZdoId(ZDOID a, ZDOID b) { int num = ((ZDOID)(ref a)).UserID.CompareTo(((ZDOID)(ref b)).UserID); if (num != 0) { return num; } return ((ZDOID)(ref a)).ID.CompareTo(((ZDOID)(ref b)).ID); } private static void LogDuplicateGroups(List<DuplicateGroupStats> groups) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) if (groups.Count == 0) { return; } int num = Math.Max(0, BalguitoFixSave.RepairTopGroupsToLog.Value); foreach (DuplicateGroupStats item in groups.Take(num)) { Vector3 val = item.Key.ToApproxPosition(BalguitoFixSave.RepairPositionPrecision.Value); BalguitoFixSave.Log.LogWarning((object)$"[DedupeSave] group prefabHash={item.Key.Prefab}, count={item.Count:N0}, approxPos=({val.x:F2},{val.y:F2},{val.z:F2}), keeper={DescribeZdo(item.Keeper.Zdo)}"); } if (groups.Count > num) { BalguitoFixSave.Log.LogWarning((object)$"[DedupeSave] ... {groups.Count - num:N0} more duplicate group(s) not logged."); } } private static int RemoveFromSaveCloneLists(List<Tuple<ChunkIndex, List<ZDO>>> objectsByChunk, List<ZdoRef> refsToRemove) { Dictionary<int, HashSet<ZDO>> dictionary = new Dictionary<int, HashSet<ZDO>>(); foreach (ZdoRef item2 in refsToRemove) { if (!dictionary.TryGetValue(item2.ChunkListIndex, out var value)) { value = new HashSet<ZDO>(); dictionary[item2.ChunkListIndex] = value; } value.Add(item2.Zdo); } int num = 0; foreach (KeyValuePair<int, HashSet<ZDO>> entry in dictionary) { int key = entry.Key; if (key < 0 || key >= objectsByChunk.Count) { continue; } List<ZDO> item = objectsByChunk[key].Item2; if (item != null && item.Count != 0) { int count = item.Count; item.RemoveAll((ZDO zdo) => entry.Value.Contains(zdo)); num += count - item.Count; } } return num; } private static string DescribeZdo(ZDO zdo) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) if (zdo == null) { return "null"; } Vector3 position = zdo.GetPosition(); return $"uid={zdo.m_uid}, prefabHash={zdo.GetPrefab()}, pos=({position.x:F2},{position.y:F2},{position.z:F2}), dataRev={zdo.DataRevision}, ownerRev={zdo.OwnerRevision}"; } } internal static class ChunkDebug { public static void LogOverlaps(Dictionary<ChunkIndex, ChunkInfo> chunks, string phase) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_005b: 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_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) List<ChunkIndex> list = chunks.Keys.ToList(); int num = 0; for (int i = 0; i < list.Count; i++) { for (int j = i + 1; j < list.Count; j++) { ChunkIndex val = list[i]; ChunkIndex val2 = list[j]; if (Overlaps(val, val2)) { num++; BalguitoFixSave.Log.LogError((object)$"[ChunkedSaveFix] OVERLAP {phase} #{num}: A={DescribeChunk(val, chunks[val])} | B={DescribeChunk(val2, chunks[val2])}"); } } } if (num == 0) { BalguitoFixSave.Log.LogInfo((object)("[ChunkedSaveFix] No chunk overlaps detected in manifest " + phase + ".")); } else { BalguitoFixSave.Log.LogError((object)$"[ChunkedSaveFix] Detected {num:N0} chunk overlap(s) in manifest {phase}."); } } public static bool Overlaps(ChunkIndex a, ChunkIndex b) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) ValueTuple<int, int> zoneFromChunk = ZoneSystem.GetZoneFromChunk(a); (int, int) zoneFromChunk2 = ZoneSystem.GetZoneFromChunk(b); int num = ChunkWidth(a); int num2 = ChunkWidth(b); int item = zoneFromChunk.Item1; int item2 = zoneFromChunk.Item2; int num3 = item + num; int num4 = item2 + num; int item3 = zoneFromChunk2.Item1; int item4 = zoneFromChunk2.Item2; int num5 = item3 + num2; int num6 = item4 + num2; if (item < num5 && num3 > item3 && item2 < num6) { return num4 > item4; } return false; } public static int ChunkWidth(ChunkIndex chunkIndex) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) return 64 >> (int)chunkIndex.m_chunkSize; } public static string DescribeChunkIndex(ChunkIndex chunkIndex) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) (int, int) zoneFromChunk = ZoneSystem.GetZoneFromChunk(chunkIndex); return $"chunk=0x{chunkIndex.Chunk:x4}, " + $"size={chunkIndex.m_chunkSize}, " + $"zone=({zoneFromChunk.Item1},{zoneFromChunk.Item2}), " + $"width={ChunkWidth(chunkIndex)}, " + $"filePrefix={chunkIndex.Chunk >> 8:x2}_{chunkIndex.Chunk & 0xFF:x2}__{chunkIndex.m_chunkSize}"; } public static string DescribeChunk(ChunkIndex chunkIndex, ChunkInfo info) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) if (info == null) { return DescribeChunkIndex(chunkIndex) + ", info=null"; } return DescribeChunkIndex(chunkIndex) + ", " + $"version={info.m_version}, " + $"zdos={info.m_numZDOs:N0}, " + $"sizeChanged={info.m_sizeChanged}, " + "file=" + ChunkFilename(chunkIndex, info); } public static string ChunkFilename(ChunkIndex chunkIndex, ChunkInfo info) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) return $"{chunkIndex.Chunk >> 8:x2}_" + $"{chunkIndex.Chunk & 0xFF:x2}__" + $"{chunkIndex.m_chunkSize}_" + $"{info.m_version}.chunk"; } } internal static class FullRepairState { public static bool Active; public static HashSet<ChunkIndex> WrittenChunks = new HashSet<ChunkIndex>(); public static void BeforeGetSaveClonePerChunk(ZDOMan zdoMan) { Active = false; WrittenChunks.Clear(); if (BalguitoFixSave.Enabled.Value && BalguitoFixSave.ForceFullRepairSave.Value) { if (NormalizeMode(BalguitoFixSave.RepairMode.Value) == "disabled") { BalguitoFixSave.Log.LogWarning((object)"[FullRepair] ForceFullRepairSave=true but RepairMode=Disabled; skipping full repair."); return; } int num = MarkAllPossibleChunksDirty(zdoMan); Active = true; BalguitoFixSave.Log.LogWarning((object)$"[FullRepair] ForceFullRepairSave active. Marked all possible non-portal chunk indexes as dirty. AddedToDirtySet={num:N0}"); } } public static void AfterGetSaveClonePerChunk(List<Tuple<ChunkIndex, List<ZDO>>> objectsByChunk) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) WrittenChunks.Clear(); if (!Active || objectsByChunk == null) { return; } foreach (Tuple<ChunkIndex, List<ZDO>> item in objectsByChunk) { WrittenChunks.Add(item.Item1); } BalguitoFixSave.Log.LogWarning((object)$"[FullRepair] Full repair save output contains {WrittenChunks.Count:N0} chunk(s) to keep in manifest."); } public static int PruneManifest(Dictionary<ChunkIndex, ChunkInfo> chunks) { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) if (!Active) { return 0; } if (!BalguitoFixSave.PruneManifestToFullRepairChunks.Value) { return 0; } if (WrittenChunks == null || WrittenChunks.Count == 0) { BalguitoFixSave.Log.LogError((object)"[FullRepair] Refusing to prune manifest: WrittenChunks is empty."); return 0; } List<ChunkIndex> list = (from chunkIndex in chunks.Keys where !IsPortalChunk(chunkIndex) where !WrittenChunks.Contains(chunkIndex) select chunkIndex).ToList(); foreach (ChunkIndex item in list) { ChunkInfo info = chunks[item]; chunks.Remove(item); BalguitoFixSave.Log.LogWarning((object)("[FullRepair] Pruned stale non-rewritten manifest chunk: " + ChunkDebug.DescribeChunk(item, info))); } if (list.Count > 0) { BalguitoFixSave.Log.LogWarning((object)$"[FullRepair] Pruned {list.Count:N0} stale manifest chunk(s) not present in full repair output."); } return list.Count; } private static int MarkAllPossibleChunksDirty(ZDOMan zdoMan) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) if (zdoMan == null || zdoMan.m_dirtyChunks == null || zdoMan.m_dirtyChunks.Length == 0) { BalguitoFixSave.Log.LogError((object)"[FullRepair] Cannot mark dirty chunks: ZDOMan.m_dirtyChunks unavailable."); return 0; } HashSet<ChunkIndex> hashSet = zdoMan.m_dirtyChunks[0]; if (hashSet == null) { BalguitoFixSave.Log.LogError((object)"[FullRepair] Cannot mark dirty chunks: m_dirtyChunks[0] is null."); return 0; } int count = hashSet.Count; for (byte b = 0; b <= 3; b++) { for (int i = 0; i <= 65535; i++) { hashSet.Add(new ChunkIndex((ushort)i, b)); } } return hashSet.Count - count; } private static bool IsPortalChunk(ChunkIndex chunkIndex) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) return ((ChunkIndex)(ref chunkIndex)).Equals(ZoneSystem.ChunkPortal); } private static string NormalizeMode(string mode) { return (mode ?? string.Empty).Trim().Replace("_", string.Empty).Replace("-", string.Empty) .ToLowerInvariant(); } } internal static class PluginInfo { public const string PLUGIN_GUID = "balguito.valheim.fixsave"; public const string PLUGIN_NAME = "Balguito Fix Save"; public const string PLUGIN_VERSION = "0.2.0"; public const string BUILD_ID = "20260629-211115"; }