From 6e9cece70661a237d7723fccd9c07ad37712ee01 Mon Sep 17 00:00:00 2001 From: js6pak Date: Tue, 22 Dec 2020 19:34:54 +0100 Subject: [PATCH] Initial commit --- .editorconfig | 3 + .gitignore | 7 + AmongUs.props | 76 ++ README.md | 18 + Reactor.Debugger/DebuggerPlugin.cs | 97 +++ Reactor.Debugger/Dumping.cs | 71 ++ Reactor.Debugger/Patches/Test.cs | 51 ++ Reactor.Debugger/Reactor.Debugger.csproj | 16 + Reactor.Greenhouse/Compiler.cs | 199 +++++ Reactor.Greenhouse/Extensions.cs | 28 + Reactor.Greenhouse/Generator.cs | 138 +++ Reactor.Greenhouse/Program.cs | 98 +++ Reactor.Greenhouse/Reactor.Greenhouse.csproj | 19 + Reactor.Greenhouse/Setup/Game.cs | 88 ++ Reactor.Greenhouse/Setup/GameManager.cs | 70 ++ Reactor.Greenhouse/Setup/GameVersionParser.cs | 32 + .../Setup/Provider/BaseProvider.cs | 13 + .../Setup/Provider/ItchProvider.cs | 103 +++ .../Setup/Provider/SteamProvider.cs | 99 +++ Reactor.Greenhouse/lib/Il2CppDumper.dll | Bin 0 -> 148992 bytes Reactor.Greenhouse/mappings.schema.json | 112 +++ Reactor.Greenhouse/mappings/2020.11.17.json | 1 + Reactor.Greenhouse/mappings/2020.12.9.json | 809 ++++++++++++++++++ Reactor.Greenhouse/mappings/2020.12.9s.json | 134 +++ Reactor.OxygenFilter/Extensions.cs | 53 ++ Reactor.OxygenFilter/Mappings.cs | 199 +++++ Reactor.OxygenFilter/ObfuscationMapper.cs | 237 +++++ Reactor.OxygenFilter/OxygenFilter.cs | 24 + .../Reactor.OxygenFilter.csproj | 10 + Reactor.sln | 33 + Reactor/Assets/default.bundle | Bin 0 -> 57957 bytes Reactor/Extensions/Extensions.cs | 81 ++ Reactor/Extensions/GUIExtensions.cs | 128 +++ Reactor/Patches/ModdedHandshake.cs | 46 + Reactor/Patches/ReactorVersionShower.cs | 55 ++ Reactor/Patches/RegionInfoWatcher.cs | 35 + Reactor/PluginSingleton.cs | 34 + Reactor/Reactor.csproj | 16 + Reactor/ReactorPlugin.cs | 61 ++ Reactor/ReactorPluginSideAttribute.cs | 44 + Reactor/Unstrip/AssetBundle.cs | 63 ++ Reactor/Unstrip/DefaultBundle.cs | 38 + Reactor/Unstrip/ImageConversion.cs | 21 + Reactor/Unstrip/Patches.cs | 213 +++++ 44 files changed, 3673 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 AmongUs.props create mode 100644 README.md create mode 100644 Reactor.Debugger/DebuggerPlugin.cs create mode 100644 Reactor.Debugger/Dumping.cs create mode 100644 Reactor.Debugger/Patches/Test.cs create mode 100644 Reactor.Debugger/Reactor.Debugger.csproj create mode 100644 Reactor.Greenhouse/Compiler.cs create mode 100644 Reactor.Greenhouse/Extensions.cs create mode 100644 Reactor.Greenhouse/Generator.cs create mode 100644 Reactor.Greenhouse/Program.cs create mode 100644 Reactor.Greenhouse/Reactor.Greenhouse.csproj create mode 100644 Reactor.Greenhouse/Setup/Game.cs create mode 100644 Reactor.Greenhouse/Setup/GameManager.cs create mode 100644 Reactor.Greenhouse/Setup/GameVersionParser.cs create mode 100644 Reactor.Greenhouse/Setup/Provider/BaseProvider.cs create mode 100644 Reactor.Greenhouse/Setup/Provider/ItchProvider.cs create mode 100644 Reactor.Greenhouse/Setup/Provider/SteamProvider.cs create mode 100644 Reactor.Greenhouse/lib/Il2CppDumper.dll create mode 100644 Reactor.Greenhouse/mappings.schema.json create mode 120000 Reactor.Greenhouse/mappings/2020.11.17.json create mode 100644 Reactor.Greenhouse/mappings/2020.12.9.json create mode 100644 Reactor.Greenhouse/mappings/2020.12.9s.json create mode 100644 Reactor.OxygenFilter/Extensions.cs create mode 100644 Reactor.OxygenFilter/Mappings.cs create mode 100644 Reactor.OxygenFilter/ObfuscationMapper.cs create mode 100644 Reactor.OxygenFilter/OxygenFilter.cs create mode 100644 Reactor.OxygenFilter/Reactor.OxygenFilter.csproj create mode 100644 Reactor.sln create mode 100644 Reactor/Assets/default.bundle create mode 100644 Reactor/Extensions/Extensions.cs create mode 100644 Reactor/Extensions/GUIExtensions.cs create mode 100644 Reactor/Patches/ModdedHandshake.cs create mode 100644 Reactor/Patches/ReactorVersionShower.cs create mode 100644 Reactor/Patches/RegionInfoWatcher.cs create mode 100644 Reactor/PluginSingleton.cs create mode 100644 Reactor/Reactor.csproj create mode 100644 Reactor/ReactorPlugin.cs create mode 100644 Reactor/ReactorPluginSideAttribute.cs create mode 100644 Reactor/Unstrip/AssetBundle.cs create mode 100644 Reactor/Unstrip/DefaultBundle.cs create mode 100644 Reactor/Unstrip/ImageConversion.cs create mode 100644 Reactor/Unstrip/Patches.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1c33738 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,3 @@ +[*] +end_of_line = lf +insert_final_newline = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..19ee5f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ +.idea +work \ No newline at end of file diff --git a/AmongUs.props b/AmongUs.props new file mode 100644 index 0000000..1622102 --- /dev/null +++ b/AmongUs.props @@ -0,0 +1,76 @@ + + + + + $(AmongUs)\BepInEx\unhollowed\Assembly-CSharp.dll + False + + + $(AmongUs)\BepInEx\unhollowed\Hazel.dll + False + + + + + $(AmongUs)\BepInEx\core\0Harmony.dll + False + + + $(AmongUs)\BepInEx\core\BepInEx.Core.dll + False + + + $(AmongUs)\BepInEx\core\BepInEx.IL2CPP.dll + False + + + $(AmongUs)\BepInEx\core\SemVer.dll + False + + + + + $(AmongUs)\BepInEx\core\UnhollowerBaseLib.dll + False + + + $(AmongUs)\BepInEx\unhollowed\Il2Cppmscorlib.dll + False + + + $(AmongUs)\BepInEx\unhollowed\Il2CppSystem.Core.dll + False + + + + + $(AmongUs)\BepInEx\unhollowed\UnityEngine.dll + False + + + $(AmongUs)\BepInEx\unhollowed\UnityEngine.CoreModule.dll + False + + + $(AmongUs)\BepInEx\unhollowed\UnityEngine.InputLegacyModule.dll + False + + + $(AmongUs)\BepInEx\unhollowed\UnityEngine.IMGUIModule.dll + + + $(AmongUs)\BepInEx\unhollowed\UnityEngine.UI.dll + False + + + $(AmongUs)\BepInEx\unhollowed\UnityEngine.UIModule.dll + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c7bbc74 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# Reactor + +Core (BepInEx) mod and API for Among Us. + +Featuring amazing features like: + +- modded handshake +- *partially* unstripped IMGUI and UI +- a lot of utility methods + +## Reactor.OxygenFilter + +Library for mapping output of Il2cppDumper, used in +[NuclearPowered/Il2CppAssemblyUnhollower](https://github.com/NuclearPowered/Il2CppAssemblyUnhollower) + +## Reactor.Greenhouse + +Standalone program which generates mappings for OxygenFilter diff --git a/Reactor.Debugger/DebuggerPlugin.cs b/Reactor.Debugger/DebuggerPlugin.cs new file mode 100644 index 0000000..e480117 --- /dev/null +++ b/Reactor.Debugger/DebuggerPlugin.cs @@ -0,0 +1,97 @@ +using System; +using BepInEx; +using BepInEx.IL2CPP; +using HarmonyLib; +using Reactor.Extensions; +using UnhollowerBaseLib; +using UnhollowerRuntimeLib; +using UnityEngine; + +namespace Reactor.Debugger +{ + [BepInPlugin(Id)] + [BepInProcess("Among Us.exe")] + [BepInDependency(ReactorPlugin.Id, "^0.2.0")] + [ReactorPluginSide(PluginSide.Client)] + public class DebuggerPlugin : BasePlugin + { + public const string Id = "gg.reactor.debugger"; + + public Harmony Harmony { get; } = new Harmony(Id); + public DebuggerComponent Component { get; private set; } + + public override void Load() + { + ClassInjector.RegisterTypeInIl2Cpp(); + + var gameObject = new GameObject(nameof(DebuggerPlugin)).DontDestroy(); + Component = gameObject.AddComponent(); + + Harmony.PatchAll(); + + Extensions.Extensions.once_sceneLoaded((_, _) => + { + Dumping.Dump(); + }); + } + + public class DebuggerComponent : MonoBehaviour + { + public bool DisableGameEnd { get; set; } + + public DragWindow TestWindow { get; } + + public DebuggerComponent(IntPtr ptr) : base(ptr) + { + TestWindow = new DragWindow(new Rect(20, 20, 120, 120), "Debugger", () => + { + GUILayout.Label("Name: " + SaveManager.PlayerName, new Il2CppReferenceArray(0)); + DisableGameEnd = GUILayout.Toggle(DisableGameEnd, "Disable game end", new Il2CppReferenceArray(0)); + + if (AmongUsClient.Instance.AmHost && ShipStatus.Instance && GUILayout.Button("Force game end", new Il2CppReferenceArray(0))) + { + ShipStatus.Instance.enabled = false; + ShipStatus.RpcEndGame(GameOverReason.ImpostorDisconnect, false); + } + + if (PlayerControl.LocalPlayer) + { + var position = PlayerControl.LocalPlayer.transform.position; + GUILayout.Label($"x: {position.x}", new Il2CppReferenceArray(0)); + GUILayout.Label($"y: {position.y}", new Il2CppReferenceArray(0)); + } + }) + { + Enabled = false + }; + } + + private void Update() + { + if (Input.GetKeyDown(KeyCode.F1)) + { + TestWindow.Enabled = !TestWindow.Enabled; + } + } + + private void OnGUI() + { + TestWindow.OnGUI(); + } + } + } + + // [HarmonyPatch] + // public static class TracePatch + // { + // public static IEnumerable TargetMethods() + // { + // return typeof(AmongUsClient).GetMethods(AccessTools.all).Where(x => x.ReturnType == typeof(Il2CppStructArray) && !x.GetParameters().Any()); + // } + // + // public static void Postfix(MethodBase __originalMethod) + // { + // PluginSingleton.Instance.Log.LogInfo(__originalMethod.FullDescription()); + // } + // } +} diff --git a/Reactor.Debugger/Dumping.cs b/Reactor.Debugger/Dumping.cs new file mode 100644 index 0000000..dd55485 --- /dev/null +++ b/Reactor.Debugger/Dumping.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using BepInEx; +using UnityEngine; + +namespace Reactor.Debugger +{ + public static class Dumping + { + public static string DumpPath { get; } = Path.Combine(Paths.PluginPath, "dump"); + + public static void Dump() + { + Directory.CreateDirectory(DumpPath); + + WritePalette(); + WriteEnum("Hat", HatManager.Instance.AllHats.ToArray().Select(hat => hat.name)); + WriteEnum("Pet", HatManager.Instance.AllPets.ToArray().Select(hat => hat.name)); + WriteEnum("Skin", HatManager.Instance.AllSkins.ToArray().Select(hat => hat.name)); + } + + private static void WritePalette() + { + var builder = new StringBuilder(); + + builder.AppendLine("public static class Palette"); + builder.AppendLine("{"); + + foreach (var propertyInfo in typeof(Palette).GetProperties()) + { + if (propertyInfo.GetMethod == null || !propertyInfo.GetMethod.IsStatic) + continue; + + var value = propertyInfo.GetValue(null); + + if (value is Color f) + { + value = (Color32) f; + } + + if (value is Color32 color) + { + builder.AppendLine($" public static readonly Color {propertyInfo.Name} = Color.FromArgb({(color.a != byte.MaxValue ? $"{color.a}, " : string.Empty)}{color.r}, {color.g}, {color.b});"); + } + } + + builder.AppendLine("}"); + + File.WriteAllText(Path.Combine(DumpPath, $"Palette.cs"), builder.ToString()); + } + + private static void WriteEnum(string name, IEnumerable values) + { + var builder = new StringBuilder(); + + builder.AppendLine("public enum " + name); + builder.AppendLine("{"); + + foreach (var value in values) + { + builder.AppendLine(" " + value.Replace(" ", "").Replace("'", "") + ","); + } + + builder.AppendLine("}"); + + File.WriteAllText(Path.Combine(DumpPath, $"{name}.cs"), builder.ToString()); + } + } +} diff --git a/Reactor.Debugger/Patches/Test.cs b/Reactor.Debugger/Patches/Test.cs new file mode 100644 index 0000000..60f21b7 --- /dev/null +++ b/Reactor.Debugger/Patches/Test.cs @@ -0,0 +1,51 @@ +using System.Linq; +using HarmonyLib; + +namespace Reactor.Debugger.Patches +{ + public static class Test + { + static Test() + { + GameOptionsData.MaxImpostors = GameOptionsData.RecommendedImpostors = Enumerable.Repeat((int) byte.MaxValue, byte.MaxValue).ToArray(); + GameOptionsData.MinPlayers = Enumerable.Repeat(1, 4).ToArray(); + } + + [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))] + public static class UpdatePatch + { + public static void Prefix(GameStartManager __instance) + { + __instance.MinPlayers = 1; + } + } + + [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))] + public static class CountdownPatch + { + public static void Prefix(GameStartManager __instance) + { + __instance.countDownTimer = 0; + } + } + + [HarmonyPatch(typeof(StatsManager), nameof(StatsManager.AmBanned), MethodType.Getter)] + public static class AmBannedPatch + { + public static bool Prefix(ref bool __result) + { + return __result = false; + } + } + + [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.CheckEndCriteria))] + [HarmonyPriority(Priority.First)] + public static class CheckEndCriteriaPatch + { + public static bool Prefix() + { + return !PluginSingleton.Instance.Component.DisableGameEnd; + } + } + } +} diff --git a/Reactor.Debugger/Reactor.Debugger.csproj b/Reactor.Debugger/Reactor.Debugger.csproj new file mode 100644 index 0000000..e6bf4d7 --- /dev/null +++ b/Reactor.Debugger/Reactor.Debugger.csproj @@ -0,0 +1,16 @@ + + + net472 + + 0.1.0 + Debugging mod for Reactor + NuclearPowered + latest + + + + + + + + diff --git a/Reactor.Greenhouse/Compiler.cs b/Reactor.Greenhouse/Compiler.cs new file mode 100644 index 0000000..4d22319 --- /dev/null +++ b/Reactor.Greenhouse/Compiler.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text.RegularExpressions; +using Mono.Cecil; +using Reactor.OxygenFilter; + +namespace Reactor.Greenhouse +{ + public static class Compiler + { + public static void Apply(this Mappings current, Mappings mappings) + { + static void ApplyType(List types, MappedType newType) + { + var type = types.SingleOrDefault(x => newType.Equals(x)); + if (type == null) + { + types.Add(newType); + } + else + { + type.Mapped = newType.Mapped; + + void ApplyMembers(List members, List newMembers) + { + foreach (var newMember in newMembers) + { + var member = members.SingleOrDefault(x => newMember.Equals(x, false)); + + if (member == null) + { + members.Add(newMember); + } + else + { + member.Mapped = newMember.Mapped; + member.Original = newMember.Original; + } + } + } + + ApplyMembers(type.Fields, newType.Fields); + ApplyMembers(type.Properties, newType.Properties); + + foreach (var newMember in newType.Methods) + { + var member = type.Methods.SingleOrDefault(x => newMember.Equals(x, false)); + + if (member == null) + { + type.Methods.Add(newMember); + } + else + { + member.Mapped = newMember.Mapped; + member.Original = newMember.Original; + member.Parameters = newMember.Parameters; + } + } + + foreach (var nestedType in newType.Nested) + { + ApplyType(type.Nested, nestedType); + } + } + } + + foreach (var newType in mappings.Types) + { + ApplyType(current.Types, newType); + } + } + + private static Regex Regex { get; } = new Regex(@"{{(?[\w\.]+)}}", RegexOptions.Compiled); + + public static string MapSignature(this OriginalDescriptor original, Mappings mappings) + { + var signature = original.Signature; + + var matches = Regex.Matches(signature); + foreach (Match match in matches) + { + var group = match.Groups["expression"]; + if (group.Success) + { + signature = signature.Replace(match.Value, mappings.FindByMapped(group.Value).Original.Name); + } + } + + return signature; + } + + private static bool TestField(MappedMember field, TypeDefinition typeDef, FieldDefinition fieldDef, Mappings mappings) + { + return (field.Original.Name == null || field.Original.Name == fieldDef.Name) && + (field.Original.Index == null || field.Original.Index == typeDef.Fields.IndexOf(fieldDef)) && + (field.Original.Signature == null || field.Original.MapSignature(mappings) == fieldDef.GetSignature()) && + (field.Original.Const == null || !typeDef.IsEnum && field.Original.Const.Value.Equals(fieldDef.Constant)); + } + + private static bool TestMethod(MappedMember method, TypeDefinition typeDef, MethodDefinition methodDef, Mappings mappings) + { + return (method.Original.Index == null || method.Original.Index == typeDef.Methods.IndexOf(methodDef)) && + (method.Original.Signature == null || method.Original.MapSignature(mappings) == methodDef.GetSignature()) && + (method.Original.Name == null || method.Original.Name == methodDef.Name); + } + + private static bool TestProperty(MappedMember property, TypeDefinition typeDef, PropertyDefinition propertyDef, Mappings mappings) + { + return (property.Original.Name == null || property.Original.Name == propertyDef.Name) && + (property.Original.Index == null || property.Original.Index == typeDef.Properties.IndexOf(propertyDef)) && + (property.Original.Signature == null || property.Original.MapSignature(mappings) == propertyDef.GetSignature()); + } + + private static bool TestType(MappedType type, TypeDefinition typeDef, Mappings mappings) + { + return (type.Original.Name == null || type.Original.Name == typeDef.Name) && + type.Methods.All(method => typeDef.Methods.Any(m => TestMethod(method, typeDef, m, mappings))) && + type.Fields.All(field => typeDef.Fields.Any(f => TestField(field, typeDef, f, mappings))) && + type.Properties.All(property => typeDef.Properties.Any(p => TestProperty(property, typeDef, p, mappings))); + } + + private static void Compile(this MappedType type, TypeDefinition typeDef, Mappings mappings) + { + type.Original = new OriginalDescriptor { Name = typeDef.Name }; + + foreach (var nested in type.Nested) + { + var nestedDef = typeDef.NestedTypes.Single(t => + TestType(type, t, mappings) && + nested.Original.Index == null || typeDef.NestedTypes.IndexOf(t) == nested.Original.Index + ); + + nested.Compile(nestedDef, mappings); + } + + foreach (var property in type.Properties) + { + try + { + var propertyDef = typeDef.Properties.Single(p => TestProperty(property, typeDef, p, mappings)); + + property.Original = new OriginalDescriptor { Name = propertyDef.Name }; + } + catch (Exception e) + { + throw new Exception($"Compilation of {property} failed", e); + } + } + + foreach (var field in type.Fields) + { + try + { + var fieldDef = typeDef.Fields.Single(f => TestField(field, typeDef, f, mappings)); + + field.Original = new OriginalDescriptor { Name = fieldDef.Name }; + } + catch (Exception e) + { + throw new Exception($"Compilation of {field} failed", e); + } + } + + foreach (var method in type.Methods) + { + try + { + var methodDef = typeDef.Methods.Single(m => TestMethod(method, typeDef, m, mappings)); + + method.Original = new OriginalDescriptor { Name = methodDef.Name, Signature = methodDef.GetSignature() }; + } + catch (Exception e) + { + throw new Exception($"Compilation of {method} failed", e); + } + } + } + + public static void Compile(this Mappings mappings, ModuleDefinition moduleDef) + { + foreach (var type in mappings.Types) + { + try + { + var typeDef = moduleDef.Types.Single(t => TestType(type, t, mappings)); + + type.Compile(typeDef, mappings); + } + catch (Exception e) + { + throw new Exception($"Compilation of {type} failed", e); + } + } + } + } +} diff --git a/Reactor.Greenhouse/Extensions.cs b/Reactor.Greenhouse/Extensions.cs new file mode 100644 index 0000000..0b75195 --- /dev/null +++ b/Reactor.Greenhouse/Extensions.cs @@ -0,0 +1,28 @@ +using System.Linq; +using Mono.Cecil; + +namespace Reactor.Greenhouse +{ + public static class Extensions + { + public static CustomAttribute GetCustomAttribute(this ICustomAttributeProvider cap, string attribute) + { + if (!cap.HasCustomAttributes) + { + return null; + } + + return cap.CustomAttributes.FirstOrDefault(attrib => attrib.AttributeType.FullName == attribute); + } + + public static uint? GetOffset(this MethodDefinition methodDef) + { + var attribute = methodDef.GetCustomAttribute("Il2CppDummyDll.AddressAttribute"); + if (attribute == null) + return null; + + var offset = attribute.Fields.Single(x => x.Name == "Offset"); + return new System.ComponentModel.UInt32Converter().ConvertFrom(offset) as uint?; + } + } +} diff --git a/Reactor.Greenhouse/Generator.cs b/Reactor.Greenhouse/Generator.cs new file mode 100644 index 0000000..e3ffde1 --- /dev/null +++ b/Reactor.Greenhouse/Generator.cs @@ -0,0 +1,138 @@ +using System.Linq; +using Mono.Cecil; +using Reactor.OxygenFilter; + +namespace Reactor.Greenhouse +{ + public static class Generator + { + public static Mappings Generate(ModuleDefinition old, ModuleDefinition latest) + { + var result = new Mappings(); + + foreach (var oldType in old.Types) + { + if (oldType.Name.StartsWith("<") || oldType.Namespace.StartsWith("GoogleMobileAds")) + continue; + + void AddType(TypeDefinition type) + { + var mapped = new MappedType(new OriginalDescriptor { Name = type.Name }, oldType.Name); + + var i = 0; + foreach (var field in type.Fields) + { + var j = 0; + var i1 = i; + var oldFields = oldType.Fields.Where(x => j++ == i1 && x.GetSignature().ToString() == field.GetSignature().ToString()).ToArray(); + if (oldFields.Length == 1) + { + var oldField = oldFields.First(); + if (oldField.Name == field.Name || !field.Name.IsObfuscated()) + continue; + + mapped.Fields.Add(new MappedMember(new OriginalDescriptor { Index = i1 }, oldField.Name)); + } + + i++; + } + + foreach (var method in type.Methods) + { + if (!method.HasParameters || method.IsSetter || method.IsGetter) + { + continue; + } + + var oldMethods = oldType.Methods.Where(x => x.Name == method.Name && x.Parameters.Count == method.Parameters.Count).ToArray(); + if (oldMethods.Length != 1) + { + continue; + } + + var oldMethod = oldMethods.Single(); + + var oldParameters = oldMethod.Parameters.Select(x => x.Name).ToList(); + + if (method.Parameters.Select(x => x.Name).SequenceEqual(oldParameters)) + { + continue; + } + + mapped.Methods.Add(new MappedMethod(new OriginalDescriptor { Name = method.Name, Signature = method.GetSignature() }, null) + { + Parameters = oldParameters + }); + } + + if (type.Name == oldType.Name || (!type.Name.IsObfuscated() && !mapped.Fields.Any() && !mapped.Methods.Any())) + { + return; + } + + result.Types.Add(mapped); + } + + var exact = latest.Types.FirstOrDefault(x => x.FullName == oldType.FullName); + if (exact != null) + { + AddType(exact); + continue; + } + + if (oldType.IsEnum) + { + var first = oldType.Fields.Select(x => x.Name).ToArray(); + var type = latest.Types.SingleOrDefault(x => x.IsEnum && x.Fields.Select(f => f.Name).SequenceEqual(first)); + if (type != null) + { + AddType(type); + } + + continue; + } + + static bool Test(TypeReference typeReference) + { + return typeReference.IsGenericParameter || typeReference.Namespace != string.Empty || !typeReference.Name.IsObfuscated(); + } + + var methods = oldType.Methods.Where(x => Test(x.ReturnType) && x.Parameters.All(p => Test(p.ParameterType))).Select(x => x.GetSignature()).ToArray(); + var fields = oldType.Fields.Where(x => Test(x.FieldType) && (!x.FieldType.HasGenericParameters || x.FieldType.GenericParameters.All(Test))).Select(x => x.GetSignature()).ToArray(); + var properties = oldType.Properties.Select(x => x.Name).ToArray(); + + var types = latest.Types + .Where(t => t.Attributes == oldType.Attributes) + .ToArray(); + + TypeDefinition winner = null; + var winnerPoints = -1; + + foreach (var t in types) + { + var points = 0; + points += t.Properties.Count(p => properties.Contains((p.GetMethod?.Name ?? p.SetMethod.Name).Substring(4))); + points += fields.Count(s => t.Fields.Any(f => f.GetSignature().ToString() == s.ToString())); + points += methods.Count(s => t.Methods.Any(m => m.GetSignature().ToString() == s.ToString())); + + if (points > winnerPoints) + { + winnerPoints = points; + winner = t; + } + else if (points == winnerPoints) + { + winner = null; + } + } + + if (winner != null && winnerPoints > 0) + { + AddType(winner); + } + } + + return result; + } + } +} diff --git a/Reactor.Greenhouse/Program.cs b/Reactor.Greenhouse/Program.cs new file mode 100644 index 0000000..c4cf1a7 --- /dev/null +++ b/Reactor.Greenhouse/Program.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.CommandLine; +using System.CommandLine.Invocation; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Mono.Cecil; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Reactor.Greenhouse.Setup; +using Reactor.OxygenFilter; + +namespace Reactor.Greenhouse +{ + internal static class Program + { + private static Task Main(string[] args) + { + var rootCommand = new RootCommand(); + + rootCommand.Handler = CommandHandler.Create(SearchAsync); + + return rootCommand.InvokeAsync(args); + } + + private static async Task SearchAsync() + { + var gameManager = new GameManager(); + await gameManager.SetupAsync(); + + JsonConvert.DefaultSettings = () => new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + DefaultValueHandling = DefaultValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + ContractResolver = ShouldSerializeContractResolver.Instance, + }; + + Console.WriteLine($"Generating mappings from {gameManager.PreObfuscation.Name} ({gameManager.PreObfuscation.Version})"); + using var old = ModuleDefinition.ReadModule(File.OpenRead(gameManager.PreObfuscation.Dll)); + + await GenerateAsync(gameManager.Steam, old); + await GenerateAsync(gameManager.Itch, old); + } + + private static async Task GenerateAsync(Game game, ModuleDefinition old) + { + Console.WriteLine($"Compiling mappings for {game.Name} ({game.Version})"); + + using var moduleDef = ModuleDefinition.ReadModule(File.OpenRead(game.Dll)); + var version = game.Version; + var postfix = game.Postfix; + + var generated = Generator.Generate(old, moduleDef); + + await File.WriteAllTextAsync(Path.Combine("work", version + postfix + ".generated.json"), JsonConvert.SerializeObject(generated, Formatting.Indented)); + + Apply(generated, Path.Combine("mappings", version + ".json")); + Apply(generated, Path.Combine("mappings", version + postfix + ".json")); + + generated.Compile(moduleDef); + + await File.WriteAllTextAsync(Path.Combine("work", version + postfix + ".json"), JsonConvert.SerializeObject(generated)); + } + + private static void Apply(Mappings generated, string file) + { + if (File.Exists(file)) + { + var mappings = JsonConvert.DeserializeObject(File.ReadAllText(file)); + generated.Apply(mappings); + } + } + + public class ShouldSerializeContractResolver : CamelCasePropertyNamesContractResolver + { + public static ShouldSerializeContractResolver Instance { get; } = new ShouldSerializeContractResolver(); + + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + + if (property.PropertyType != null && property.PropertyType != typeof(string)) + { + if (property.PropertyType.GetInterface(nameof(IEnumerable)) != null) + { + property.ShouldSerialize = instance => (instance?.GetType().GetProperty(property.UnderlyingName!)!.GetValue(instance) as IEnumerable)?.Count() > 0; + } + } + + return property; + } + } + } +} diff --git a/Reactor.Greenhouse/Reactor.Greenhouse.csproj b/Reactor.Greenhouse/Reactor.Greenhouse.csproj new file mode 100644 index 0000000..65c3ae9 --- /dev/null +++ b/Reactor.Greenhouse/Reactor.Greenhouse.csproj @@ -0,0 +1,19 @@ + + + Exe + net5.0 + true + + + + + + + + + + + lib\Il2CppDumper.dll + + + diff --git a/Reactor.Greenhouse/Setup/Game.cs b/Reactor.Greenhouse/Setup/Game.cs new file mode 100644 index 0000000..6ce2ef4 --- /dev/null +++ b/Reactor.Greenhouse/Setup/Game.cs @@ -0,0 +1,88 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using Il2CppDumper; +using Reactor.Greenhouse.Setup.Provider; +using IOPath = System.IO.Path; + +namespace Reactor.Greenhouse.Setup +{ + public class Game + { + public BaseProvider Provider { get; } + public string Name { get; } + public char Postfix { get; } + public string Path { get; } + public string Dll { get; } + public string Version { get; private set; } + + public Game(BaseProvider provider, string name, string path) + { + Provider = provider; + provider.Game = this; + Name = name; + Postfix = char.ToLowerInvariant(name[0]); + Path = path; + Dll = IOPath.Combine(Path, "DummyDll", "Assembly-CSharp.dll"); + } + + public async Task DownloadAsync() + { + Provider.Setup(); + await Provider.DownloadAsync(); + UpdateVersion(); + } + + public void UpdateVersion() + { + if (Version != null) + return; + + Version = GameVersionParser.Parse(System.IO.Path.Combine(Path, "Among Us_Data", "globalgamemanagers")); + } + + public void Dump() + { + Console.WriteLine($"Dumping {Name}"); + + var hash = ComputeHash(IOPath.Combine(Path, "GameAssembly.dll")); + var hashFile = IOPath.Combine(Path, "Assembly-CSharp.dll.md5"); + + if (File.Exists(hashFile) && File.ReadAllText(hashFile) == hash) + { + return; + } + + if (!Il2CppDumper.Il2CppDumper.PerformDump( + IOPath.Combine(Path, "GameAssembly.dll"), + IOPath.Combine(Path, "Among Us_Data", "il2cpp_data", "Metadata", "global-metadata.dat"), + Path, + new Config + { + RequireAnyKey = false, + GenerateScript = false, + DumpProperty = true, + DumpAttribute = true + }, + Console.WriteLine + )) + { + throw new Exception("Il2CppDumper failed"); + } + + File.WriteAllText(hashFile, hash); + } + + private static string ComputeHash(string file) + { + using var md5 = MD5.Create(); + using var assemblyStream = File.OpenRead(file); + + var hash = md5.ComputeHash(assemblyStream); + + return Encoding.UTF8.GetString(hash); + } + } +} diff --git a/Reactor.Greenhouse/Setup/GameManager.cs b/Reactor.Greenhouse/Setup/GameManager.cs new file mode 100644 index 0000000..c771450 --- /dev/null +++ b/Reactor.Greenhouse/Setup/GameManager.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using DepotDownloader; +using Reactor.Greenhouse.Setup.Provider; + +namespace Reactor.Greenhouse.Setup +{ + public class GameManager + { + public string WorkPath { get; } + + public Game PreObfuscation { get; } + public Game Steam { get; } + public Game Itch { get; } + + public GameManager() + { + WorkPath = Path.GetFullPath("work"); + PreObfuscation = new Game(new SteamProvider(true), "preobfuscation", Path.Combine(WorkPath, "preobfuscation")); + Steam = new Game(new SteamProvider(false), "steam", Path.Combine(WorkPath, "steam")); + Itch = new Game(new ItchProvider(), "itch", Path.Combine(WorkPath, "itch")); + } + + public async Task SetupAsync() + { + var preObfuscation = PreObfuscation.Provider.IsUpdateNeeded(); + var steam = Steam.Provider.IsUpdateNeeded(); + var itch = Itch.Provider.IsUpdateNeeded(); + + if (preObfuscation || steam || itch) + { + ContentDownloader.ShutdownSteam3(); + + if (preObfuscation) + { + await PreObfuscation.DownloadAsync(); + Console.WriteLine($"Downloaded {nameof(PreObfuscation)} ({PreObfuscation.Version})"); + } + + if (steam) + { + await Steam.DownloadAsync(); + Console.WriteLine($"Downloaded {nameof(Steam)} ({Steam.Version})"); + } + + if (itch) + { + await Itch.DownloadAsync(); + Console.WriteLine($"Downloaded {nameof(Itch)} ({Itch.Version})"); + } + } + + ContentDownloader.ShutdownSteam3(); + + PreObfuscation.UpdateVersion(); + Steam.UpdateVersion(); + Itch.UpdateVersion(); + + if (PreObfuscation.Version != "2020.9.9") + { + throw new ArgumentException("Pre obfuscation version is invalid"); + } + + PreObfuscation.Dump(); + Steam.Dump(); + Itch.Dump(); + } + } +} diff --git a/Reactor.Greenhouse/Setup/GameVersionParser.cs b/Reactor.Greenhouse/Setup/GameVersionParser.cs new file mode 100644 index 0000000..283446d --- /dev/null +++ b/Reactor.Greenhouse/Setup/GameVersionParser.cs @@ -0,0 +1,32 @@ +using System.IO; +using System.Linq; +using System.Text; + +namespace Reactor.Greenhouse.Setup +{ + public static class GameVersionParser + { + public static int IndexOf(this byte[] source, byte[] pattern) + { + for (var i = 0; i < source.Length; i++) + { + if (source.Skip(i).Take(pattern.Length).SequenceEqual(pattern)) + { + return i; + } + } + + return -1; + } + + public static string Parse(string file) + { + var bytes = File.ReadAllBytes(file); + + var pattern = Encoding.UTF8.GetBytes("public.app-category.games"); + var index = bytes.IndexOf(pattern) + pattern.Length + 127; + + return Encoding.UTF8.GetString(bytes.Skip(index).TakeWhile(x => x != 0).ToArray()); + } + } +} diff --git a/Reactor.Greenhouse/Setup/Provider/BaseProvider.cs b/Reactor.Greenhouse/Setup/Provider/BaseProvider.cs new file mode 100644 index 0000000..b552ab7 --- /dev/null +++ b/Reactor.Greenhouse/Setup/Provider/BaseProvider.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; + +namespace Reactor.Greenhouse.Setup.Provider +{ + public abstract class BaseProvider + { + public Game Game { get; internal set; } + + public abstract void Setup(); + public abstract Task DownloadAsync(); + public abstract bool IsUpdateNeeded(); + } +} diff --git a/Reactor.Greenhouse/Setup/Provider/ItchProvider.cs b/Reactor.Greenhouse/Setup/Provider/ItchProvider.cs new file mode 100644 index 0000000..d151c5e --- /dev/null +++ b/Reactor.Greenhouse/Setup/Provider/ItchProvider.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using AngleSharp.Html.Dom; +using AngleSharp.Html.Parser; +using DepotDownloader; +using Newtonsoft.Json; + +namespace Reactor.Greenhouse.Setup.Provider +{ + public class ItchProvider : BaseProvider + { + private HttpClient HttpClient { get; } = new HttpClient(new HttpClientHandler + { + CookieContainer = new CookieContainer() + }); + + public string Username { get; set; } + public string Password { get; set; } + + public override void Setup() + { + Console.Write("itch.io username: "); + Username = Console.ReadLine(); + + Console.Write("itch.io password: "); + Password = Util.ReadPassword(); + + Console.WriteLine(); + } + + public override async Task DownloadAsync() + { + var htmlParser = new HtmlParser(); + + var csrfResponse = await HttpClient.GetAsync("https://itch.io/login"); + + var csrfDocument = htmlParser.ParseDocument(await csrfResponse.Content.ReadAsStreamAsync()); + + var loginResponse = await HttpClient.PostAsync("https://itch.io/login", new FormUrlEncodedContent(new[] + { + new KeyValuePair("csrf_token", ((IHtmlInputElement) csrfDocument.QuerySelector("input[name=csrf_token]")).Value), + new KeyValuePair("username", Username), + new KeyValuePair("password", Password) + })); + + if (!loginResponse.IsSuccessStatusCode || (await loginResponse.Content.ReadAsStringAsync()).Contains("Errors")) + { + throw new Exception("itch.io authentication failed"); + } + + Console.WriteLine("Logged into itch.io"); + + var pageDocument = htmlParser.ParseDocument(await HttpClient.GetStringAsync("https://innersloth.itch.io/among-us")); + var downloadPageUrl = ((IHtmlAnchorElement) pageDocument.QuerySelector("a[class=button]")).Href; + + var downloadDocument = htmlParser.ParseDocument(await HttpClient.GetStringAsync(downloadPageUrl)); + var uploadId = ((IHtmlAnchorElement) downloadDocument.QuerySelector("a[class='button download_btn']")).Dataset["upload_id"]; + + var keyRegex = new Regex("key\":\"(.+)\","); + var key = downloadDocument.QuerySelectorAll("script[type='text/javascript']") + .Cast() + .Select(x => keyRegex.Match(x.InnerHtml)) + .Single(x => x.Success) + .Groups[1].Value; + + var json = await HttpClient.PostAsync($"https://innersloth.itch.io/among-us/file/{uploadId}?key={key}", null); + var response = JsonConvert.DeserializeObject(await json.Content.ReadAsStringAsync()); + + Console.WriteLine($"Downloading {response.Url}"); + + var files = new[] { "GameAssembly.dll", "global-metadata.dat", "globalgamemanagers" }; + using var zipArchive = new ZipArchive(await HttpClient.GetStreamAsync(response.Url), ZipArchiveMode.Read); + foreach (var entry in zipArchive.Entries) + { + if (files.Contains(entry.Name)) + { + var path = Path.Combine("work", "itch", entry.FullName.Substring(entry.FullName.IndexOf("/", StringComparison.Ordinal) + 1)); + + Directory.GetParent(path)!.Create(); + entry.ExtractToFile(path, true); + } + } + } + + public override bool IsUpdateNeeded() + { + return !Directory.Exists(Game.Path); + } + + public class DownloadResponse + { + public string Url { get; set; } + public bool External { get; set; } + } + } +} diff --git a/Reactor.Greenhouse/Setup/Provider/SteamProvider.cs b/Reactor.Greenhouse/Setup/Provider/SteamProvider.cs new file mode 100644 index 0000000..05ffe4a --- /dev/null +++ b/Reactor.Greenhouse/Setup/Provider/SteamProvider.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using DepotDownloader; +using SteamKit2; + +namespace Reactor.Greenhouse.Setup.Provider +{ + public class SteamProvider : BaseProvider + { + private const uint AppId = 945360; + private const uint DepotId = 945361; + private const ulong PreObfuscationManifest = 3596575937380717449; + + public bool IsPreObfuscation { get; } + + public SteamProvider(bool isPreObfuscation) + { + IsPreObfuscation = isPreObfuscation; + } + + public override bool IsUpdateNeeded() + { + DepotConfigStore.LoadFromFile(Path.Combine(Game.Path, ".DepotDownloader", "depot.config")); + if (DepotConfigStore.Instance.InstalledManifestIDs.TryGetValue(DepotId, out var installedManifest)) + { + if (IsPreObfuscation) + { + if (installedManifest == PreObfuscationManifest) + { + return false; + } + } + else + { + if (ContentDownloader.steam3 == null) + { + ContentDownloader.InitializeSteam3(); + } + + ContentDownloader.steam3!.RequestAppInfo(AppId); + + var depots = ContentDownloader.GetSteam3AppSection(AppId, EAppInfoSection.Depots); + if (installedManifest == depots[DepotId.ToString()]["manifests"][ContentDownloader.DEFAULT_BRANCH].AsUnsignedLong()) + { + return false; + } + } + } + + return true; + } + + public override void Setup() + { + if (ContentDownloader.steam3 != null && ContentDownloader.steam3.bConnected) + { + return; + } + + AccountSettingsStore.LoadFromFile("account.config"); + + ContentDownloader.Config.RememberPassword = true; + + Console.Write("Steam username: "); + var username = Console.ReadLine(); + + string password = null; + + if (!AccountSettingsStore.Instance.LoginKeys.ContainsKey(username)) + { + Console.Write("Steam password: "); + password = ContentDownloader.Config.SuppliedPassword = Util.ReadPassword(); + Console.WriteLine(); + } + + ContentDownloader.InitializeSteam3(username, password); + + ContentDownloader.Config.UsingFileList = true; + ContentDownloader.Config.FilesToDownload = new List + { + "GameAssembly.dll" + }; + ContentDownloader.Config.FilesToDownloadRegex = new List + { + new Regex("^Among Us_Data/il2cpp_data/Metadata/global-metadata.dat$".Replace("/", "[\\\\|/]"), RegexOptions.Compiled | RegexOptions.IgnoreCase), + new Regex("^Among Us_Data/globalgamemanagers$".Replace("/", "[\\\\|/]"), RegexOptions.Compiled | RegexOptions.IgnoreCase) + }; + } + + public override Task DownloadAsync() + { + ContentDownloader.Config.InstallDirectory = Game.Path; + return ContentDownloader.DownloadAppAsync(AppId, DepotId, IsPreObfuscation ? PreObfuscationManifest : ContentDownloader.INVALID_MANIFEST_ID); + } + } +} diff --git a/Reactor.Greenhouse/lib/Il2CppDumper.dll b/Reactor.Greenhouse/lib/Il2CppDumper.dll new file mode 100644 index 0000000000000000000000000000000000000000..8d7774368f845a7e7e92f7a1fc55f95e947d60c9 GIT binary patch literal 148992 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", + "index": 23 + }, + "mapped": "purchases" + } + ], + "properties": [ + { + "mapped": "LastAnnouncement", + "original": 0 + }, + { + "mapped": "BoughtNoAds", + "original": 1 + }, + { + "mapped": "VSync", + "original": 2 + }, + { + "mapped": "CensorChat", + "original": 3 + }, + { + "mapped": "ShowAdsScreen", + "original": 4 + }, + { + "mapped": "ShowMinPlayerWarning", + "original": 6 + }, + { + "mapped": "ShowOnlineHelp", + "original": 7 + }, + { + "mapped": "SfxVolume", + "original": 8 + }, + { + "mapped": "MusicVolume", + "original": 9 + }, + { + "mapped": "TouchConfig", + "original": 10 + }, + { + "mapped": "JoystickSize", + "original": 11 + }, + { + "mapped": "PlayerName", + "original": 12 + }, + { + "mapped": "LastPet", + "original": 13 + }, + { + "mapped": "LastHat", + "original": 14 + }, + { + "mapped": "LastSkin", + "original": 15 + }, + { + "mapped": "LastLanguage", + "original": 16 + }, + { + "mapped": "BodyColor", + "original": 17 + }, + { + "mapped": "GameHostOptions", + "original": 18 + }, + { + "mapped": "GameSearchOptions", + "original": 19 + } + ] + }, + { + "original": "^", + "mapped": "InnerNetClient", + "methods": [ + { + "original": { + "signature": "System.Byte[] ()" + }, + "mapped": "GetConnectionData" + }, + { + "original": { + "signature": "System.Void ({{GameOverReason}},System.Boolean)" + }, + "mapped": "OnGameEnd" + } + ] + }, + { + "original": "^", + "mapped": "GameOptionsData", + "fields": [ + { + "original": 22, + "mapped": "isDefaults" + }, + { + "original": 23, + "mapped": "ConfirmEjects" + }, + { + "original": 24, + "mapped": "VisualTasks" + }, + { + "original": 25, + "mapped": "TaskBarUpdate" + }, + { + "original": 26, + "mapped": "AnonymousVotes" + }, + { + "original": 27, + "mapped": "RecommendedKillCooldown" + }, + { + "original": 28, + "mapped": "RecommendedImpostors" + }, + { + "original": 29, + "mapped": "MaxImpostors" + }, + { + "original": 30, + "mapped": "MinPlayers" + } + ] + }, + { + "original": {}, + "mapped": "VersionShower", + "methods": [ + { + "original": { + "name": "Start", + "signature": "System.Void ()" + } + } + ], + "fields": [ + { + "original": { + "index": 0, + "name": "text" + } + } + ] + }, + { + "original": {}, + "mapped": "PingTracker", + "methods": [ + { + "original": { + "name": "Update", + "signature": "System.Void ()" + } + } + ], + "fields": [ + { + "original": { + "index": 0, + "name": "text" + } + } + ] + }, + { + "original": "^", + "mapped": "GameStartManager", + "fields": [ + { + "original": 3, + "mapped": "LastPlayerCount" + }, + { + "original": 12, + "mapped": "startState" + }, + { + "original": 13, + "mapped": "countDownTimer" + } + ], + "nested": [ + { + "original": 0, + "mapped": "StartingStates" + } + ] + }, + { + "original": "^", + "mapped": "GameData", + "nested": [ + { + "original": 1, + "mapped": "PlayerInfo", + "fields": [ + { + "original": 0, + "mapped": "PlayerId" + }, + { + "original": 1, + "mapped": "PlayerName" + }, + { + "original": 2, + "mapped": "ColorId" + }, + { + "original": 3, + "mapped": "HatId" + }, + { + "original": 4, + "mapped": "PetId" + }, + { + "original": 5, + "mapped": "SkinId" + }, + { + "original": 6, + "mapped": "Disconnected" + }, + { + "original": 7, + "mapped": "Tasks" + }, + { + "original": 8, + "mapped": "IsImpostor" + }, + { + "original": 9, + "mapped": "IsDead" + }, + { + "original": 10, + "mapped": "_object" + } + ], + "properties": [ + { + "original": 0, + "mapped": "Object" + } + ] + } + ] + }, + { + "original": {}, + "mapped": "RegionInfo", + "fields": [ + { + "original": { + "index": 0, + "signature": "System.String" + }, + "mapped": "Name" + }, + { + "original": { + "index": 1, + "signature": "System.String" + }, + "mapped": "PingServer" + }, + { + "original": { + "index": 2, + "signature": "{{ServerInfo}}[]" + }, + "mapped": "Servers" + } + ] + }, + { + "original": "^", + "mapped": "PlayerPhysics", + "fields": [ + { + "original": { + "signature": "UnityEngine.Rigidbody2D" + }, + "mapped": "body" + } + ] + }, + { + "original": "^", + "mapped": "PlayerControl", + "fields": [ + { + "original": { + "signature": "UnityEngine.SpriteRenderer" + }, + "mapped": "myRend" + } + ] + }, + { + "original": {}, + "mapped": "MapBehaviour", + "methods": [ + { + "original": { + "name": "ShowInfectedMap", + "signature": "System.Void ()" + } + } + ] + }, + { + "original": {}, + "mapped": "IntroCutscene", + "nested": [ + { + "original": 0, + "mapped": "CoBegin__d" + } + ], + "fields": [ + { + "original": { + "name": "IntroStinger", + "signature": "UnityEngine.AudioClip" + } + } + ] + }, + { + "original": {}, + "mapped": "GameOptionsMenu", + "fields": [ + { + "original": 2, + "mapped": "Children" + }, + { + "original": { + "name": "ResetButton", + "signature": "UnityEngine.GameObject" + } + } + ] + }, + { + "original": {}, + "mapped": "GameSettingMenu", + "fields": [ + { + "original": { + "name": "HideForOnline", + "signature": "UnityEngine.Transform[]" + } + } + ] + }, + { + "original": "^", + "mapped": "AmongUsClient", + "nested": [ + { + "original": 0, + "mapped": "CoStartGame__d" + } + ] + }, + { + "original": {}, + "mapped": "Minigame", + "methods": [ + { + "original": { + "name": "ForceClose", + "signature": "System.Void ()" + } + } + ] + }, + { + "original": {}, + "mapped": "KeyboardJoystick", + "methods": [ + { + "original": "Update" + }, + { + "original": "get_Delta" + } + ] + }, + { + "original": {}, + "mapped": "PlayerVoteArea", + "methods": [ + { + "original": { + "name": "CoAnimateOverlay", + "signature": "System.Collections.IEnumerator ()" + } + } + ] + }, + { + "original": "^", + "mapped": "PlayerTab", + "fields": [ + { + "original": { + "signature": "System.Collections.Generic.HashSet`1" + }, + "mapped": "AvailableColors" + } + ], + "nested": [ + { + "original": 0, + "mapped": "OnEnable__0" + } + ] + }, + { + "original": "^", + "mapped": "MeetingHud", + "nested": [ + { + "original": 0, + "mapped": "VoteStates" + } + ], + "fields": [ + { + "original": 0, + "mapped": "ResultsTime" + }, + { + "original": 1, + "mapped": "Depth" + }, + { + "original": 7, + "mapped": "CounterOrigin" + }, + { + "original": 8, + "mapped": "CounterOffsets" + }, + { + "original": 10, + "mapped": "playerStates" + }, + { + "original": 19, + "mapped": "state" + }, + { + "original": 23, + "mapped": "exiledPlayer" + }, + { + "original": 24, + "mapped": "wasTie" + }, + { + "original": 27, + "mapped": "reporterId" + }, + { + "original": 28, + "mapped": "amDead" + }, + { + "original": 29, + "mapped": "resultsStartedAt" + }, + { + "original": 30, + "mapped": "lastSecond" + } + ] + }, + { + "original": "^", + "mapped": "MessageExtensions", + "methods": [ + { + "original": 0, + "mapped": "WriteNetObject" + }, + { + "original": 1, + "mapped": "ReadNetObject" + } + ] + }, + { + "original": "^", + "mapped": "PhysicsHelpers", + "methods": [ + { + "original": { + "signature": "System.Boolean (UnityEngine.Vector2,UnityEngine.Vector2,System.Single,System.Int32)" + }, + "mapped": "AnyNonTriggersBetween" + } + ] + }, + { + "original": "^", + "mapped": "AspectPosition", + "nested": [ + { + "original": 0, + "mapped": "EdgeAlignments" + } + ] + }, + { + "original": "^", + "mapped": "GameCode", + "methods": [ + { + "original": 0, + "mapped": "IntToGameName" + }, + { + "original": 1, + "mapped": "GameNameToInt" + }, + { + "original": 2, + "mapped": "CreateGameId" + }, + { + "original": 3, + "mapped": "IntToGameNameV2" + } + ] + }, + { + "original": "^", + "mapped": "Effects", + "methods": [ + { + "original": { + "signature": "System.Collections.IEnumerator (System.Single,UnityEngine.Transform,System.Single,System.Single)" + }, + "mapped": "Bloop" + } + ] + }, + { + "original": {}, + "mapped": "StringOption", + "fields": [ + { + "original": { + "name": "Values", + "signature": "StringNames[]" + } + } + ] + }, + { + "original": {}, + "mapped": "PlainShipRoom", + "fields": [ + { + "original": { + "index": 0, + "name": "RoomId" + } + }, + { + "original": { + "index": 1, + "name": "survCamera" + } + }, + { + "original": { + "index": 2, + "name": "roomArea" + } + } + ] + }, + { + "original": "^", + "mapped": "CooldownHelpers", + "methods": [ + { + "original": { + "index": 0, + "signature": "System.Void (UnityEngine.SpriteRenderer)" + }, + "mapped": "SetCooldownNormalizedUvs" + } + ] + }, + { + "original": {}, + "mapped": "WinningPlayerData", + "fields": [ + { + "original": { + "index": 0, + "signature": "System.String" + }, + "mapped": "Name" + }, + { + "original": { + "index": 1, + "signature": "System.Boolean" + }, + "mapped": "IsDead" + }, + { + "original": { + "index": 2, + "signature": "System.Boolean" + }, + "mapped": "IsImpostor" + }, + { + "original": { + "index": 3, + "signature": "System.Int32" + }, + "mapped": "ColorId" + }, + { + "original": { + "index": 4, + "signature": "System.UInt32" + }, + "mapped": "SkinId" + }, + { + "original": { + "index": 5, + "signature": "System.UInt32" + }, + "mapped": "HatId" + }, + { + "original": { + "index": 6, + "signature": "System.UInt32" + }, + "mapped": "PetId" + }, + { + "original": { + "index": 7, + "signature": "System.Boolean" + }, + "mapped": "IsYou" + } + ] + }, + { + "original": {}, + "mapped": "TempData", + "methods": [ + { + "original": { + "index": 0, + "signature": "System.Boolean ({{GameOverReason}})" + }, + "mapped": "DidHumansWin" + } + ], + "fields": [ + { + "original": 0, + "mapped": "LastDeathReason" + }, + { + "original": 1, + "mapped": "EndReason" + }, + { + "original": 2, + "mapped": "showAd" + }, + { + "original": 3, + "mapped": "winners" + } + ] + } + ] +} \ No newline at end of file diff --git a/Reactor.Greenhouse/mappings/2020.12.9s.json b/Reactor.Greenhouse/mappings/2020.12.9s.json new file mode 100644 index 0000000..106a694 --- /dev/null +++ b/Reactor.Greenhouse/mappings/2020.12.9s.json @@ -0,0 +1,134 @@ +{ + "$schema": "../mappings.schema.json", + "types": [ + { + "original": "^", + "mapped": "MeetingHud", + "methods": [ + { + "notes": "MeetingHud#CastVote", + "original": "EIGGEKBEOEA", + "mapped": "CheckForEndVoting" + }, + { + "notes": "void (byte[], GameData.PlayerInfo, bool)", + "original": "KGIPOIOFBJH", + "mapped": "VotingComplete" + }, + { + "notes": "MeetingHud#VotingComplete", + "original": "FBNKEAEKKJE", + "mapped": "PopulateResults" + }, + { + "notes": "MeetingHud#VotingComplete", + "original": "ECLBANBAFCN", + "mapped": "SetupProceedButton" + } + ] + }, + { + "original": "^", + "mapped": "ShipStatus", + "methods": [ + { + "notes": "ShipStatus#FixedUpdate", + "original": "ECNNLBKGIKE", + "mapped": "CheckEndCriteria" + }, + { + "notes": "CheckEndCriteria", + "original": "PLBGOMIEONF", + "mapped": "RpcEndGame" + }, + { + "original": "NJOJCFDGEAE", + "mapped": "SelectInfected" + } + ] + }, + { + "original": "^", + "mapped": "ServerManager", + "methods": [ + { + "notes": "Last call in ServerManager#Awake", + "original": "BBIDOJKFGPM", + "mapped": "LoadServers" + } + ] + }, + { + "original": {}, + "mapped": "StatsManager", + "properties": [ + { + "notes": "Second call in *GameButton#OnClick", + "original": "IDEHLLDOODH", + "mapped": "AmBanned" + } + ], + "fields": [ + { + "original": { + "const": { + "type": "System.Byte", + "value": 3 + } + }, + "mapped": "StatsVersion" + }, + { + "original": { + "const": { + "type": "System.Int32", + "value": 2 + } + }, + "mapped": "PointsUntilBanStarts" + }, + { + "original": { + "const": { + "type": "System.Int32", + "value": 5 + } + }, + "mapped": "MinutesPerBanPoint" + } + ] + }, + { + "original": "^", + "mapped": "PlayerControl", + "methods": [ + { + "notes": "Only method with [CallerCount(2)] and PlayerControl ()", + "original": "OAFBLFBAOJJ", + "mapped": "FindClosestTarget" + }, + { + "notes": "first call in CmdReportDeadBody", + "original": "MKAIDPJOJFP", + "mapped": "ReportDeadBody" + }, + { + "notes": "Only method with [CallerCount(1)] and void (byte[])", + "original": "OJPCECLPCCF", + "mapped": "SetInfected" + } + ] + }, + { + "original": "^", + "mapped": "EndGameManager", + "methods": [ + { + "notes": "first call in EndGameManager#Start", + "original": "MFIBPNBCMKM", + "mapped": "SetEverythingUp" + } + ] + } + ] +} \ No newline at end of file diff --git a/Reactor.OxygenFilter/Extensions.cs b/Reactor.OxygenFilter/Extensions.cs new file mode 100644 index 0000000..33dfcca --- /dev/null +++ b/Reactor.OxygenFilter/Extensions.cs @@ -0,0 +1,53 @@ +using System.Linq; +using System.Text; +using Mono.Cecil; + +namespace Reactor.OxygenFilter +{ + public static class Extensions + { + public static bool IsObfuscated(this string text) + { + return text.Length == 11 && text.All(char.IsUpper); + } + + public static string GetSignature(this MethodDefinition methodDefinition) + { + var sb = new StringBuilder(); + + sb.Append(methodDefinition.ReturnType.FullName); + sb.Append(" "); + + sb.Append("("); + if (methodDefinition.HasParameters) + { + for (var i = 0; i < methodDefinition.Parameters.Count; i++) + { + var parameterType = methodDefinition.Parameters[i].ParameterType; + + if (i > 0) + sb.Append(","); + + if (parameterType is SentinelType) + sb.Append("...,"); + + sb.Append(parameterType.FullName); + } + } + + sb.Append(")"); + + return sb.ToString(); + } + + public static string GetSignature(this FieldDefinition fieldDefinition) + { + return fieldDefinition.FieldType.FullName; + } + + public static string GetSignature(this PropertyDefinition propertyDefinition) + { + return propertyDefinition.PropertyType.FullName; + } + } +} diff --git a/Reactor.OxygenFilter/Mappings.cs b/Reactor.OxygenFilter/Mappings.cs new file mode 100644 index 0000000..5282e9d --- /dev/null +++ b/Reactor.OxygenFilter/Mappings.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; + +namespace Reactor.OxygenFilter +{ + public class Mappings + { + public List Types { get; set; } = new List(); + + public MappedType Find(string name, Func predicate) + { + var split = name.Split('.').SelectMany(x => x.Split('+')).ToArray(); + + if (split.Length > 1) + { + MappedType type = null; + + foreach (var s in split) + { + if (type == null) + { + type = Find(s, predicate); + } + else + { + type = type.Nested.SingleOrDefault(x => predicate(x) == s); + } + } + + return type; + } + + return Types.SingleOrDefault(x => predicate(x) == name); + } + + public MappedType FindByMapped(string name) + { + return Find(name, x => x.Mapped); + } + + public MappedType FindByOriginal(string name) + { + return Find(name, x => x.Original.Name); + } + } + + public class OriginalDescriptor + { + public string Name { get; set; } + public int? Index { get; set; } + public string Signature { get; set; } + public Constant Const { get; set; } + + public bool Equals(OriginalDescriptor obj) + { + return Name == obj.Name && Index == obj.Index && Signature == obj.Signature && Const == obj.Const; + } + + public bool IsEmpty() + { + return Name == null && Index == null; + } + + public override string ToString() + { + return Name ?? Index?.ToString(); + } + + public class Constant + { + private object _value; + + public object Value + { + get => Convert.ChangeType(_value, Type); + set => _value = value; + } + + public Type Type { get; set; } + } + } + + public class OriginalDescriptorConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, OriginalDescriptor value, JsonSerializer serializer) + { + if (value.Name != null && value.Index == null && value.Signature == null) + { + writer.WriteValue(value.Name); + } + else if (value.Index != null && value.Name == null && value.Signature == null) + { + writer.WriteValue(value.Index); + } + else + { + serializer.Serialize(writer, value); + } + } + + public override OriginalDescriptor ReadJson(JsonReader reader, Type objectType, OriginalDescriptor existingValue, bool hasExistingValue, JsonSerializer serializer) + { + if (reader.Value is string name) + { + return new OriginalDescriptor { Name = name }; + } + + if (reader.Value is long index) + { + return new OriginalDescriptor { Index = (int?) index }; + } + + if (reader.TokenType == JsonToken.StartObject) + { + return serializer.Deserialize(reader); + } + + throw new JsonSerializationException(); + } + } + + public class MappedMember + { + [JsonProperty(Order = -3)] + [JsonConverter(typeof(OriginalDescriptorConverter))] + public OriginalDescriptor Original { get; set; } + + [JsonProperty(Order = -2)] + public string Mapped { get; set; } + + public override string ToString() + { + var result = Original.ToString(); + + if (Mapped != null) + { + result += $" (name: {Mapped})"; + } + + return result; + } + + public MappedMember() + { + } + + public MappedMember(OriginalDescriptor original, string mapped) + { + Original = original; + + if (original.Name != mapped) + { + Mapped = mapped; + } + } + + public bool Equals(MappedMember obj, bool compareOriginal = true) + { + if (obj == null) + { + return false; + } + + return Mapped == obj.Mapped && (!compareOriginal || (Original.Name == "^" && obj.Mapped != null) || Original.Equals(obj.Original)); + } + } + + public class MappedMethod : MappedMember + { + public List Parameters { get; set; } = new List(); + + public MappedMethod() + { + } + + public MappedMethod(OriginalDescriptor original, string mapped) : base(original, mapped) + { + } + } + + public class MappedType : MappedMember + { + public List Properties { get; set; } = new List(); + public List Fields { get; set; } = new List(); + public List Methods { get; set; } = new List(); + + public List Nested { get; set; } = new List(); + + public MappedType() + { + } + + public MappedType(OriginalDescriptor original, string mapped) : base(original, mapped) + { + } + } +} diff --git a/Reactor.OxygenFilter/ObfuscationMapper.cs b/Reactor.OxygenFilter/ObfuscationMapper.cs new file mode 100644 index 0000000..1c2ead6 --- /dev/null +++ b/Reactor.OxygenFilter/ObfuscationMapper.cs @@ -0,0 +1,237 @@ +using System; +using System.IO; +using System.Linq; +using Mono.Cecil; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Reactor.OxygenFilter +{ + public class ObfuscationMapper + { + public ModuleDefinition ModuleDef { get; } + public Mappings Mappings { get; private set; } + + public ObfuscationMapper(ModuleDefinition moduleDef) + { + ModuleDef = moduleDef; + } + + public void Map() + { + var mappedAttribute = new TypeDefinition("", "MappedAttribute", TypeAttributes.Public, ModuleDef.ImportReference(typeof(Attribute))) + { + Attributes = TypeAttributes.Class & TypeAttributes.Public + }; + + ModuleDef.Types.Add(mappedAttribute); + + var mappedAttributeCtor = new MethodReference(".ctor", ModuleDef.ImportReference(typeof(void)), mappedAttribute) + { + HasThis = true, + Parameters = { new ParameterDefinition(ModuleDef.ImportReference(typeof(string))) } + }; + + void MapMember(ICustomAttributeProvider attributes, string mapped) + { + attributes.CustomAttributes.Add(new CustomAttribute(mappedAttributeCtor) + { + ConstructorArguments = { new CustomAttributeArgument(ModuleDef.ImportReference(typeof(string)), mapped) } + }); + } + + // beebyte 1 iq? + foreach (var propertyDef in ModuleDef.Types.SelectMany(x => x.NestedTypes.Prepend(x)).SelectMany(x => x.Properties)) + { + const string getPrefix = "get_"; + const string setPrefix = "set_"; + + var getName = propertyDef.GetMethod != null && propertyDef.GetMethod.Name.StartsWith(getPrefix) ? propertyDef.GetMethod.Name.Substring(getPrefix.Length) : null; + var setName = propertyDef.SetMethod != null && propertyDef.SetMethod.Name.StartsWith(setPrefix) ? propertyDef.SetMethod.Name.Substring(setPrefix.Length) : null; + + if (getName != null && setName != null && getName != setName) + { + throw new Exception($"{propertyDef.FullName} has 2 different accessor names"); + } + + if (getName != null || setName != null) + { + var name = getName ?? setName; + + MapMember(propertyDef, name); + + if (propertyDef.GetMethod != null) + MapMember(propertyDef.GetMethod, "get_" + name); + + if (propertyDef.SetMethod != null) + MapMember(propertyDef.SetMethod, "set_" + name); + } + } + + void MapType(MappedType type, TypeDefinition typeDef) + { + if (typeDef == null) + { + throw new NullReferenceException($"Type {type} was not found!"); + } + + if (type.Mapped != null) + { + MapMember(typeDef, type.Mapped); + } + + foreach (var property in type.Properties) + { + var propertyDef = typeDef.Properties.SingleOrDefault(x => x.Name == property.Original.Name); + + if (propertyDef == null) + { + throw new NullReferenceException($"Property {property} was not found in {type}!"); + } + + if (property.Mapped != null) + { + MapMember(propertyDef, property.Mapped); + + if (propertyDef.GetMethod != null) + MapMember(propertyDef.GetMethod, "get_" + property.Mapped); + + if (propertyDef.SetMethod != null) + MapMember(propertyDef.SetMethod, "set_" + property.Mapped); + } + } + + foreach (var field in type.Fields) + { + var fieldDef = typeDef.Fields.SingleOrDefault(x => x.Name == field.Original.Name); + + if (fieldDef == null) + { + throw new NullReferenceException($"Field {field} was not found in {type}!"); + } + + if (field.Mapped != null) + { + MapMember(fieldDef, field.Mapped); + } + } + + foreach (var method in type.Methods) + { + var methodDef = typeDef.Methods + .SingleOrDefault(x => x.Name == method.Original.Name && (method.Original.Signature == null || x.GetSignature().ToString() == method.Original.Signature)); + + if (methodDef == null) + { + throw new NullReferenceException($"Method {method} was not found in {type}!"); + } + + foreach (var methodDef2 in ModuleDef.Types + .Where(x => x.BaseType == typeDef) + .Select(x => x.Methods.SingleOrDefault(m => m.Name == method.Original.Name && (method.Original.Signature == null || m.GetSignature().ToString() == method.Original.Signature))) + .Where(x => x != null) + .Prepend(methodDef) + ) + { + if (method.Mapped != null) + { + MapMember(methodDef2, method.Mapped); + } + + for (var i = 0; i < method.Parameters.Count; i++) + { + MapMember(methodDef2.Parameters.ElementAt(i), method.Parameters[i]); + } + } + } + + foreach (var nested in type.Nested) + { + MapType(nested, typeDef.NestedTypes.SingleOrDefault(x => x.Name == nested.Original.Name)); + } + } + + foreach (var type in Mappings.Types) + { + MapType(type, ModuleDef.GetType(type.Original.Name)); + } + + foreach (var typeDef in ModuleDef.Types) + { + var i = 0; + + foreach (var member in typeDef.Properties) + { + if (member.Name.IsObfuscated() && member.CustomAttributes.All(x => x.AttributeType != mappedAttribute)) + { + MapMember(member, $"Property_{i}"); + } + + i++; + } + + i = 0; + + foreach (var member in typeDef.Fields) + { + if (member.Name.IsObfuscated() && member.CustomAttributes.All(x => x.AttributeType != mappedAttribute)) + { + MapMember(member, $"Field_{i}"); + } + + i++; + } + + i = 0; + + foreach (var member in typeDef.Methods) + { + if (member.Name.IsObfuscated() && member.CustomAttributes.All(x => x.AttributeType != mappedAttribute)) + { + MapMember(member, $"Method_{i}"); + } + + var j = 0; + foreach (var parameter in member.Parameters) + { + if (parameter.Name.IsObfuscated() && parameter.CustomAttributes.All(x => x.AttributeType != mappedAttribute)) + { + MapMember(parameter, $"Parameter_{j}"); + } + + j++; + } + + i++; + } + + i = 0; + + foreach (var member in typeDef.NestedTypes) + { + if (member.Name.IsObfuscated() && member.CustomAttributes.All(x => x.AttributeType != mappedAttribute)) + { + MapMember(member, $"Nested_{i}"); + } + + i++; + } + } + } + + public void LoadMappings(FileInfo file) + { + if (!file.Exists) + { + throw new FileNotFoundException(); + } + + JsonConvert.DefaultSettings = () => new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + }; + + Mappings = JsonConvert.DeserializeObject(File.ReadAllText(file.FullName)); + } + } +} diff --git a/Reactor.OxygenFilter/OxygenFilter.cs b/Reactor.OxygenFilter/OxygenFilter.cs new file mode 100644 index 0000000..1cd5ec4 --- /dev/null +++ b/Reactor.OxygenFilter/OxygenFilter.cs @@ -0,0 +1,24 @@ +using System.IO; +using Mono.Cecil; + +namespace Reactor.OxygenFilter +{ + public class OxygenFilter + { + public void Start(FileInfo mappingsFile, FileInfo dumpedDll, FileInfo outputDll) + { + using var inputStream = dumpedDll.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.Read); + using var outputStream = dumpedDll == outputDll ? inputStream : dumpedDll.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.Read); + + var resolver = new DefaultAssemblyResolver(); + resolver.AddSearchDirectory(dumpedDll.DirectoryName); + + using var module = ModuleDefinition.ReadModule(inputStream, new ReaderParameters(ReadingMode.Immediate) { AssemblyResolver = resolver }); + var mapper = new ObfuscationMapper(module); + mapper.LoadMappings(mappingsFile); + mapper.Map(); + module.Dispose(); + module.Write(outputStream); + } + } +} diff --git a/Reactor.OxygenFilter/Reactor.OxygenFilter.csproj b/Reactor.OxygenFilter/Reactor.OxygenFilter.csproj new file mode 100644 index 0000000..bffc4eb --- /dev/null +++ b/Reactor.OxygenFilter/Reactor.OxygenFilter.csproj @@ -0,0 +1,10 @@ + + + net472;netstandard2.0 + latest + + + + + + \ No newline at end of file diff --git a/Reactor.sln b/Reactor.sln new file mode 100644 index 0000000..b9e2daf --- /dev/null +++ b/Reactor.sln @@ -0,0 +1,33 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reactor", "Reactor\Reactor.csproj", "{91906039-9753-472B-ACC3-12C0561FFAD8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reactor.OxygenFilter", "Reactor.OxygenFilter\Reactor.OxygenFilter.csproj", "{154C08E6-8F9F-43A3-86C3-A3A606316D2B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reactor.Debugger", "Reactor.Debugger\Reactor.Debugger.csproj", "{68589B4D-9EF5-40DD-8D6D-10977ED07259}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reactor.Greenhouse", "Reactor.Greenhouse\Reactor.Greenhouse.csproj", "{00BF8959-652F-46B8-AA0F-891B4DA1112B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {91906039-9753-472B-ACC3-12C0561FFAD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {91906039-9753-472B-ACC3-12C0561FFAD8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {91906039-9753-472B-ACC3-12C0561FFAD8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {91906039-9753-472B-ACC3-12C0561FFAD8}.Release|Any CPU.Build.0 = Release|Any CPU + {154C08E6-8F9F-43A3-86C3-A3A606316D2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {154C08E6-8F9F-43A3-86C3-A3A606316D2B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {154C08E6-8F9F-43A3-86C3-A3A606316D2B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {154C08E6-8F9F-43A3-86C3-A3A606316D2B}.Release|Any CPU.Build.0 = Release|Any CPU + {68589B4D-9EF5-40DD-8D6D-10977ED07259}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68589B4D-9EF5-40DD-8D6D-10977ED07259}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68589B4D-9EF5-40DD-8D6D-10977ED07259}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68589B4D-9EF5-40DD-8D6D-10977ED07259}.Release|Any CPU.Build.0 = Release|Any CPU + {00BF8959-652F-46B8-AA0F-891B4DA1112B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {00BF8959-652F-46B8-AA0F-891B4DA1112B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {00BF8959-652F-46B8-AA0F-891B4DA1112B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {00BF8959-652F-46B8-AA0F-891B4DA1112B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Reactor/Assets/default.bundle b/Reactor/Assets/default.bundle new file mode 100644 index 0000000000000000000000000000000000000000..bc2934d21ca46cd3e5f05ab0005a96dc94648007 GIT binary patch literal 57957 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 AW&i*H literal 0 HcmV?d00001 diff --git a/Reactor/Extensions/Extensions.cs b/Reactor/Extensions/Extensions.cs new file mode 100644 index 0000000..eedf8b2 --- /dev/null +++ b/Reactor/Extensions/Extensions.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEngine; +using UnityEngine.Events; +using UnityEngine.SceneManagement; +using Object = UnityEngine.Object; + +namespace Reactor.Extensions +{ + /// + /// General utilities + /// + public static class Extensions + { + /// + /// Stops from being destroyed + /// + /// Object to stop from being destroyed + /// Passed + public static T DontDestroy(this T obj) where T : Object + { + obj.hideFlags |= HideFlags.HideAndDontSave; + Object.DontDestroyOnLoad(obj); + + return obj; + } + + /// + /// Adds a delegate to get one notification when a scene has loaded. + /// + public static void once_sceneLoaded(Action value) + { + UnityAction unityAction = null; + + unityAction = (Action) ((scene, loadMode) => + { + SceneManager.remove_sceneLoaded(unityAction); + value.Invoke(scene, loadMode); + }); + + SceneManager.add_sceneLoaded(unityAction); + } + + /// + /// Returns random from + /// + public static T Random(this IEnumerable input) + { + var list = input as IList ?? input.ToList(); + return list.Count == 0 ? default : list[UnityEngine.Random.Range(0, list.Count)]; + } + + /// + /// Returns the color as a hexadecimal string in the format "RRGGBBAA". + /// + /// The color to be converted. + /// Hexadecimal string representing the color. + /// https://docs.unity3d.com/ScriptReference/ColorUtility.ToHtmlStringRGBA.html + public static string ToHtmlStringRGBA(this Color32 color) + { + return $"{color.r:X2}{color.g:X2}{color.b:X2}{color.a:X2}"; + } + + /// + public static string ToHtmlStringRGBA(this Color color) + { + return ((Color32) color).ToHtmlStringRGBA(); + } + + public static byte[] ReadFully(this Stream input) + { + using (var ms = new MemoryStream()) + { + input.CopyTo(ms); + return ms.ToArray(); + } + } + } +} diff --git a/Reactor/Extensions/GUIExtensions.cs b/Reactor/Extensions/GUIExtensions.cs new file mode 100644 index 0000000..4b24c3d --- /dev/null +++ b/Reactor/Extensions/GUIExtensions.cs @@ -0,0 +1,128 @@ +using System; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; + +namespace Reactor.Extensions +{ + /// + /// GUI utilities + /// + public static class GUIExtensions + { + /// + /// Shortcut for empty texture + /// + public static Texture2D CreateEmptyTexture(int width = 0, int height = 0) + { + return new Texture2D(width, height, TextureFormat.RGBA32, Texture.GenerateAllMips, false, IntPtr.Zero); + } + + /// + /// Clamp Rect to screen size + /// + public static Rect ClampScreen(this Rect rect) + { + rect.x = Mathf.Clamp(rect.x, 0, Screen.width - rect.width); + rect.y = Mathf.Clamp(rect.y, 0, Screen.height - rect.height); + + return rect; + } + + /// + /// Create from + /// + public static Sprite CreateSprite(this Texture2D tex) + { + return Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), 100f); + } + + public static DefaultControls.Resources StandardResources { get; internal set; } + + public static GameObject CreateCanvas() + { + var gameObject = new GameObject("Canvas"); + var canvas = gameObject.AddComponent(); + canvas.renderMode = RenderMode.ScreenSpaceOverlay; + + var canvasScaler = gameObject.AddComponent(); + + canvasScaler.screenMatchMode = CanvasScaler.ScreenMatchMode.Expand; + canvasScaler.referencePixelsPerUnit = 100f; + + gameObject.AddComponent(); + + return gameObject; + } + + public static GameObject CreateEventSystem() + { + var gameObject = new GameObject("EventSystem"); + gameObject.AddComponent(); + gameObject.AddComponent(); + gameObject.AddComponent(); + + return gameObject; + } + + public static void SetSize(this RectTransform rectTransform, float width, float height) + { + rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, width); + rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, height); + } + } + + public class Window + { + private static int _lastWindowId; + + public static int NextWindowId() + { + return _lastWindowId++; + } + + public int Id { get; set; } = NextWindowId(); + + public bool Enabled { get; set; } = true; + public Rect Rect { get; set; } + public Action Func { get; set; } + public string Title { get; set; } + + public Window(Rect rect, string title, Action func) + { + Rect = rect; + Title = title; + Func = func; + } + + public Window(Rect rect, string title, Action func) : this(rect, title, id => func()) + { + } + + public void OnGUI() + { + if (Enabled) + { + Rect = GUI.Window(Id, Rect, Func, Title); + } + } + } + + public class DragWindow : Window + { + public DragWindow(Rect rect, string title, Action func) : base(rect, title, func) + { + Func = id => + { + func(id); + + GUI.DragWindow(new Rect(0, 0, 10000, 20)); + Rect = Rect.ClampScreen(); + }; + } + + public DragWindow(Rect rect, string title, Action func) : this(rect, title, id => func()) + { + } + } +} diff --git a/Reactor/Patches/ModdedHandshake.cs b/Reactor/Patches/ModdedHandshake.cs new file mode 100644 index 0000000..bf15dae --- /dev/null +++ b/Reactor/Patches/ModdedHandshake.cs @@ -0,0 +1,46 @@ +using System.Linq; +using System.Reflection; +using BepInEx.IL2CPP; +using HarmonyLib; +using Hazel; +using UnhollowerBaseLib; + +namespace Reactor.Patches +{ + internal static class ModdedHandshake + { + [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.GetConnectionData))] + public static class HandshakePatch + { + public static bool Prefix(out Il2CppStructArray __result) + { + var handshake = MessageWriter.Get(SendOption.Reliable); + + var plugins = Preloader.Chainloader.Plugins; + + if (plugins.Values + .Select(pluginInfo => pluginInfo.Instance.GetType().GetCustomAttribute()) + .Any(attribute => attribute == null || attribute.Side == PluginSide.Both)) + { + handshake.Write(-1); + } + + handshake.Write(Constants.GetBroadcastVersion()); + handshake.Write(SaveManager.PlayerName); + + handshake.WritePacked(plugins.Count); + + foreach (var plugin in plugins) + { + handshake.Write(plugin.Key); + handshake.Write(plugin.Value.Metadata.Version.ToString()); + } + + __result = handshake.ToByteArray(false); + handshake.Recycle(); + + return false; + } + } + } +} diff --git a/Reactor/Patches/ReactorVersionShower.cs b/Reactor/Patches/ReactorVersionShower.cs new file mode 100644 index 0000000..21a9891 --- /dev/null +++ b/Reactor/Patches/ReactorVersionShower.cs @@ -0,0 +1,55 @@ +using System; +using System.Reflection; +using BepInEx; +using BepInEx.IL2CPP; +using UnityEngine.SceneManagement; + +namespace Reactor.Patches +{ + public static class ReactorVersionShower + { + public static TextRenderer Text { get; private set; } + + public static event TextUpdatedHandler TextUpdated; + + public delegate void TextUpdatedHandler(TextRenderer text); + + internal static void Initialize() + { + SceneManager.add_sceneLoaded((Action) ((scene, loadMode) => + { + var original = UnityEngine.Object.FindObjectOfType(); + if (!original) + return; + + var gameObject = UnityEngine.Object.Instantiate(original.gameObject); + gameObject.name = "ReactorVersion"; + + var versionShower = gameObject.GetComponent(); + if (versionShower) + { + versionShower.enabled = false; + } + + var aspectPosition = gameObject.GetComponent(); + + var position = aspectPosition.DistanceFromEdge; + position.y += 0.2f; + aspectPosition.DistanceFromEdge = position; + + aspectPosition.AdjustPosition(); + + Text = gameObject.GetComponentInChildren(); + UpdateText(); + })); + } + + public static void UpdateText() + { + Text.Text = "Reactor " + typeof(ReactorPlugin).Assembly.GetCustomAttribute().InformationalVersion; + Text.Text += "\nBepInEx: " + Paths.BepInExVersion; + Text.Text += "\nMods: " + Preloader.Chainloader.Plugins.Count; + TextUpdated?.Invoke(Text); + } + } +} diff --git a/Reactor/Patches/RegionInfoWatcher.cs b/Reactor/Patches/RegionInfoWatcher.cs new file mode 100644 index 0000000..286c21e --- /dev/null +++ b/Reactor/Patches/RegionInfoWatcher.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; +using UnityEngine; + +namespace Reactor.Patches +{ + internal class RegionInfoWatcher : IDisposable + { + private FileSystemWatcher Watcher { get; } + public bool Reload { get; set; } + + internal RegionInfoWatcher() + { + Watcher = new FileSystemWatcher( + string.IsNullOrEmpty(Application.persistentDataPath) ? Directory.GetCurrentDirectory() : Application.persistentDataPath, + "regionInfo.dat" + ); + + Watcher.Changed += (s, e) => + { + if (new FileInfo(e.Name).Length > 0) + { + Reload = true; + } + }; + + Watcher.EnableRaisingEvents = true; + } + + public void Dispose() + { + Watcher?.Dispose(); + } + } +} diff --git a/Reactor/PluginSingleton.cs b/Reactor/PluginSingleton.cs new file mode 100644 index 0000000..fee99a7 --- /dev/null +++ b/Reactor/PluginSingleton.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using BepInEx.IL2CPP; + +namespace Reactor +{ + public class PluginSingleton where T : BasePlugin + { + private static T _instance; + + public static T Instance + { + get + { + if (_instance == null) + { + _instance = Preloader.Chainloader.Plugins.Values.Select(x => x.Instance).OfType().Single(); + } + + return _instance; + } + + set + { + if (_instance != null) + { + throw new Exception($"Instance for {typeof(T).FullName} is already set"); + } + + _instance = value; + } + } + } +} diff --git a/Reactor/Reactor.csproj b/Reactor/Reactor.csproj new file mode 100644 index 0000000..f86f109 --- /dev/null +++ b/Reactor/Reactor.csproj @@ -0,0 +1,16 @@ + + + net472 + latest + + 0.2.0 + Core mod and API for Among Us + NuclearPowered + + + + + + + + \ No newline at end of file diff --git a/Reactor/ReactorPlugin.cs b/Reactor/ReactorPlugin.cs new file mode 100644 index 0000000..bf78186 --- /dev/null +++ b/Reactor/ReactorPlugin.cs @@ -0,0 +1,61 @@ +using System; +using BepInEx; +using BepInEx.IL2CPP; +using HarmonyLib; +using Reactor.Extensions; +using Reactor.Patches; +using Reactor.Unstrip; +using UnhollowerRuntimeLib; +using UnityEngine; + +namespace Reactor +{ + [BepInPlugin(Id)] + [BepInProcess("Among Us.exe")] + [ReactorPluginSide(PluginSide.Client)] + public class ReactorPlugin : BasePlugin + { + public const string Id = "gg.reactor.api"; + + public Harmony Harmony { get; } = new Harmony(Id); + private RegionInfoWatcher RegionInfoWatcher { get; } = new RegionInfoWatcher(); + + public override void Load() + { + ClassInjector.RegisterTypeInIl2Cpp(); + ClassInjector.RegisterTypeInIl2Cpp(); + + var gameObject = new GameObject(nameof(ReactorPlugin)).DontDestroy(); + gameObject.AddComponent(); + + Harmony.PatchAll(); + ReactorVersionShower.Initialize(); + DefaultBundle.Load(); + } + + public override bool Unload() + { + RegionInfoWatcher?.Dispose(); + + return base.Unload(); + } + + public class ReactorComponent : MonoBehaviour + { + public ReactorComponent(IntPtr ptr) : base(ptr) + { + } + + private void Update() + { + var watcher = PluginSingleton.Instance.RegionInfoWatcher; + if (watcher.Reload) + { + watcher.Reload = false; + ServerManager.Instance.LoadServers(); + PluginSingleton.Instance.Log.LogInfo("Region file reloaded"); + } + } + } + } +} diff --git a/Reactor/ReactorPluginSideAttribute.cs b/Reactor/ReactorPluginSideAttribute.cs new file mode 100644 index 0000000..3637598 --- /dev/null +++ b/Reactor/ReactorPluginSideAttribute.cs @@ -0,0 +1,44 @@ +using System; + +namespace Reactor +{ + /// + /// This attribute specifies plugin's side + /// + [AttributeUsage(AttributeTargets.Class)] + public class ReactorPluginSideAttribute : Attribute + { + public PluginSide Side { get; } + + public ReactorPluginSideAttribute(PluginSide side) + { + if (side == PluginSide.Server) + { + throw new ArgumentException("BepInEx plugin can't be server only!"); + } + + Side = side; + } + } + + /// + /// Plugin side used in modded handshake + /// + public enum PluginSide + { + /// + /// Required by both sides, reject connection if missing on the other side + /// + Both, + + /// + /// Required only by client + /// + Client, + + /// + /// Required only by server + /// + Server + } +} diff --git a/Reactor/Unstrip/AssetBundle.cs b/Reactor/Unstrip/AssetBundle.cs new file mode 100644 index 0000000..3422bd2 --- /dev/null +++ b/Reactor/Unstrip/AssetBundle.cs @@ -0,0 +1,63 @@ +// https://github.com/sinai-dev/UnityExplorer/blob/master/src/Unstrip/AssetBundleUnstrip.cs + +using System; +using UnhollowerBaseLib; +using UnhollowerRuntimeLib; + +namespace Reactor.Unstrip +{ + public class AssetBundle + { + private delegate IntPtr d_LoadFromFile(IntPtr path, uint crc, ulong offset); + + private static readonly d_LoadFromFile i_LoadFromFile = IL2CPP.ResolveICall("UnityEngine.AssetBundle::LoadFromFile_Internal"); + + public static AssetBundle LoadFromFile(string path, uint crc = 0, ulong offset = 0) + { + return new AssetBundle(i_LoadFromFile.Invoke(IL2CPP.ManagedStringToIl2Cpp(path), crc, offset)); + } + + private delegate IntPtr d_LoadFromMemory(IntPtr binary, uint crc); + + private static readonly d_LoadFromMemory i_LoadFromMemory = IL2CPP.ResolveICall("UnityEngine.AssetBundle::LoadFromMemory_Internal"); + + public static AssetBundle LoadFromMemory(byte[] binary, uint crc = 0) + { + return new AssetBundle(i_LoadFromMemory(((Il2CppStructArray) binary).Pointer, crc)); + } + + public IntPtr Pointer { get; } + + public AssetBundle(IntPtr ptr) + { + Pointer = ptr; + } + + private delegate IntPtr d_LoadAssetWithSubAssets_Internal(IntPtr __instance, IntPtr name, IntPtr type); + + private static readonly d_LoadAssetWithSubAssets_Internal i_LoadAssetWithSubAssets_Internal = IL2CPP.ResolveICall("UnityEngine.AssetBundle::LoadAssetWithSubAssets_Internal"); + + public UnityEngine.Object[] LoadAllAssets() + { + var ptr = i_LoadAssetWithSubAssets_Internal.Invoke(Pointer, IL2CPP.ManagedStringToIl2Cpp(string.Empty), Il2CppType.Of().Pointer); + + if (ptr == IntPtr.Zero) + { + return new UnityEngine.Object[0]; + } + + return new Il2CppReferenceArray(ptr); + } + + private delegate IntPtr d_LoadAsset_Internal(IntPtr __instance, IntPtr name, IntPtr type); + + private static readonly d_LoadAsset_Internal i_LoadAsset_Internal = IL2CPP.ResolveICall("UnityEngine.AssetBundle::LoadAsset_Internal"); + + public T LoadAsset(string name) where T : UnityEngine.Object + { + var ptr = i_LoadAsset_Internal.Invoke(Pointer, IL2CPP.ManagedStringToIl2Cpp(name), Il2CppType.Of().Pointer); + + return ptr == IntPtr.Zero ? null : new UnityEngine.Object(ptr).TryCast(); + } + } +} diff --git a/Reactor/Unstrip/DefaultBundle.cs b/Reactor/Unstrip/DefaultBundle.cs new file mode 100644 index 0000000..1642a39 --- /dev/null +++ b/Reactor/Unstrip/DefaultBundle.cs @@ -0,0 +1,38 @@ +using System; +using Reactor.Extensions; +using UnityEngine; +using UnityEngine.UI; + +namespace Reactor.Unstrip +{ + internal static class DefaultBundle + { + public static void Load() + { + var bundle = AssetBundle.LoadFromMemory(typeof(ReactorPlugin).Assembly.GetManifestResourceStream("Reactor.Assets.default.bundle").ReadFully()); + + var backupShader = bundle.LoadAsset("UI-Default"); + + if (!Graphic.defaultGraphicMaterial.shader || Graphic.defaultGraphicMaterial.shader.name != "UI/Default") + { + Graphic.defaultGraphicMaterial.shader = backupShader; + } + + var tex = new Texture2D(1, 1, TextureFormat.RGBA32, Texture.GenerateAllMips, false, IntPtr.Zero); + tex.SetPixel(0, 0, Color.red); + + var sprite = tex.CreateSprite(); + + GUIExtensions.StandardResources = new DefaultControls.Resources + { + background = sprite, + checkmark = sprite, + dropdown = sprite, + knob = sprite, + mask = sprite, + standard = sprite, + inputField = sprite + }; + } + } +} diff --git a/Reactor/Unstrip/ImageConversion.cs b/Reactor/Unstrip/ImageConversion.cs new file mode 100644 index 0000000..40f74e7 --- /dev/null +++ b/Reactor/Unstrip/ImageConversion.cs @@ -0,0 +1,21 @@ +using System; +using UnhollowerBaseLib; +using UnityEngine; + +namespace Reactor.Unstrip +{ + public static class ImageConversion + { + private delegate bool d_LoadImage(IntPtr tex, IntPtr data, bool markNonReadable); + + private static readonly d_LoadImage i_LoadImage = IL2CPP.ResolveICall("UnityEngine.ImageConversion::LoadImage"); + + /// + /// Load image from to + /// + public static bool LoadImage(this Texture2D tex, byte[] data, bool markNonReadable = false) + { + return i_LoadImage(tex.Pointer, ((Il2CppStructArray) data).Pointer, markNonReadable); + } + } +} diff --git a/Reactor/Unstrip/Patches.cs b/Reactor/Unstrip/Patches.cs new file mode 100644 index 0000000..93e774c --- /dev/null +++ b/Reactor/Unstrip/Patches.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using HarmonyLib; +using UnhollowerBaseLib; +using UnityEngine; +using Object = Il2CppSystem.Object; +using Type = Il2CppSystem.Type; + +namespace Reactor.Unstrip +{ + internal static class Patches + { + [HarmonyPatch(typeof(GUIUtility), nameof(GUIUtility.HasKeyFocus))] + public static class HasKeyFocusPatch + { + public static bool Prefix(int controlID, ref bool __result) + { + __result = controlID == GUIUtility.keyboardControl; + return false; + } + } + + [HarmonyPatch(typeof(GUILayoutGroup), nameof(GUILayoutGroup.Add))] + public static class AddPatch + { + public static bool Prefix(GUILayoutGroup __instance, GUILayoutEntry e) + { + __instance.entries.Add(e); + return false; + } + } + + [HarmonyPatch(typeof(GUILayoutGroup), nameof(GUILayoutGroup.GetNext))] + public static class GetNextPatch + { + public static bool Prefix(GUILayoutGroup __instance, ref GUILayoutEntry __result) + { + if (__instance.m_Cursor < __instance.entries.Count) + { + __result = __instance.entries[__instance.m_Cursor]; + __instance.m_Cursor++; + return false; + } + + throw new ArgumentException(string.Concat("Getting control ", __instance.m_Cursor, "'s position in a group with only ", __instance.entries.Count, " controls when doing ", Event.current.rawType, "\nAborting")); + } + } + + [HarmonyPatch(typeof(GUILayoutEntry), nameof(GUILayoutEntry.CalcWidth))] + public static class CalcWidthPatch + { + public static bool Prefix(GUILayoutEntry __instance) + { + var cast = __instance.TryCast(); + if (cast != null) + { + cast.CalcWidth(); + return false; + } + + return true; + } + } + + [HarmonyPatch(typeof(GUILayoutEntry), nameof(GUILayoutEntry.CalcHeight))] + public static class CalcHeightPatch + { + public static bool Prefix(GUILayoutEntry __instance) + { + var cast = __instance.TryCast(); + if (cast != null) + { + cast.CalcHeight(); + return false; + } + + return true; + } + } + + internal sealed class GUIWordWrapSizer : GUILayoutEntry + { + private readonly GUIContent m_Content; + private readonly float m_ForcedMinHeight; + private readonly float m_ForcedMaxHeight; + + public GUIWordWrapSizer(IntPtr ptr) : base(ptr) + { + CalcWidth(); + CalcHeight(); + } + + public GUIWordWrapSizer(GUIStyle style, GUIContent content, GUILayoutOption[] options) + : base(0.0f, 0.0f, 0.0f, 0.0f, style) + { + m_Content = new GUIContent(content.text, content.image, content.tooltip); + ApplyOptions(options); + m_ForcedMinHeight = minHeight; + m_ForcedMaxHeight = maxHeight; + + CalcWidth(); + CalcHeight(); + } + + public new void CalcWidth() + { + if (this.minWidth != 0.0 && this.maxWidth != 0.0) + return; + float minWidth; + float maxWidth; + style.CalcMinMaxWidth(m_Content, out minWidth, out maxWidth); + minWidth = Mathf.Ceil(minWidth); + maxWidth = Mathf.Ceil(maxWidth); + if (this.minWidth == 0.0) + this.minWidth = minWidth; + if (this.maxWidth == 0.0) + this.maxWidth = maxWidth; + } + + public new void CalcHeight() + { + if (m_ForcedMinHeight != 0.0 && m_ForcedMaxHeight != 0.0) + return; + var num = style.CalcHeight(m_Content, rect.width); + if (m_ForcedMinHeight == 0.0) + minHeight = num; + else + minHeight = m_ForcedMinHeight; + if (m_ForcedMaxHeight == 0.0) + maxHeight = num; + else + maxHeight = m_ForcedMaxHeight; + } + } + + [HarmonyPatch(typeof(GUILayoutUtility), nameof(GUILayoutUtility.DoGetRect), typeof(GUIContent), typeof(GUIStyle), typeof(Il2CppReferenceArray))] + public static class DoGetRectPatch + { + public static bool Prefix(GUIContent content, GUIStyle style, Il2CppReferenceArray options, ref Rect __result) + { + GUIUtility.CheckOnGUI(); + var type = Event.current.type; + if (type != EventType.Layout) + { + if (type != EventType.Used) + { + var next = GUILayoutUtility.current.topLevel.GetNext(); + __result = next.rect; + } + else + { + __result = GUILayoutUtility.kDummyRect; + } + } + else + { + var isHeightDependantOnWidth = style.isHeightDependantOnWidth; + if (isHeightDependantOnWidth) + { + GUILayoutUtility.current.topLevel.Add(new GUIWordWrapSizer(style, content, options)); + } + else + { + var constraints = new Vector2(0f, 0f); + if (options != null) + { + foreach (var guilayoutOption in options) + { + var type2 = guilayoutOption.type; + if (type2 != GUILayoutOption.Type.maxWidth) + { + if (type2 == GUILayoutOption.Type.maxHeight) + { + constraints.y = guilayoutOption.value.Unbox(); + } + } + else + { + constraints.x = guilayoutOption.value.Unbox(); + } + } + } + + var vector = style.CalcSizeWithConstraints(content, constraints); + vector.x = Mathf.Ceil(vector.x); + vector.y = Mathf.Ceil(vector.y); + GUILayoutUtility.current.topLevel.Add(new GUILayoutEntry(vector.x, vector.x, vector.y, vector.y, style)); + } + + __result = GUILayoutUtility.kDummyRect; + } + + return false; + } + } + + [HarmonyPatch(typeof(GUIUtility), nameof(GUIUtility.GetStateObject))] + public static class GetStateObjectPatch + { + private static Dictionary s_StateCache = new Dictionary(); + + public static bool Prefix(Type t, int controlID, out Object __result) + { + if (!s_StateCache.TryGetValue(controlID, out __result) || __result.GetIl2CppType() != t) + { + s_StateCache[controlID] = __result = t.GetConstructor(new Il2CppReferenceArray(0)).Invoke(null, new Il2CppReferenceArray(0)); + } + + return false; + } + } + } +}