Decompiled source of OdinSaves v1.4.0

OdinSaves.dll

Decompiled a year ago
using System;
using System.Collections;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("OdinSaves")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("OdinSaves")]
[assembly: AssemblyCopyright("Copyright ©  2021")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("649fad21-6342-4434-8747-28db2fe311bc")]
[assembly: AssemblyFileVersion("1.4.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.4.0.0")]
[module: UnverifiableCode]
namespace OdinSaves;

[BepInPlugin("redseiko.valheim.odinsaves", "OdinSaves", "1.4.0")]
public class OdinSaves : BaseUnityPlugin
{
	public const string PluginGuid = "redseiko.valheim.odinsaves";

	public const string PluginName = "OdinSaves";

	public const string PluginVersion = "1.4.0";

	private static ManualLogSource _logger;

	private Harmony _harmony;

	private void Awake()
	{
		PluginConfig.BindConfig(((BaseUnityPlugin)this).Config);
		_logger = ((BaseUnityPlugin)this).Logger;
		_harmony = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "redseiko.valheim.odinsaves");
	}

	private void OnDestroy()
	{
		Harmony harmony = _harmony;
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
	}

	public static void LogInfo(string message)
	{
		_logger.LogInfo((object)("[" + DateTime.Now.ToString(DateTimeFormatInfo.InvariantInfo) + "] " + message));
	}
}
[HarmonyPatch(typeof(FejdStartup))]
internal static class FejdStartupPatch
{
	private static readonly ZPackage _mapPackage = new ZPackage();

	private static GameObject _profileCompressionRoot;

	private static Button _compressMapDataButton;

	private static TMP_Text _profileCompressionText;

	private static byte[] CompressMapData(ref byte[] mapData)
	{
		if (mapData == null || IsCompressedMapData(mapData))
		{
			return mapData;
		}
		_mapPackage.Clear();
		_mapPackage.Write(Minimap.MAPVERSION);
		_mapPackage.Write(Utils.Compress(mapData));
		return _mapPackage.GetArray();
	}

	private static bool IsCompressedMapData(byte[] data)
	{
		if (data != null && data.Length >= 4)
		{
			return BitConverter.ToInt32(data, 0) >= 7;
		}
		return false;
	}

	private static bool HasUncompressedData(PlayerProfile profile)
	{
		return profile.m_worldData.Values.Any((WorldPlayerData value) => value.m_mapData != null && !IsCompressedMapData(value.m_mapData));
	}

	[HarmonyPostfix]
	[HarmonyPatch("Awake")]
	private static void AwakePostfix(ref FejdStartup __instance)
	{
		if (PluginConfig.IsModEnabled.Value && PluginConfig.EnableMapDataCompression.Value)
		{
			_profileCompressionRoot = CreateCompressionRoot(__instance.m_selectCharacterPanel.transform);
			_compressMapDataButton = CreateCompressMapDataButton(__instance, _profileCompressionRoot.transform);
			_profileCompressionText = CreateProfileCompressionText(__instance, _profileCompressionRoot.transform);
		}
	}

	[HarmonyPostfix]
	[HarmonyPatch("UpdateCharacterList")]
	private static void UpdateCharacterListPostfix(ref FejdStartup __instance)
	{
		if (PluginConfig.IsModEnabled.Value && PluginConfig.EnableMapDataCompression.Value && Object.op_Implicit((Object)(object)_profileCompressionRoot) && __instance.m_profileIndex >= 0 && __instance.m_profileIndex < __instance.m_profiles.Count)
		{
			PlayerProfile profile = __instance.m_profiles[__instance.m_profileIndex];
			UpdateCompressMapDataButton(__instance, profile);
			UpdateProfileCompressionText(profile);
		}
	}

