Decompiled source of EasyLogin v1.0.0

plugins/EasyLogin/EasyLogin.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
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 BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("EasyLogin")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("EasyLogin")]
[assembly: AssemblyTitle("EasyLogin")]
[assembly: AssemblyVersion("1.0.0.0")]
[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 EasyLogin
{
	[BepInPlugin("easylogin.client.only", "EasyLogin", "1.0.0")]
	public class EasyLoginPlugin : BaseUnityPlugin
	{
		public const string Guid = "easylogin.client.only";

		public const string PluginName = "EasyLogin";

		public const string PluginVersion = "1.0.0";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<bool> AutoSubmit;

		internal static ConfigEntry<bool> ForgetOnPasswordError;

		internal static ConfigEntry<bool> RememberManualInput;

		internal static ConfigEntry<bool> MatchByJoinCode;

		internal static ConfigEntry<bool> EncryptWithWindowsUserKey;

		internal static ConfigEntry<bool> ShowPasswordInLog;

		internal static ConfigEntry<bool> VerboseLog;

		private Harmony harmony;

		private float backfillTimer;

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - 自动登录", "Enabled", true, "总开关。关闭后模组完全不介入,和原版一样每次手输密码。");
			AutoSubmit = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - 自动登录", "AutoSubmit", true, "true  = 认出服务器后直接提交密码(你会感觉「秒进」)。\nfalse = 只把密码填进输入框,弹窗保持显示,等你自己按回车确认。");
			ForgetOnPasswordError = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - 自动登录", "ForgetOnPasswordError", true, "服务器说密码不对时,把这条记录删掉。\n★ 强烈建议保持 true:否则房主改了密码后,你会一直自动填那个旧密码,\n  连密码框都弹不出来,等于把自己锁在门外(只能手动删 json)。");
			RememberManualInput = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - 自动登录", "RememberManualInput", true, "你手动敲进去的密码也记下来。\n关掉的话,只有「已经记录过的服务器」才会自动填,新服务器不会新增记录。");
			MatchByJoinCode = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - 服务器识别", "MatchByJoinCode", true, "除了 host 的 PlayFab ID 之外,再把跨平台联机的「房间码」当成一层兜底的识别依据。\n只在跨平台后端下生效,Steam / 直连 IP 两条路不受影响。");
			EncryptWithWindowsUserKey = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - 存储", "EncryptWithWindowsUserKey", true, "用 Windows 用户级密钥(DPAPI)加密密码,只有当前这台机器的这个 Windows 账号能解。\n如果运行环境不支持,会自动退化成「Base64 + 异或」的简单混淆并打一行警告,功能不受影响。\n注意:开了这一项之后,把 servers.json 拷到别的电脑/别的 Windows 账号下会读不出来。");
			ShowPasswordInLog = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - 存储", "ShowPasswordInLog", false, "把密码原文打进 BepInEx 日志(只在排错时开,平时千万别开)。");
			VerboseLog = ((BaseUnityPlugin)this).Config.Bind<bool>("4 - 日志", "VerboseLog", false, "详细日志:每次识别服务器都把 key / 房间码 / IP 等打出来。");
			Log.LogInfo((object)string.Format("{0} {1} 开始挂载 Harmony 补丁……存储文件:{2}", "EasyLogin", "1.0.0", PasswordStore.FilePath));
			PasswordStore.Init();
			ServerIdentity.Init();
			ApplyPatches();
			VerifyPatches();
			Log.LogInfo((object)$"EasyLogin 就绪:已载入 {PasswordStore.Count} 条服务器密码记录。");
		}

		private void ApplyPatches()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected O, but got Unknown
			harmony = new Harmony("easylogin.client.only");
			Type[] array = new Type[3]
			{
				typeof(Patch_ClientHandshake),
				typeof(Patch_OnPasswordEntered),
				typeof(Patch_RpcError)
			};
			int num = 0;
			Type[] array2 = array;
			foreach (Type type in array2)
			{
				try
				{
					harmony.CreateClassProcessor(type).Patch();
					Log.LogInfo((object)("  已挂载补丁类:" + type.Name));
					num++;
				}
				catch (Exception ex)
				{
					Log.LogError((object)("  挂载失败 " + type.Name + ":" + ex.Message));
				}
			}
			Log.LogInfo((object)$"Harmony 补丁挂载完成:{num}/{array.Length} 成功。");
		}

		private void VerifyPatches()
		{
			MethodBase[] obj = new MethodBase[3]
			{
				AccessTools.Method(typeof(ZNet), "RPC_ClientHandshake", (Type[])null, (Type[])null),
				AccessTools.Method(typeof(ZNet), "OnPasswordEntered", (Type[])null, (Type[])null),
				AccessTools.Method(typeof(ZNet), "RPC_Error", (Type[])null, (Type[])null)
			};
			Log.LogInfo((object)"---- 补丁挂载自检 ----");
			MethodBase[] array = obj;
			foreach (MethodBase methodBase in array)
			{
				if (methodBase == null)
				{
					Log.LogWarning((object)"  找不到目标方法(可能游戏版本变了)");
					continue;
				}
				Patches patchInfo = Harmony.GetPatchInfo(methodBase);
				int num = ((patchInfo != null && patchInfo.Prefixes != null) ? patchInfo.Prefixes.Count : 0);
				int num2 = ((patchInfo != null && patchInfo.Postfixes != null) ? patchInfo.Postfixes.Count : 0);
				string text = ((num + num2 > 0) ? "OK" : "!! 未挂上 !!");
				Log.LogInfo((object)$"  {methodBase.DeclaringType.Name}.{methodBase.Name}()  prefix={num} postfix={num2}  {text}");
			}
			Log.LogInfo((object)"-----------------------");
		}

		private void Update()
		{
			backfillTimer += Time.unscaledDeltaTime;
			if (!(backfillTimer < 5f))
			{
				backfillTimer = 0f;
				PasswordStore.BackfillServerName();
			}
		}

		private void OnDestroy()
		{
			PasswordStore.Flush();
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class Json
	{
		internal static void WriteString(StringBuilder sb, string value)
		{
			if (value == null)
			{
				sb.Append("null");
				return;
			}
			sb.Append('"');
			foreach (char c in value)
			{
				switch (c)
				{
				case '"':
					sb.Append("\\\"");
					continue;
				case '\\':
					sb.Append("\\\\");
					continue;
				case '\b':
					sb.Append("\\b");
					continue;
				case '\f':
					sb.Append("\\f");
					continue;
				case '\n':
					sb.Append("\\n");
					continue;
				case '\r':
					sb.Append("\\r");
					continue;
				case '\t':
					sb.Append("\\t");
					continue;
				}
				if (c < ' ')
				{
					StringBuilder stringBuilder = sb.Append("\\u");
					int num = c;
					stringBuilder.Append(num.ToString("x4", CultureInfo.InvariantCulture));
				}
				else
				{
					sb.Append(c);
				}
			}
			sb.Append('"');
		}

		internal static void WriteField(StringBuilder sb, string name, string value, bool comma)
		{
			sb.Append("    ");
			WriteString(sb, name);
			sb.Append(": ");
			WriteString(sb, value);
			sb.Append(comma ? ",\n" : "\n");
		}

		internal static void WriteField(StringBuilder sb, string name, long value, bool comma)
		{
			sb.Append("    ");
			WriteString(sb, name);
			sb.Append(": ").Append(value.ToString(CultureInfo.InvariantCulture));
			sb.Append(comma ? ",\n" : "\n");
		}

		internal static object Parse(string text)
		{
			int i = 0;
			if (text.Length > 0 && text[0] == '\ufeff')
			{
				i = 1;
			}
			SkipWs(text, ref i);
			object result = ParseValue(text, ref i);
			SkipWs(text, ref i);
			return result;
		}

		private static object ParseValue(string s, ref int i)
		{
			if (i >= s.Length)
			{
				throw new FormatException("JSON 意外结束。");
			}
			switch (s[i])
			{
			case '{':
				return ParseObject(s, ref i);
			case '[':
				return ParseArray(s, ref i);
			case '"':
				return ParseString(s, ref i);
			case 't':
				Expect(s, ref i, "true");
				return true;
			case 'f':
				Expect(s, ref i, "false");
				return false;
			case 'n':
				Expect(s, ref i, "null");
				return null;
			default:
				return ParseNumber(s, ref i);
			}
		}

		private static Dictionary<string, object> ParseObject(string s, ref int i)
		{
			Dictionary<string, object> dictionary = new Dictionary<string, object>();
			i++;
			SkipWs(s, ref i);
			if (i < s.Length && s[i] == '}')
			{
				i++;
				return dictionary;
			}
			while (true)
			{
				SkipWs(s, ref i);
				if (i >= s.Length || s[i] != '"')
				{
					throw new FormatException("JSON 对象里期待一个字符串键,位置 " + i + "。");
				}
				string text = ParseString(s, ref i);
				SkipWs(s, ref i);
				if (i >= s.Length || s[i] != ':')
				{
					throw new FormatException("键 " + text + " 后面缺少冒号。");
				}
				i++;
				SkipWs(s, ref i);
				dictionary[text] = ParseValue(s, ref i);
				SkipWs(s, ref i);
				if (i >= s.Length || s[i] != ',')
				{
					break;
				}
				i++;
			}
			if (i < s.Length && s[i] == '}')
			{
				i++;
				return dictionary;
			}
			throw new FormatException("JSON 对象里期待 , 或 },位置 " + i + "。");
		}

		private static List<object> ParseArray(string s, ref int i)
		{
			List<object> list = new List<object>();
			i++;
			SkipWs(s, ref i);
			if (i < s.Length && s[i] == ']')
			{
				i++;
				return list;
			}
			while (true)
			{
				SkipWs(s, ref i);
				list.Add(ParseValue(s, ref i));
				SkipWs(s, ref i);
				if (i >= s.Length || s[i] != ',')
				{
					break;
				}
				i++;
			}
			if (i < s.Length && s[i] == ']')
			{
				i++;
				return list;
			}
			throw new FormatException("JSON 数组里期待 , 或 ],位置 " + i + "。");
		}

		private static string ParseString(string s, ref int i)
		{
			i++;
			StringBuilder stringBuilder = new StringBuilder();
			while (i < s.Length)
			{
				char c = s[i++];
				switch (c)
				{
				case '"':
					return stringBuilder.ToString();
				default:
					stringBuilder.Append(c);
					break;
				case '\\':
				{
					if (i >= s.Length)
					{
						throw new FormatException("JSON 转义符后面没有内容。");
					}
					char c2 = s[i++];
					switch (c2)
					{
					case '"':
						stringBuilder.Append('"');
						break;
					case '\\':
						stringBuilder.Append('\\');
						break;
					case '/':
						stringBuilder.Append('/');
						break;
					case 'b':
						stringBuilder.Append('\b');
						break;
					case 'f':
						stringBuilder.Append('\f');
						break;
					case 'n':
						stringBuilder.Append('\n');
						break;
					case 'r':
						stringBuilder.Append('\r');
						break;
					case 't':
						stringBuilder.Append('\t');
						break;
					case 'u':
						if (i + 4 > s.Length)
						{
							throw new FormatException("\\u 转义不完整。");
						}
						stringBuilder.Append((char)Convert.ToInt32(s.Substring(i, 4), 16));
						i += 4;
						break;
					default:
						throw new FormatException("看不懂的转义:\\" + c2);
					}
					break;
				}
				}
			}
			throw new FormatException("JSON 字符串没有收尾引号。");
		}

		private static object ParseNumber(string s, ref int i)
		{
			int num = i;
			while (i < s.Length && "+-0123456789.eE".IndexOf(s[i]) >= 0)
			{
				i++;
			}
			if (num == i)
			{
				throw new FormatException("JSON 里出现非法字符 '" + s[num] + "',位置 " + num + "。");
			}
			string text = s.Substring(num, i - num);
			if (!double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				throw new FormatException("不是合法的数字:" + text);
			}
			return result;
		}

		private static void Expect(string s, ref int i, string word)
		{
			if (i + word.Length > s.Length || s.Substring(i, word.Length) != word)
			{
				throw new FormatException("JSON 里期待 " + word + ",位置 " + i + "。");
			}
			i += word.Length;
		}

		private static void SkipWs(string s, ref int i)
		{
			while (i < s.Length && (s[i] == ' ' || s[i] == '\t' || s[i] == '\r' || s[i] == '\n'))
			{
				i++;
			}
		}

		internal static string AsString(object value)
		{
			if (value == null)
			{
				return "";
			}
			return (value as string) ?? "";
		}

		internal static int AsInt(object value)
		{
			if (value is double)
			{
				return (int)(double)value;
			}
			return 0;
		}

		internal static long AsLong(object value)
		{
			if (value is double)
			{
				return (long)(double)value;
			}
			return 0L;
		}

		internal static Dictionary<string, object> AsObject(object value)
		{
			return value as Dictionary<string, object>;
		}

		internal static List<object> AsArray(object value)
		{
			return value as List<object>;
		}

		internal static object Field(Dictionary<string, object> obj, string name)
		{
			if (obj != null && obj.TryGetValue(name, out var value))
			{
				return value;
			}
			return null;
		}
	}
	public sealed class ServerRecord
	{
		public string Key { get; set; }

		public string Kind { get; set; }

		public string SteamId { get; set; }

		public string PlayFabId { get; set; }

		public string Host { get; set; }

		public int Port { get; set; }

		public string JoinCode { get; set; }

		public string ServerName { get; set; }

		public string Password { get; set; }

		public long LastJoinedUtc { get; set; }

		public string LastJoinedLocal { get; set; }

		public int UseCount { get; set; }

		public string Note { get; set; }
	}
	internal static class PasswordStore
	{
		private const string FileName = "servers.json";

		private static readonly List<ServerRecord> Records = new List<ServerRecord>();

		private static bool dirty;

		private static string lastTouchedKey;

		internal static string FilePath { get; private set; }

		internal static int Count => Records.Count;

		internal static void Init()
		{
			try
			{
				InitAt(Path.Combine(Paths.ConfigPath, "EasyLogin"));
			}
			catch (Exception ex)
			{
				FilePath = "<不可用>";
				EasyLoginPlugin.Log.LogError((object)("初始化存储失败:" + ex.Message));
			}
		}

		internal static void InitAt(string dir)
		{
			Directory.CreateDirectory(dir);
			FilePath = Path.Combine(dir, "servers.json");
			Load();
		}

		private static void Load()
		{
			Records.Clear();
			if (!File.Exists(FilePath))
			{
				EasyLoginPlugin.Log.LogInfo((object)("存储文件还不存在,先不动它:" + FilePath));
				return;
			}
			try
			{
				string text = File.ReadAllText(FilePath);
				if (text == null || text.Trim().Length == 0)
				{
					return;
				}
				List<object> obj = Json.AsArray(Json.Field(Json.AsObject(Json.Parse(text)) ?? throw new FormatException("根节点不是 JSON 对象。"), "Records")) ?? throw new FormatException("找不到 Records 数组。");
				int num = 0;
				foreach (object item in obj)
				{
					Dictionary<string, object> dictionary = Json.AsObject(item);
					if (dictionary == null)
					{
						num++;
						continue;
					}
					ServerRecord serverRecord = new ServerRecord
					{
						Key = Json.AsString(Json.Field(dictionary, "Key")),
						Kind = Json.AsString(Json.Field(dictionary, "Kind")),
						SteamId = Json.AsString(Json.Field(dictionary, "SteamId")),
						PlayFabId = Json.AsString(Json.Field(dictionary, "PlayFabId")),
						Host = Json.AsString(Json.Field(dictionary, "Host")),
						Port = Json.AsInt(Json.Field(dictionary, "Port")),
						JoinCode = Json.AsString(Json.Field(dictionary, "JoinCode")),
						ServerName = Json.AsString(Json.Field(dictionary, "ServerName")),
						Password = Json.AsString(Json.Field(dictionary, "Password")),
						LastJoinedUtc = Json.AsLong(Json.Field(dictionary, "LastJoinedUtc")),
						LastJoinedLocal = Json.AsString(Json.Field(dictionary, "LastJoinedLocal")),
						UseCount = Json.AsInt(Json.Field(dictionary, "UseCount")),
						Note = Json.AsString(Json.Field(dictionary, "Note"))
					};
					if (serverRecord.Key.Length == 0)
					{
						num++;
					}
					else
					{
						Records.Add(serverRecord);
					}
				}
				if (num > 0)
				{
					EasyLoginPlugin.Log.LogWarning((object)$"存储文件里有 {num} 条记录读不出来(缺 Key 或格式不对),已跳过。");
				}
			}
			catch (Exception ex)
			{
				Records.Clear();
				EasyLoginPlugin.Log.LogError((object)$"存储文件读不出来({ex.GetType().Name} {ex.Message}),先按空表继续。坏文件会改名保留,不会丢。");
				BackupBrokenFile();
			}
		}

		private static void BackupBrokenFile()
		{
			try
			{
				string text = FilePath + ".broken-" + DateTime.Now.ToString("yyyyMMdd-HHmmss");
				File.Move(FilePath, text);
				EasyLoginPlugin.Log.LogWarning((object)("坏文件已改名为:" + text));
			}
			catch (Exception ex)
			{
				EasyLoginPlugin.Log.LogWarning((object)("坏文件改名也失败了(" + ex.Message + "),下次保存会直接覆盖它。"));
			}
		}

		internal static ServerRecord Find(ServerIdentity.Identity id)
		{
			if (id == null || id.Key == null)
			{
				return null;
			}
			ServerRecord serverRecord = FirstMatching((ServerRecord r) => r.Key == id.Key);
			if (serverRecord != null)
			{
				return serverRecord;
			}
			if (!string.IsNullOrEmpty(id.SteamId))
			{
				serverRecord = FirstMatching((ServerRecord r) => r.SteamId == id.SteamId);
				if (serverRecord != null)
				{
					return serverRecord;
				}
			}
			if (!string.IsNullOrEmpty(id.PlayFabId))
			{
				serverRecord = FirstMatching((ServerRecord r) => r.PlayFabId == id.PlayFabId);
				if (serverRecord != null)
				{
					return serverRecord;
				}
			}
			if (!string.IsNullOrEmpty(id.Host))
			{
				serverRecord = FirstMatching((ServerRecord r) => r.Host == id.Host && r.Port == id.Port);
				if (serverRecord != null)
				{
					return serverRecord;
				}
			}
			if (EasyLoginPlugin.MatchByJoinCode.Value && id.Backend == 1 && !string.IsNullOrEmpty(id.JoinCode))
			{
				serverRecord = FirstMatching((ServerRecord r) => r.JoinCode == id.JoinCode);
				if (serverRecord != null)
				{
					return serverRecord;
				}
			}
			return null;
		}

		private static ServerRecord FirstMatching(Func<ServerRecord, bool> predicate)
		{
			for (int i = 0; i < Records.Count; i++)
			{
				if (predicate(Records[i]))
				{
					return Records[i];
				}
			}
			return null;
		}

		private static ServerRecord ByKey(string key)
		{
			if (string.IsNullOrEmpty(key))
			{
				return null;
			}
			return FirstMatching((ServerRecord r) => r.Key == key);
		}

		internal static bool Remember(ServerIdentity.Identity id, string plainPassword)
		{
			if (id == null || id.Key == null || string.IsNullOrEmpty(plainPassword))
			{
				return false;
			}
			ServerRecord serverRecord = Find(id);
			bool flag = false;
			if (serverRecord == null)
			{
				if (!EasyLoginPlugin.RememberManualInput.Value)
				{
					EasyLoginPlugin.Log.LogInfo((object)("没有开启「记住手动输入的密码」,跳过:" + id.Describe()));
					return false;
				}
				serverRecord = new ServerRecord
				{
					Key = id.Key,
					Kind = id.Kind,
					SteamId = (id.SteamId ?? ""),
					PlayFabId = (id.PlayFabId ?? ""),
					Host = (id.Host ?? ""),
					Port = id.Port,
					JoinCode = (id.JoinCode ?? ""),
					ServerName = (id.ServerName ?? ""),
					Note = ""
				};
				Records.Add(serverRecord);
				flag = true;
			}
			if (!string.IsNullOrEmpty(id.JoinCode) && string.IsNullOrEmpty(serverRecord.JoinCode))
			{
				serverRecord.JoinCode = id.JoinCode;
			}
			if (!string.IsNullOrEmpty(id.ServerName) && string.IsNullOrEmpty(serverRecord.ServerName))
			{
				serverRecord.ServerName = id.ServerName;
			}
			if (!string.IsNullOrEmpty(id.SteamId) && string.IsNullOrEmpty(serverRecord.SteamId))
			{
				serverRecord.SteamId = id.SteamId;
			}
			if (!string.IsNullOrEmpty(id.PlayFabId) && string.IsNullOrEmpty(serverRecord.PlayFabId))
			{
				serverRecord.PlayFabId = id.PlayFabId;
			}
			bool flag2 = Secret.Unprotect(serverRecord.Password) != plainPassword;
			serverRecord.Password = Secret.Protect(plainPassword);
			serverRecord.LastJoinedUtc = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
			serverRecord.LastJoinedLocal = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
			serverRecord.UseCount++;
			lastTouchedKey = serverRecord.Key;
			dirty = true;
			Save();
			string text = (EasyLoginPlugin.ShowPasswordInLog.Value ? plainPassword : "********");
			EasyLoginPlugin.Log.LogInfo((object)string.Format("{0}服务器密码:{1} → {2}(密码 {3}{4},共 {5} 条记录)", flag ? "新增" : "更新", id.Describe(), serverRecord.Key, text, flag2 ? ",内容有变" : "", Records.Count));
			return true;
		}

		internal static bool Forget(ServerIdentity.Identity id, string reason)
		{
			ServerRecord serverRecord = Find(id);
			if (serverRecord == null && id != null)
			{
				serverRecord = ByKey(id.Key);
			}
			if (serverRecord == null)
			{
				EasyLoginPlugin.Log.LogInfo((object)("服务器报告密码错误,但本地没有找到对应记录:" + ((id == null) ? "?" : id.Describe())));
				return false;
			}
			Records.Remove(serverRecord);
			dirty = true;
			Save();
			EasyLoginPlugin.Log.LogWarning((object)$"服务器 {((id == null) ? serverRecord.Key : id.Describe())} 报告密码错误({reason}),已删掉这条记录 —— 下次进它会重新让你手输密码。");
			return true;
		}

		internal static void BackfillServerName()
		{
			if (string.IsNullOrEmpty(lastTouchedKey))
			{
				return;
			}
			string text = ServerIdentity.CurrentServerName();
			if (!string.IsNullOrEmpty(text))
			{
				ServerRecord serverRecord = ByKey(lastTouchedKey);
				if (serverRecord != null && !(serverRecord.ServerName == text))
				{
					serverRecord.ServerName = text;
					dirty = true;
					Save();
					EasyLoginPlugin.Log.LogInfo((object)("已回填服务器名字:" + serverRecord.Key + " → " + text));
				}
			}
		}

		internal static void Save()
		{
			if (FilePath == null || FilePath == "<不可用>")
			{
				return;
			}
			try
			{
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.Append("{\n");
				stringBuilder.Append("  \"Version\": 1,\n");
				stringBuilder.Append("  \"Comment\": ");
				Json.WriteString(stringBuilder, "EasyLogin 自动生成。Note 字段随便写,模组不会动它;Password 字段带 dpapi:/obf:/plain: 前缀,别手改前缀。");
				stringBuilder.Append(",\n");
				stringBuilder.Append("  \"Records\": [\n");
				for (int i = 0; i < Records.Count; i++)
				{
					ServerRecord serverRecord = Records[i];
					stringBuilder.Append("    {\n");
					AppendString(stringBuilder, "Key", serverRecord.Key, last: false);
					AppendString(stringBuilder, "Kind", serverRecord.Kind, last: false);
					AppendString(stringBuilder, "SteamId", serverRecord.SteamId, last: false);
					AppendString(stringBuilder, "PlayFabId", serverRecord.PlayFabId, last: false);
					AppendString(stringBuilder, "Host", serverRecord.Host, last: false);
					AppendNumber(stringBuilder, "Port", serverRecord.Port, last: false);
					AppendString(stringBuilder, "JoinCode", serverRecord.JoinCode, last: false);
					AppendString(stringBuilder, "ServerName", serverRecord.ServerName, last: false);
					AppendString(stringBuilder, "Password", serverRecord.Password, last: false);
					AppendNumber(stringBuilder, "LastJoinedUtc", serverRecord.LastJoinedUtc, last: false);
					AppendString(stringBuilder, "LastJoinedLocal", serverRecord.LastJoinedLocal, last: false);
					AppendNumber(stringBuilder, "UseCount", serverRecord.UseCount, last: false);
					AppendString(stringBuilder, "Note", serverRecord.Note, last: true);
					stringBuilder.Append((i < Records.Count - 1) ? "    },\n" : "    }\n");
				}
				stringBuilder.Append("  ]\n");
				stringBuilder.Append("}\n");
				File.WriteAllText(FilePath, stringBuilder.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
				dirty = false;
			}
			catch (Exception ex)
			{
				EasyLoginPlugin.Log.LogError((object)("写存储文件失败:" + ex.GetType().Name + " " + ex.Message));
			}
		}

		private static void AppendString(StringBuilder sb, string name, string value, bool last)
		{
			sb.Append("      ");
			Json.WriteString(sb, name);
			sb.Append(": ");
			Json.WriteString(sb, value);
			sb.Append(last ? "\n" : ",\n");
		}

		private static void AppendNumber(StringBuilder sb, string name, long value, bool last)
		{
			sb.Append("      ");
			Json.WriteString(sb, name);
			sb.Append(": ");
			sb.Append(value.ToString(CultureInfo.InvariantCulture));
			sb.Append(last ? "\n" : ",\n");
		}

		internal static void Flush()
		{
			if (dirty)
			{
				Save();
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "RPC_ClientHandshake")]
	internal static class Patch_ClientHandshake
	{
		private static MethodInfo mOnPasswordEntered;

		private static void Postfix(ZNet __instance, bool needPassword)
		{
			try
			{
				if (!needPassword || (Object)(object)__instance == (Object)null || !EasyLoginPlugin.Enabled.Value)
				{
					return;
				}
				if (!__instance.InPasswordDialog())
				{
					if (EasyLoginPlugin.VerboseLog.Value)
					{
						EasyLoginPlugin.Log.LogInfo((object)"弹窗已关闭(原版自己提交过了),不介入。");
					}
					return;
				}
				ServerIdentity.Identity identity = ServerIdentity.Current();
				if (identity == null)
				{
					return;
				}
				ServerRecord serverRecord = PasswordStore.Find(identity);
				if (serverRecord == null)
				{
					EasyLoginPlugin.Log.LogInfo((object)("没存过这个服务器的密码,等你手输:" + identity.Describe()));
					return;
				}
				string text = Secret.Unprotect(serverRecord.Password);
				if (string.IsNullOrEmpty(text))
				{
					EasyLoginPlugin.Log.LogWarning((object)("有记录但密码解不出来,等你手输:" + identity.Describe()));
				}
				else if (EasyLoginPlugin.AutoSubmit.Value)
				{
					if (mOnPasswordEntered == null)
					{
						mOnPasswordEntered = AccessTools.Method(typeof(ZNet), "OnPasswordEntered", (Type[])null, (Type[])null);
					}
					if (mOnPasswordEntered == null)
					{
						EasyLoginPlugin.Log.LogError((object)"找不到 ZNet.OnPasswordEntered(游戏版本变了吗?),无法自动提交。");
						return;
					}
					EasyLoginPlugin.Log.LogInfo((object)$"自动登录:{identity.Describe()}  → 直接提交已保存的密码(第 {serverRecord.UseCount + 1} 次使用)。");
					mOnPasswordEntered.Invoke(__instance, new object[1] { text });
				}
				else
				{
					FillOnly(__instance, text);
					PasswordStore.Remember(identity, text);
					EasyLoginPlugin.Log.LogInfo((object)$"已把密码填进输入框(未提交):{identity.Describe()}  —— 按回车即可进入。");
				}
			}
			catch (Exception ex)
			{
				EasyLoginPlugin.Log.LogError((object)("自动填密码时出错(不影响你手动输入):" + ex));
			}
		}

		private static void FillOnly(ZNet net, string pwd)
		{
			RectTransform passwordDialog = net.m_passwordDialog;
			if ((Object)(object)passwordDialog == (Object)null)
			{
				EasyLoginPlugin.Log.LogWarning((object)"找不到密码弹窗对象,无法填入。");
				return;
			}
			Type type = AccessTools.TypeByName("GuiInputField");
			if (type == null)
			{
				EasyLoginPlugin.Log.LogWarning((object)"找不到 GuiInputField 类型(游戏版本变了吗?),无法填入。");
				return;
			}
			Component component = ((Component)passwordDialog).GetComponent(type);
			if ((Object)(object)component == (Object)null)
			{
				EasyLoginPlugin.Log.LogWarning((object)"密码弹窗上没有 GuiInputField 组件,无法填入。");
				return;
			}
			PropertyInfo property = type.GetProperty("text");
			if (property == null || !property.CanWrite)
			{
				EasyLoginPlugin.Log.LogWarning((object)"GuiInputField 没有可写的 text 属性,无法填入。");
			}
			else
			{
				property.SetValue(component, pwd, null);
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "OnPasswordEntered")]
	internal static class Patch_OnPasswordEntered
	{
		private static void Postfix(string pwd)
		{
			try
			{
				if (EasyLoginPlugin.Enabled.Value && !string.IsNullOrEmpty(pwd))
				{
					ServerIdentity.Identity identity = ServerIdentity.Current();
					if (identity != null)
					{
						PasswordStore.Remember(identity, pwd);
					}
				}
			}
			catch (Exception ex)
			{
				EasyLoginPlugin.Log.LogError((object)("记录密码时出错:" + ex));
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "RPC_Error")]
	internal static class Patch_RpcError
	{
		private const int ErrorPassword = 6;

		private static void Postfix(int error)
		{
			try
			{
				if (error == 6 && EasyLoginPlugin.Enabled.Value)
				{
					if (!EasyLoginPlugin.ForgetOnPasswordError.Value)
					{
						EasyLoginPlugin.Log.LogWarning((object)"服务器报告「密码错误」,但配置里关掉了自动遗忘,记录保留 —— 下次还会自动填同样的密码,注意别再被锁在外面。");
					}
					else
					{
						PasswordStore.Forget(ServerIdentity.Current(), "服务端返回 Error(6) = ConnectionStatus.ErrorPassword");
					}
				}
			}
			catch (Exception ex)
			{
				EasyLoginPlugin.Log.LogError((object)("处理密码错误时出错:" + ex));
			}
		}
	}
	internal static class Secret
	{
		private const string XorKey = "EasyLogin/1.0/valheim-server-password-store";

		private static readonly byte[] XorBytes = Encoding.UTF8.GetBytes("EasyLogin/1.0/valheim-server-password-store");

		private static bool dpapiUsable = true;

		public static string Protect(string plain)
		{
			if (plain == null)
			{
				plain = "";
			}
			if (EasyLoginPlugin.EncryptWithWindowsUserKey.Value && dpapiUsable)
			{
				try
				{
					byte[] inArray = DpapiProtect(Encoding.UTF8.GetBytes(plain));
					return "dpapi:" + Convert.ToBase64String(inArray);
				}
				catch (Exception ex)
				{
					dpapiUsable = false;
					EasyLoginPlugin.Log.LogWarning((object)("DPAPI 不可用,退化为简单混淆存储(功能不受影响):" + ex.GetType().Name + " " + ex.Message));
				}
			}
			return "obf:" + Convert.ToBase64String(Xor(Encoding.UTF8.GetBytes(plain)));
		}

		public static string Unprotect(string stored)
		{
			if (string.IsNullOrEmpty(stored))
			{
				return "";
			}
			int num = stored.IndexOf(':');
			if (num <= 0)
			{
				return stored;
			}
			string text = stored.Substring(0, num);
			string text2 = stored.Substring(num + 1);
			try
			{
				switch (text)
				{
				case "plain":
					return text2;
				case "obf":
					return Encoding.UTF8.GetString(Xor(Convert.FromBase64String(text2)));
				case "dpapi":
					return Encoding.UTF8.GetString(DpapiUnprotect(Convert.FromBase64String(text2)));
				default:
					EasyLoginPlugin.Log.LogWarning((object)("看不懂的密码存储格式:" + text + ",按明文处理。"));
					return stored;
				}
			}
			catch (Exception ex)
			{
				EasyLoginPlugin.Log.LogError((object)("解密密码失败(" + text + "):" + ex.GetType().Name + " " + ex.Message + "。如果你换过 Windows 账号或换过电脑,把 servers.json 里这条记录删掉重输即可。"));
				return "";
			}
		}

		private static byte[] DpapiProtect(byte[] data)
		{
			return ProtectedData.Protect(data, (byte[])null, (DataProtectionScope)0);
		}

		private static byte[] DpapiUnprotect(byte[] data)
		{
			return ProtectedData.Unprotect(data, (byte[])null, (DataProtectionScope)0);
		}

		private static byte[] Xor(byte[] data)
		{
			byte[] array = new byte[data.Length];
			for (int i = 0; i < data.Length; i++)
			{
				array[i] = (byte)(data[i] ^ XorBytes[i % XorBytes.Length]);
			}
			return array;
		}
	}
	internal static class ServerIdentity
	{
		internal sealed class Identity
		{
			public string Key;

			public string Kind = "Unknown";

			public string SteamId = "";

			public string PlayFabId = "";

			public string Host = "";

			public string JoinCode = "";

			public string ServerName = "";

			public int Port;

			public int Backend = -1;

			internal string BackendName => Backend switch
			{
				0 => "Steam", 
				1 => "PlayFab", 
				2 => "EOS", 
				3 => "CustomSocket", 
				_ => "?", 
			};

			internal string Describe()
			{
				string text = Kind switch
				{
					"Steam" => "Steam ID " + SteamId, 
					"PlayFab" => "跨平台 host " + PlayFabId + ((JoinCode.Length > 0) ? ("(房间码 " + JoinCode + ")") : ""), 
					"Code" => "跨平台房间码 " + JoinCode, 
					"IP" => "IP " + Host + ":" + Port, 
					_ => "未知(" + BackendName + " 后端,字段都是空的)", 
				};
				if (!string.IsNullOrEmpty(ServerName))
				{
					text = text + "「" + ServerName + "」";
				}
				return text;
			}
		}

		internal const int BackendSteam = 0;

		internal const int BackendPlayFab = 1;

		private static FieldInfo fSteamId;

		private static FieldInfo fPlayFabId;

		private static FieldInfo fHost;

		private static FieldInfo fPort;

		private static PropertyInfo pJoinCode;

		private static bool joinCodeGivenUp;

		internal static void Init()
		{
			fSteamId = AccessTools.Field(typeof(ZNet), "m_serverSteamID");
			fPlayFabId = AccessTools.Field(typeof(ZNet), "m_serverPlayFabPlayerId");
			fHost = AccessTools.Field(typeof(ZNet), "m_serverHost");
			fPort = AccessTools.Field(typeof(ZNet), "m_serverHostPort");
			if (fSteamId == null || fPlayFabId == null || fHost == null || fPort == null)
			{
				EasyLoginPlugin.Log.LogError((object)("读不到 ZNet 的服务器标识字段(游戏版本可能变了)。steam=" + (fSteamId != null) + " playfab=" + (fPlayFabId != null) + " host=" + (fHost != null) + " port=" + (fPort != null)));
			}
		}

		internal static Identity Current()
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected I4, but got Unknown
			try
			{
				ZNet instance = ZNet.instance;
				if ((Object)(object)instance == (Object)null)
				{
					return null;
				}
				Identity identity = new Identity();
				identity.Backend = (int)ZNet.m_onlineBackend;
				ulong num = ReadUlong(fSteamId);
				identity.SteamId = ((num == 0L) ? "" : num.ToString());
				identity.PlayFabId = ReadString(fPlayFabId);
				identity.Host = ReadString(fHost);
				identity.Port = ReadInt(fPort);
				identity.JoinCode = ReadJoinCode();
				identity.ServerName = WorldName(instance);
				if (identity.Backend == 0 && identity.SteamId.Length > 0)
				{
					identity.Key = "steam:" + identity.SteamId;
					identity.Kind = "Steam";
				}
				else if (identity.Backend == 1)
				{
					if (identity.PlayFabId.Length > 0)
					{
						identity.Key = "playfab:" + identity.PlayFabId;
						identity.Kind = "PlayFab";
					}
					else if (identity.JoinCode.Length > 0)
					{
						identity.Key = "code:" + identity.JoinCode;
						identity.Kind = "Code";
					}
				}
				if (identity.Key == null && identity.Host.Length > 0)
				{
					identity.Key = "ip:" + identity.Host + ":" + identity.Port;
					identity.Kind = "IP";
				}
				if (identity.Key == null)
				{
					EasyLoginPlugin.Log.LogWarning((object)("识别不出当前服务器的身份(后端 " + identity.BackendName + "):steamId='" + identity.SteamId + "' playfabId='" + identity.PlayFabId + "' host='" + identity.Host + "' 房间码='" + identity.JoinCode + "'"));
					return null;
				}
				if (EasyLoginPlugin.VerboseLog.Value)
				{
					EasyLoginPlugin.Log.LogInfo((object)$"当前服务器:{identity.Describe()}  |  key={identity.Key}  steamId='{identity.SteamId}' playfabId='{identity.PlayFabId}' host='{identity.Host}' port={identity.Port} 房间码='{identity.JoinCode}' 世界名='{identity.ServerName}'");
				}
				return identity;
			}
			catch (Exception ex)
			{
				EasyLoginPlugin.Log.LogError((object)("识别服务器身份时出错:" + ex.GetType().Name + " " + ex.Message));
				return null;
			}
		}

		internal static string CurrentServerName()
		{
			try
			{
				return WorldName(ZNet.instance);
			}
			catch (Exception)
			{
				return "";
			}
		}

		private static string WorldName(ZNet net)
		{
			if ((Object)(object)net == (Object)null)
			{
				return "";
			}
			string worldName = net.GetWorldName();
			if (worldName != null)
			{
				return worldName;
			}
			return "";
		}

		private static string ReadString(FieldInfo f)
		{
			if (f == null)
			{
				return "";
			}
			object value = f.GetValue(null);
			if (value != null)
			{
				return (string)value;
			}
			return "";
		}

		private static ulong ReadUlong(FieldInfo f)
		{
			if (f == null)
			{
				return 0uL;
			}
			object value = f.GetValue(null);
			if (value == null)
			{
				return 0uL;
			}
			try
			{
				return Convert.ToUInt64(value);
			}
			catch (Exception)
			{
				return 0uL;
			}
		}

		private static int ReadInt(FieldInfo f)
		{
			if (f == null)
			{
				return 0;
			}
			object value = f.GetValue(null);
			if (value == null)
			{
				return 0;
			}
			try
			{
				return Convert.ToInt32(value);
			}
			catch (Exception)
			{
				return 0;
			}
		}

		private static string ReadJoinCode()
		{
			if (joinCodeGivenUp)
			{
				return "";
			}
			try
			{
				if (pJoinCode == null)
				{
					Type type = AccessTools.TypeByName("ZPlayFabMatchmaking");
					if (type == null)
					{
						joinCodeGivenUp = true;
						return "";
					}
					pJoinCode = type.GetProperty("JoinCode", BindingFlags.Static | BindingFlags.Public);
					if (pJoinCode == null)
					{
						joinCodeGivenUp = true;
						return "";
					}
				}
				object value = pJoinCode.GetValue(null, null);
				return (value == null) ? "" : ((string)value);
			}
			catch (Exception ex)
			{
				joinCodeGivenUp = true;
				EasyLoginPlugin.Log.LogWarning((object)("读房间码失败,之后不再尝试:" + ex.GetType().Name + " " + ex.Message));
				return "";
			}
		}
	}
}