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;
}
}
}