mirror of
https://github.com/NuclearPowered/Reactor.OxygenFilter.git
synced 2026-10-19 15:52:01 +00:00
Initial
This commit is contained in:
30 files changed
+2362
No files matched your search
@@ -0,0 +1,7 @@
|
||||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
work
|
||||
.idea
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "Reactor.Greenhouse/Mappings"]
|
||||
path = Reactor.Greenhouse/Mappings
|
||||
url = https://github.com/NuclearPowered/Mappings
|
||||
@@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
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.
|
||||
@@ -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
|
||||
@@ -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<MappedType> 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<MappedMember> members, List<MappedMember> 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(@"{{(?<expression>[\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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Submodule
+1
Submodule Reactor.Greenhouse/Mappings added at 216cbf4c94.
@@ -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<int> Main(string[] args)
|
||||
{
|
||||
var rootCommand = new RootCommand
|
||||
{
|
||||
new Option<bool>("steam"),
|
||||
new Option<bool>("itch"),
|
||||
};
|
||||
|
||||
rootCommand.Handler = CommandHandler.Create<bool, bool>(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<Mappings>(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<object>)?.Count() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
return property;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AngleSharp" Version="0.14.0" />
|
||||
<PackageReference Include="DepotDownloader.Library" Version="0.1.2" />
|
||||
<PackageReference Include="Iced" Version="1.10.0" />
|
||||
<PackageReference Include="Mono.Cecil" Version="0.11.3" />
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20574.7" />
|
||||
<ProjectReference Include="..\Reactor.OxygenFilter\Reactor.OxygenFilter.csproj" />
|
||||
<PackageReference Include="js6pak.Il2CppDumper" Version="6.4.20" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Il2CppDumper;
|
||||
using Reactor.Greenhouse.Setup.Provider;
|
||||
using IOPath = System.IO.Path;
|
||||
|
||||
namespace Reactor.Greenhouse.Setup
|
||||
{
|
||||
public class Game
|
||||
{
|
||||
public BaseProvider Provider { get; }
|
||||
public string Name { get; }
|
||||
public char Postfix { get; }
|
||||
public string Path { get; }
|
||||
public string Dll { get; }
|
||||
public string Version { get; private set; }
|
||||
|
||||
public Game(BaseProvider provider, string name, string path)
|
||||
{
|
||||
Provider = provider;
|
||||
provider.Game = this;
|
||||
Name = name;
|
||||
Postfix = char.ToLowerInvariant(name[0]);
|
||||
Path = path;
|
||||
Dll = IOPath.Combine(Path, "DummyDll", "Assembly-CSharp.dll");
|
||||
}
|
||||
|
||||
public async Task DownloadAsync()
|
||||
{
|
||||
Provider.Setup();
|
||||
await Provider.DownloadAsync();
|
||||
UpdateVersion();
|
||||
}
|
||||
|
||||
public void UpdateVersion()
|
||||
{
|
||||
if (Version != null)
|
||||
return;
|
||||
|
||||
Version = GameVersionParser.Parse(System.IO.Path.Combine(Path, "Among Us_Data", "globalgamemanagers"));
|
||||
}
|
||||
|
||||
public void Dump()
|
||||
{
|
||||
Console.WriteLine($"Dumping {Name}");
|
||||
|
||||
var hash = ComputeHash(IOPath.Combine(Path, "GameAssembly.dll"));
|
||||
var hashFile = IOPath.Combine(Path, "Assembly-CSharp.dll.md5");
|
||||
|
||||
if (File.Exists(hashFile) && File.ReadAllText(hashFile) == hash)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Il2CppDumper.Il2CppDumper.PerformDump(
|
||||
IOPath.Combine(Path, "GameAssembly.dll"),
|
||||
IOPath.Combine(Path, "Among Us_Data", "il2cpp_data", "Metadata", "global-metadata.dat"),
|
||||
Path,
|
||||
new Config
|
||||
{
|
||||
RequireAnyKey = false,
|
||||
GenerateScript = false,
|
||||
DumpProperty = true,
|
||||
DumpAttribute = true
|
||||
},
|
||||
Console.WriteLine
|
||||
))
|
||||
{
|
||||
throw new Exception("Il2CppDumper failed");
|
||||
}
|
||||
|
||||
File.WriteAllText(hashFile, hash);
|
||||
}
|
||||
|
||||
private static string ComputeHash(string file)
|
||||
{
|
||||
using var md5 = MD5.Create();
|
||||
using var assemblyStream = File.OpenRead(file);
|
||||
|
||||
var hash = md5.ComputeHash(assemblyStream);
|
||||
|
||||
return Encoding.UTF8.GetString(hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Reactor.Greenhouse.Setup
|
||||
{
|
||||
public static class GameVersionParser
|
||||
{
|
||||
public static int IndexOf(this byte[] source, byte[] pattern)
|
||||
{
|
||||
for (var i = 0; i < source.Length; i++)
|
||||
{
|
||||
if (source.Skip(i).Take(pattern.Length).SequenceEqual(pattern))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static string Parse(string file)
|
||||
{
|
||||
var bytes = File.ReadAllBytes(file);
|
||||
|
||||
var pattern = Encoding.UTF8.GetBytes("public.app-category.games");
|
||||
var index = bytes.IndexOf(pattern) + pattern.Length + 127;
|
||||
|
||||
return Encoding.UTF8.GetString(bytes.Skip(index).TakeWhile(x => x != 0).ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Reactor.Greenhouse.Setup.Provider
|
||||
{
|
||||
public abstract class BaseProvider
|
||||
{
|
||||
public Game Game { get; internal set; }
|
||||
|
||||
public abstract void Setup();
|
||||
public abstract Task DownloadAsync();
|
||||
public abstract bool IsUpdateNeeded();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
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()
|
||||
{
|
||||
var environmentVariable = Environment.GetEnvironmentVariable("ITCH");
|
||||
|
||||
if (environmentVariable != null)
|
||||
{
|
||||
var split = environmentVariable.Split(":");
|
||||
Username = split[0];
|
||||
Password = split[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
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<string, string>("csrf_token", ((IHtmlInputElement) csrfDocument.QuerySelector("input[name=csrf_token]")).Value),
|
||||
new KeyValuePair<string, string>("username", Username),
|
||||
new KeyValuePair<string, string>("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<IHtmlScriptElement>()
|
||||
.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<DownloadResponse>(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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
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<string>
|
||||
{
|
||||
"GameAssembly.dll"
|
||||
};
|
||||
ContentDownloader.Config.FilesToDownloadRegex = new List<Regex>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Reactor.OxygenFilter.MSBuild
|
||||
{
|
||||
public static class Context
|
||||
{
|
||||
public static string DataPath { get; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), ".reactor");
|
||||
public static string TempPath { get; } = Path.Combine(DataPath, "temp");
|
||||
|
||||
public static string ComputeHash(FileInfo file)
|
||||
{
|
||||
using var md5 = MD5.Create();
|
||||
using var assemblyStream = file.OpenRead();
|
||||
|
||||
var hash = md5.ComputeHash(assemblyStream);
|
||||
|
||||
return Encoding.UTF8.GetString(hash);
|
||||
}
|
||||
|
||||
public static string ComputeHash(string text)
|
||||
{
|
||||
using var md5 = MD5.Create();
|
||||
|
||||
var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(text));
|
||||
|
||||
return Encoding.UTF8.GetString(hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
using Mono.Cecil;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Reactor.OxygenFilter.MSBuild
|
||||
{
|
||||
public class Deobfuscate : Task
|
||||
{
|
||||
[Required]
|
||||
public string AmongUs { get; set; }
|
||||
|
||||
[Required]
|
||||
public string[] Input { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Mappings { get; set; }
|
||||
|
||||
[Output]
|
||||
public string[] Deobfuscated { get; set; }
|
||||
|
||||
public override bool Execute()
|
||||
{
|
||||
var path = Path.Combine(Context.DataPath, "mapped");
|
||||
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
};
|
||||
|
||||
var mappings = JsonConvert.DeserializeObject<Mappings>(Mappings);
|
||||
|
||||
var resolver = new DefaultAssemblyResolver();
|
||||
resolver.AddSearchDirectory(Path.Combine(Context.DataPath, "references"));
|
||||
resolver.AddSearchDirectory(Path.Combine(AmongUs, "BepInEx", "core"));
|
||||
resolver.AddSearchDirectory(Path.Combine(AmongUs, "BepInEx", "plugins"));
|
||||
resolver.AddSearchDirectory(Path.Combine(AmongUs, "BepInEx", "unhollowed"));
|
||||
|
||||
var deobfuscated = new List<string>();
|
||||
|
||||
foreach (var input in Input)
|
||||
{
|
||||
var fileName = Path.Combine(path, Path.GetFileName(input));
|
||||
deobfuscated.Add(fileName);
|
||||
|
||||
var hash = Context.ComputeHash(new FileInfo(input));
|
||||
var hashFile = fileName + ".md5";
|
||||
|
||||
if (File.Exists(hashFile) && File.ReadAllText(hashFile) == hash)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
using var stream = File.Open(input, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
|
||||
using var moduleDefinition = ModuleDefinition.ReadModule(stream, new ReaderParameters { AssemblyResolver = resolver });
|
||||
var toDeobfuscate = new Dictionary<MemberReference, string>();
|
||||
|
||||
foreach (var type in moduleDefinition.GetTypeReferences())
|
||||
{
|
||||
var typeDefinition = type.Resolve();
|
||||
|
||||
if (typeDefinition == null)
|
||||
{
|
||||
Log.LogWarning($"Unresolved type reference: {type.FullName}, {type.Scope}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var mapped = mappings.FindByOriginal(typeDefinition.FullName);
|
||||
|
||||
if (mapped?.Mapped != null)
|
||||
{
|
||||
toDeobfuscate[type] = mapped.Mapped;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var member in moduleDefinition.GetMemberReferences())
|
||||
{
|
||||
var memberDefinition = member.Resolve();
|
||||
|
||||
if (memberDefinition == null)
|
||||
{
|
||||
Log.LogWarning($"Unresolved member reference: {member.FullName}, {member.DeclaringType.Scope}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var mappedType = mappings.FindByOriginal(memberDefinition.DeclaringType.FullName);
|
||||
|
||||
if (mappedType != null)
|
||||
{
|
||||
var mappedMembers = mappedType.Fields.Concat(mappedType.Properties).Concat(mappedType.Methods);
|
||||
|
||||
var mapped = mappedMembers.FirstOrDefault(x => x.Original.Name == member.Name);
|
||||
|
||||
if (mapped?.Mapped != null)
|
||||
{
|
||||
toDeobfuscate[member] = mapped.Mapped;
|
||||
}
|
||||
else if (memberDefinition is MethodDefinition methodDefinition)
|
||||
{
|
||||
if (methodDefinition.IsGetter)
|
||||
{
|
||||
var property = memberDefinition.DeclaringType.Properties.FirstOrDefault(x => x.GetMethod?.Name == memberDefinition.Name);
|
||||
if (property != null)
|
||||
{
|
||||
mapped = mappedType.Properties.FirstOrDefault(x => x.Original.Name == property.Name);
|
||||
if (mapped?.Mapped != null)
|
||||
{
|
||||
toDeobfuscate[property] = mapped.Mapped;
|
||||
toDeobfuscate[member] = "get_" + mapped.Mapped;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (methodDefinition.IsSetter)
|
||||
{
|
||||
var property = memberDefinition.DeclaringType.Properties.FirstOrDefault(x => x.SetMethod?.Name == memberDefinition.Name);
|
||||
|
||||
if (property != null)
|
||||
{
|
||||
mapped = mappedType.Properties.FirstOrDefault(x => x.Original.Name == property.Name);
|
||||
|
||||
if (mapped?.Mapped != null)
|
||||
{
|
||||
toDeobfuscate[property] = mapped.Mapped;
|
||||
toDeobfuscate[member] = "set_" + mapped.Mapped;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var pair in toDeobfuscate)
|
||||
{
|
||||
pair.Key.Name = pair.Value;
|
||||
}
|
||||
|
||||
foreach (var typeReference in moduleDefinition.GetTypeReferences())
|
||||
{
|
||||
if (typeReference.Scope.Name == "Assembly-CSharp")
|
||||
{
|
||||
typeReference.Scope.Name += "-Deobfuscated";
|
||||
}
|
||||
}
|
||||
|
||||
moduleDefinition.Write(fileName);
|
||||
File.WriteAllText(hashFile, hash);
|
||||
}
|
||||
|
||||
Deobfuscated = deobfuscated.ToArray();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using System.IO;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace Reactor.OxygenFilter.MSBuild
|
||||
{
|
||||
public class GenerateReferences : Task
|
||||
{
|
||||
[Required]
|
||||
public string AmongUs { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Mappings { get; set; }
|
||||
|
||||
[Output]
|
||||
public string ReferencesPath { get; set; }
|
||||
|
||||
public override bool Execute()
|
||||
{
|
||||
ReferencesPath = Path.Combine(Context.DataPath, "references");
|
||||
|
||||
Directory.CreateDirectory(Context.DataPath);
|
||||
|
||||
var skip = true;
|
||||
|
||||
var gameAssemblyPath = Path.Combine(AmongUs, "GameAssembly.dll");
|
||||
var hash = Context.ComputeHash(new FileInfo(gameAssemblyPath));
|
||||
var hashPath = Path.Combine(Context.DataPath, "GameAssembly.dll.md5");
|
||||
|
||||
if (!File.Exists(hashPath) || hash != File.ReadAllText(hashPath))
|
||||
{
|
||||
skip = false;
|
||||
}
|
||||
|
||||
var mappingsHash = Context.ComputeHash(Mappings);
|
||||
var mappingsHashPath = Path.Combine(Context.DataPath, "mappings.md5");
|
||||
|
||||
if (!File.Exists(mappingsHashPath) || mappingsHash != File.ReadAllText(mappingsHashPath))
|
||||
{
|
||||
skip = false;
|
||||
}
|
||||
|
||||
if (skip)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Directory.Exists(Context.TempPath))
|
||||
{
|
||||
Directory.Delete(Context.TempPath, true);
|
||||
}
|
||||
|
||||
var dumperConfig = new Il2CppDumper.Config
|
||||
{
|
||||
GenerateScript = false,
|
||||
GenerateDummyDll = true
|
||||
};
|
||||
|
||||
Log.LogMessage(MessageImportance.High, "Generating Il2CppDumper intermediate assemblies");
|
||||
|
||||
Il2CppDumper.Il2CppDumper.PerformDump(
|
||||
gameAssemblyPath,
|
||||
Path.Combine(AmongUs, "Among Us_Data", "il2cpp_data", "Metadata", "global-metadata.dat"),
|
||||
Context.TempPath, dumperConfig, _ =>
|
||||
{
|
||||
}
|
||||
);
|
||||
|
||||
Log.LogMessage(MessageImportance.High, "Executing Reactor.OxygenFilter");
|
||||
|
||||
var oxygenFilter = new OxygenFilter();
|
||||
|
||||
var dumpedDll = new FileInfo(Path.Combine(Context.TempPath, "DummyDll", "Assembly-CSharp.dll"));
|
||||
oxygenFilter.Start(Mappings, dumpedDll, dumpedDll);
|
||||
|
||||
Log.LogMessage(MessageImportance.High, "Executing Il2CppUnhollower generator");
|
||||
|
||||
UnhollowerBaseLib.LogSupport.WarningHandler += s => Log.LogWarning(s);
|
||||
UnhollowerBaseLib.LogSupport.ErrorHandler += s => Log.LogError(s);
|
||||
|
||||
var unityBaseLibDir = Path.Combine(AmongUs, "BepInEx", "unhollowed", "base");
|
||||
|
||||
var unhollowerOptions = new AssemblyUnhollower.UnhollowerOptions
|
||||
{
|
||||
GameAssemblyPath = gameAssemblyPath,
|
||||
MscorlibPath = Path.Combine(AmongUs, "mono", "Managed", "mscorlib.dll"),
|
||||
SourceDir = Path.Combine(Context.TempPath, "DummyDll"),
|
||||
OutputDir = Path.Combine(Context.TempPath, "unhollowed"),
|
||||
UnityBaseLibsDir = unityBaseLibDir,
|
||||
NoCopyUnhollowerLibs = true
|
||||
};
|
||||
|
||||
AssemblyUnhollower.Program.Main(unhollowerOptions);
|
||||
|
||||
Directory.CreateDirectory(ReferencesPath);
|
||||
|
||||
var assemblyDefinition = AssemblyDefinition.ReadAssembly(Path.Combine(unhollowerOptions.OutputDir, "Assembly-CSharp.dll"));
|
||||
|
||||
assemblyDefinition.Name = new AssemblyNameDefinition(assemblyDefinition.Name.Name + "-Deobfuscated", assemblyDefinition.Name.Version);
|
||||
assemblyDefinition.MainModule.Name += "-Deobfuscated";
|
||||
|
||||
assemblyDefinition.Write(Path.Combine(ReferencesPath, "Assembly-CSharp-Deobfuscated.dll"));
|
||||
|
||||
File.WriteAllText(hashPath, hash);
|
||||
File.WriteAllText(mappingsHashPath, mappingsHash);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
|
||||
namespace Reactor.OxygenFilter.MSBuild
|
||||
{
|
||||
public class LoadMappings : Task
|
||||
{
|
||||
public string TargetGamePlatform { get; set; } = "Steam";
|
||||
|
||||
[Required]
|
||||
public string Mappings { get; set; }
|
||||
|
||||
[Output]
|
||||
public string MappingsJson { get; set; }
|
||||
|
||||
public override bool Execute()
|
||||
{
|
||||
if (File.Exists(Mappings))
|
||||
{
|
||||
MappingsJson = File.ReadAllText(Mappings);
|
||||
return true;
|
||||
}
|
||||
|
||||
var file = Path.Combine(Context.TempPath, Mappings.Replace("/", "_") + $"-{TargetGamePlatform.ToLower()}" + ".json");
|
||||
|
||||
if (File.Exists(file))
|
||||
{
|
||||
MappingsJson = File.ReadAllText(file);
|
||||
return true;
|
||||
}
|
||||
|
||||
var split = Mappings.Split(':');
|
||||
var repo = split[0];
|
||||
var version = split[1];
|
||||
|
||||
var httpClient = new HttpClient();
|
||||
var json = httpClient.GetStringAsync($"https://github.com/{repo}/releases/download/{version}/{TargetGamePlatform.ToLower()}.json").GetAwaiter().GetResult();
|
||||
|
||||
MappingsJson = json;
|
||||
File.WriteAllText(file, json);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<Project TreatAsLocalProperty="TaskFolder;TaskAssembly">
|
||||
<PropertyGroup>
|
||||
<IsNetCoreApp Condition="$(MSBuildRuntimeType) == 'Core'">true</IsNetCoreApp>
|
||||
<IsNetCoreApp Condition="$(IsNetCoreApp) == ''">false</IsNetCoreApp>
|
||||
<TaskFolder Condition="$(IsNetCoreApp)">netstandard2.1</TaskFolder>
|
||||
<TaskFolder Condition="!$(IsNetCoreApp)">net472</TaskFolder>
|
||||
<TaskAssembly>$(MSBuildThisFileDirectory)..\lib\$(TaskFolder)\Reactor.OxygenFilter.MSBuild.dll</TaskAssembly>
|
||||
</PropertyGroup>
|
||||
|
||||
<UsingTask TaskName="LoadMappings" AssemblyFile="$(TaskAssembly)" />
|
||||
<UsingTask TaskName="GenerateReferences" AssemblyFile="$(TaskAssembly)" />
|
||||
<UsingTask TaskName="Reobfuscate" AssemblyFile="$(TaskAssembly)" />
|
||||
<UsingTask TaskName="Deobfuscate" AssemblyFile="$(TaskAssembly)" />
|
||||
|
||||
<Target Name="LoadMappings">
|
||||
<LoadMappings TargetGamePlatform="$(TargetGamePlatform)" Mappings="$(Mappings)">
|
||||
<Output TaskParameter="MappingsJson" PropertyName="MappingsJson" />
|
||||
</LoadMappings>
|
||||
</Target>
|
||||
|
||||
<Target Name="GenerateReferences" BeforeTargets="PrepareForBuild" DependsOnTargets="LoadMappings">
|
||||
<GenerateReferences AmongUs="$(AmongUs)" Mappings="$(MappingsJson)">
|
||||
<Output TaskParameter="ReferencesPath" PropertyName="ReferencesPath" />
|
||||
</GenerateReferences>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="$(ReferencesPath)\*.dll" />
|
||||
|
||||
<Reference Include="$(AmongUs)\BepInEx\unhollowed\*.dll" />
|
||||
<Reference Remove="$(AmongUs)\BepInEx\unhollowed\netstandard.dll;$(AmongUs)\BepInEx\unhollowed\Assembly-CSharp.dll" />
|
||||
<Reference Include="$(AmongUs)\BepInEx\core\*.dll" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="Deobfuscate" AfterTargets="GenerateReferences">
|
||||
<Deobfuscate AmongUs="$(AmongUs)" Input="@(Deobfuscate)" Mappings="$(MappingsJson)">
|
||||
<Output TaskParameter="Deobfuscated" PropertyName="Deobfuscated" />
|
||||
</Deobfuscate>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="$(Deobfuscated)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="Reobfuscate" AfterTargets="Build">
|
||||
<Reobfuscate Input="$(ProjectDir)$(OutputPath)$(AssemblyName).dll" ReferencedAssemblies="@(ReferencePath)" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,3 @@
|
||||
<Project>
|
||||
<Import Project="..\build\Reactor.OxygenFilter.MSBuild.props" />
|
||||
</Project>
|
||||
@@ -0,0 +1,41 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.1;net472</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
|
||||
<Version>0.1.0</Version>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/NuclearPowered/Reactor.OxygenFilter</RepositoryUrl>
|
||||
<PackageLicenseExpression>LGPL-3.0-or-later</PackageLicenseExpression>
|
||||
<Description>Library for using Reactor.OxygenFilter with msbuild</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Build.Framework" Version="16.8.0" />
|
||||
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="16.8.0" />
|
||||
|
||||
<ProjectReference Include="..\Reactor.OxygenFilter\Reactor.OxygenFilter.csproj" />
|
||||
<PackageReference Include="js6pak.Il2CppDumper" Version="6.4.20" />
|
||||
<PackageReference Include="NuclearPowered.AssemblyUnhollower" Version="0.4.12" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net472' ">
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Reactor.OxygenFilter.MSBuild.TargetFramework.props" Pack="true" PackagePath="build\Reactor.OxygenFilter.MSBuild.props" />
|
||||
<Content Include="Reactor.OxygenFilter.MSBuild.TargetFrameworks.props" Pack="true" PackagePath="buildMultiTargeting\Reactor.OxygenFilter.MSBuild.props" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PackTaskDependencies" BeforeTargets="GenerateNuspec">
|
||||
<ItemGroup>
|
||||
<_PackageFiles Include="bin\$(Configuration)\*\Reactor.OxygenFilter.dll;bin\$(Configuration)\*\Newtonsoft.Json.dll;bin\$(Configuration)\*\Il2CppDumper.dll;bin\$(Configuration)\*\AssemblyUnhollower.dll;bin\$(Configuration)\*\Unhollower*Lib.dll;bin\$(Configuration)\*\Iced.dll;bin\$(Configuration)\*\Mono.*.dll">
|
||||
<PackagePath>lib%(RecursiveDir)</PackagePath>
|
||||
<Visible>false</Visible>
|
||||
<BuildAction>Content</BuildAction>
|
||||
</_PackageFiles>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,245 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using AssemblyUnhollower;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Rocks;
|
||||
|
||||
namespace Reactor.OxygenFilter.MSBuild
|
||||
{
|
||||
public class Reobfuscate : Task
|
||||
{
|
||||
[Required]
|
||||
public string Input { get; set; }
|
||||
|
||||
[Required]
|
||||
public string[] ReferencedAssemblies { get; set; }
|
||||
|
||||
private static string GetObfuscated(ICustomAttributeProvider cap)
|
||||
{
|
||||
var attribute = cap.GetCustomAttribute("UnhollowerBaseLib.Attributes.ObfuscatedNameAttribute");
|
||||
return attribute?.ConstructorArguments.Single().Value as string;
|
||||
}
|
||||
|
||||
private const string postfix = "-Deobfuscated";
|
||||
|
||||
public override bool Execute()
|
||||
{
|
||||
using var stream = File.Open(Input, FileMode.Open, FileAccess.ReadWrite);
|
||||
var resolver = new DefaultAssemblyResolver();
|
||||
|
||||
foreach (var directory in ReferencedAssemblies.Select(Path.GetDirectoryName).Distinct())
|
||||
{
|
||||
resolver.AddSearchDirectory(directory);
|
||||
}
|
||||
|
||||
using var moduleDefinition = ModuleDefinition.ReadModule(stream, new ReaderParameters { AssemblyResolver = resolver });
|
||||
var toObfuscate = new Dictionary<MemberReference, string>();
|
||||
|
||||
foreach (var type in moduleDefinition.GetTypeReferences())
|
||||
{
|
||||
var typeDefinition = type.Resolve();
|
||||
|
||||
if (typeDefinition == null)
|
||||
{
|
||||
Log.LogWarning($"Unresolved type reference: {type.FullName}, {type.Scope}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var obfuscated = GetObfuscated(typeDefinition);
|
||||
|
||||
if (obfuscated != null)
|
||||
{
|
||||
toObfuscate[type] = obfuscated;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var member in moduleDefinition.GetMemberReferences())
|
||||
{
|
||||
var memberDefinition = member.Resolve();
|
||||
|
||||
if (memberDefinition == null)
|
||||
{
|
||||
Log.LogWarning($"Unresolved member reference: {member.FullName}, {member.DeclaringType.Scope}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var obfuscated = GetObfuscated(memberDefinition);
|
||||
|
||||
if (obfuscated != null)
|
||||
{
|
||||
toObfuscate[member] = obfuscated;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var type in moduleDefinition.GetAllTypes())
|
||||
{
|
||||
foreach (var customAttribute in type.CustomAttributes)
|
||||
{
|
||||
TypeReference lastType = null;
|
||||
|
||||
foreach (var argument in customAttribute.ConstructorArguments.ToList())
|
||||
{
|
||||
if (argument.Type.FullName == "System.Type")
|
||||
{
|
||||
lastType = (TypeReference) argument.Value;
|
||||
|
||||
var t = lastType;
|
||||
|
||||
while (t != null)
|
||||
{
|
||||
var obfuscated = GetObfuscated(t.Resolve());
|
||||
|
||||
if (obfuscated != null)
|
||||
{
|
||||
toObfuscate[t] = obfuscated;
|
||||
}
|
||||
|
||||
t = t.DeclaringType;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// obfuscate nameof
|
||||
if (argument.Type.FullName == "System.String" && lastType != null)
|
||||
{
|
||||
var value = (string) argument.Value;
|
||||
|
||||
var lastTypeDef = lastType.Resolve();
|
||||
|
||||
IMemberDefinition member = null;
|
||||
|
||||
var field = lastTypeDef.Fields.SingleOrDefault(x => x.Name == value);
|
||||
|
||||
if (field != null)
|
||||
{
|
||||
member = field;
|
||||
}
|
||||
|
||||
var method = lastTypeDef.Methods.FirstOrDefault(x => x.Name == value);
|
||||
|
||||
if (method != null)
|
||||
{
|
||||
member = method;
|
||||
}
|
||||
|
||||
var property = lastTypeDef.Properties.SingleOrDefault(x => x.Name == value);
|
||||
|
||||
if (property != null)
|
||||
{
|
||||
member = property;
|
||||
}
|
||||
|
||||
if (member != null)
|
||||
{
|
||||
var obfuscated = GetObfuscated(member);
|
||||
|
||||
if (obfuscated != null)
|
||||
{
|
||||
customAttribute.ConstructorArguments[customAttribute.ConstructorArguments.IndexOf(argument)]
|
||||
= new CustomAttributeArgument(argument.Type, obfuscated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastType = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fix generic methods
|
||||
foreach (var methodDefinition in moduleDefinition.GetAllTypes().SelectMany(x => x.Methods))
|
||||
{
|
||||
if (!methodDefinition.HasBody)
|
||||
continue;
|
||||
|
||||
foreach (var instruction in methodDefinition.Body.Instructions)
|
||||
{
|
||||
if (instruction.Operand is MethodReference deobfuscatedCallReference)
|
||||
{
|
||||
var deobfuscatedCall = deobfuscatedCallReference.Resolve();
|
||||
if (deobfuscatedCall == null)
|
||||
continue;
|
||||
|
||||
var obfuscated = GetObfuscated(deobfuscatedCall);
|
||||
if (obfuscated != null)
|
||||
{
|
||||
// get same type from obfuscated assembly
|
||||
|
||||
var assemblyDefinition = resolver.Resolve(new AssemblyNameDefinition("Assembly-CSharp", null));
|
||||
|
||||
var hierarchy = new List<TypeDefinition>();
|
||||
var type = deobfuscatedCall.DeclaringType;
|
||||
|
||||
while (type != null)
|
||||
{
|
||||
hierarchy.Insert(0, type);
|
||||
type = type.DeclaringType;
|
||||
}
|
||||
|
||||
var typeDefinition = assemblyDefinition.MainModule.GetType(GetObfuscated(hierarchy.First()));
|
||||
|
||||
foreach (var element in hierarchy.Skip(1))
|
||||
{
|
||||
typeDefinition = typeDefinition.NestedTypes.Single(x => x.Name == GetObfuscated(element));
|
||||
}
|
||||
|
||||
// get same method from obfuscated assembly
|
||||
MethodReference definition = typeDefinition.GetMethods().Single(x => x.Name == obfuscated);
|
||||
|
||||
// obfuscate generics
|
||||
if (deobfuscatedCallReference is GenericInstanceMethod generic)
|
||||
{
|
||||
var genericDefinition = new GenericInstanceMethod(definition);
|
||||
definition = genericDefinition;
|
||||
|
||||
foreach (var parameter in generic.GenericArguments)
|
||||
{
|
||||
var resolved = parameter.Resolve();
|
||||
if (resolved != null)
|
||||
{
|
||||
var s = GetObfuscated(resolved);
|
||||
if (s != null)
|
||||
{
|
||||
parameter.Name = s;
|
||||
}
|
||||
}
|
||||
|
||||
genericDefinition.GenericArguments.Add(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
instruction.Operand = deobfuscatedCallReference.Module.ImportReference(definition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var pair in toObfuscate)
|
||||
{
|
||||
pair.Key.Name = pair.Value;
|
||||
}
|
||||
|
||||
foreach (var typeReference in moduleDefinition.GetTypeReferences())
|
||||
{
|
||||
typeReference.Module.Name = typeReference.Module.Name.Replace(postfix, string.Empty);
|
||||
typeReference.Scope.Name = typeReference.Scope.Name.Replace(postfix, string.Empty);
|
||||
}
|
||||
|
||||
foreach (var memberReference in moduleDefinition.GetMemberReferences())
|
||||
{
|
||||
memberReference.Module.Name = memberReference.Module.Name.Replace(postfix, string.Empty);
|
||||
memberReference.DeclaringType.Scope.Name = memberReference.DeclaringType.Scope.Name.Replace(postfix, string.Empty);
|
||||
}
|
||||
|
||||
var outputDirectory = Path.Combine(Path.GetDirectoryName(Input), "reobfuscated");
|
||||
Directory.CreateDirectory(outputDirectory);
|
||||
moduleDefinition.Write(Path.Combine(outputDirectory, Path.GetFileName(Input)));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reactor.OxygenFilter", "Reactor.OxygenFilter\Reactor.OxygenFilter.csproj", "{D32CB975-7C2A-495F-8D6A-2955ADF684D4}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reactor.Greenhouse", "Reactor.Greenhouse\Reactor.Greenhouse.csproj", "{AF5C3614-4FC9-4D28-BA58-1B9103B5BAB1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reactor.OxygenFilter.MSBuild", "Reactor.OxygenFilter.MSBuild\Reactor.OxygenFilter.MSBuild.csproj", "{C65EE22A-784F-4B6A-974A-BA27418235E5}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{D32CB975-7C2A-495F-8D6A-2955ADF684D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D32CB975-7C2A-495F-8D6A-2955ADF684D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D32CB975-7C2A-495F-8D6A-2955ADF684D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D32CB975-7C2A-495F-8D6A-2955ADF684D4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AF5C3614-4FC9-4D28-BA58-1B9103B5BAB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AF5C3614-4FC9-4D28-BA58-1B9103B5BAB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AF5C3614-4FC9-4D28-BA58-1B9103B5BAB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AF5C3614-4FC9-4D28-BA58-1B9103B5BAB1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C65EE22A-784F-4B6A-974A-BA27418235E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C65EE22A-784F-4B6A-974A-BA27418235E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C65EE22A-784F-4B6A-974A-BA27418235E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C65EE22A-784F-4B6A-974A-BA27418235E5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace Reactor.OxygenFilter
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static bool IsObfuscated(this string text)
|
||||
{
|
||||
return text.Length == 11 && text.All(char.IsUpper);
|
||||
}
|
||||
|
||||
public static string GetSignature(this MethodDefinition methodDefinition)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.Append(methodDefinition.ReturnType.FullName);
|
||||
sb.Append(" ");
|
||||
|
||||
sb.Append("(");
|
||||
if (methodDefinition.HasParameters)
|
||||
{
|
||||
for (var i = 0; i < methodDefinition.Parameters.Count; i++)
|
||||
{
|
||||
var parameterType = methodDefinition.Parameters[i].ParameterType;
|
||||
|
||||
if (i > 0)
|
||||
sb.Append(",");
|
||||
|
||||
if (parameterType is SentinelType)
|
||||
sb.Append("...,");
|
||||
|
||||
sb.Append(parameterType.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append(")");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string GetSignature(this FieldDefinition fieldDefinition)
|
||||
{
|
||||
return fieldDefinition.FieldType.FullName;
|
||||
}
|
||||
|
||||
public static string GetSignature(this PropertyDefinition propertyDefinition)
|
||||
{
|
||||
return propertyDefinition.PropertyType.FullName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Reactor.OxygenFilter
|
||||
{
|
||||
public class Mappings
|
||||
{
|
||||
public List<MappedType> Types { get; set; } = new List<MappedType>();
|
||||
|
||||
public MappedType Find(string name, Func<MappedType, string> 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<OriginalDescriptor>
|
||||
{
|
||||
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<OriginalDescriptor>(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<string> Parameters { get; set; } = new List<string>();
|
||||
|
||||
public MappedMethod()
|
||||
{
|
||||
}
|
||||
|
||||
public MappedMethod(OriginalDescriptor original, string mapped) : base(original, mapped)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class MappedType : MappedMember
|
||||
{
|
||||
public List<MappedMember> Properties { get; set; } = new List<MappedMember>();
|
||||
public List<MappedMember> Fields { get; set; } = new List<MappedMember>();
|
||||
public List<MappedMethod> Methods { get; set; } = new List<MappedMethod>();
|
||||
|
||||
public List<MappedType> Nested { get; set; } = new List<MappedType>();
|
||||
|
||||
public MappedType()
|
||||
{
|
||||
}
|
||||
|
||||
public MappedType(OriginalDescriptor original, string mapped) : base(original, mapped)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
using System;
|
||||
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.TypeSystem.Void, mappedAttribute)
|
||||
{
|
||||
HasThis = true,
|
||||
Parameters = { new ParameterDefinition(ModuleDef.TypeSystem.String) }
|
||||
};
|
||||
|
||||
void MapMember(ICustomAttributeProvider attributes, string mapped)
|
||||
{
|
||||
attributes.CustomAttributes.Add(new CustomAttribute(mappedAttributeCtor)
|
||||
{
|
||||
ConstructorArguments = { new CustomAttributeArgument(ModuleDef.TypeSystem.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(string mappings)
|
||||
{
|
||||
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
};
|
||||
|
||||
Mappings = JsonConvert.DeserializeObject<Mappings>(mappings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.IO;
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace Reactor.OxygenFilter
|
||||
{
|
||||
public class OxygenFilter
|
||||
{
|
||||
public void Start(string mappings, 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(mappings);
|
||||
mapper.Map();
|
||||
module.Dispose();
|
||||
module.Write(outputStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net472;netstandard2.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
|
||||
<Version>0.1.0</Version>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/NuclearPowered/Reactor.OxygenFilter</RepositoryUrl>
|
||||
<PackageLicenseExpression>LGPL-3.0-or-later</PackageLicenseExpression>
|
||||
<Description>Library for deobfuscating Among Us</Description>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Mono.Cecil" Version="0.11.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in new issue
Block a user