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 WorldSavePlus v1.0.1
plugins\WorldSavePlus.dll
Decompiled a week agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; using WorldSavePlus.Localization; using WorldSavePlus.Save; using WorldSavePlus.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_guiutils")] [assembly: IgnoresAccessChecksTo("assembly_utils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("PONEIS")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Save dedicated Valheim worlds from the ESC menu with configurable permissions and cooldown")] [assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1+b16a24734ce7df434951783cff7b515348998db4")] [assembly: AssemblyProduct("WorldSavePlus")] [assembly: AssemblyTitle("WorldSavePlus")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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 WorldSavePlus { internal static class ModConfig { internal static ConfigEntry<bool> AllowEveryone; internal static ConfigEntry<int> CooldownSeconds; internal static ConfigEntry<bool> EnableHotkey; internal static ConfigEntry<KeyCode> Hotkey; internal static ConfigEntry<string> Language; internal static void Bind(ConfigFile config) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown AllowEveryone = config.Bind<bool>("Permissions", "AllowEveryone", false, "SERVER: If true, any player can request a world save. If false, only adminlist.txt entries can (list must be non-empty)."); CooldownSeconds = config.Bind<int>("Permissions", "CooldownSeconds", 30, new ConfigDescription("SERVER: Global cooldown in seconds after an accepted client save request.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(5, 300), Array.Empty<object>())); EnableHotkey = config.Bind<bool>("Client", "EnableHotkey", true, "CLIENT: Allow requesting a world save with Hotkey."); Hotkey = config.Bind<KeyCode>("Client", "Hotkey", (KeyCode)287, "CLIENT: Key to request a world save (same as ESC Save on dedicated). None disables."); Language = config.Bind<string>("Localization", "Language", "Auto", "Language for WorldSavePlus messages. Auto follows the game. Values: Auto, English, Portuguese."); Language.SettingChanged += delegate { Loc.ApplyLanguagePreference(); }; } } [BepInPlugin("poneis.valheim.WorldSavePlus", "WorldSavePlus", "1.0.1")] public class Plugin : BaseUnityPlugin { public const string PluginGUID = "poneis.valheim.WorldSavePlus"; public const string PluginName = "WorldSavePlus"; public const string PluginVersion = "1.0.1"; internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } private void Awake() { Instance = this; Log = ((BaseUnityPlugin)this).Logger; ModConfig.Bind(((BaseUnityPlugin)this).Config); Loc.Init(); SaveCooldown.Reset(); Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "poneis.valheim.WorldSavePlus"); ((Component)this).gameObject.AddComponent<HotkeyListener>(); Log.LogInfo((object)"WorldSavePlus 1.0.1 loaded."); } } internal sealed class HotkeyListener : MonoBehaviour { private void Update() { //IL_0020: 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) if (ModConfig.EnableHotkey != null && ModConfig.EnableHotkey.Value && ModConfig.Hotkey != null && (int)ModConfig.Hotkey.Value != 0 && Input.GetKeyDown(ModConfig.Hotkey.Value) && !Console.IsVisible() && (!((Object)(object)Chat.instance != (Object)null) || !Chat.instance.HasFocus())) { WorldSaveService.RequestFromClient(); } } } } namespace WorldSavePlus.UI { internal static class ServerSaveButton { [CompilerGenerated] private static class <>O { public static UnityAction <0>__OnClicked; } internal const string ObjectName = "WorldSavePlus_SaveServer"; private static Button _button; private static TMP_Text _label; internal static void Ensure(Menu menu) { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Expected O, but got Unknown //IL_00c7: 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_00d2: Expected O, but got Unknown if ((Object)(object)menu == (Object)null || (Object)(object)menu.m_saveButton == (Object)null) { return; } if ((Object)(object)_button != (Object)null && Object.op_Implicit((Object)(object)_button)) { RefreshLabel(); return; } GameObject gameObject = ((Component)menu.m_saveButton).gameObject; Transform parent = gameObject.transform.parent; GameObject val = Object.Instantiate<GameObject>(gameObject, parent); ((Object)val).name = "WorldSavePlus_SaveServer"; int siblingIndex = gameObject.transform.GetSiblingIndex(); val.transform.SetSiblingIndex(siblingIndex + 1); _button = val.GetComponent<Button>(); _label = val.GetComponentInChildren<TMP_Text>(true); if ((Object)(object)_button != (Object)null) { _button.onClick = new ButtonClickedEvent(); ButtonClickedEvent onClick = _button.onClick; object obj = <>O.<0>__OnClicked; if (obj == null) { UnityAction val2 = OnClicked; <>O.<0>__OnClicked = val2; obj = (object)val2; } ((UnityEvent)onClick).AddListener((UnityAction)obj); } RefreshLabel(); Plugin.Log.LogInfo((object)"ESC 'Save Server' button injected."); } internal static void UpdateVisibility(Menu menu) { Ensure(menu); if (!((Object)(object)_button == (Object)null) && Object.op_Implicit((Object)(object)_button)) { bool flag = ShouldShow(); if (((Component)_button).gameObject.activeSelf != flag) { ((Component)_button).gameObject.SetActive(flag); } if (flag) { RefreshLabel(); } if ((Object)(object)menu != (Object)null) { menu.m_rebuildLayout = true; } } } internal static void RefreshLabel() { if (!((Object)(object)_label == (Object)null) && Object.op_Implicit((Object)(object)_label)) { _label.text = Loc.T("wsp_btn_save_server"); } } private static bool ShouldShow() { if ((Object)(object)ZNet.instance == (Object)null) { return false; } if (ZNet.IsSinglePlayer) { return false; } return true; } private static void OnClicked() { WorldSaveService.RequestFromClient(); } internal static void Reset() { _button = null; _label = null; } } } namespace WorldSavePlus.Save { internal static class SaveCooldown { private static float _nextAllowedUnscaledTime; internal static void Reset() { _nextAllowedUnscaledTime = 0f; } internal static bool TryBegin(out float remainingSeconds) { float unscaledTime = Time.unscaledTime; if (unscaledTime < _nextAllowedUnscaledTime) { remainingSeconds = _nextAllowedUnscaledTime - unscaledTime; return false; } int num = ModConfig.CooldownSeconds?.Value ?? 30; if (num < 1) { num = 1; } _nextAllowedUnscaledTime = unscaledTime + (float)num; remainingSeconds = 0f; return true; } internal static void Rollback() { _nextAllowedUnscaledTime = 0f; } } internal static class SavePermission { internal static bool AdminListHasEntries(ZNet znet) { if ((Object)(object)znet == (Object)null) { return false; } List<string> adminList = znet.GetAdminList(); if (adminList == null || adminList.Count == 0) { return false; } for (int i = 0; i < adminList.Count; i++) { if (!string.IsNullOrWhiteSpace(adminList[i])) { return true; } } return false; } internal static bool CanRequestSave(ZNet znet, ZRpc rpc, out string denyKey) { denyKey = null; if ((Object)(object)znet == (Object)null || rpc == null) { denyKey = "wsp_denied"; return false; } if (ModConfig.AllowEveryone.Value) { return true; } if (!AdminListHasEntries(znet)) { denyKey = "wsp_denied_empty_list"; return false; } ISocket socket = rpc.GetSocket(); string text = ((socket != null) ? socket.GetHostName() : null); if (string.IsNullOrEmpty(text) || !znet.IsAdmin(text)) { denyKey = "wsp_denied"; return false; } return true; } } internal static class WorldSaveService { internal const string ToastRpc = "WorldSavePlus.Toast"; internal static void RegisterRpcs() { if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register<string, int>("WorldSavePlus.Toast", (Action<long, string, int>)RPC_Toast); } } private static void RPC_Toast(long sender, string key, int arg) { if (!string.IsNullOrEmpty(key)) { ShowLocal((arg >= 0) ? Loc.Tf(key, arg) : Loc.T(key)); } } internal static void NotifyPeer(ZNet znet, ZRpc rpc, string key, int arg = -1) { if (!((Object)(object)znet == (Object)null) && rpc != null && !string.IsNullOrEmpty(key)) { string text = ((arg >= 0) ? Loc.Tf(key, arg) : Loc.T(key)); znet.RemotePrint(rpc, text); ZNetPeer peer = znet.GetPeer(rpc); if (peer != null && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(peer.m_uid, "WorldSavePlus.Toast", new object[2] { key, arg }); } } } internal static void RequestFromClient() { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { ShowLocal(Loc.T("wsp_no_server")); } else if (instance.IsServer()) { Game instance2 = Game.instance; if (instance2 != null) { instance2.SavePlayerProfile(true, false); } instance.SaveWorldAndPlayerProfiles(); ShowLocal(Loc.T("wsp_saving")); } else if (instance.GetServerRPC() == null) { ShowLocal(Loc.T("wsp_no_server")); } else { ShowLocal(Loc.T("wsp_saving")); instance.SaveWorldAndPlayerProfiles(); } } internal static void ShowLocal(string message) { if (!string.IsNullOrEmpty(message) && (Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, message, 0, (Sprite)null, false, true); } } internal static bool TryExecuteClientSave(ZNet znet, ZRpc rpc) { bool flag = default(bool); if (!znet.EnoughDiskSpaceAvailable(ref flag, false, (Action<bool>)null)) { NotifyPeer(znet, rpc, "wsp_disk"); return false; } NotifyPeer(znet, rpc, "wsp_saving"); Game instance = Game.instance; if (instance != null) { instance.SavePlayerProfile(true, false); } znet.Save(false, true, !znet.IsDedicated()); NotifyPeer(znet, rpc, "wsp_saved"); Plugin.Log.LogInfo((object)"World save requested by client and accepted."); return true; } } } namespace WorldSavePlus.Patches { [HarmonyPatch(typeof(Game), "Start")] internal static class GameStartPatch { private static void Postfix() { WorldSaveService.RegisterRpcs(); Loc.ApplyLanguagePreference(); } } [HarmonyPatch(typeof(Menu), "Show")] internal static class MenuShowPatch { private static void Postfix(Menu __instance) { ServerSaveButton.UpdateVisibility(__instance); } } [HarmonyPatch(typeof(Menu), "SetButtonsEnabled")] internal static class MenuSetButtonsPatch { private static void Postfix(Menu __instance) { ServerSaveButton.UpdateVisibility(__instance); } } [HarmonyPatch(typeof(Menu), "OnDestroy")] internal static class MenuOnDestroyPatch { private static void Postfix() { ServerSaveButton.Reset(); } } [HarmonyPatch(typeof(ZNet), "RPC_Save")] internal static class RpcSavePatch { private static bool Prefix(ZNet __instance, ZRpc rpc) { if (!__instance.IsServer() || rpc == null) { return true; } if (!SavePermission.CanRequestSave(__instance, rpc, out var denyKey)) { WorldSaveService.NotifyPeer(__instance, rpc, denyKey ?? "wsp_denied"); return false; } if (!SaveCooldown.TryBegin(out var remainingSeconds)) { int arg = Mathf.Max(1, Mathf.CeilToInt(remainingSeconds)); WorldSaveService.NotifyPeer(__instance, rpc, "wsp_cooldown", arg); return false; } if (!WorldSaveService.TryExecuteClientSave(__instance, rpc)) { SaveCooldown.Rollback(); return false; } return false; } } } namespace WorldSavePlus.Localization { internal static class Loc { private static readonly Dictionary<string, Dictionary<string, string>> Tables = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase); private static string _activeCode = "en"; private static string LangDir => Path.Combine(Paths.ConfigPath, "WorldSavePlus", "lang"); internal static void Init() { EnsureLangFolder(); LoadEmbedded("en"); LoadEmbedded("pt"); LoadDiskOverrides(); ApplyLanguagePreference(); Plugin.Log.LogInfo((object)("Localization ready (active=" + _activeCode + ", files in " + LangDir + ")")); } internal static string T(string key) { if (string.IsNullOrEmpty(key)) { return key; } if (Tables.TryGetValue(_activeCode, out var value) && value.TryGetValue(key, out var value2)) { return value2; } if (Tables.TryGetValue("en", out var value3) && value3.TryGetValue(key, out value2)) { return value2; } return key; } internal static string Tf(string key, params object[] args) { try { return string.Format(T(key), args); } catch (FormatException) { return T(key); } } private static void EnsureLangFolder() { Directory.CreateDirectory(LangDir); WriteEmbedded("en"); WriteEmbedded("pt"); } private static void WriteEmbedded(string code) { string path = Path.Combine(LangDir, code + ".json"); string text = ReadEmbedded(code); if (!string.IsNullOrEmpty(text)) { File.WriteAllText(path, text, Encoding.UTF8); } } private static void LoadEmbedded(string code) { string text = ReadEmbedded(code); if (!string.IsNullOrEmpty(text)) { Tables[code] = ParseFlatJson(text); } } private static void LoadDiskOverrides() { if (!Directory.Exists(LangDir)) { return; } string[] files = Directory.GetFiles(LangDir, "*.json"); foreach (string text in files) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text); try { Dictionary<string, string> dictionary = ParseFlatJson(File.ReadAllText(text, Encoding.UTF8)); if (dictionary.Count == 0) { continue; } if (!Tables.TryGetValue(fileNameWithoutExtension, out var value)) { Tables[fileNameWithoutExtension] = dictionary; continue; } foreach (KeyValuePair<string, string> item in dictionary) { value[item.Key] = item.Value; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to load lang file " + text + ": " + ex.Message)); } } } private static string ReadEmbedded(string code) { string text = "WorldSavePlus.Languages." + code + ".json"; using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(text); if (stream == null) { Plugin.Log.LogWarning((object)("Missing embedded language resource: " + text)); return null; } using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8); return streamReader.ReadToEnd(); } internal static void ApplyLanguagePreference() { string a = ModConfig.Language?.Value ?? "Auto"; if (string.Equals(a, "Portuguese", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "pt", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "pt-BR", StringComparison.OrdinalIgnoreCase)) { _activeCode = (Tables.ContainsKey("pt") ? "pt" : "en"); return; } if (string.Equals(a, "English", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "en", StringComparison.OrdinalIgnoreCase)) { _activeCode = "en"; return; } try { string text = ((Localization.instance != null) ? Localization.instance.GetSelectedLanguage() : null); if (!string.IsNullOrEmpty(text) && (text.IndexOf("Portuguese", StringComparison.OrdinalIgnoreCase) >= 0 || text.StartsWith("pt", StringComparison.OrdinalIgnoreCase) || text.IndexOf("Brazil", StringComparison.OrdinalIgnoreCase) >= 0)) { _activeCode = (Tables.ContainsKey("pt") ? "pt" : "en"); return; } } catch { } _activeCode = "en"; } internal static Dictionary<string, string> ParseFlatJson(string json) { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrWhiteSpace(json)) { return dictionary; } int i = 0; SkipWs(json, ref i); if (i >= json.Length || json[i] != '{') { return dictionary; } i++; while (i < json.Length) { SkipWs(json, ref i); if (i < json.Length && json[i] == '}') { break; } if (i < json.Length && json[i] == ',') { i++; continue; } if (!TryReadString(json, ref i, out var value)) { break; } SkipWs(json, ref i); if (i >= json.Length || json[i] != ':') { break; } i++; SkipWs(json, ref i); if (!TryReadString(json, ref i, out var value2)) { break; } dictionary[value] = value2; } return dictionary; } private static void SkipWs(string s, ref int i) { while (i < s.Length && char.IsWhiteSpace(s[i])) { i++; } } private static bool TryReadString(string s, ref int i, out string value) { value = null; SkipWs(s, ref i); if (i >= s.Length || s[i] != '"') { return false; } i++; StringBuilder stringBuilder = new StringBuilder(); while (i < s.Length) { char c = s[i++]; switch (c) { case '"': value = stringBuilder.ToString(); return true; case '\\': if (i < s.Length) { char c2 = s[i++]; StringBuilder stringBuilder2 = stringBuilder; stringBuilder2.Append(c2 switch { 'n' => '\n', 'r' => '\r', 't' => '\t', '"' => '"', '\\' => '\\', _ => c2, }); continue; } break; } stringBuilder.Append(c); } return false; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }