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).");
}
}