Decompiled source of PortalPass v0.1.2

PortalPass.dll

Decompiled 3 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;

[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("PortalPass")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Client-side Valheim password injection with endpoint-aware matching")]
[assembly: AssemblyFileVersion("0.1.2.0")]
[assembly: AssemblyInformationalVersion("0.1.2+d55f562a6d3c4b925f9056f3fb79acc0862411a1")]
[assembly: AssemblyProduct("PortalPass")]
[assembly: AssemblyTitle("PortalPass")]
[assembly: AssemblyVersion("0.1.2.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 Jcruse03.PortalPass
{
	internal static class ConnectionEndpoint
	{
		private static string? _pendingHost;

		private static int _pendingPort;

		internal static void Capture(string? host, int port)
		{
			if (string.IsNullOrWhiteSpace(host) || port < 1 || port > 65535)
			{
				Clear();
				return;
			}
			_pendingHost = host;
			_pendingPort = port;
		}

		internal static void Clear()
		{
			_pendingHost = null;
			_pendingPort = 0;
		}

		internal static bool TryConsume(string? liveHost, int livePort, out string host, out int port)
		{
			if (!string.IsNullOrWhiteSpace(_pendingHost))
			{
				int pendingPort = _pendingPort;
				if (pendingPort >= 1 && pendingPort <= 65535)
				{
					host = _pendingHost;
					port = _pendingPort;
					Clear();
					return true;
				}
			}
			Clear();
			if (!string.IsNullOrWhiteSpace(liveHost) && livePort >= 1 && livePort <= 65535)
			{
				host = liveHost;
				port = livePort;
				return true;
			}
			host = string.Empty;
			port = 0;
			return false;
		}
	}
	internal readonly struct EndpointKey : IEquatable<EndpointKey>
	{
		internal string Host { get; }

		internal int? Port { get; }

		internal EndpointKey(string host, int? port)
		{
			Host = host;
			Port = port;
		}

		internal static EndpointKey Parse(string text)
		{
			if (string.IsNullOrWhiteSpace(text))
			{
				throw new FormatException("Endpoint is empty.");
			}
			string text2 = text.Trim();
			int? port = null;
			string value;
			if (text2[0] == '[')
			{
				int num = text2.IndexOf(']');
				if (num < 0)
				{
					throw new FormatException("Bracketed IPv6 endpoint is missing ']'.");
				}
				value = text2.Substring(1, num - 1);
				string text3 = text2.Substring(num + 1);
				if (text3.Length > 0)
				{
					if (text3[0] != ':')
					{
						throw new FormatException("Unexpected text after bracketed host.");
					}
					port = ParsePort(text3.Substring(1));
				}
			}
			else
			{
				int num2 = text2.IndexOf(':');
				int num3 = text2.LastIndexOf(':');
				if (num2 >= 0 && num2 == num3)
				{
					value = text2.Substring(0, num2);
					port = ParsePort(text2.Substring(num2 + 1));
				}
				else
				{
					value = text2;
				}
			}
			return new EndpointKey(NormalizeHost(value), port);
		}

		internal static EndpointKey FromConnection(string host, int port)
		{
			if (port < 1 || port > 65535)
			{
				throw new FormatException("Connection port is outside 1-65535.");
			}
			return new EndpointKey(NormalizeHost(host), port);
		}

		internal EndpointKey WithoutPort()
		{
			return new EndpointKey(Host, null);
		}

		private static int ParsePort(string value)
		{
			if (!int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result < 1 || result > 65535)
			{
				throw new FormatException("Port must be an integer from 1 through 65535.");
			}
			return result;
		}

		private static string NormalizeHost(string value)
		{
			string text = value.Trim();
			if (text.Length == 0)
			{
				throw new FormatException("Host is empty.");
			}
			if (text.IndexOfAny(new char[9] { '/', '\\', '@', '#', '=', ' ', '\t', '\r', '\n' }) >= 0)
			{
				throw new FormatException("Host contains unsupported characters.");
			}
			if (IPAddress.TryParse(text, out IPAddress address))
			{
				return address.ToString().ToLowerInvariant();
			}
			text = text.TrimEnd(new char[1] { '.' }).ToLowerInvariant();
			if (text.Length == 0)
			{
				throw new FormatException("Host is empty after normalization.");
			}
			return text;
		}

		public bool Equals(EndpointKey other)
		{
			if (Port == other.Port)
			{
				return string.Equals(Host, other.Host, StringComparison.Ordinal);
			}
			return false;
		}

		public override bool Equals(object? obj)
		{
			if (obj is EndpointKey other)
			{
				return Equals(other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return (Host.GetHashCode() * 397) ^ Port.GetValueOrDefault();
		}

		public override string ToString()
		{
			if (!Port.HasValue)
			{
				return Host;
			}
			if (!Host.Contains(":"))
			{
				return $"{Host}:{Port.Value}";
			}
			return $"[{Host}]:{Port.Value}";
		}
	}
	[HarmonyPatch(typeof(FejdStartup), "SetServerToJoin")]
	internal static class ServerSelectionPatch
	{
		private static void Prefix(ServerJoinData serverData)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			if ((int)serverData.m_type == 3)
			{
				ServerJoinDataDedicated dedicated = ((ServerJoinData)(ref serverData)).Dedicated;
				ConnectionEndpoint.Capture(((ServerJoinDataDedicated)(ref dedicated)).GetHost(), dedicated.m_port);
			}
			else
			{
				ConnectionEndpoint.Clear();
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "RPC_ClientHandshake")]
	internal static class PasswordHandshakePatch
	{
		private static readonly FieldInfo ServerHostField = AccessTools.Field(typeof(ZNet), "m_serverHost");

		private static readonly FieldInfo ServerPortField = AccessTools.Field(typeof(ZNet), "m_serverHostPort");

		private static readonly FieldInfo ServerPasswordField = AccessTools.Field(typeof(FejdStartup), "<ServerPassword>k__BackingField");

		private static string? _injectedPassword;

		private static void Prefix(bool needPassword)
		{
			ClearOwnedPassword();
			if (!needPassword)
			{
				ConnectionEndpoint.Clear();
				return;
			}
			string liveHost = ServerHostField.GetValue(null) as string;
			int livePort = ((ServerPortField.GetValue(null) is int num) ? num : 0);
			if (!ConnectionEndpoint.TryConsume(liveHost, livePort, out string host, out int port))
			{
				PortalPassPlugin.Log.LogWarning((object)"Could not determine the requested dedicated endpoint after backend resolution. Leaving the vanilla password prompt active.");
				return;
			}
			if (!SecretFile.TryResolve(PortalPassPlugin.SecretFilePath.Value, host, port, PortalPassPlugin.Log, out string password))
			{
				PortalPassPlugin.Log.LogInfo((object)("No configured password matched " + DisplayEndpoint(host, port) + ". Leaving the vanilla password prompt active."));
				return;
			}
			_injectedPassword = password;
			ServerPasswordField.SetValue(null, password);
			PortalPassPlugin.Log.LogInfo((object)("Supplying a configured password for " + DisplayEndpoint(host, port) + "."));
		}

		private static void Postfix()
		{
			ClearOwnedPassword();
		}

		private static void ClearOwnedPassword()
		{
			if (_injectedPassword != null && string.Equals(FejdStartup.ServerPassword, _injectedPassword, StringComparison.Ordinal))
			{
				ServerPasswordField.SetValue(null, null);
			}
			_injectedPassword = null;
		}

		private static string DisplayEndpoint(string host, int port)
		{
			if (!host.Contains(":"))
			{
				return $"{host}:{port}";
			}
			return $"[{host}]:{port}";
		}
	}
	internal sealed class PasswordMap
	{
		private readonly Dictionary<EndpointKey, string> _passwords;

		private PasswordMap(Dictionary<EndpointKey, string> passwords)
		{
			_passwords = passwords;
		}

		internal static PasswordMap Parse(IEnumerable<string> lines)
		{
			Dictionary<EndpointKey, string> dictionary = new Dictionary<EndpointKey, string>();
			int num = 0;
			foreach (string line in lines)
			{
				num++;
				string text = line.Trim();
				if (text.Length != 0 && !text.StartsWith("#", StringComparison.Ordinal))
				{
					int num2 = text.IndexOf('=');
					if (num2 < 1)
					{
						throw new FormatException($"Line {num}: expected endpoint=password.");
					}
					EndpointKey endpointKey;
					try
					{
						endpointKey = EndpointKey.Parse(text.Substring(0, num2));
					}
					catch (FormatException ex)
					{
						throw new FormatException($"Line {num}: {ex.Message}", ex);
					}
					string text2 = Unquote(text.Substring(num2 + 1).Trim());
					if (text2.Length == 0)
					{
						throw new FormatException($"Line {num}: password is empty.");
					}
					if (dictionary.ContainsKey(endpointKey))
					{
						throw new FormatException($"Line {num}: duplicate endpoint after normalization: {endpointKey}.");
					}
					dictionary.Add(endpointKey, text2);
				}
			}
			return new PasswordMap(dictionary);
		}

		internal static PasswordMap Load(string path)
		{
			return Parse(File.ReadLines(path));
		}

		internal bool TryResolve(string host, int port, out string password)
		{
			EndpointKey key = EndpointKey.FromConnection(host, port);
			if (!_passwords.TryGetValue(key, out password))
			{
				return _passwords.TryGetValue(key.WithoutPort(), out password);
			}
			return true;
		}

		private static string Unquote(string value)
		{
			if (value.Length < 2)
			{
				return value;
			}
			char c = value[0];
			char c2 = value[value.Length - 1];
			if ((c == '\'' && c2 == '\'') || (c == '"' && c2 == '"'))
			{
				return value.Substring(1, value.Length - 2);
			}
			if (c == '\'' || c == '"' || c2 == '\'' || c2 == '"')
			{
				throw new FormatException("Password has an unmatched quote.");
			}
			return value;
		}
	}
	[BepInPlugin("com.jcruse03.portalpass", "PortalPass", "0.1.2")]
	public sealed class PortalPassPlugin : BaseUnityPlugin
	{
		internal const string PluginGuid = "com.jcruse03.portalpass";

		internal const string PluginName = "PortalPass";

		internal const string PluginVersion = "0.1.2";

		internal static ConfigEntry<string> SecretFilePath;

		internal static ManualLogSource Log;

		private Harmony? _harmony;

		private void Awake()
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			SecretFilePath = ((BaseUnityPlugin)this).Config.Bind<string>("General", "SecretFilePath", SecretFile.DefaultPath(), "Absolute path (or ~/ path) to the endpoint=password secret file. Keep this outside r2modman profiles.");
			_harmony = new Harmony("com.jcruse03.portalpass");
			_harmony.PatchAll(Assembly.GetExecutingAssembly());
			((BaseUnityPlugin)this).Logger.LogInfo((object)("PortalPass 0.1.2 loaded (client-only). Secrets: " + SecretFile.ExpandPath(SecretFilePath.Value)));
		}

		private void OnDestroy()
		{
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class SecretFile
	{
		internal static string DefaultPath()
		{
			if (Environment.OSVersion.Platform == PlatformID.Win32NT)
			{
				return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "PortalPass", "passwords.env");
			}
			string text = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
			if (string.IsNullOrWhiteSpace(text))
			{
				text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config");
			}
			return Path.Combine(text, "portalpass", "passwords.env");
		}

		internal static string ExpandPath(string path)
		{
			string text = Environment.ExpandEnvironmentVariables(path.Trim());
			if (text == "~")
			{
				return Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
			}
			if (text.StartsWith("~/", StringComparison.Ordinal) || text.StartsWith("~\\", StringComparison.Ordinal))
			{
				return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), text.Substring(2));
			}
			return Path.GetFullPath(text);
		}

		internal static bool TryResolve(string path, string host, int port, ManualLogSource logger, out string password)
		{
			password = string.Empty;
			try
			{
				string text = ExpandPath(path);
				if (!File.Exists(text))
				{
					logger.LogWarning((object)("Secret file not found: " + text + ". Leaving the vanilla password prompt active."));
					return false;
				}
				return PasswordMap.Load(text).TryResolve(host, port, out password);
			}
			catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException || ex is FormatException || ex is ArgumentException)
			{
				logger.LogWarning((object)("Could not load PortalPass secrets (" + ex.Message + "). Leaving the vanilla password prompt active."));
				return false;
			}
		}
	}
}