From 5eb7ab1073965efc398f0848354725cf9fc79605 Mon Sep 17 00:00:00 2001 From: js6pak Date: Wed, 30 Dec 2020 15:24:15 +0100 Subject: [PATCH] Use Reactor.OxygenFilter.MSBuild --- .gitignore | 1 - .gitmodules | 3 - AmongUs.props | 80 +- LICENSE | 165 ---- README.md | 9 - Reactor.Greenhouse/Compiler.cs | 199 ----- Reactor.Greenhouse/Extensions.cs | 28 - Reactor.Greenhouse/Generator.cs | 138 --- Reactor.Greenhouse/Program.cs | 109 --- Reactor.Greenhouse/Reactor.Greenhouse.csproj | 19 - Reactor.Greenhouse/Setup/Game.cs | 88 -- Reactor.Greenhouse/Setup/GameManager.cs | 86 -- Reactor.Greenhouse/Setup/GameVersionParser.cs | 32 - .../Setup/Provider/BaseProvider.cs | 13 - .../Setup/Provider/ItchProvider.cs | 103 --- .../Setup/Provider/SteamProvider.cs | 109 --- 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 | 16 - Reactor/Coroutines.cs | 157 ++++ Reactor/CustomStringName.cs | 54 ++ Reactor/Extensions/Extensions.cs | 20 + Reactor/Reactor.csproj | 2 +- Reactor/ReactorPlugin.cs | 64 +- build/Build.csproj | 11 +- build/Program.cs | 15 - lib/Il2CppAssemblyUnhollower | 1 - lib/Il2CppDumper.dll | Bin 148992 -> 0 bytes 35 files changed, 303 insertions(+), 2798 deletions(-) delete mode 100644 .gitmodules delete mode 100644 LICENSE delete mode 100644 Reactor.Greenhouse/Compiler.cs delete mode 100644 Reactor.Greenhouse/Extensions.cs delete mode 100644 Reactor.Greenhouse/Generator.cs delete mode 100644 Reactor.Greenhouse/Program.cs delete mode 100644 Reactor.Greenhouse/Reactor.Greenhouse.csproj delete mode 100644 Reactor.Greenhouse/Setup/Game.cs delete mode 100644 Reactor.Greenhouse/Setup/GameManager.cs delete mode 100644 Reactor.Greenhouse/Setup/GameVersionParser.cs delete mode 100644 Reactor.Greenhouse/Setup/Provider/BaseProvider.cs delete mode 100644 Reactor.Greenhouse/Setup/Provider/ItchProvider.cs delete mode 100644 Reactor.Greenhouse/Setup/Provider/SteamProvider.cs delete mode 100644 Reactor.Greenhouse/mappings.schema.json delete mode 120000 Reactor.Greenhouse/mappings/2020.11.17.json delete mode 100644 Reactor.Greenhouse/mappings/2020.12.9.json delete mode 100644 Reactor.Greenhouse/mappings/2020.12.9s.json delete mode 100644 Reactor.OxygenFilter/Extensions.cs delete mode 100644 Reactor.OxygenFilter/Mappings.cs delete mode 100644 Reactor.OxygenFilter/ObfuscationMapper.cs delete mode 100644 Reactor.OxygenFilter/OxygenFilter.cs delete mode 100644 Reactor.OxygenFilter/Reactor.OxygenFilter.csproj create mode 100644 Reactor/Coroutines.cs create mode 100644 Reactor/CustomStringName.cs delete mode 160000 lib/Il2CppAssemblyUnhollower delete mode 100644 lib/Il2CppDumper.dll diff --git a/.gitignore b/.gitignore index 19ee5f3..4867c4e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,3 @@ obj/ riderModule.iml /_ReSharper.Caches/ .idea -work \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 822bf2e..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "lib/Il2CppAssemblyUnhollower"] - path = lib/Il2CppAssemblyUnhollower - url = https://github.com/NuclearPowered/Il2CppAssemblyUnhollower diff --git a/AmongUs.props b/AmongUs.props index 1936a08..8d64e39 100644 --- a/AmongUs.props +++ b/AmongUs.props @@ -1,78 +1,14 @@ + + Steam + NuclearPowered/Mappings:0.1.0-alpha.1 + + - - - $(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 - False - - - $(AmongUs)\BepInEx\unhollowed\UnityEngine.UI.dll - False - - - $(AmongUs)\BepInEx\unhollowed\UnityEngine.UIModule.dll - False - + - - - - - + + diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 0a04128..0000000 --- a/LICENSE +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/README.md b/README.md index c7bbc74..6f84f17 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,3 @@ 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.Greenhouse/Compiler.cs b/Reactor.Greenhouse/Compiler.cs deleted file mode 100644 index 4d22319..0000000 --- a/Reactor.Greenhouse/Compiler.cs +++ /dev/null @@ -1,199 +0,0 @@ -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 deleted file mode 100644 index 0b75195..0000000 --- a/Reactor.Greenhouse/Extensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -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 deleted file mode 100644 index e3ffde1..0000000 --- a/Reactor.Greenhouse/Generator.cs +++ /dev/null @@ -1,138 +0,0 @@ -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 deleted file mode 100644 index a29a8ed..0000000 --- a/Reactor.Greenhouse/Program.cs +++ /dev/null @@ -1,109 +0,0 @@ -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 - { - new Option("steam"), - new Option("itch"), - }; - - rootCommand.Handler = CommandHandler.Create(GenerateAsync); - - return rootCommand.InvokeAsync(args); - } - - private static async Task GenerateAsync(bool steam, bool itch) - { - var gameManager = new GameManager(); - await gameManager.SetupAsync(steam, itch); - - 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)); - - if (steam) - { - await GenerateAsync(gameManager.Steam, old); - } - - if (itch) - { - 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 deleted file mode 100644 index 89afe54..0000000 --- a/Reactor.Greenhouse/Reactor.Greenhouse.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - Exe - net5.0 - true - - - - - - - - - - - ..\lib\Il2CppDumper.dll - - - diff --git a/Reactor.Greenhouse/Setup/Game.cs b/Reactor.Greenhouse/Setup/Game.cs deleted file mode 100644 index 6ce2ef4..0000000 --- a/Reactor.Greenhouse/Setup/Game.cs +++ /dev/null @@ -1,88 +0,0 @@ -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 deleted file mode 100644 index f899e6e..0000000 --- a/Reactor.Greenhouse/Setup/GameManager.cs +++ /dev/null @@ -1,86 +0,0 @@ -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(bool setupSteam, bool setupItch) - { - var preObfuscation = PreObfuscation.Provider.IsUpdateNeeded(); - var steam = setupSteam && Steam.Provider.IsUpdateNeeded(); - var itch = setupItch && 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(); - - if (setupSteam) - { - Steam.UpdateVersion(); - } - - if (setupItch) - { - Itch.UpdateVersion(); - } - - if (PreObfuscation.Version != "2020.9.9") - { - throw new ArgumentException("Pre obfuscation version is invalid"); - } - - PreObfuscation.Dump(); - - if (setupSteam) - { - Steam.Dump(); - } - - if (setupItch) - { - Itch.Dump(); - } - } - } -} diff --git a/Reactor.Greenhouse/Setup/GameVersionParser.cs b/Reactor.Greenhouse/Setup/GameVersionParser.cs deleted file mode 100644 index 283446d..0000000 --- a/Reactor.Greenhouse/Setup/GameVersionParser.cs +++ /dev/null @@ -1,32 +0,0 @@ -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 deleted file mode 100644 index b552ab7..0000000 --- a/Reactor.Greenhouse/Setup/Provider/BaseProvider.cs +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index d151c5e..0000000 --- a/Reactor.Greenhouse/Setup/Provider/ItchProvider.cs +++ /dev/null @@ -1,103 +0,0 @@ -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 deleted file mode 100644 index 543e323..0000000 --- a/Reactor.Greenhouse/Setup/Provider/SteamProvider.cs +++ /dev/null @@ -1,109 +0,0 @@ -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"); - - var environmentVariable = Environment.GetEnvironmentVariable("STEAM"); - - if (environmentVariable != null) - { - var split = environmentVariable.Split(":"); - ContentDownloader.InitializeSteam3(split[0], split[1]); - } - else - { - 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/mappings.schema.json b/Reactor.Greenhouse/mappings.schema.json deleted file mode 100644 index 23fade5..0000000 --- a/Reactor.Greenhouse/mappings.schema.json +++ /dev/null @@ -1,112 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://reactor.gg/schemas/mappings.schema.json", - "title": "Reactor.OxygenFilter - Mappings", - "type": "object", - "definitions": { - "member": { - "type": "object", - "properties": { - "original": { - "anyOf": [ - { - "type": "object", - "properties": { - "index": { - "type": "number" - }, - "signature": { - "type": "string" - }, - "name": { - "type": "string" - }, - "const": { - "type": "object", - "properties": { - "value": { - }, - "type": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "string" - }, - { - "type": "number" - } - ] - }, - "mapped": { - "type": "string" - }, - "notes": { - "type": "string" - }, - "additionalProperties": false - }, - "required": [ - "original" - ], - "additionalProperties": false - }, - "type": { - "type": "object", - "$ref": "#/definitions/member", - "properties": { - "nested": { - "type": "array", - "items": { - "$ref": "#/definitions/type" - } - }, - "fields": { - "type": "array", - "items": { - "$ref": "#/definitions/member" - } - }, - "properties": { - "type": "array", - "items": { - "$ref": "#/definitions/member" - } - }, - "methods": { - "type": "array", - "items": { - "$ref": "#/definitions/member", - "properties": { - "parameters": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - } - } - } - }, - "properties": { - "types": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/type" - }, - "required": [ - "original" - ] - } - }, - "required": [ - "types" - ] -} \ No newline at end of file diff --git a/Reactor.Greenhouse/mappings/2020.11.17.json b/Reactor.Greenhouse/mappings/2020.11.17.json deleted file mode 120000 index d0b2a1e..0000000 --- a/Reactor.Greenhouse/mappings/2020.11.17.json +++ /dev/null @@ -1 +0,0 @@ -2020.12.9.json \ No newline at end of file diff --git a/Reactor.Greenhouse/mappings/2020.12.9.json b/Reactor.Greenhouse/mappings/2020.12.9.json deleted file mode 100644 index 90c79aa..0000000 --- a/Reactor.Greenhouse/mappings/2020.12.9.json +++ /dev/null @@ -1,809 +0,0 @@ -{ - "$schema": "../mappings.schema.json", - "types": [ - { - "original": "^", - "mapped": "Constants", - "methods": [ - { - "original": { - "signature": "System.Byte[] ()" - }, - "mapped": "GetBroadcastVersion" - } - ], - "fields": [ - { - "original": 0, - "mapped": "GamePlayPort" - }, - { - "original": 1, - "mapped": "AnnouncementPort" - }, - { - "original": 2, - "mapped": "ShipOnlyMask" - }, - { - "original": 3, - "mapped": "ShipAndObjectsMask" - }, - { - "original": 4, - "mapped": "ShipAndAllObjectsMask" - }, - { - "original": 5, - "mapped": "NotShipMask" - }, - { - "original": 6, - "mapped": "Usables" - }, - { - "original": 7, - "mapped": "PlayersOnlyMask" - }, - { - "original": 8, - "mapped": "ShadowMask" - }, - { - "original": 9, - "mapped": "CompatVersions" - }, - { - "original": 10, - "mapped": "Year" - }, - { - "original": 11, - "mapped": "Month" - }, - { - "original": 12, - "mapped": "Day" - }, - { - "original": 13, - "mapped": "Revision" - }, - { - "original": 14, - "mapped": "field_14" - }, - { - "original": 15, - "mapped": "LocalNetAddress" - }, - { - "original": 16, - "mapped": "InfinitySymbol" - } - ] - }, - { - "original": {}, - "mapped": "SaveManager", - "fields": [ - { - "original": { - "signature": "System.Collections.Generic.HashSet`1", - "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 deleted file mode 100644 index 106a694..0000000 --- a/Reactor.Greenhouse/mappings/2020.12.9s.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "$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 deleted file mode 100644 index 33dfcca..0000000 --- a/Reactor.OxygenFilter/Extensions.cs +++ /dev/null @@ -1,53 +0,0 @@ -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 deleted file mode 100644 index 5282e9d..0000000 --- a/Reactor.OxygenFilter/Mappings.cs +++ /dev/null @@ -1,199 +0,0 @@ -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 deleted file mode 100644 index 1c2ead6..0000000 --- a/Reactor.OxygenFilter/ObfuscationMapper.cs +++ /dev/null @@ -1,237 +0,0 @@ -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 deleted file mode 100644 index 1cd5ec4..0000000 --- a/Reactor.OxygenFilter/OxygenFilter.cs +++ /dev/null @@ -1,24 +0,0 @@ -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 deleted file mode 100644 index bffc4eb..0000000 --- a/Reactor.OxygenFilter/Reactor.OxygenFilter.csproj +++ /dev/null @@ -1,10 +0,0 @@ - - - net472;netstandard2.0 - latest - - - - - - \ No newline at end of file diff --git a/Reactor.sln b/Reactor.sln index b98aa3a..cb0d764 100644 --- a/Reactor.sln +++ b/Reactor.sln @@ -1,20 +1,12 @@ 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 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Build", "build\Build.csproj", "{58AC4924-AC3D-45EA-80D7-E9F860FD1DD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{E2F4DAFA-E98C-4A7D-BA47-AAB0271A769D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssemblyUnhollower", "lib/Il2CppAssemblyUnhollower\AssemblyUnhollower\AssemblyUnhollower.csproj", "{64ABFD11-CE06-4220-A626-55B39AB85B62}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnhollowerBaseLib", "lib/Il2CppAssemblyUnhollower\UnhollowerBaseLib\UnhollowerBaseLib.csproj", "{17B4E6AE-EA5A-43BE-9184-5BBB6594C5E7}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -25,18 +17,10 @@ Global {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 {58AC4924-AC3D-45EA-80D7-E9F860FD1DD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {58AC4924-AC3D-45EA-80D7-E9F860FD1DD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {58AC4924-AC3D-45EA-80D7-E9F860FD1DD2}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/Reactor/Coroutines.cs b/Reactor/Coroutines.cs new file mode 100644 index 0000000..301617f --- /dev/null +++ b/Reactor/Coroutines.cs @@ -0,0 +1,157 @@ +// https://github.com/HerpDerpinstine/MelonLoader/blob/master/MelonLoader.Support.Il2Cpp/MelonCoroutines.cs + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnhollowerBaseLib; +using UnityEngine; + +namespace Reactor +{ + public static class Coroutines + { + private struct CoroutineTuple + { + public object WaitCondition; + public IEnumerator Coroutine; + } + + private static readonly List _ourCoroutinesStore = new List(); + private static readonly List _ourNextFrameCoroutines = new List(); + private static readonly List _ourWaitForFixedUpdateCoroutines = new List(); + private static readonly List _ourWaitForEndOfFrameCoroutines = new List(); + + private static readonly List _tempList = new List(); + + public static object Start(IEnumerator routine) + { + if (routine != null) ProcessNextOfCoroutine(routine); + return routine; + } + + public static void Stop(IEnumerator enumerator) + { + if (_ourNextFrameCoroutines.Contains(enumerator)) // the coroutine is running itself + { + _ourNextFrameCoroutines.Remove(enumerator); + } + else + { + var coroutineTupleIndex = _ourCoroutinesStore.FindIndex(c => c.Coroutine == enumerator); + if (coroutineTupleIndex != -1) // the coroutine is waiting for a subroutine + { + var waitCondition = _ourCoroutinesStore[coroutineTupleIndex].WaitCondition; + if (waitCondition is IEnumerator waitEnumerator) + { + Stop(waitEnumerator); + } + + _ourCoroutinesStore.RemoveAt(coroutineTupleIndex); + } + } + } + + private static void ProcessCoroutineList(List target) + { + if (target.Count == 0) return; + + // use a temp list to make sure waits made during processing are not handled by same processing invocation + // additionally, a temp list reduces allocations compared to an array + _tempList.AddRange(target); + target.Clear(); + foreach (var enumerator in _tempList) ProcessNextOfCoroutine(enumerator); + _tempList.Clear(); + } + + internal static void Process() + { + for (var i = _ourCoroutinesStore.Count - 1; i >= 0; i--) + { + var tuple = _ourCoroutinesStore[i]; + if (tuple.WaitCondition is WaitForSeconds waitForSeconds) + { + if ((waitForSeconds.m_Seconds -= Time.deltaTime) <= 0) + { + _ourCoroutinesStore.RemoveAt(i); + ProcessNextOfCoroutine(tuple.Coroutine); + } + } + } + + ProcessCoroutineList(_ourNextFrameCoroutines); + } + + internal static void ProcessWaitForFixedUpdate() => ProcessCoroutineList(_ourWaitForFixedUpdateCoroutines); + + internal static void ProcessWaitForEndOfFrame() => ProcessCoroutineList(_ourWaitForEndOfFrameCoroutines); + + private static void ProcessNextOfCoroutine(IEnumerator enumerator) + { + try + { + if (!enumerator.MoveNext()) // Run the next step of the coroutine. If it's done, restore the parent routine + { + var indices = _ourCoroutinesStore.Select((it, idx) => (idx, it)).Where(it => it.it.WaitCondition == enumerator).Select(it => it.idx).ToList(); + for (var i = indices.Count - 1; i >= 0; i--) + { + var index = indices[i]; + _ourNextFrameCoroutines.Add(_ourCoroutinesStore[index].Coroutine); + _ourCoroutinesStore.RemoveAt(index); + } + + return; + } + } + catch (Exception e) + { + PluginSingleton.Instance.Log.LogError(e.ToString()); + Stop(FindOriginalCoroutine(enumerator)); // We want the entire coroutine hierarchy to stop when an error happen + } + + var next = enumerator.Current; + switch (next) + { + case null: + _ourNextFrameCoroutines.Add(enumerator); + return; + case WaitForFixedUpdate _: + _ourWaitForFixedUpdateCoroutines.Add(enumerator); + return; + case WaitForEndOfFrame _: + _ourWaitForEndOfFrameCoroutines.Add(enumerator); + return; + case WaitForSeconds _: + break; // do nothing, this one is supported in Process + case Il2CppObjectBase il2CppObjectBase: + var nextAsEnumerator = il2CppObjectBase.TryCast(); + if (nextAsEnumerator != null) // il2cpp IEnumerator also handles CustomYieldInstruction + next = new Il2CppEnumeratorWrapper(nextAsEnumerator); + else + PluginSingleton.Instance.Log.LogWarning($"Unknown coroutine yield object of type {il2CppObjectBase} for coroutine {enumerator}"); + break; + } + + _ourCoroutinesStore.Add(new CoroutineTuple { WaitCondition = next, Coroutine = enumerator }); + + if (next is IEnumerator nextCoroutine) + ProcessNextOfCoroutine(nextCoroutine); + } + + private static IEnumerator FindOriginalCoroutine(IEnumerator enumerator) + { + var index = _ourCoroutinesStore.FindIndex(ct => ct.WaitCondition == enumerator); + return index == -1 ? enumerator : FindOriginalCoroutine(_ourCoroutinesStore[index].Coroutine); + } + + private class Il2CppEnumeratorWrapper : IEnumerator + { + private readonly Il2CppSystem.Collections.IEnumerator _il2CPPEnumerator; + + public Il2CppEnumeratorWrapper(Il2CppSystem.Collections.IEnumerator il2CppEnumerator) => _il2CPPEnumerator = il2CppEnumerator; + public bool MoveNext() => _il2CPPEnumerator.MoveNext(); + public void Reset() => _il2CPPEnumerator.Reset(); + public object Current => _il2CPPEnumerator.Current; + } + } +} diff --git a/Reactor/CustomStringName.cs b/Reactor/CustomStringName.cs new file mode 100644 index 0000000..285b379 --- /dev/null +++ b/Reactor/CustomStringName.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Linq; +using HarmonyLib; +using UnhollowerBaseLib; + +namespace Reactor +{ + public class CustomStringName + { + private static int _lastId = -1; + + private static readonly List _list = new List(); + + public static IReadOnlyList List => _list.AsReadOnly(); + + public static CustomStringName Register(string value) + { + var customStringName = new CustomStringName(_lastId--, value); + _list.Add(customStringName); + + return customStringName; + } + + public int Id { get; } + + public string Value { get; } + + private CustomStringName(int id, string value) + { + Id = id; + Value = value; + } + + public static implicit operator StringNames(CustomStringName name) => (StringNames) name.Id; + public static explicit operator CustomStringName(StringNames name) => List.SingleOrDefault(x => x.Id == (int) name); + + [HarmonyPatch(typeof(TranslationController), nameof(TranslationController.GetString), typeof(StringNames), typeof(Il2CppReferenceArray))] + private static class GetStringPatch + { + public static bool Prefix([HarmonyArgument(0)] StringNames stringId, [HarmonyArgument(1)] Il2CppReferenceArray parts, ref string __result) + { + var customStringName = (CustomStringName) stringId; + + if (customStringName != null) + { + __result = string.Format(customStringName.Value, parts); + return false; + } + + return true; + } + } + } +} diff --git a/Reactor/Extensions/Extensions.cs b/Reactor/Extensions/Extensions.cs index eedf8b2..f0ecf6c 100644 --- a/Reactor/Extensions/Extensions.cs +++ b/Reactor/Extensions/Extensions.cs @@ -2,8 +2,10 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using UnityEngine; using UnityEngine.Events; +using UnityEngine.Networking; using UnityEngine.SceneManagement; using Object = UnityEngine.Object; @@ -69,6 +71,9 @@ namespace Reactor.Extensions return ((Color32) color).ToHtmlStringRGBA(); } + /// + /// Fully read stream, can be used as workaround for il2cpp streams. + /// public static byte[] ReadFully(this Stream input) { using (var ms = new MemoryStream()) @@ -77,5 +82,20 @@ namespace Reactor.Extensions return ms.ToArray(); } } + + /// + /// Sends and return task that finishes on completion. + /// + public static Task SendAsync(this UnityWebRequest request) + { + var task = new TaskCompletionSource(); + + request.Send().m_completeCallback = (Action) (x => + { + task.SetResult(null); + }); + + return task.Task; + } } } diff --git a/Reactor/Reactor.csproj b/Reactor/Reactor.csproj index f86f109..644e1af 100644 --- a/Reactor/Reactor.csproj +++ b/Reactor/Reactor.csproj @@ -13,4 +13,4 @@ - \ No newline at end of file + diff --git a/Reactor/ReactorPlugin.cs b/Reactor/ReactorPlugin.cs index bf78186..a2097c8 100644 --- a/Reactor/ReactorPlugin.cs +++ b/Reactor/ReactorPlugin.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using BepInEx; using BepInEx.IL2CPP; using HarmonyLib; @@ -26,7 +27,7 @@ namespace Reactor ClassInjector.RegisterTypeInIl2Cpp(); var gameObject = new GameObject(nameof(ReactorPlugin)).DontDestroy(); - gameObject.AddComponent(); + gameObject.AddComponent().Plugin = this; Harmony.PatchAll(); ReactorVersionShower.Initialize(); @@ -42,19 +43,72 @@ namespace Reactor public class ReactorComponent : MonoBehaviour { + public ReactorPlugin Plugin { get; internal set; } + public ReactorComponent(IntPtr ptr) : base(ptr) { } + public void Start() + { + Camera.onPostRender = Camera.onPostRender == null + ? new Action(OnPostRenderM) + : Il2CppSystem.Delegate.Combine(Camera.onPostRender, Il2CppSystem.Delegate.CreateDelegate(GetIl2CppType(), GetIl2CppType().GetMethod(nameof(OnPostRenderM), Il2CppSystem.Reflection.BindingFlags.Static | Il2CppSystem.Reflection.BindingFlags.Public))).Cast(); + } + + public static Camera OnPostRenderCam { get; private set; } + + private static void OnPostRenderM(Camera camera) + { + if (OnPostRenderCam == null) + { + OnPostRenderCam = camera; + } + + if (OnPostRenderCam == camera) + { + Coroutines.ProcessWaitForEndOfFrame(); + } + } + private void Update() { - var watcher = PluginSingleton.Instance.RegionInfoWatcher; - if (watcher.Reload) + Coroutines.Process(); + + if (Plugin.RegionInfoWatcher.Reload) { - watcher.Reload = false; + Plugin.RegionInfoWatcher.Reload = false; ServerManager.Instance.LoadServers(); - PluginSingleton.Instance.Log.LogInfo("Region file reloaded"); + Plugin.Log.LogInfo("Region file reloaded"); } + + if (Input.GetKeyDown(KeyCode.F5)) + { + Plugin.Log.LogInfo("Reloading all configs"); + + foreach (var pluginInfo in Preloader.Chainloader.Plugins.Values) + { + var config = ((BasePlugin) pluginInfo.Instance).Config; + if (!config.Any()) + { + continue; + } + + try + { + config.Reload(); + } + catch (Exception e) + { + Plugin.Log.LogWarning($"Exception occured during reload of {pluginInfo.Metadata.Name}: {e}"); + } + } + } + } + + private void FixedUpdate() + { + Coroutines.ProcessWaitForFixedUpdate(); } } } diff --git a/build/Build.csproj b/build/Build.csproj index 01bdca4..380df88 100644 --- a/build/Build.csproj +++ b/build/Build.csproj @@ -7,11 +7,8 @@ - - ..\lib\Il2CppDumper.dll - - - - + + + - \ No newline at end of file + diff --git a/build/Program.cs b/build/Program.cs index 1c0c84b..9dd793b 100644 --- a/build/Program.cs +++ b/build/Program.cs @@ -11,7 +11,6 @@ using Cake.Common.Tools.DotNetCore.Run; using Cake.Core; using Cake.Frosting; using DepotDownloader; -using Reactor.OxygenFilter; public static class Program { @@ -81,20 +80,6 @@ public sealed class GenerateProxyAssemblyTask : FrostingTask context.TempPath, dumperConfig, context.Debug ); - context.Information("Executing Reactor.Greenhouse"); - - context.DotNetCoreRun(".", "steam", new DotNetCoreRunSettings - { - WorkingDirectory = "Reactor.Greenhouse" - }); - - context.Information("Executing Reactor.OxygenFilter"); - - var oxygenFilter = new OxygenFilter(); - - var dumpedDll = new FileInfo(Path.Combine(context.TempPath, "DummyDll", "Assembly-CSharp.dll")); - oxygenFilter.Start(new FileInfo(Path.Combine("Reactor.Greenhouse", "work", "2020.12.9s.json")), dumpedDll, dumpedDll); - context.Information("Executing Il2CppUnhollower generator"); UnhollowerBaseLib.LogSupport.InfoHandler += context.Information; diff --git a/lib/Il2CppAssemblyUnhollower b/lib/Il2CppAssemblyUnhollower deleted file mode 160000 index 0d7585f..0000000 --- a/lib/Il2CppAssemblyUnhollower +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0d7585fa90844fe0887e13c2828ecd2e4f84784d diff --git a/lib/Il2CppDumper.dll b/lib/Il2CppDumper.dll deleted file mode 100644 index 8d7774368f845a7e7e92f7a1fc55f95e947d60c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 148992 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