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 RememberServerPassword v1.2.4
BepInEx/plugins/RememberServerPassword/MagiCorp.RememberServerPassword.dll
Decompiled a day agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using BepInEx; using BepInEx.Configuration; 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: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("MagiCorp.RememberServerPassword")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.2.4.0")] [assembly: AssemblyInformationalVersion("1.2.4+7b8abd5dee3ec64ceec8e1941f1c66822a53c8ff")] [assembly: AssemblyProduct("MagiCorp.RememberServerPassword")] [assembly: AssemblyTitle("MagiCorp.RememberServerPassword")] [assembly: AssemblyVersion("1.2.4.0")] namespace MagiCorp.RememberServerPassword; [BepInPlugin("magicorp.valheim.rememberserverpassword", "Remember Server Password", "1.2.4")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "magicorp.valheim.rememberserverpassword"; public const string Name = "Remember Server Password"; public const string Version = "1.2.4"; private const string QuickConnectObjectName = "MagiCorp_RememberServerPassword_QuickConnect"; internal static Plugin Instance; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<bool> AutoSubmit; internal static ConfigEntry<bool> RememberPasswords; internal static ConfigEntry<bool> ShowQuickConnectButton; internal static ConfigEntry<string> LastServer; internal static ConfigEntry<bool> ForgetAllPasswords; internal static ConfigEntry<bool> ForgetLastServer; private static readonly Dictionary<string, string> Passwords = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); private static readonly byte[] DpapiEntropy = Encoding.UTF8.GetBytes("MagiCorp.RememberServerPassword.v1"); private static string StorePath; private static string KeyPath; private static string PendingServerKey; private static string PendingPassword; private static string AutoAttemptServerKey; private static string ActiveServerKey; private static bool QuickConnectPending; private static float NextPasswordPromptAttempt; private void Awake() { //IL_01ab: Unknown result type (might be due to invalid IL or missing references) Instance = this; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable remembered server passwords."); AutoSubmit = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "AutoSubmit", true, "Automatically submit a remembered password when a known server requests one."); RememberPasswords = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "RememberPasswords", true, "Remember manually entered passwords after a successful connection."); ShowQuickConnectButton = ((BaseUnityPlugin)this).Config.Bind<bool>("Quick Connect", "ShowQuickConnectButton", true, "Add a Quick Connect button to the Valheim main menu for the last successfully joined server."); LastServer = ((BaseUnityPlugin)this).Config.Bind<string>("Quick Connect", "LastServer", "", "Last successfully joined server identity. Managed automatically, but may be cleared manually."); ForgetAllPasswords = ((BaseUnityPlugin)this).Config.Bind<bool>("Maintenance", "ForgetAllPasswords", false, "Set true and restart the game to erase all remembered passwords. It resets to false automatically."); ForgetLastServer = ((BaseUnityPlugin)this).Config.Bind<bool>("Maintenance", "ForgetLastServer", false, "Set true and restart the game to clear the Quick Connect destination. It resets to false automatically."); StorePath = Path.Combine(Paths.ConfigPath, "MagiCorp.RememberServerPassword.dat"); KeyPath = Path.Combine(Paths.ConfigPath, "MagiCorp.RememberServerPassword.key"); bool flag = false; if (ForgetAllPasswords.Value) { Passwords.Clear(); TryDelete(StorePath); TryDelete(KeyPath); ForgetAllPasswords.Value = false; flag = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)"All remembered server passwords were erased."); } if (ForgetLastServer.Value) { LastServer.Value = ""; ForgetLastServer.Value = false; flag = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Quick Connect last server was cleared."); } if (flag) { ((BaseUnityPlugin)this).Config.Save(); } LoadStore(); new Harmony("magicorp.valheim.rememberserverpassword").PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Remember Server Password 1.2.4 loaded. Remembered servers: " + Passwords.Count)); } internal static void TryAutoSubmitVisiblePasswordPrompt() { if ((Object)(object)Instance == (Object)null || !Enabled.Value || !AutoSubmit.Value || Time.unscaledTime < NextPasswordPromptAttempt) { return; } try { ZNet instance = ZNet.instance; if (!((Object)(object)instance == (Object)null)) { object? obj = AccessTools.Field(typeof(ZNet), "m_passwordDialog")?.GetValue(instance); RectTransform val = (RectTransform)((obj is RectTransform) ? obj : null); if (!((Object)(object)val == (Object)null) && ((Component)val).gameObject.activeInHierarchy) { NextPasswordPromptAttempt = Time.unscaledTime + 1f; LogInfo("[Flow] Password dialog detected; attempting remembered-password auto-submit fallback."); AutoSubmitRememberedPassword(instance, passwordRequired: true); } } } catch (Exception ex) { NextPasswordPromptAttempt = Time.unscaledTime + 1f; LogError("Password-dialog watcher failed: " + ex.GetBaseException().Message); } } internal unsafe static void CaptureJoinRequest(ServerJoinData joinData) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0031: 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) string serverKey = GetServerKey(joinData); string[] obj = new string[6] { "[Flow] Join request: valid=", ((ServerJoinData)(ref joinData)).IsValid.ToString(), ", raw=", null, null, null }; ServerJoinData val = joinData; obj[3] = ((object)(*(ServerJoinData*)(&val))/*cast due to .constrained prefix*/).ToString(); obj[4] = ", key="; obj[5] = serverKey ?? "<none>"; LogInfo(string.Concat(obj)); if (!string.IsNullOrEmpty(serverKey)) { ActiveServerKey = serverKey; LogInfo("[Flow] Active server key set: " + serverKey); } } internal unsafe static string GetServerKey(ServerJoinData joinData) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected I4, but got Unknown if (!((ServerJoinData)(ref joinData)).IsValid) { return null; } try { ServerJoinDataType val = (ServerJoinDataType)((AccessTools.Field(typeof(ServerJoinData), "m_type")?.GetValue(joinData) is ServerJoinDataType val2) ? ((int)val2) : 0); string text = ((object)(*(ServerJoinData*)(&joinData))/*cast due to .constrained prefix*/).ToString(); if (string.IsNullOrWhiteSpace(text)) { return null; } return (val - 1) switch { 0 => "steam/" + text.Trim(), 1 => "playfab/" + text.Trim().ToLowerInvariant(), 2 => "host/" + text.Trim().ToLowerInvariant(), _ => null, }; } catch (Exception ex) { LogWarning("Could not identify join request: " + ex.Message); return null; } } internal static string GetCurrentServerKey() { if (!string.IsNullOrEmpty(ActiveServerKey)) { return ActiveServerKey; } try { string text = AccessTools.Field(typeof(ZNet), "m_serverHost")?.GetValue(null) as string; object obj = AccessTools.Field(typeof(ZNet), "m_serverHostPort")?.GetValue(null); object obj2 = AccessTools.Field(typeof(ZNet), "m_serverSteamID")?.GetValue(null); string text2 = AccessTools.Field(typeof(ZNet), "m_serverPlayFabPlayerId")?.GetValue(null) as string; int num = ((obj is int num2) ? num2 : 0); ulong num3 = ((obj2 is ulong num4) ? num4 : 0); if (!string.IsNullOrWhiteSpace(text) && num > 0) { return FormatHostKey(text, num); } if (num3 != 0L) { return "steam/" + num3; } if (!string.IsNullOrWhiteSpace(text2)) { return "playfab/" + text2.Trim().ToLowerInvariant(); } } catch (Exception ex) { LogWarning("Could not identify current server: " + ex.Message); } return null; } private static string FormatHostKey(string host, int port) { host = host.Trim().ToLowerInvariant(); if (host.IndexOf(':') >= 0 && (!host.StartsWith("[") || !host.EndsWith("]"))) { host = "[" + host + "]"; } return "host/" + host + ":" + port; } internal static bool TryGetPassword(string serverKey, out string password) { password = null; bool flag = Enabled.Value && AutoSubmit.Value && !string.IsNullOrEmpty(serverKey) && Passwords.TryGetValue(serverKey, out password) && !string.IsNullOrEmpty(password); LogInfo("[Flow] Password lookup: key=" + (serverKey ?? "<none>") + ", enabled=" + Enabled.Value + ", autoSubmit=" + AutoSubmit.Value + ", storeCount=" + Passwords.Count + ", result=" + (flag ? "HIT" : "MISS")); return flag; } internal static void CapturePendingPassword(string serverKey, string password) { bool flag = !string.IsNullOrEmpty(AutoAttemptServerKey) && string.Equals(AutoAttemptServerKey, serverKey, StringComparison.OrdinalIgnoreCase); LogInfo("[Flow] Password-entry callback: key=" + (serverKey ?? "<none>") + ", enabled=" + Enabled.Value + ", remember=" + RememberPasswords.Value + ", valuePresent=" + !string.IsNullOrEmpty(password) + ", auto=" + flag); if (flag) { LogInfo("[Flow] Ignoring password-entry capture because this submission came from remembered-password auto-submit."); } else if (Enabled.Value && RememberPasswords.Value && !string.IsNullOrEmpty(serverKey) && !string.IsNullOrEmpty(password)) { PendingServerKey = serverKey; PendingPassword = password; LogInfo("[Flow] Manual password captured for " + serverKey + "; awaiting successful connection before saving."); } } internal static void MarkAutoAttempt(string serverKey) { AutoAttemptServerKey = serverKey; LogInfo("[Flow] Remembered password injected for " + serverKey + "; awaiting handshake result."); } internal static bool AutoSubmitRememberedPassword(ZNet net, bool passwordRequired) { if (!passwordRequired || (Object)(object)net == (Object)null) { return false; } string currentServerKey = GetCurrentServerKey(); LogInfo("[Flow] Password auto-submit stage: key=" + (currentServerKey ?? "<none>") + ", passwordRequired=" + passwordRequired); if (!TryGetPassword(currentServerKey, out var password)) { LogInfo("[Flow] Handshake fallback has no remembered password to submit."); return false; } try { MethodInfo methodInfo = AccessTools.Method(typeof(ZNet), "OnPasswordEntered", new Type[1] { typeof(string) }, (Type[])null); if (methodInfo == null) { throw new MissingMethodException("ZNet.OnPasswordEntered(string)"); } AccessTools.Field(typeof(ZNet), "m_serverPassword")?.SetValue(net, password); SetServerPassword(password, "password prompt fallback"); MarkAutoAttempt(currentServerKey); methodInfo.Invoke(net, new object[1] { password }); LogInfo("[Flow] Remembered password auto-submitted via ZNet.OnPasswordEntered reflection fallback."); return true; } catch (Exception ex) { LogError("Remembered password auto-submit fallback failed: " + ex.GetBaseException().Message); return false; } } internal static void AutoSelectLastCharacter(FejdStartup startup) { if (!QuickConnectPending || (Object)(object)startup == (Object)null) { return; } try { string text = PlatformPrefs.GetString("profile", string.Empty); LogInfo("[Flow] Quick Connect character stage: lastProfile=" + (string.IsNullOrEmpty(text) ? "<none>" : text)); if (string.IsNullOrEmpty(text)) { LogWarning("Quick Connect could not skip character selection because Valheim has no last-used profile."); QuickConnectPending = false; return; } MethodInfo methodInfo = AccessTools.Method(typeof(FejdStartup), "SetSelectedProfile", new Type[1] { typeof(string) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(FejdStartup), "OnCharacterStart", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { throw new MissingMethodException("FejdStartup character selection methods"); } methodInfo.Invoke(startup, new object[1] { text }); int num = (int)(AccessTools.Field(typeof(FejdStartup), "m_profileIndex")?.GetValue(startup) ?? ((object)(-1))); IList list = AccessTools.Field(typeof(FejdStartup), "m_profiles")?.GetValue(startup) as IList; if (num < 0 || list == null || num >= list.Count) { LogWarning("Quick Connect could not find the last-used profile '" + text + "'; leaving character selection open."); QuickConnectPending = false; return; } LogInfo("[Flow] Quick Connect selected last-used profile '" + text + "' at index " + num + "; starting join."); QuickConnectPending = false; methodInfo2.Invoke(startup, null); } catch (Exception ex) { QuickConnectPending = false; LogError("Quick Connect character auto-select failed: " + ex.GetBaseException().Message); } } internal static void RecordSuccessfulConnection() { string currentServerKey = GetCurrentServerKey(); LogInfo("[Flow] Connection success path: key=" + (currentServerKey ?? "<none>") + ", autoAttempt=" + (AutoAttemptServerKey ?? "<none>") + ", pending=" + (PendingServerKey ?? "<none>")); if (!string.IsNullOrEmpty(currentServerKey) && LastServer.Value != currentServerKey) { LastServer.Value = currentServerKey; ((BaseUnityPlugin)Instance).Config.Save(); } CommitPendingPassword(); } internal static void CommitPendingPassword() { AutoAttemptServerKey = null; SetServerPassword(string.Empty, "clear transient password"); if (!string.IsNullOrEmpty(PendingServerKey) && !string.IsNullOrEmpty(PendingPassword)) { string value; bool num = !Passwords.TryGetValue(PendingServerKey, out value) || value != PendingPassword; Passwords[PendingServerKey] = PendingPassword; if (num) { SaveStore(); LogInfo("Remembered password for " + PendingServerKey); } PendingServerKey = null; PendingPassword = null; } } internal static void HandleWrongPassword() { string currentServerKey = GetCurrentServerKey(); LogInfo("[Flow] Wrong-password path: key=" + (currentServerKey ?? "<none>") + ", autoAttempt=" + (AutoAttemptServerKey ?? "<none>")); PendingServerKey = null; PendingPassword = null; if (!string.IsNullOrEmpty(AutoAttemptServerKey) && string.Equals(AutoAttemptServerKey, currentServerKey, StringComparison.OrdinalIgnoreCase) && Passwords.Remove(currentServerKey)) { SaveStore(); LogWarning("Stored password was rejected and removed for " + currentServerKey + ". The next join will prompt normally."); } AutoAttemptServerKey = null; SetServerPassword(string.Empty, "clear transient password"); } internal static void ClearTransientState() { PendingServerKey = null; PendingPassword = null; AutoAttemptServerKey = null; QuickConnectPending = false; NextPasswordPromptAttempt = 0f; } internal static void EnsureQuickConnectButton(FejdStartup startup) { //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Expected O, but got Unknown if (!ShowQuickConnectButton.Value || (Object)(object)startup == (Object)null) { return; } try { object? obj = AccessTools.Field(typeof(FejdStartup), "m_menuList")?.GetValue(startup); GameObject val = (GameObject)((obj is GameObject) ? obj : null); if ((Object)(object)val == (Object)null) { return; } Button val2 = null; Button[] componentsInChildren = val.GetComponentsInChildren<Button>(true); Button val3 = null; Button[] array = componentsInChildren; foreach (Button val4 in array) { if (!((Object)(object)val4 == (Object)null)) { if (((Object)((Component)val4).gameObject).name == "MagiCorp_RememberServerPassword_QuickConnect") { val2 = val4; } else if ((Object)(object)val3 == (Object)null && ((Component)val4).gameObject.activeSelf) { val3 = val4; } } } if ((Object)(object)val2 == (Object)null) { if ((Object)(object)val3 == (Object)null) { LogWarning("Could not add Quick Connect button because the main menu button template was not found."); return; } GameObject val5 = Object.Instantiate<GameObject>(((Component)val3).gameObject, ((Component)val3).transform.parent); ((Object)val5).name = "MagiCorp_RememberServerPassword_QuickConnect"; Button component = val5.GetComponent<Button>(); if ((Object)(object)component == (Object)null) { Object.Destroy((Object)(object)val5); return; } Graphic targetGraphic = ((Selectable)component).targetGraphic; Transition transition = ((Selectable)component).transition; ColorBlock colors = ((Selectable)component).colors; SpriteState spriteState = ((Selectable)component).spriteState; Navigation navigation = ((Selectable)component).navigation; Object.DestroyImmediate((Object)(object)component); val2 = val5.AddComponent<Button>(); ((Selectable)val2).targetGraphic = targetGraphic; ((Selectable)val2).transition = transition; ((Selectable)val2).colors = colors; ((Selectable)val2).spriteState = spriteState; ((Selectable)val2).navigation = navigation; ((UnityEvent)val2.onClick).AddListener((UnityAction)delegate { QuickConnect(startup); }); PlaceQuickConnectButton(val2, val3); } ServerJoinData joinData; bool flag = (((Selectable)val2).interactable = TryBuildJoinData(LastServer.Value, out joinData)); TMP_Text componentInChildren = ((Component)val2).GetComponentInChildren<TMP_Text>(true); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.text = (flag ? ("Quick Connect: " + DisplayServer(LastServer.Value)) : "Quick Connect: No recent server"); } } catch (Exception ex) { LogWarning("Could not create Quick Connect button: " + ex.Message); } } private static void PlaceQuickConnectButton(Button button, Button template) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) Transform transform = ((Component)button).transform; RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null); Transform parent = ((Component)button).transform.parent; RectTransform val2 = (RectTransform)(object)((parent is RectTransform) ? parent : null); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null) { return; } if ((Object)(object)((Component)val2).GetComponent<LayoutGroup>() != (Object)null) { ((Component)button).transform.SetSiblingIndex(Math.Min(((Component)template).transform.GetSiblingIndex() + 1, ((Transform)val2).childCount - 1)); LayoutRebuilder.ForceRebuildLayoutImmediate(val2); return; } Transform transform2 = ((Component)template).transform; RectTransform val3 = (RectTransform)(object)((transform2 is RectTransform) ? transform2 : null); float num = ((val3 != null) ? val3.anchoredPosition.y : val.anchoredPosition.y); Rect rect = val.rect; float num2 = Math.Max(((Rect)(ref rect)).height, 35f); Button[] componentsInChildren = ((Component)val2).GetComponentsInChildren<Button>(true); foreach (Button val4 in componentsInChildren) { if (!((Object)(object)val4 == (Object)(object)button) && !((Object)(object)((Component)val4).transform.parent != (Object)(object)val2)) { Transform transform3 = ((Component)val4).transform; RectTransform val5 = (RectTransform)(object)((transform3 is RectTransform) ? transform3 : null); if (val5 != null) { num = Math.Min(num, val5.anchoredPosition.y); } } } val.anchoredPosition = new Vector2(val.anchoredPosition.x, num - num2 - 8f); } private unsafe static void QuickConnect(FejdStartup startup) { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) LogInfo("[Flow] Quick Connect clicked: lastServer=" + (LastServer.Value ?? "<none>")); if (!TryBuildJoinData(LastServer.Value, out var joinData)) { LogWarning("Quick Connect has no valid last server."); return; } try { string[] obj = new string[6] { "[Flow] Quick Connect join data: valid=", ((ServerJoinData)(ref joinData)).IsValid.ToString(), ", raw=", null, null, null }; ServerJoinData val = joinData; obj[3] = ((object)(*(ServerJoinData*)(&val))/*cast due to .constrained prefix*/).ToString(); obj[4] = ", lastProfile="; obj[5] = PlatformPrefs.GetString("profile", string.Empty); LogInfo(string.Concat(obj)); QuickConnectPending = true; SetServerPassword(string.Empty, "Quick Connect pre-join clear"); MethodInfo methodInfo = AccessTools.Method(typeof(FejdStartup), "ProceedJoinRequest", (Type[])null, (Type[])null); if (methodInfo == null) { throw new MissingMethodException("FejdStartup.ProceedJoinRequest"); } LogInfo("[Flow] Invoking FejdStartup.ProceedJoinRequest via reflection."); methodInfo.Invoke(startup, new object[1] { joinData }); LogInfo("Quick Connect requested for " + DisplayServer(LastServer.Value)); } catch (Exception ex) { QuickConnectPending = false; LogError("Quick Connect failed: " + ex.GetBaseException().Message); } } private static bool TryBuildJoinData(string serverKey, out ServerJoinData joinData) { //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_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: 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_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) joinData = ServerJoinData.None; if (string.IsNullOrWhiteSpace(serverKey)) { return false; } try { if (serverKey.StartsWith("steam/", StringComparison.OrdinalIgnoreCase)) { if (!ulong.TryParse(serverKey.Substring(6), out var result) || result == 0L) { return false; } joinData = new ServerJoinData(new ServerJoinDataSteamUser(result)); return ((ServerJoinData)(ref joinData)).IsValid; } if (serverKey.StartsWith("playfab/", StringComparison.OrdinalIgnoreCase)) { string text = serverKey.Substring(8); if (string.IsNullOrWhiteSpace(text)) { return false; } joinData = new ServerJoinData(new ServerJoinDataPlayFabUser(text)); return ((ServerJoinData)(ref joinData)).IsValid; } if (serverKey.StartsWith("host/", StringComparison.OrdinalIgnoreCase)) { string text2 = serverKey.Substring(5); string text3; string s; if (text2.StartsWith("[", StringComparison.Ordinal)) { int num = text2.LastIndexOf("]:", StringComparison.Ordinal); if (num <= 1) { return false; } text3 = text2.Substring(1, num - 1); s = text2.Substring(num + 2); } else { int num2 = text2.LastIndexOf(':'); if (num2 <= 0) { return false; } text3 = text2.Substring(0, num2); s = text2.Substring(num2 + 1); } if (!ushort.TryParse(s, out var result2) || result2 == 0 || string.IsNullOrWhiteSpace(text3)) { return false; } joinData = new ServerJoinData(new ServerJoinDataDedicated(text3, result2)); return ((ServerJoinData)(ref joinData)).IsValid; } } catch (Exception ex) { LogWarning("Could not parse last server identity: " + ex.Message); } return false; } private static string DisplayServer(string serverKey) { if (string.IsNullOrWhiteSpace(serverKey)) { return "No recent server"; } int num = serverKey.IndexOf('/'); string text = ((num >= 0 && num + 1 < serverKey.Length) ? serverKey.Substring(num + 1) : serverKey); if (text.Length > 42) { return text.Substring(0, 39) + "..."; } return text; } private static void LoadStore() { Passwords.Clear(); if (!File.Exists(StorePath)) { return; } try { string[] array = File.ReadAllLines(StorePath); foreach (string text in array) { if (string.IsNullOrWhiteSpace(text)) { continue; } string[] array2 = text.Split(new char[1] { '\t' }, 2); if (array2.Length == 2) { string text2 = Encoding.UTF8.GetString(Convert.FromBase64String(array2[0])); string text3 = Decrypt(array2[1]); if (!string.IsNullOrEmpty(text2) && text3 != null) { Passwords[text2] = text3; } } } } catch (Exception ex) { LogWarning("Could not read password store: " + ex.Message); } } private static void SaveStore() { try { List<string> list = new List<string>(); foreach (KeyValuePair<string, string> password in Passwords) { string text = Convert.ToBase64String(Encoding.UTF8.GetBytes(password.Key)); list.Add(text + "\t" + Encrypt(password.Value)); } File.WriteAllLines(StorePath, list.ToArray()); } catch (Exception ex) { LogError("Could not save password store: " + ex.Message); } } private static string Encrypt(string value) { byte[] bytes = Encoding.UTF8.GetBytes(value); try { byte[] inArray = ProtectedData.Protect(bytes, DpapiEntropy, (DataProtectionScope)0); return "D:" + Convert.ToBase64String(inArray); } catch (PlatformNotSupportedException) { return EncryptPortable(bytes); } catch (CryptographicException) { return EncryptPortable(bytes); } } private static string Decrypt(string value) { if (value.StartsWith("D:", StringComparison.Ordinal)) { byte[] array = Convert.FromBase64String(value.Substring(2)); try { return Encoding.UTF8.GetString(ProtectedData.Unprotect(array, DpapiEntropy, (DataProtectionScope)0)); } catch (PlatformNotSupportedException) { return null; } catch (CryptographicException) { return null; } } if (value.StartsWith("A:", StringComparison.Ordinal)) { return DecryptPortable(value.Substring(2)); } return null; } private static string EncryptPortable(byte[] clear) { byte[] orCreatePortableKey = GetOrCreatePortableKey(); using Aes aes = Aes.Create(); aes.Key = orCreatePortableKey; aes.GenerateIV(); using ICryptoTransform cryptoTransform = aes.CreateEncryptor(); byte[] array = cryptoTransform.TransformFinalBlock(clear, 0, clear.Length); byte[] array2 = new byte[aes.IV.Length + array.Length]; Buffer.BlockCopy(aes.IV, 0, array2, 0, aes.IV.Length); Buffer.BlockCopy(array, 0, array2, aes.IV.Length, array.Length); return "A:" + Convert.ToBase64String(array2); } private static string DecryptPortable(string value) { try { byte[] orCreatePortableKey = GetOrCreatePortableKey(); byte[] array = Convert.FromBase64String(value); using Aes aes = Aes.Create(); int num = aes.BlockSize / 8; if (array.Length <= num) { return null; } byte[] array2 = new byte[num]; byte[] array3 = new byte[array.Length - num]; Buffer.BlockCopy(array, 0, array2, 0, num); Buffer.BlockCopy(array, num, array3, 0, array3.Length); aes.Key = orCreatePortableKey; aes.IV = array2; using ICryptoTransform cryptoTransform = aes.CreateDecryptor(); byte[] bytes = cryptoTransform.TransformFinalBlock(array3, 0, array3.Length); return Encoding.UTF8.GetString(bytes); } catch { return null; } } private static byte[] GetOrCreatePortableKey() { if (File.Exists(KeyPath)) { return Convert.FromBase64String(File.ReadAllText(KeyPath).Trim()); } byte[] array = new byte[32]; using (RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create()) { randomNumberGenerator.GetBytes(array); } File.WriteAllText(KeyPath, Convert.ToBase64String(array)); LogWarning("OS protected storage was unavailable. Using a per-install AES key in the BepInEx config directory instead."); return array; } internal static bool SetServerPassword(string value, string reason) { value = value ?? string.Empty; try { MethodInfo methodInfo = AccessTools.Property(typeof(FejdStartup), "ServerPassword")?.GetSetMethod(nonPublic: true); if (methodInfo != null) { methodInfo.Invoke(null, new object[1] { value }); LogInfo("[Flow] ServerPassword reflection setter succeeded: reason=" + reason + ", state=" + ((value.Length == 0) ? "empty" : "present")); return true; } FieldInfo fieldInfo = AccessTools.Field(typeof(FejdStartup), "<ServerPassword>k__BackingField"); if (fieldInfo != null) { fieldInfo.SetValue(null, value); LogInfo("[Flow] ServerPassword backing-field fallback succeeded: reason=" + reason + ", state=" + ((value.Length == 0) ? "empty" : "present")); return true; } LogError("Could not set FejdStartup.ServerPassword: setter and backing field were not found. Reason=" + reason); } catch (Exception ex) { LogError("Could not set FejdStartup.ServerPassword via reflection: reason=" + reason + ", error=" + ex.GetBaseException().Message); } return false; } private static void TryDelete(string path) { try { if (File.Exists(path)) { File.Delete(path); } } catch (Exception ex) { LogWarning("Could not delete " + path + ": " + ex.Message); } } internal static void LogFlow(string message) { } private static void LogInfo(string message) { if ((Object)(object)Instance != (Object)null && !message.StartsWith("[Flow]", StringComparison.Ordinal)) { ((BaseUnityPlugin)Instance).Logger.LogInfo((object)message); } } private static void LogWarning(string message) { if ((Object)(object)Instance != (Object)null) { ((BaseUnityPlugin)Instance).Logger.LogWarning((object)message); } } private static void LogError(string message) { if ((Object)(object)Instance != (Object)null) { ((BaseUnityPlugin)Instance).Logger.LogError((object)message); } } } [HarmonyPatch(typeof(FejdStartup), "SetupGui")] internal static class MainMenuPatch { private static void Postfix(FejdStartup __instance) { Plugin.EnsureQuickConnectButton(__instance); } } [HarmonyPatch(typeof(FejdStartup), "ProceedJoinRequest")] internal static class JoinRequestPatch { private static void Prefix(ServerJoinData joinData) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) Plugin.CaptureJoinRequest(joinData); } } [HarmonyPatch(typeof(FejdStartup), "ShowCharacterSelection")] internal static class CharacterSelectionPatch { private static void Postfix(FejdStartup __instance) { Plugin.AutoSelectLastCharacter(__instance); } } [HarmonyPatch(typeof(ZNet), "RPC_ClientHandshake", new Type[] { typeof(ZRpc), typeof(bool), typeof(string) })] internal static class ClientHandshakePatch { private static void Prefix(bool __1) { string currentServerKey = Plugin.GetCurrentServerKey(); Plugin.LogFlow("RPC_ClientHandshake prefix: key=" + (currentServerKey ?? "<none>") + ", passwordRequired=" + __1); if (__1 && Plugin.TryGetPassword(currentServerKey, out var password)) { if (Plugin.SetServerPassword(password, "RPC_ClientHandshake remembered-password injection")) { Plugin.MarkAutoAttempt(currentServerKey); } } else if (__1) { Plugin.LogFlow("RPC_ClientHandshake has no remembered password to inject."); } } } [HarmonyPatch(typeof(ZNet), "OnPasswordEntered")] internal static class PasswordEnteredPatch { private static void Prefix(string __0) { Plugin.CapturePendingPassword(Plugin.GetCurrentServerKey(), __0); } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] internal static class PeerInfoPatch { private static void Postfix(ZNet __instance) { if (!__instance.IsServer()) { Plugin.RecordSuccessfulConnection(); } } } [HarmonyPatch(typeof(ZNet), "RPC_Error")] internal static class RpcErrorPatch { private static void Postfix(int __1) { if (__1 == 6) { Plugin.HandleWrongPassword(); } } } [HarmonyPatch(typeof(ZNet), "ResetServerHost")] internal static class ResetServerHostPatch { private static void Prefix() { Plugin.ClearTransientState(); } }