Decompiled source of MX AHG v1.0.1

MX-AHG.dll

Decompiled a month ago
using 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.Cryptography;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using MxAhg.Shared;
using Photon.Pun;
using Photon.Realtime;
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("maxkir041")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Lightweight PEAK anti-cheat agent for PEAK-MX hosts.")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyInformationalVersion("1.0.1+521ac582fe7571a9dd12b215c7ef64114eb85713")]
[assembly: AssemblyProduct("MX-AHG")]
[assembly: AssemblyTitle("MX-AHG")]
[assembly: AssemblyVersion("1.0.1.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 MxAhg
{
	[BepInPlugin("com.maxkir041.mxahg", "MX-AHG", "1.0.1")]
	public sealed class AgentPlugin : BaseUnityPlugin
	{
		public const string Guid = "com.maxkir041.mxahg";

		public const string Name = "MX-AHG";

		public const string Version = "1.0.1";

		internal static ManualLogSource Log;

		private Snapshot _snapshot;

		private bool _subscribed;

		private float _nextScan;

		private float _nextPublish;

		private int _heartbeat;

		private string _roomName = "";

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			EnsureSubscribed();
			RefreshSnapshot();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"MX-AHG v1.0.1 loaded. Scans only BepInEx plugin state inside PEAK.");
		}

		private void OnDestroy()
		{
			if (_subscribed)
			{
				try
				{
					PhotonNetwork.NetworkingClient.EventReceived -= OnEvent;
				}
				catch
				{
				}
				_subscribed = false;
			}
		}

		private void Update()
		{
			EnsureSubscribed();
			if (!PhotonNetwork.InRoom || PhotonNetwork.LocalPlayer == null)
			{
				_roomName = "";
				_nextPublish = 0f;
				_heartbeat = 0;
				return;
			}
			Room currentRoom = PhotonNetwork.CurrentRoom;
			string text = ((currentRoom != null) ? currentRoom.Name : null) ?? "room";
			if (!string.Equals(text, _roomName, StringComparison.Ordinal))
			{
				_roomName = text;
				_nextPublish = 0f;
				_nextScan = 0f;
				_heartbeat = 0;
			}
			if (Time.realtimeSinceStartup >= _nextScan)
			{
				RefreshSnapshot();
				_nextScan = Time.realtimeSinceStartup + 30f;
			}
			if (Time.realtimeSinceStartup >= _nextPublish)
			{
				PublishHeartbeat();
				_nextPublish = Time.realtimeSinceStartup + 5f;
			}
		}

		private void EnsureSubscribed()
		{
			if (_subscribed || PhotonNetwork.NetworkingClient == null)
			{
				return;
			}
			try
			{
				PhotonNetwork.NetworkingClient.EventReceived += OnEvent;
				_subscribed = true;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogDebug((object)("Event subscription failed: " + ex.Message));
			}
		}

		private void RefreshSnapshot()
		{
			try
			{
				_snapshot = Scanner.Scan(typeof(AgentPlugin).Assembly.Location, excludePeakMx: true);
				if (!string.IsNullOrWhiteSpace(_snapshot.Error))
				{
					((BaseUnityPlugin)this).Logger.LogDebug((object)("Scan notes: " + _snapshot.Error));
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Scan failed: " + ex.Message));
				_snapshot = new Snapshot
				{
					Error = ex.Message
				};
				_snapshot.Recalculate();
			}
		}

		private void PublishHeartbeat()
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: 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_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Expected O, but got Unknown
			try
			{
				if (_snapshot == null)
				{
					RefreshSnapshot();
				}
				Hashtable val = new Hashtable
				{
					[(object)"mxahg_v"] = "1.0.1",
					[(object)"mxahg_p"] = 1,
					[(object)"mxahg_s"] = Protocol.Short(_snapshot.FileSignature),
					[(object)"mxahg_c"] = _snapshot.Files.Count,
					[(object)"mxahg_l"] = Protocol.Short(_snapshot.LoadedSignature),
					[(object)"mxahg_lc"] = _snapshot.Plugins.Count,
					[(object)"mxahg_h"] = ++_heartbeat,
					[(object)"mxahg_i"] = Protocol.Short(_snapshot.SelfHash)
				};
				PhotonNetwork.LocalPlayer.SetCustomProperties(val, (Hashtable)null, (WebFlags)null);
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogDebug((object)("Heartbeat failed: " + ex.Message));
			}
		}

		private void OnEvent(EventData data)
		{
			try
			{
				if (data != null && data.Code == 172 && data.CustomData is object[] array && array.Length >= 4 && string.Equals(array[0]?.ToString(), "MX_AHG", StringComparison.Ordinal) && Convert.ToInt32(array[1]) == 1 && Convert.ToInt32(array[2]) == 1 && PhotonNetwork.MasterClient != null && data.Sender == PhotonNetwork.MasterClient.ActorNumber)
				{
					string text = Protocol.Limit(array[3]?.ToString() ?? "", 96);
					if (text.Length >= 12)
					{
						RefreshSnapshot();
						PublishHeartbeat();
						SendReport(data.Sender, text);
					}
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogDebug((object)("Challenge failed: " + ex.Message));
			}
		}

		private void SendReport(int targetActor, string nonce)
		{
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Expected O, but got Unknown
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			if (_snapshot != null && targetActor > 0)
			{
				string text = Protocol.ComputeProof(nonce, _snapshot);
				object[] array = new object[14]
				{
					"MX_AHG",
					1,
					2,
					nonce,
					Protocol.ToFlatFiles(_snapshot.Files),
					Protocol.ToFlatPlugins(_snapshot.Plugins),
					_snapshot.HarmonyOwners.ToArray(),
					_snapshot.FileSignature,
					_snapshot.LoadedSignature,
					_snapshot.SelfHash,
					text,
					Protocol.Limit(_snapshot.Error, 500),
					"1.0.1",
					_snapshot.Truncated
				};
				RaiseEventOptions val = new RaiseEventOptions();
				val.TargetActors = new int[1] { targetActor };
				RaiseEventOptions val2 = val;
				PhotonNetwork.RaiseEvent((byte)172, (object)array, val2, SendOptions.SendReliable);
			}
		}
	}
}
namespace MxAhg.Shared
{
	internal static class Protocol
	{
		public const byte EventCode = 172;

		public const string Magic = "MX_AHG";

		public const int Version = 1;

		public const int CommandChallenge = 1;

		public const int CommandReport = 2;

		public const string AgentVersionKey = "mxahg_v";

		public const string ProtocolKey = "mxahg_p";

		public const string FileSignatureKey = "mxahg_s";

		public const string FileCountKey = "mxahg_c";

		public const string LoadedSignatureKey = "mxahg_l";

		public const string LoadedCountKey = "mxahg_lc";

		public const string HeartbeatKey = "mxahg_h";

		public const string SelfHashKey = "mxahg_i";

		public const int MaxFiles = 512;

		public const int MaxPlugins = 256;

		public const int MaxHarmonyOwners = 256;

		public static string Short(string value, int length = 16)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return "?";
			}
			if (value.Length > length)
			{
				return value.Substring(0, length);
			}
			return value;
		}

		public static string HashFile(string path)
		{
			using SHA256 sHA = SHA256.Create();
			using FileStream inputStream = File.OpenRead(path);
			return ToHex(sHA.ComputeHash(inputStream));
		}

		public static string HashText(string value)
		{
			using SHA256 sHA = SHA256.Create();
			return ToHex(sHA.ComputeHash(Encoding.UTF8.GetBytes(value ?? "")));
		}

		public static string ComputeProof(string nonce, Snapshot snapshot)
		{
			if (snapshot == null)
			{
				return "";
			}
			return HashText(nonce + "|" + snapshot.FileSignature + "|" + snapshot.LoadedSignature + "|" + snapshot.SelfHash + "|" + (snapshot.Truncated ? "1" : "0") + "|" + snapshot.Error + "|" + snapshot.BuildCanonical());
		}

		public static string[] ToFlatFiles(IReadOnlyList<FileEntry> entries)
		{
			if (entries == null || entries.Count == 0)
			{
				return Array.Empty<string>();
			}
			string[] array = new string[entries.Count * 4];
			for (int i = 0; i < entries.Count; i++)
			{
				int num = i * 4;
				array[num] = entries[i].RelativePath ?? "";
				array[num + 1] = entries[i].Sha256 ?? "";
				array[num + 2] = entries[i].AssemblyName ?? "";
				array[num + 3] = entries[i].AssemblyVersion ?? "";
			}
			return array;
		}

		public static string[] ToFlatPlugins(IReadOnlyList<PluginEntry> entries)
		{
			if (entries == null || entries.Count == 0)
			{
				return Array.Empty<string>();
			}
			string[] array = new string[entries.Count * 5];
			for (int i = 0; i < entries.Count; i++)
			{
				int num = i * 5;
				array[num] = entries[i].Guid ?? "";
				array[num + 1] = entries[i].Name ?? "";
				array[num + 2] = entries[i].Version ?? "";
				array[num + 3] = entries[i].RelativePath ?? "";
				array[num + 4] = entries[i].Sha256 ?? "";
			}
			return array;
		}

		public static List<FileEntry> ParseFiles(object value)
		{
			string[] array = ReadStringArray(value);
			List<FileEntry> list = new List<FileEntry>();
			int num = Math.Min(array.Length / 4, 512);
			for (int i = 0; i < num; i++)
			{
				int num2 = i * 4;
				list.Add(new FileEntry
				{
					RelativePath = Limit(array[num2], 320),
					Sha256 = Limit(array[num2 + 1], 64),
					AssemblyName = Limit(array[num2 + 2], 160),
					AssemblyVersion = Limit(array[num2 + 3], 80)
				});
			}
			return list;
		}

		public static List<PluginEntry> ParsePlugins(object value)
		{
			string[] array = ReadStringArray(value);
			List<PluginEntry> list = new List<PluginEntry>();
			int num = Math.Min(array.Length / 5, 256);
			for (int i = 0; i < num; i++)
			{
				int num2 = i * 5;
				list.Add(new PluginEntry
				{
					Guid = Limit(array[num2], 180),
					Name = Limit(array[num2 + 1], 180),
					Version = Limit(array[num2 + 2], 80),
					RelativePath = Limit(array[num2 + 3], 320),
					Sha256 = Limit(array[num2 + 4], 64)
				});
			}
			return list;
		}

		public static string[] ReadStringArray(object value)
		{
			if (value is string[] result)
			{
				return result;
			}
			if (value is object[] array)
			{
				int num = Math.Min(array.Length, 2560);
				string[] array2 = new string[num];
				for (int i = 0; i < num; i++)
				{
					array2[i] = array[i]?.ToString() ?? "";
				}
				return array2;
			}
			return Array.Empty<string>();
		}

		public static string Limit(string value, int maxLength)
		{
			if (string.IsNullOrEmpty(value))
			{
				return "";
			}
			if (value.Length > maxLength)
			{
				return value.Substring(0, maxLength);
			}
			return value;
		}

		private static string ToHex(byte[] bytes)
		{
			StringBuilder stringBuilder = new StringBuilder(bytes.Length * 2);
			for (int i = 0; i < bytes.Length; i++)
			{
				stringBuilder.Append(bytes[i].ToString("x2"));
			}
			return stringBuilder.ToString();
		}
	}
	internal sealed class FileEntry
	{
		public string RelativePath = "";

		public string Sha256 = "";

		public string AssemblyName = "";

		public string AssemblyVersion = "";
	}
	internal sealed class PluginEntry
	{
		public string Guid = "";

		public string Name = "";

		public string Version = "";

		public string RelativePath = "";

		public string Sha256 = "";
	}
	internal sealed class Snapshot
	{
		public readonly List<FileEntry> Files = new List<FileEntry>();

		public readonly List<PluginEntry> Plugins = new List<PluginEntry>();

		public readonly List<string> HarmonyOwners = new List<string>();

		public string FileSignature = "";

		public string LoadedSignature = "";

		public string SelfHash = "";

		public string Error = "";

		public bool Truncated;

		public void Recalculate()
		{
			Files.Sort((FileEntry a, FileEntry b) => string.Compare(a.RelativePath, b.RelativePath, StringComparison.OrdinalIgnoreCase));
			Plugins.Sort((PluginEntry a, PluginEntry b) => string.Compare(a.Guid, b.Guid, StringComparison.OrdinalIgnoreCase));
			HarmonyOwners.Sort(StringComparer.OrdinalIgnoreCase);
			FileSignature = Protocol.HashText(BuildFileCanonical());
			LoadedSignature = Protocol.HashText(BuildLoadedCanonical());
		}

		public string BuildCanonical()
		{
			return BuildFileCanonical() + "\n--loaded--\n" + BuildLoadedCanonical();
		}

		private string BuildFileCanonical()
		{
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < Files.Count; i++)
			{
				AppendField(stringBuilder, Files[i].RelativePath.ToLowerInvariant());
				AppendField(stringBuilder, Files[i].Sha256.ToLowerInvariant());
				AppendField(stringBuilder, Files[i].AssemblyName.ToLowerInvariant());
				AppendField(stringBuilder, Files[i].AssemblyVersion);
				stringBuilder.Append('\n');
			}
			return stringBuilder.ToString();
		}

		private string BuildLoadedCanonical()
		{
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < Plugins.Count; i++)
			{
				AppendField(stringBuilder, Plugins[i].Guid.ToLowerInvariant());
				AppendField(stringBuilder, Plugins[i].Name.ToLowerInvariant());
				AppendField(stringBuilder, Plugins[i].Version);
				AppendField(stringBuilder, Plugins[i].RelativePath.ToLowerInvariant());
				AppendField(stringBuilder, Plugins[i].Sha256.ToLowerInvariant());
				stringBuilder.Append('\n');
			}
			stringBuilder.Append("owners\n");
			for (int j = 0; j < HarmonyOwners.Count; j++)
			{
				AppendField(stringBuilder, HarmonyOwners[j].ToLowerInvariant());
			}
			return stringBuilder.ToString();
		}

		private static void AppendField(StringBuilder sb, string value)
		{
			value = value ?? "";
			sb.Append(value.Length).Append(':').Append(value)
				.Append('|');
		}
	}
	internal static class Scanner
	{
		public const string AgentGuid = "com.maxkir041.mxahg";

		public const string PeakMxGuid = "com.maxkir041.peakmx";

		public static Snapshot Scan(string selfAssemblyPath, bool excludePeakMx = false)
		{
			Snapshot snapshot = new Snapshot();
			List<string> list = new List<string>();
			string text = Paths.PluginPath;
			if (string.IsNullOrWhiteSpace(text) || !Directory.Exists(text))
			{
				text = Path.GetDirectoryName(selfAssemblyPath) ?? "";
			}
			try
			{
				if (!string.IsNullOrWhiteSpace(selfAssemblyPath) && File.Exists(selfAssemblyPath))
				{
					snapshot.SelfHash = Protocol.HashFile(selfAssemblyPath);
				}
			}
			catch (Exception ex)
			{
				list.Add("self: " + ex.Message);
			}
			try
			{
				string[] array = (Directory.Exists(text) ? Directory.GetFiles(text, "*.dll", SearchOption.AllDirectories) : Array.Empty<string>());
				Array.Sort(array, (IComparer<string>?)StringComparer.OrdinalIgnoreCase);
				for (int i = 0; i < array.Length; i++)
				{
					if (snapshot.Files.Count >= 512)
					{
						snapshot.Truncated = true;
						break;
					}
					if (ShouldExcludeFile(Path.GetFileName(array[i]), excludePeakMx))
					{
						continue;
					}
					try
					{
						AssemblyName assemblyName = null;
						try
						{
							assemblyName = AssemblyName.GetAssemblyName(array[i]);
						}
						catch
						{
						}
						snapshot.Files.Add(new FileEntry
						{
							RelativePath = RelativePath(text, array[i]),
							Sha256 = Protocol.HashFile(array[i]),
							AssemblyName = (assemblyName?.Name ?? Path.GetFileNameWithoutExtension(array[i])),
							AssemblyVersion = (assemblyName?.Version?.ToString() ?? "")
						});
					}
					catch (Exception ex2)
					{
						list.Add(Path.GetFileName(array[i]) + ": " + ex2.Message);
					}
				}
			}
			catch (Exception ex3)
			{
				list.Add("files: " + ex3.Message);
			}
			try
			{
				foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos)
				{
					if (snapshot.Plugins.Count >= 256)
					{
						snapshot.Truncated = true;
						break;
					}
					PluginInfo value = pluginInfo.Value;
					object obj2;
					if (value == null)
					{
						obj2 = null;
					}
					else
					{
						BepInPlugin metadata = value.Metadata;
						obj2 = ((metadata != null) ? metadata.GUID : null);
					}
					if (obj2 == null)
					{
						obj2 = pluginInfo.Key ?? "";
					}
					string guid = (string)obj2;
					if (ShouldExcludePlugin(guid, excludePeakMx))
					{
						continue;
					}
					string text2 = ((value != null) ? value.Location : null) ?? "";
					string sha = "";
					try
					{
						if (File.Exists(text2))
						{
							sha = Protocol.HashFile(text2);
						}
					}
					catch
					{
					}
					List<PluginEntry> plugins = snapshot.Plugins;
					PluginEntry obj4 = new PluginEntry
					{
						Guid = guid
					};
					object obj5;
					if (value == null)
					{
						obj5 = null;
					}
					else
					{
						BepInPlugin metadata2 = value.Metadata;
						obj5 = ((metadata2 != null) ? metadata2.Name : null);
					}
					if (obj5 == null)
					{
						obj5 = "";
					}
					obj4.Name = (string)obj5;
					object obj6;
					if (value == null)
					{
						obj6 = null;
					}
					else
					{
						BepInPlugin metadata3 = value.Metadata;
						obj6 = ((metadata3 == null) ? null : metadata3.Version?.ToString());
					}
					if (obj6 == null)
					{
						obj6 = "";
					}
					obj4.Version = (string)obj6;
					obj4.RelativePath = RelativePath(text, text2);
					obj4.Sha256 = sha;
					plugins.Add(obj4);
				}
			}
			catch (Exception ex4)
			{
				list.Add("plugins: " + ex4.Message);
			}
			try
			{
				HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
				foreach (MethodBase allPatchedMethod in Harmony.GetAllPatchedMethods())
				{
					Patches patchInfo = Harmony.GetPatchInfo(allPatchedMethod);
					if (((patchInfo != null) ? patchInfo.Owners : null) == null)
					{
						continue;
					}
					foreach (string owner in patchInfo.Owners)
					{
						if (!string.IsNullOrWhiteSpace(owner) && !ShouldExcludePlugin(owner, excludePeakMx) && !IsUnstableHarmonyOwner(owner))
						{
							hashSet.Add(owner);
						}
					}
				}
				foreach (string item in hashSet)
				{
					if (snapshot.HarmonyOwners.Count >= 256)
					{
						snapshot.Truncated = true;
						break;
					}
					snapshot.HarmonyOwners.Add(item);
				}
			}
			catch (Exception ex5)
			{
				list.Add("harmony: " + ex5.Message);
			}
			snapshot.Error = string.Join("; ", list.ToArray());
			snapshot.Recalculate();
			return snapshot;
		}

		public static bool IsTechnicalFile(string path)
		{
			string fileName = Path.GetFileName(path ?? "");
			if (!string.Equals(fileName, "MX-AHG.dll", StringComparison.OrdinalIgnoreCase))
			{
				return string.Equals(fileName, "PEAK-MX.dll", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		public static bool IsTechnicalPlugin(string guid)
		{
			if (!string.Equals(guid, "com.maxkir041.mxahg", StringComparison.OrdinalIgnoreCase))
			{
				return string.Equals(guid, "com.maxkir041.peakmx", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		public static bool IsUnstableHarmonyOwner(string owner)
		{
			if (!string.IsNullOrWhiteSpace(owner))
			{
				return owner.StartsWith("harmony-auto-", StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		private static bool ShouldExcludeFile(string fileName, bool excludePeakMx)
		{
			if (string.Equals(fileName, "MX-AHG.dll", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			if (excludePeakMx)
			{
				return string.Equals(fileName, "PEAK-MX.dll", StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		private static bool ShouldExcludePlugin(string guid, bool excludePeakMx)
		{
			if (string.Equals(guid, "com.maxkir041.mxahg", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			if (excludePeakMx)
			{
				return string.Equals(guid, "com.maxkir041.peakmx", StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		private static string RelativePath(string root, string file)
		{
			if (string.IsNullOrWhiteSpace(file))
			{
				return "";
			}
			try
			{
				Uri uri = new Uri(AppendSlash(Path.GetFullPath(root)));
				Uri uri2 = new Uri(Path.GetFullPath(file));
				return Uri.UnescapeDataString(uri.MakeRelativeUri(uri2).ToString()).Replace('/', Path.DirectorySeparatorChar);
			}
			catch
			{
				return Path.GetFileName(file);
			}
		}

		private static string AppendSlash(string value)
		{
			char directorySeparatorChar = Path.DirectorySeparatorChar;
			if (!value.EndsWith(directorySeparatorChar.ToString(), StringComparison.Ordinal))
			{
				directorySeparatorChar = Path.AltDirectorySeparatorChar;
				if (!value.EndsWith(directorySeparatorChar.ToString(), StringComparison.Ordinal))
				{
					directorySeparatorChar = Path.DirectorySeparatorChar;
					return value + directorySeparatorChar;
				}
			}
			return value;
		}
	}
}