diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..cfae9ab
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+bin/
+obj/
+/packages/
+riderModule.iml
+/_ReSharper.Caches/
+work
+.idea
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..7a52b92
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "Reactor.Greenhouse/Mappings"]
+ path = Reactor.Greenhouse/Mappings
+ url = https://github.com/NuclearPowered/Mappings
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0a04128
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,165 @@
+ 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
new file mode 100644
index 0000000..3a90407
--- /dev/null
+++ b/README.md
@@ -0,0 +1,11 @@
+## Reactor.Greenhouse
+
+Standalone program which generates mappings for OxygenFilter
+
+## Reactor.OxygenFilter
+
+Library for mapping stuff
+
+## Reactor.OxygenFilter.MSBuild
+
+Library for using mappings in a project
\ No newline at end of file
diff --git a/Reactor.Greenhouse/Compiler.cs b/Reactor.Greenhouse/Compiler.cs
new file mode 100644
index 0000000..9d31da4
--- /dev/null
+++ b/Reactor.Greenhouse/Compiler.cs
@@ -0,0 +1,198 @@
+using System;
+using System.Collections.Generic;
+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/Mappings b/Reactor.Greenhouse/Mappings
new file mode 160000
index 0000000..216cbf4
--- /dev/null
+++ b/Reactor.Greenhouse/Mappings
@@ -0,0 +1 @@
+Subproject commit 216cbf4c946b5ab10ef12b9cca39af72d2394496
diff --git a/Reactor.Greenhouse/Program.cs b/Reactor.Greenhouse/Program.cs
new file mode 100644
index 0000000..744d047
--- /dev/null
+++ b/Reactor.Greenhouse/Program.cs
@@ -0,0 +1,110 @@
+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);
+ }
+
+ public 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", "universal.json"));
+ Apply(generated, Path.Combine("Mappings", version + postfix + ".json"));
+
+ generated.Compile(moduleDef);
+
+ Directory.CreateDirectory(Path.Combine("Mappings", "bin"));
+ await File.WriteAllTextAsync(Path.Combine("Mappings", "bin", game.Name.ToLower() + ".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