	private static GameObject CreateCompressionRoot(Transform parent)
	{
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: 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_005c: 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_0085: Unknown result type (might be due to invalid IL or missing references)
		//IL_008f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bf: Expected O, but got Unknown
		GameObject val = new GameObject("CompressionRoot", new Type[2]
		{
			typeof(RectTransform),
			typeof(VerticalLayoutGroup)
		});
		val.transform.SetParent(parent);
		RectTransform component = val.GetComponent<RectTransform>();
		component.anchorMin = new Vector2(0.5f, 0f);
		component.anchorMax = new Vector2(0.5f, 0f);
		component.pivot = new Vector2(0.5f, 0f);
		component.anchoredPosition = new Vector2(410f, 74f);
		VerticalLayoutGroup component2 = val.GetComponent<VerticalLayoutGroup>();
		((HorizontalOrVerticalLayoutGroup)component2).childControlHeight = false;
		((HorizontalOrVerticalLayoutGroup)component2).childControlWidth = false;
		((HorizontalOrVerticalLayoutGroup)component2).childForceExpandHeight = false;
		((HorizontalOrVerticalLayoutGroup)component2).childForceExpandWidth = false;
		((LayoutGroup)component2).childAlignment = (TextAnchor)1;
		val.SetActive(true);
		return val;
	}

	private static Button CreateCompressMapDataButton(FejdStartup fejdStartup, Transform parent)
	{
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Expected O, but got Unknown
		Button obj = Object.Instantiate<Button>(fejdStartup.m_csNewButton, parent);
		((UnityEventBase)obj.onClick).RemoveAllListeners();
		obj.onClick = new ButtonClickedEvent();
		((Component)obj).GetComponent<RectTransform>().SetSizeWithCurrentAnchors((Axis)0, 200f);
		TMP_Text componentInChildren = ((Component)obj).GetComponentInChildren<TMP_Text>();
		((Component)componentInChildren).GetComponent<RectTransform>().SetSizeWithCurrentAnchors((Axis)0, 200f);
		componentInChildren.text = "Compression";
		((Object)((Component)obj).gameObject).name = "CompressMapData.Button";
		((Component)obj).gameObject.SetActive(false);
		return obj;
	}

	private static TMP_Text CreateProfileCompressionText(FejdStartup fejdStartup, Transform parent)
	{
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0064: Unknown result type (might be due to invalid IL or missing references)
		TMP_Text obj = Object.Instantiate<TMP_Text>(fejdStartup.m_csName, parent);
		obj.fontSize = 20f;
		((Object)((Component)obj).gameObject).name = "ProfileCompression.Text";
		obj.text = "Profile Compression Text";
		RectTransform component = ((Component)obj).GetComponent<RectTransform>();
		component.anchorMin = Vector2.zero;
		component.anchorMax = Vector2.zero;
		component.pivot = Vector2.zero;
		component.anchoredPosition = new Vector2(-146f, -89f);
		component.ForceUpdateRectTransforms();
		return obj;
	}

	private static void UpdateCompressMapDataButton(FejdStartup fejdStartup, PlayerProfile profile)
	{
		//IL_005e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Expected O, but got Unknown
		bool flag = HasUncompressedData(profile);
		((Component)_compressMapDataButton).GetComponentInChildren<TMP_Text>().text = (flag ? "Compress MapData" : "Compressed!");
		((UnityEventBase)_compressMapDataButton.onClick).RemoveAllListeners();
		((UnityEvent)_compressMapDataButton.onClick).AddListener((UnityAction)delegate
		{
			((MonoBehaviour)fejdStartup).StartCoroutine(CompressProfileMapDataCoroutine(fejdStartup, profile));
		});
		((Selectable)_compressMapDataButton).interactable = flag;
		((Component)_compressMapDataButton).gameObject.SetActive(true);
	}

	private static void UpdateProfileCompressionText(PlayerProfile profile)
	{
		float num = profile.m_worldData.Values.Select(delegate(WorldPlayerData value)
		{
			byte[] mapData = value.m_mapData;
			return (mapData != null) ? mapData.Length : 0;
		}).Sum();
		int num2 = profile.m_worldData.Values.Select((WorldPlayerData value) => (value.m_mapData == null || IsCompressedMapData(value.m_mapData)) ? 1 : 0).Sum();
		_profileCompressionText.text = string.Format("Worlds: <color={0}>{1}</color>/<color={0}>{2}</color> compressed   MapData: <color={0}>{3}</color> KB", "orange", profile.m_worldData.Count, num2, (num / 1024f).ToString("N0"));
	}

	private static IEnumerator CompressProfileMapDataCoroutine(FejdStartup fejdStartup, PlayerProfile profile)
	{
		Selectable[] selectables = Object.FindObjectsOfType<Selectable>();
		Selectable[] array = selectables;
		for (int i = 0; i < array.Length; i++)
		{
			array[i].interactable = false;
		}
		long count = 0L;
		TMP_Text buttonText = ((Component)_compressMapDataButton).GetComponentInChildren<TMP_Text>();
		foreach (WorldPlayerData value in profile.m_worldData.Values)
		{
			long num = count + 1;
			count = num;
			buttonText.text = $"Compressing... {num}/{profile.m_worldData.Count}";
			value.m_mapData = CompressMapData(ref value.m_mapData);
			yield return null;
		}
		array = selectables;
		for (int i = 0; i < array.Length; i++)
		{
			array[i].interactable = true;
		}
		profile.SavePlayerToDisk();
		fejdStartup.UpdateCharacterList();
		_mapPackage.Clear();
	}
}
[HarmonyPatch(typeof(Game))]
internal static class GamePatch
{
	private static float _savePlayerProfileTimer;

	[HarmonyPostfix]
	[HarmonyPatch("SavePlayerProfile")]
	private static void SavePlayerProfilePostfix()
	{
		_savePlayerProfileTimer = 0f;
	}

	[HarmonyPrefix]
	[HarmonyPatch("UpdateSaving")]
	private static void UpdateSavingPrefix(float dt)
	{
		_savePlayerProfileTimer += dt;
	}

	[HarmonyPostfix]
	[HarmonyPatch("UpdateSaving")]
	private static void UpdateSavingPostfix(ref Game __instance, float dt)
	{
		if (PluginConfig.IsModEnabled.Value && !(_savePlayerProfileTimer <= 0f) && !(_savePlayerProfileTimer < (float)PluginConfig.SavePlayerProfileInterval.Value))
		{
			if (PluginConfig.ShowMessageOnModSave.Value)
			{
				MessageHud.instance.ShowMessage((MessageType)2, "Saving player profile...", 0, (Sprite)null);
			}
			_savePlayerProfileTimer = 0f;
			__instance.SavePlayerProfile(PluginConfig.SetLogoutPointOnSave.Value);
		}
	}
}
[HarmonyPatch(typeof(Player))]
internal static class PlayerPatch
{
	[HarmonyPostfix]
	[HarmonyPatch("OnDeath")]
	private static void OnDeathPostfix(ref Player __instance)
	{
		Game.instance.m_playerProfile.ClearLoguoutPoint();
	}
}
public static class PluginConfig
{
	public static ConfigFile Instance { get; private set; }

	public static ConfigEntry<bool> IsModEnabled { get; private set; }

	public static ConfigEntry<int> SavePlayerProfileInterval { get; private set; }

	public static ConfigEntry<bool> SetLogoutPointOnSave { get; private set; }

	public static ConfigEntry<bool> ShowMessageOnModSave { get; private set; }

	public static ConfigEntry<bool> EnableMapDataCompression { get; private set; }

	public static void BindConfig(ConfigFile config)
	{
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Expected O, but got Unknown
		Instance = config;
		IsModEnabled = config.Bind<bool>("Global", "isModEnabled", true, "Globally enable or disable this mod.");
		SavePlayerProfileInterval = config.Bind<int>("Global", "savePlayerProfileInterval", 300, new ConfigDescription("Interval (seconds) for how often to save the player profile. Game default (and maximum) is 1200s.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(5, 1200), Array.Empty<object>()));
		SetLogoutPointOnSave = config.Bind<bool>("Global", "setLogoutPointOnSave", true, "Sets your logout point to your current position when the mod performs a save.");
		ShowMessageOnModSave = config.Bind<bool>("Global", "saveMessageOnModSave", true, "Show a message (in the middle of your screen) when the mod tries to save.");
		EnableMapDataCompression = config.Bind<bool>("MapData.Compression", "enableMapDataCompression", false, "Enables the MapData compression feature on the character select screen (restart required).");
	}
}